Added counts to each category
Counts update on certain events and show or hide various elements Fixed various little ui element issues fixes #6
This commit is contained in:
		| @@ -13,6 +13,7 @@ | ||||
| <script> | ||||
|  | ||||
| // import io from 'socket.io-client' | ||||
| import axios from 'axios' | ||||
|  | ||||
| export default { | ||||
| 	components: { | ||||
| @@ -50,7 +51,6 @@ export default { | ||||
|  | ||||
| 	}, | ||||
| 	mounted: function(){ | ||||
| 		 | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		loggedIn () { | ||||
|   | ||||
| @@ -28,7 +28,7 @@ | ||||
| } | ||||
|  | ||||
| html { | ||||
| 	scrollbar-width: none; | ||||
| 	/*scrollbar-width: none;*/ | ||||
| } | ||||
|  | ||||
| div.ui.basic.segment.no-fluf-segment { | ||||
|   | ||||
| @@ -192,6 +192,7 @@ | ||||
| 						this.unfolded = false | ||||
| 						setTimeout( () => { | ||||
| 							this.visible = false | ||||
| 							this.$store.dispatch('fetchAndUpdateUserTotals') | ||||
| 						}, 600) | ||||
| 					} | ||||
| 				}) | ||||
|   | ||||
| @@ -68,6 +68,7 @@ | ||||
| 						const imageCode = `<img alt="image" src="/api/static/thumb_${location}">` | ||||
|  | ||||
| 						this.$bus.$emit('new_file_upload', {noteId: this.noteId, imageCode}) | ||||
| 						this.$store.dispatch('fetchAndUpdateUserTotals') | ||||
| 					} | ||||
| 				}) | ||||
| 				.catch(results => { | ||||
|   | ||||
| @@ -154,6 +154,7 @@ | ||||
| 			<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()"> | ||||
| 					<i class="file outline icon"></i>Notes | ||||
| 					<!-- <span v-if="$store.getters.totals">{{ $store.getters.totals['totalNotes'] }}</span> --> | ||||
| 				</router-link> | ||||
| 				<div> | ||||
| 					<!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> --> | ||||
| @@ -163,21 +164,10 @@ | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="menu-section" v-if="loggedIn"> | ||||
| 				<div v-on:click="updateFastFilters(3)" class="menu-item menu-button"> | ||||
| 					<i class="mail outline icon"></i>Inbox | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="menu-section" v-if="loggedIn"> | ||||
| 				<div v-on:click="updateFastFilters(2)" class="menu-item menu-button"> | ||||
| 					<i class="hdd outline icon"></i>Archived | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="menu-section" v-if="loggedIn"> | ||||
| 			<div class="menu-section" v-if="loggedIn && $store.getters.totals && $store.getters.totals['totalFiles']"> | ||||
| 				<router-link class="menu-item menu-button" exact-active-class="active" to="/attachments"> | ||||
| 					<i class="folder open outline icon"></i>Files | ||||
| 					<!-- <span>{{ $store.getters.totals['totalFiles'] }}</span> --> | ||||
| 				</router-link> | ||||
| 			</div> | ||||
|  | ||||
| @@ -242,6 +232,8 @@ | ||||
| 			this.mobile = this.$store.getters.getIsUserOnMobile | ||||
| 			this.collapsed = this.$store.getters.getIsUserOnMobile | ||||
|  | ||||
| 			// {{ totals['totalNotes'] }} | ||||
|  | ||||
| 			if(this.mobile){ | ||||
| 				this.menuOpen = false | ||||
| 			} | ||||
|   | ||||
| @@ -30,6 +30,7 @@ | ||||
| 				axios.post('/api/note/delete', {'noteId':this.noteId}).then(response => { | ||||
| 					if(response.data == true){ | ||||
| 						this.$bus.$emit('note_deleted', this.noteId) | ||||
| 						this.$store.dispatch('fetchAndUpdateUserTotals') | ||||
| 					} | ||||
| 				}) | ||||
| 			}, | ||||
|   | ||||
| @@ -202,6 +202,7 @@ | ||||
| 				loading: true, | ||||
| 				loadingMessage: 'Loading Note', | ||||
| 				currentNoteId: 0, | ||||
| 				modified: false, | ||||
| 				noteText: '', | ||||
| 				rawTextId: 0, | ||||
| 				shareUsername: null, | ||||
| @@ -873,21 +874,16 @@ | ||||
| 						'archived':this.archived, | ||||
| 					} | ||||
|  | ||||
| 					let vm = this | ||||
| 					//Debounce save to prevent spamming | ||||
| 					// clearTimeout(this.saveDebounce) | ||||
| 					// this.saveDebounce = setTimeout(() => { | ||||
| 						//Only notify user if saving - may help with debugging in the future | ||||
| 						vm.statusText = 'Saving' | ||||
| 					this.statusText = 'Saving' | ||||
| 					axios.post('/api/note/update', postData).then( response => { | ||||
| 							vm.statusText = 'Saved' | ||||
| 							vm.updated = Math.round((+new Date)/1000) | ||||
| 						this.statusText = 'Saved' | ||||
| 						this.updated = Math.round((+new Date)/1000) | ||||
| 						this.modified = true | ||||
|  | ||||
| 						//Update last saved note hash | ||||
| 							vm.lastNoteHash = vm.hashString( currentNoteText ) | ||||
| 						this.lastNoteHash = this.hashString( currentNoteText ) | ||||
| 						return resolve(true) | ||||
| 					}) | ||||
| 					// }, 300) | ||||
| 				}) | ||||
| 			}, | ||||
| 			checkForUpdatedNote(){ | ||||
| @@ -949,7 +945,9 @@ | ||||
| 					this.sizeDown = true | ||||
| 					//This timeout allows animation to play before closing  | ||||
| 					setTimeout(() => { | ||||
| 						this.$bus.$emit('close_active_note', {position: this.position, noteId: this.noteid}) | ||||
| 						this.$bus.$emit('close_active_note', { | ||||
| 							position: this.position, noteId: this.noteid, modified: this.modified | ||||
| 						}) | ||||
| 						return | ||||
| 					}, 300) | ||||
| 				}) | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| <template> | ||||
| 	<div class="ui form"> | ||||
| 		<div class="ui left icon fluid input"> | ||||
| 			<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes and Files" /> | ||||
| 			<i class="search icon"></i> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -128,7 +128,6 @@ | ||||
|  | ||||
| 			if(this.styleObject && this.styleObject.noteBackground){ | ||||
| 				this.bgColor = this.styleObject.noteBackground | ||||
| 				console.log(this.bgColor) | ||||
| 			} | ||||
|  | ||||
| 			 | ||||
|   | ||||
| @@ -3,21 +3,39 @@ | ||||
| 		 | ||||
| 		<div class="ui grid" :class="{ 'mush-it-up':showOneColumn() }" ref="content"> | ||||
|  | ||||
| 			<div v-if="!$store.getters.getIsUserOnMobile"  | ||||
| 				:class="{ 'sixteen wide column':showOneColumn(), 'twelve wide column':!showOneColumn() }"> | ||||
| 			<div v-if="!$store.getters.getIsUserOnMobile" class="sixteen wide column"> | ||||
| 				<!-- :class="{ 'sixteen wide column':showOneColumn(), 'sixteen wide column':!showOneColumn() }" --> | ||||
| 				 | ||||
| 				<div class="ui grid"> | ||||
|  | ||||
| 					<div class="eight wide column"> | ||||
| 						<search-input></search-input> | ||||
| 					</div> | ||||
| 					 | ||||
| 					<div class="eight wide column"> | ||||
|  | ||||
| 						<div class="ui basic button" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && $store.getters.totals['sharedToNotes'] > 0" style="position: relative;"> | ||||
| 							<i class="green mail icon"></i>Inbox | ||||
| 							<span class="floating ui green label" v-if="$store.getters.totals['unreadNotes'] > 0"> | ||||
| 								{{ $store.getters.totals['unreadNotes'] }} | ||||
| 							</span> | ||||
| 						</div> | ||||
|  | ||||
| 						<div class="ui basic button" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0"> | ||||
| 							<i class="green archive icon"></i>Archived | ||||
| 							<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> --> | ||||
| 						</div> | ||||
| 					</div> | ||||
|  | ||||
| 					<div class="eight wide column" v-if="showClear"> | ||||
| 						<!-- <fast-filters /> --> | ||||
| 						<span class="ui fluid green button"  | ||||
| 							v-if="showClear" | ||||
| 							 | ||||
| 							@click="reset"> | ||||
| 							<i class="arrow circle left icon"></i>Back to All Notes | ||||
| 						</span> | ||||
| 					</div> | ||||
|  | ||||
| 				</div> | ||||
|  | ||||
| 			</div> | ||||
| @@ -64,7 +82,7 @@ | ||||
| 					<!-- pinned notes --> | ||||
| 					<div v-if="containsPinnednotes > 0" class="note-card-section"> | ||||
| 						<!-- ({{containsPinnednotes}}) --> | ||||
| 						<h4><i class="green pin icon"></i>Pinned <span v-if="!working"></span></h4> | ||||
| 						<h4><i class="green pin icon"></i>Pinned</h4> | ||||
| 						<div class="note-card-display-area"> | ||||
| 							<note-title-display-card  | ||||
| 								v-for="note in notes" | ||||
| @@ -80,7 +98,7 @@ | ||||
| 					<!-- normal notes  --> | ||||
| 					<div v-if="containsNormalNotes > 0" class="note-card-section"> | ||||
| 						<!-- ({{containsNormalNotes}}) --> | ||||
| 						<h4><i class="green file icon"></i>Notes <span v-if="!working"></span></h4> | ||||
| 						<h4><i class="green file icon"></i>Notes</h4> | ||||
| 						<div class="note-card-display-area"> | ||||
| 							<note-title-display-card  | ||||
| 								v-for="note in notes" | ||||
| @@ -194,9 +212,16 @@ | ||||
|  | ||||
| 			this.$parent.loginGateway() | ||||
|  | ||||
| 			this.$bus.$on('close_active_note', ({position, noteId}) => { | ||||
| 			//Update totals for app | ||||
| 			this.$store.dispatch('fetchAndUpdateUserTotals') | ||||
|  | ||||
| 			this.$bus.$on('close_active_note', ({position, noteId, modified}) => { | ||||
| 				this.closeNote(position) | ||||
| 				this.$store.dispatch('fetchAndUpdateUserTotals') | ||||
| 				if(modified){ | ||||
| 					this.updateSingleNote(noteId) | ||||
| 				} | ||||
| 				 | ||||
| 			}) | ||||
| 			this.$bus.$on('note_deleted', (noteId) => { | ||||
| 				//Remove deleted note from set, its deleted | ||||
| @@ -265,6 +290,7 @@ | ||||
| 			// this.$bus.$off() | ||||
| 		}, | ||||
| 		mounted() { | ||||
|  | ||||
| 			//Loads initial batch and tags | ||||
| 			this.reset() | ||||
| 		}, | ||||
| @@ -634,6 +660,31 @@ | ||||
| 						resolve(data) | ||||
| 					}) | ||||
| 				}) | ||||
| 			}, | ||||
| 			updateFastFilters(index){ | ||||
|  | ||||
| 				//clear out tags | ||||
| 				this.searchTags = [] | ||||
|  | ||||
| 				//A little hacky, brings user to notes page then filters on click | ||||
| 				if(this.$route.name != 'NotesPage'){ | ||||
| 					this.$router.push('/notes') | ||||
| 					setTimeout( () => { | ||||
| 						this.updateFastFilters(index) | ||||
| 					}, 500 ) | ||||
| 				} | ||||
|  | ||||
| 				const options = [ | ||||
| 					'withLinks', // 'Only Show Notes with Links' | ||||
| 					'withTags', // 'Only Show Notes with Tags' | ||||
| 					'onlyArchived', //'Only Show Archived Notes' | ||||
| 					'onlyShowSharedNotes', //Only show shared notes | ||||
| 				] | ||||
|  | ||||
| 				let filter = {} | ||||
| 				filter[options[index]] = 1 | ||||
|  | ||||
| 				this.$bus.$emit('update_fast_filters', filter) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -12,6 +12,7 @@ export default new Vuex.Store({ | ||||
| 		isUserOnMobile: false, | ||||
| 		isNoteSettingsOpen: false, //Little note settings pane | ||||
| 		socket: null, | ||||
| 		userTotals: null, | ||||
| 	}, | ||||
| 	mutations: { | ||||
| 		setLoginToken(state, userData){ | ||||
| @@ -88,6 +89,14 @@ export default new Vuex.Store({ | ||||
| 			//Put socket id in axios headers | ||||
| 			axios.defaults.headers.common['socketId'] = socket | ||||
| 			state.socket = socket | ||||
| 		}, | ||||
| 		setUserTotals(state, totalsObject){ | ||||
| 			//Save all the totals for the user | ||||
| 			state.userTotals = totalsObject | ||||
|  | ||||
| 			// Object.keys(totalsObject).forEach( key => { | ||||
| 			// 	console.log(key + ' -- ' + totalsObject[key]) | ||||
| 			// }) | ||||
| 		} | ||||
|  | ||||
| 	}, | ||||
| @@ -114,5 +123,16 @@ export default new Vuex.Store({ | ||||
| 		getSocket: state => { | ||||
| 			return state.socket | ||||
| 		}, | ||||
| 		totals: state => { | ||||
| 			return state.userTotals | ||||
| 		}, | ||||
| 	}, | ||||
| 	actions: { | ||||
| 		fetchAndUpdateUserTotals ({ commit }) { | ||||
| 			axios.post('/api/user/totals') | ||||
| 			.then( ({data}) => { | ||||
| 				commit('setUserTotals', data) | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| }) | ||||
| @@ -334,6 +334,10 @@ Note.get = (userId, noteId) => { | ||||
| 			WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId]) | ||||
| 		.then((rows, fields) => { | ||||
|  | ||||
| 			const created = Math.round((+new Date)/1000) | ||||
|  | ||||
| 			db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [created, noteId]) | ||||
|  | ||||
| 			//Return note data | ||||
| 			resolve(rows[0][0]) | ||||
|  | ||||
|   | ||||
| @@ -113,3 +113,56 @@ User.create = (username, password) => { | ||||
| 		 | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| //Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types | ||||
| User.getCounts = (userId) => { | ||||
| 	return new Promise((resolve, reject) => { | ||||
|  | ||||
| 		let countTotals = {} | ||||
|  | ||||
| 		db.promise().query( | ||||
| 			`SELECT | ||||
| 				SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes, | ||||
| 				SUM(pinned = 0 && archived = 1 && share_user_id IS NULL) AS archivedNotes, | ||||
| 				SUM(share_user_id IS NULL) AS totalNotes, | ||||
| 				SUM(share_user_id != ?) AS sharedToNotes, | ||||
| 				SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && updated > opened) ) AS unreadNotes | ||||
| 			FROM note  | ||||
| 			WHERE user_id = ?`, [userId, userId, userId, userId]) | ||||
| 		.then( (rows, fields) => { | ||||
|  | ||||
| 			Object.assign(countTotals, rows[0][0]) //combine results | ||||
|  | ||||
| 			return db.promise().query( | ||||
| 				`SELECT count(id) AS sharedFromNotes  | ||||
| 				FROM note WHERE share_user_id = ?`, [userId] | ||||
| 			) | ||||
| 		}) | ||||
| 		.then( (rows, fields) => { | ||||
|  | ||||
| 			Object.assign(countTotals, rows[0][0]) //combine results | ||||
|  | ||||
| 			return db.promise().query( | ||||
| 				`SELECT | ||||
| 					SUM(attachment_type = 1) as linkFiles, | ||||
| 					SUM(attachment_type != 1) as otherFiles, | ||||
| 					COUNT(id) as totalFiles | ||||
| 				FROM attachment WHERE visible = 1 | ||||
| 				AND user_id = ? | ||||
| 				`, [userId] | ||||
| 			) | ||||
| 		}).then( (rows, fields) => { | ||||
|  | ||||
| 			Object.assign(countTotals, rows[0][0]) //combine results | ||||
|  | ||||
| 			//Convert everything to an int or 0 | ||||
| 			Object.keys(countTotals).forEach( key => { | ||||
| 				const count = parseInt(countTotals[key]) | ||||
| 				countTotals[key] = count ? count : 0 | ||||
| 			}) | ||||
|  | ||||
| 			resolve(countTotals) | ||||
| 		}) | ||||
|  | ||||
| 	}) | ||||
| } | ||||
| @@ -46,4 +46,11 @@ router.post('/login', function (req, res) { | ||||
| 		}) | ||||
| }) | ||||
|  | ||||
| // fetch counts of users notes | ||||
| router.post('/totals', function (req, res) { | ||||
| 	User.getCounts(req.headers.userId) | ||||
| 	.then( countsObject => res.send( countsObject )) | ||||
| }) | ||||
|  | ||||
|  | ||||
| module.exports = router | ||||
		Reference in New Issue
	
	Block a user