Created a uniform menu for notes that works on mobile
Added list sorting Added shared notes Fixed some little bugs here and there
This commit is contained in:
		@@ -5,6 +5,8 @@
 | 
			
		||||
 | 
			
		||||
		<router-view />
 | 
			
		||||
 | 
			
		||||
		<global-notification />
 | 
			
		||||
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -15,6 +17,7 @@
 | 
			
		||||
export default {
 | 
			
		||||
	components: {
 | 
			
		||||
		'global-site-menu': require('@/components/GlobalSiteMenu.vue').default,
 | 
			
		||||
		'global-notification':require('@/components/GlobalNotificationComponent.vue').default
 | 
			
		||||
	},
 | 
			
		||||
	data: function(){ 
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,14 @@
 | 
			
		||||
	--text_color: #3d3d3d;
 | 
			
		||||
	--outline_color: rgba(34,36,38,.15);
 | 
			
		||||
	--border_color: rgba(34,36,38,.20);
 | 
			
		||||
 | 
			
		||||
	/*Global purple menu styles */
 | 
			
		||||
	--menu-border: #534c68;
 | 
			
		||||
	--menu-background: #221f2b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
	scrollbar-width: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.ui.basic.segment.no-fluf-segment {
 | 
			
		||||
@@ -28,7 +36,7 @@ div.ui.basic.segment.no-fluf-segment {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
 | 
			
		||||
body{
 | 
			
		||||
body {
 | 
			
		||||
	color: var(--text_color);
 | 
			
		||||
	background-color: var(--background_color);
 | 
			
		||||
	font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
 | 
			
		||||
@@ -101,6 +109,77 @@ a:hover {
 | 
			
		||||
	text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*//
 | 
			
		||||
//	Purple Global Menu
 | 
			
		||||
//*/
 | 
			
		||||
.note-menu {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
	/*display: block;*/
 | 
			
		||||
	display: inline-table;
 | 
			
		||||
	background: var(--menu-background);
 | 
			
		||||
	color: white;
 | 
			
		||||
	/*overflow: hidden;*/
 | 
			
		||||
	border: 1px solid var(--menu-border);
 | 
			
		||||
	/*height: 50px;*/
 | 
			
		||||
}
 | 
			
		||||
.note-menu > .nm-button {
 | 
			
		||||
	padding: 10px 15px;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	font-size: 1.2em;
 | 
			
		||||
	vertical-align: middle;
 | 
			
		||||
	/*height: 40px;*/
 | 
			
		||||
	display: table-cell;
 | 
			
		||||
	position: relative;
 | 
			
		||||
}
 | 
			
		||||
.nm-button i.icon {
 | 
			
		||||
	margin: 0;
 | 
			
		||||
}
 | 
			
		||||
.nm-button span {
 | 
			
		||||
	font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
.nm-button.right {
 | 
			
		||||
	float: right;
 | 
			
		||||
	border-left: 1px solid var(--menu-border);
 | 
			
		||||
}
 | 
			
		||||
.nm-button:hover {
 | 
			
		||||
	background-color: #534c68;
 | 
			
		||||
	color: white;
 | 
			
		||||
}
 | 
			
		||||
.nm-button + .nm-button {
 | 
			
		||||
	border-left: 1px solid #534c68;
 | 
			
		||||
}
 | 
			
		||||
/*.shrink-icons-on-mobile.note-menu span {
 | 
			
		||||
	display: none;
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
/* Shrink button text for mobile */
 | 
			
		||||
@media only screen and (max-width: 740px) {
 | 
			
		||||
	.note-menu .nm-button span {
 | 
			
		||||
		font-size: 0.7em;
 | 
			
		||||
		line-height: 0.4em;
 | 
			
		||||
		margin-left: 0;
 | 
			
		||||
	}
 | 
			
		||||
	.nm-button i.icon {
 | 
			
		||||
		width: 100%;
 | 
			
		||||
	}
 | 
			
		||||
	/*prevents buttons from being jammed into corners of round phones*/
 | 
			
		||||
	.shrink-icons-on-mobile.note-menu {
 | 
			
		||||
		padding: 0 20px;
 | 
			
		||||
	}
 | 
			
		||||
	.shrink-icons-on-mobile .nm-button {
 | 
			
		||||
		padding: 2px 3px;
 | 
			
		||||
	}
 | 
			
		||||
	.shrink-icons-on-mobile .nm-button i.icon {
 | 
			
		||||
		font-size: 0.7em;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*//
 | 
			
		||||
// Purple Global Menu 
 | 
			
		||||
//*/
 | 
			
		||||
 | 
			
		||||
.note-status-indicator {
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	width: 100px;
 | 
			
		||||
@@ -117,16 +196,17 @@ a:hover {
 | 
			
		||||
/* squire text styles */
 | 
			
		||||
	.squire-box {
 | 
			
		||||
		border: none;
 | 
			
		||||
		height: calc(100% - 60px);
 | 
			
		||||
		height: calc(100% - 69px);
 | 
			
		||||
		box-sizing: border-box;
 | 
			
		||||
		padding: 10px 15px 40px;
 | 
			
		||||
		padding: 10px 15px 10px;
 | 
			
		||||
		background: transparent;
 | 
			
		||||
		overflow-x: scroll; 
 | 
			
		||||
		/*color: var(--text_color);*/
 | 
			
		||||
		font-size: 1.2em;
 | 
			
		||||
		line-height: 1.5em;
 | 
			
		||||
		word-wrap: break-word;
 | 
			
		||||
		border-bottom: 1px solid #ccc;
 | 
			
		||||
		/*border-bottom: 1px solid #ccc;*/
 | 
			
		||||
		scrollbar-width: none;
 | 
			
		||||
	}
 | 
			
		||||
	/*Makes the first line real big */
 | 
			
		||||
	.squire-box p:first-child {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,16 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
		
 | 
			
		||||
	
 | 
			
		||||
	<div class="color-picker" :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
 | 
			
		||||
 | 
			
		||||
		<div class="floating-buttons">
 | 
			
		||||
	<div :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
 | 
			
		||||
		<div class="ui basic segment">
 | 
			
		||||
				<div class="ui fluid buttons">
 | 
			
		||||
					<div class="ui compact button" v-on:click="closeThisBitch">
 | 
			
		||||
						<i class="close icon"></i>
 | 
			
		||||
						Close
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui compact button" v-on:click="clearStyles">
 | 
			
		||||
		<div class="ui grid">
 | 
			
		||||
 | 
			
		||||
			<div class="ui sixteen wide center aligned column">
 | 
			
		||||
				<div class="ui fluid button" v-on:click="clearStyles">
 | 
			
		||||
					<i class="refresh icon"></i>
 | 
			
		||||
					Clear All Styles
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="ui basic segment">
 | 
			
		||||
		<div class="ui grid">
 | 
			
		||||
 | 
			
		||||
			<div class="row">
 | 
			
		||||
				<div class="sixteen wide column">
 | 
			
		||||
@@ -62,8 +52,7 @@
 | 
			
		||||
		</div>
 | 
			
		||||
		
 | 
			
		||||
	</div>
 | 
			
		||||
		<div class="shade-boy" v-on:click="closeThisBitch"></div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -157,43 +146,6 @@
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
<style type="text/css" scoped>
 | 
			
		||||
	.shade-boy {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
		right: 0;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		z-index: 250;
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
	}
 | 
			
		||||
	.floating-buttons {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		z-index: 444;
 | 
			
		||||
		right: 20px;
 | 
			
		||||
		left: calc(30% + 10px)
 | 
			
		||||
	}
 | 
			
		||||
	.color-picker {
 | 
			
		||||
		color: var(--text_color);
 | 
			
		||||
		background-color: var(--background_color);
 | 
			
		||||
 | 
			
		||||
		position: fixed;
 | 
			
		||||
 | 
			
		||||
		/*height: 100px;*/
 | 
			
		||||
		top: 0;
 | 
			
		||||
		right: 0;
 | 
			
		||||
		left: 30%;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
 | 
			
		||||
		padding: 10px;
 | 
			
		||||
 | 
			
		||||
		z-index: 500;
 | 
			
		||||
		border-left: 1px solid;
 | 
			
		||||
		border-color: var(--border_color) !important;
 | 
			
		||||
 | 
			
		||||
		overflow-x: scroll;
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	.icon-button {
 | 
			
		||||
		height: 40px;
 | 
			
		||||
		width: 14.2%;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,9 @@
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<form>
 | 
			
		||||
	<form data-tooltip="Upload File" data-inverted>
 | 
			
		||||
		<label :for="`upfile-${noteId}`" class="clickable">
 | 
			
		||||
			<i class="upload icon"></i>
 | 
			
		||||
			<span v-if="uploadPercentage != 0">{{uploadPercentage}}%</span>
 | 
			
		||||
			<span v-else>Upload</span>
 | 
			
		||||
			<nm-button icon="upload" :text="uploadStatusText"/>
 | 
			
		||||
		</label>
 | 
			
		||||
		<input class="hidden-up" type="file" :id="`upfile-${noteId}`" ref="file" v-on:change="handleFileUpload()" />
 | 
			
		||||
		<!-- <button v-if="file" v-on:click="uploadFileToServer()">Submit</button> -->
 | 
			
		||||
@@ -23,10 +21,13 @@
 | 
			
		||||
	export default {
 | 
			
		||||
		name: 'FileUploadButton',
 | 
			
		||||
		props: [ 'noteId' ],
 | 
			
		||||
		components: {
 | 
			
		||||
			'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
 | 
			
		||||
		},
 | 
			
		||||
		data () {
 | 
			
		||||
			return {
 | 
			
		||||
				file: null,
 | 
			
		||||
				uploadPercentage: 0,
 | 
			
		||||
				uploadStatusText: 'Upload',
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		mounted(){
 | 
			
		||||
@@ -44,12 +45,12 @@
 | 
			
		||||
					formData, {
 | 
			
		||||
						headers: { 'Content-Type': 'multipart/form-data' },
 | 
			
		||||
						onUploadProgress: ( progressEvent ) => {
 | 
			
		||||
					        this.uploadPercentage = parseInt( 
 | 
			
		||||
					        this.uploadStatusText = parseInt( 
 | 
			
		||||
					        	Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) )
 | 
			
		||||
					      }
 | 
			
		||||
					}
 | 
			
		||||
				).then(results => {
 | 
			
		||||
					this.uploadPercentage = 'Working'
 | 
			
		||||
					this.uploadStatusText = 'Working'
 | 
			
		||||
					this.file = null
 | 
			
		||||
 | 
			
		||||
					// console.log('File upload results')
 | 
			
		||||
@@ -58,17 +59,19 @@
 | 
			
		||||
					const name = results.data.fileName
 | 
			
		||||
					const location = results.data.goodFileName
 | 
			
		||||
 | 
			
		||||
					this.$bus.$emit('notification', 'Processing Upload')
 | 
			
		||||
 | 
			
		||||
					if(name && location){
 | 
			
		||||
 | 
			
		||||
						this.uploadPercentage = 0
 | 
			
		||||
						this.uploadStatusText = 'Upload File'
 | 
			
		||||
 | 
			
		||||
						const imageCode = `<img alt="image" src="/api/static/${location}">`
 | 
			
		||||
						const imageCode = `<img alt="image" src="/api/static/thumb_${location}">`
 | 
			
		||||
 | 
			
		||||
						this.$bus.$emit('new_file_upload', {noteId: this.noteId, imageCode})
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
				.catch(results => {
 | 
			
		||||
					this.uploadPercentage = 0
 | 
			
		||||
					this.uploadStatusText = 0
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			handleFileUpload() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										82
									
								
								client/src/components/GlobalNotificationComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								client/src/components/GlobalNotificationComponent.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
<style type="text/css" scoped>
 | 
			
		||||
	
 | 
			
		||||
	.popup-body {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
		bottom: 15px;
 | 
			
		||||
		right: 15px;
 | 
			
		||||
		min-height: 50px;
 | 
			
		||||
		min-width: 200px;
 | 
			
		||||
		max-width: calc(100% - 20px);
 | 
			
		||||
		z-index: 1002;
 | 
			
		||||
		border-top: 2px solid #21ba45;
 | 
			
		||||
		box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
 | 
			
		||||
		border-top-right-radius: 4px;
 | 
			
		||||
		border-top-left-radius: 4px;
 | 
			
		||||
 | 
			
		||||
		color: var(--text_color);
 | 
			
		||||
		background-color: var(--background_color);
 | 
			
		||||
	}
 | 
			
		||||
	.popup-row {
 | 
			
		||||
		padding: 1em 5px;
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
		white-space: nowrap;
 | 
			
		||||
	}
 | 
			
		||||
	.popup-row > span {
 | 
			
		||||
		width: calc(100% - 50px);
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
		text-align: center;
 | 
			
		||||
		box-sizing: border-box;
 | 
			
		||||
		padding: 0 10px 0;
 | 
			
		||||
		font-size: 1.25em;
 | 
			
		||||
	}
 | 
			
		||||
	.popup-row + .popup-row {
 | 
			
		||||
		border-top: 1px solid #000;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="popup-body" v-on:click="dismiss" v-if="notifications.length > 0">
 | 
			
		||||
		<div class="popup-row" v-for="item in notifications">
 | 
			
		||||
			<i class="disabled angle left icon"></i>
 | 
			
		||||
			<span>{{ item }}</span>
 | 
			
		||||
			<i class="disabled angle right icon"></i>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
	import axios from 'axios'
 | 
			
		||||
 | 
			
		||||
	export default {
 | 
			
		||||
		name: 'GlobalNotificationComponent',
 | 
			
		||||
		data () {
 | 
			
		||||
			return {
 | 
			
		||||
				notifications: [],
 | 
			
		||||
				totalTimeout: null,
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		beforeMount(){
 | 
			
		||||
			this.$bus.$on('notification', info => {
 | 
			
		||||
				this.displayNotification(info)
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		mounted(){
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
		},
 | 
			
		||||
		methods: {
 | 
			
		||||
			displayNotification(newNotification){
 | 
			
		||||
				this.notifications.push(newNotification)
 | 
			
		||||
				clearTimeout(this.totalTimeout)
 | 
			
		||||
				this.totalTimeout = setTimeout(() => {
 | 
			
		||||
					this.dismiss()
 | 
			
		||||
				}, 4000)
 | 
			
		||||
			},
 | 
			
		||||
			dismiss(){
 | 
			
		||||
				this.notifications = []
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -163,6 +163,12 @@
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div class="menu-section" v-if="loggedIn">
 | 
			
		||||
				<div v-on:click="updateFastFilters(3)" class="menu-item menu-button">
 | 
			
		||||
					<i class="mail 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="archive icon"></i>Archived
 | 
			
		||||
@@ -239,6 +245,7 @@
 | 
			
		||||
			if(this.mobile){
 | 
			
		||||
				this.menuOpen = false
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		},
 | 
			
		||||
		computed: {
 | 
			
		||||
			loggedIn () {
 | 
			
		||||
@@ -279,6 +286,7 @@
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			destroyLoginToken() {
 | 
			
		||||
				this.$bus.$emit('notification', 'Logged Out')
 | 
			
		||||
				this.$store.commit('destroyLoginToken')
 | 
			
		||||
				this.$router.push('/')
 | 
			
		||||
			},
 | 
			
		||||
@@ -309,6 +317,7 @@
 | 
			
		||||
					'withLinks', // 'Only Show Notes with Links'
 | 
			
		||||
					'withTags', // 'Only Show Notes with Tags'
 | 
			
		||||
					'onlyArchived', //'Only Show Archived Notes'
 | 
			
		||||
					'onlyShowSharedNotes', //Only show shared notes
 | 
			
		||||
				]
 | 
			
		||||
 | 
			
		||||
				let filter = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,30 +17,23 @@
 | 
			
		||||
 | 
			
		||||
		<div class="note-menu">
 | 
			
		||||
 | 
			
		||||
			<div class="nm-button" v-on:click="close">
 | 
			
		||||
				<i class="close icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="nm-button" v-on:click="toggleList('ol')">
 | 
			
		||||
				<i class="list ol icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="nm-button" v-on:click="toggleList('ul')">
 | 
			
		||||
				<i class="tasks icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="nm-button" v-on:click="toggleBold()">
 | 
			
		||||
				<i class="bold icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<nm-button v-on:click.native="close" icon="close" />
 | 
			
		||||
 | 
			
		||||
			<div class="nm-button" v-on:click="toggleItalic()">
 | 
			
		||||
				<i class="quote left icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<nm-button v-on:click.native="toggleList('ol')" icon="list ol" />
 | 
			
		||||
			
 | 
			
		||||
			<div class="nm-button" v-on:click="modifyFont('2.286em') ">
 | 
			
		||||
				<i class="text height icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<nm-button v-on:click.native="toggleList('ul')" icon="tasks" />
 | 
			
		||||
			
 | 
			
		||||
			<div v-if="usersOnNote > 1" class="nm-button">
 | 
			
		||||
				<i class="green user circle icon"></i>{{usersOnNote}}
 | 
			
		||||
			</div>
 | 
			
		||||
			<nm-button v-on:click.native="toggleBold()" icon="bold" />
 | 
			
		||||
			
 | 
			
		||||
			<nm-button v-on:click.native="toggleItalic()" icon="quote left" />
 | 
			
		||||
 | 
			
		||||
			<nm-button v-on:click.native="modifyFont('1.4em')" icon="text height" />
 | 
			
		||||
 | 
			
		||||
			<nm-button v-on:click.native="undoCustom()" icon="undo" />
 | 
			
		||||
 | 
			
		||||
			<nm-button v-if="usersOnNote > 1" icon="green user circle" />
 | 
			
		||||
 | 
			
		||||
			<nm-button icon="ellipsis horizontal" v-on:click.native="showNoteOptions = !showNoteOptions" />
 | 
			
		||||
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
@@ -51,47 +44,136 @@
 | 
			
		||||
			<div class="ui green button">{{statusText}}</div>
 | 
			
		||||
		</span>
 | 
			
		||||
 | 
			
		||||
		<!-- Note options on the bottom of note -->
 | 
			
		||||
		<div class="all-settings" :class="{ 'low-settings':!extraToolbarsVisible }">
 | 
			
		||||
 | 
			
		||||
			<div class="note-menu shrink-icons-on-mobile">
 | 
			
		||||
 | 
			
		||||
				<!-- Pin Button  -->
 | 
			
		||||
				<nm-button
 | 
			
		||||
					v-on:click.native="onToggleArchived"
 | 
			
		||||
					:icon="(archived == 1)?'green archive':'archive'"
 | 
			
		||||
					:text="(archived == 1)?'Archived':'Archive'"
 | 
			
		||||
					tip="Show in archive"
 | 
			
		||||
					:showText="true"
 | 
			
		||||
				></nm-button>
 | 
			
		||||
 | 
			
		||||
				<!-- archive button -->
 | 
			
		||||
				<nm-button
 | 
			
		||||
					v-on:click.native="onTogglePinned"
 | 
			
		||||
					:icon="(pinned == 1)?'green pin':'pin'"
 | 
			
		||||
					:text="(pinned == 1)?'Pinned':'Pin'"
 | 
			
		||||
					tip="Pin to top of list"
 | 
			
		||||
					:showText="true"
 | 
			
		||||
				></nm-button>
 | 
			
		||||
	
 | 
			
		||||
				<!-- colors button -->
 | 
			
		||||
				<nm-button
 | 
			
		||||
					v-on:click.native="showColorPicker"
 | 
			
		||||
					icon="paint brush"
 | 
			
		||||
					text="Colors"
 | 
			
		||||
					tip="Colors"
 | 
			
		||||
				></nm-button>
 | 
			
		||||
 | 
			
		||||
				<!-- add images panel -->
 | 
			
		||||
				<nm-button
 | 
			
		||||
					v-on:click.native="showFilesSideMenu = !showFilesSideMenu"
 | 
			
		||||
					icon="image"
 | 
			
		||||
					text="Images"
 | 
			
		||||
					tip="Images"
 | 
			
		||||
				></nm-button>
 | 
			
		||||
 | 
			
		||||
				<!-- Tags  -->
 | 
			
		||||
				<nm-button
 | 
			
		||||
					v-on:click.native="showTagSlideMenu = !showTagSlideMenu"
 | 
			
		||||
					icon="tags"
 | 
			
		||||
					text="Tags"
 | 
			
		||||
					tip="Tags"
 | 
			
		||||
				></nm-button>
 | 
			
		||||
 | 
			
		||||
				<!-- file upload button  -->
 | 
			
		||||
				<file-upload-button 
 | 
			
		||||
					class="nm-button" 
 | 
			
		||||
					:noteId="noteid" />
 | 
			
		||||
 | 
			
		||||
				<!-- files button -->
 | 
			
		||||
				<nm-button
 | 
			
		||||
					v-on:click.native="openEditAttachment"
 | 
			
		||||
					icon="folder"
 | 
			
		||||
					text="Files"
 | 
			
		||||
					tip="Files on Note"
 | 
			
		||||
					:showText="true"
 | 
			
		||||
				></nm-button>
 | 
			
		||||
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<!-- Side slide menus for colors, tags and images -->
 | 
			
		||||
	
 | 
			
		||||
		<side-slide-menu v-if="colorPickerVisible" v-on:close="colorPickerVisible = false" name="colors">
 | 
			
		||||
			<color-picker
 | 
			
		||||
			v-if="colorPickerVisible" 
 | 
			
		||||
			:location="colorPickerLocation" 
 | 
			
		||||
				@changeColor="onChangeColor"
 | 
			
		||||
				@close="onCloseColorChanger"
 | 
			
		||||
				:style-object="styleObject"
 | 
			
		||||
			/>
 | 
			
		||||
		</side-slide-menu>
 | 
			
		||||
 | 
			
		||||
		<!-- Note options on the bottom of note -->
 | 
			
		||||
		<div class="all-settings" :class="{ 'low-settings':!extraToolbarsVisible }">
 | 
			
		||||
 | 
			
		||||
			<div class="note-menu">
 | 
			
		||||
 | 
			
		||||
				<!-- <note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/><br> -->
 | 
			
		||||
 | 
			
		||||
				<!-- Pin Button  -->
 | 
			
		||||
				<div @click="onToggleArchived" class="nm-button">
 | 
			
		||||
					<i class="archive icon" :class="{green:(archived == 1)}"></i> 
 | 
			
		||||
					{{(archived == 1)?'Archived':'Archive'}}
 | 
			
		||||
		<side-slide-menu v-if="showTagSlideMenu" v-on:close="showTagSlideMenu = false" name="tags">
 | 
			
		||||
			<div class="ui basic segment">
 | 
			
		||||
				<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
 | 
			
		||||
			</div>
 | 
			
		||||
				<!-- archive button -->
 | 
			
		||||
				<div @click="onTogglePinned" class="nm-button">
 | 
			
		||||
					<i class="pin icon" :class="{green:(pinned == 1)}"></i> 
 | 
			
		||||
					{{(pinned == 1)?'Pinned':'Pin'}}
 | 
			
		||||
				</div>
 | 
			
		||||
				<!-- colors button -->
 | 
			
		||||
				<span class="nm-button" v-on:click="showColorPicker">
 | 
			
		||||
					<i class="paint brush icon"></i>
 | 
			
		||||
					Colors
 | 
			
		||||
				</span>
 | 
			
		||||
		</side-slide-menu>
 | 
			
		||||
 | 
			
		||||
				<!-- attachment button -->
 | 
			
		||||
				<div class="nm-button" v-on:click="openEditAttachment">
 | 
			
		||||
					<i class="folder icon"></i> Files
 | 
			
		||||
		<side-slide-menu v-if="showFilesSideMenu" v-on:close="showFilesSideMenu = false" name="images">
 | 
			
		||||
			<div class="ui basic segment">
 | 
			
		||||
				<simple-attachment-note
 | 
			
		||||
					v-on:close="showFilesSideMenu = false"
 | 
			
		||||
					:note-id="noteid" 
 | 
			
		||||
					:squire-editor="editor">
 | 
			
		||||
				</simple-attachment-note>
 | 
			
		||||
			</div>
 | 
			
		||||
				<!-- file upload button  -->
 | 
			
		||||
				<file-upload-button class="nm-button" :noteId="noteid" />
 | 
			
		||||
		</side-slide-menu>
 | 
			
		||||
 | 
			
		||||
		<side-slide-menu v-if="showNoteOptions" v-on:close="showNoteOptions = false" name="note-options">
 | 
			
		||||
			<div class="ui basic padded segment">
 | 
			
		||||
				<div class="ui grid">
 | 
			
		||||
					<div class="sixteen wide column">
 | 
			
		||||
						<h2>Additional Note Options</h2>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="sixteen wide column">
 | 
			
		||||
						<div class="ui labeled icon fluid basic button" v-on:click="sortList">
 | 
			
		||||
							<i class="sort amount up icon"></i>
 | 
			
		||||
							Sort List items (Move checked to bottom)
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
		<!-- <div class="shade" v-on:click="showAllSettings = false"></div> -->
 | 
			
		||||
					<div class="eight wide column">
 | 
			
		||||
						<div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems">
 | 
			
		||||
							<i class="trash icon"></i>
 | 
			
		||||
							Delete Checked Items
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="eight wide column">
 | 
			
		||||
						<div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems">
 | 
			
		||||
							<i class="list ul icon"></i>
 | 
			
		||||
							Uncheck all Checked items
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="sixteen wide column">
 | 
			
		||||
						<div class="ui labeled icon fluid basic button" v-on:click="undoCustom">
 | 
			
		||||
							<i class="undo icon"></i>
 | 
			
		||||
							Undo last change
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="sixteen wide column" v-if="rawTextId > 0">
 | 
			
		||||
						<share-note-component 
 | 
			
		||||
							:note-id="noteid"
 | 
			
		||||
							:raw-text-id="rawTextId"
 | 
			
		||||
							:share-username="shareUsername"
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</side-slide-menu>
 | 
			
		||||
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -105,10 +187,15 @@
 | 
			
		||||
	name: 'InputNotes',
 | 
			
		||||
		props: [ 'noteid', 'position' ],
 | 
			
		||||
		components:{
 | 
			
		||||
			'note-tag-edit': require('@/components/NoteTagEdit.vue').default,
 | 
			
		||||
			'color-picker': require('@/components/ColorPicker.vue').default,
 | 
			
		||||
			'file-upload-button': require('@/components/FileUploadButton.vue').default,
 | 
			
		||||
			'delete-button': require('@/components/NoteDeleteButtonComponent.vue').default,
 | 
			
		||||
			'note-tag-edit': () => import('@/components/NoteTagEdit.vue'),
 | 
			
		||||
			'color-picker': () => import('@/components/ColorPicker.vue'),
 | 
			
		||||
			'file-upload-button': () => import('@/components/FileUploadButton.vue'),
 | 
			
		||||
			// 'delete-button': () => import('@/components/NoteDeleteButtonComponent.vue'),
 | 
			
		||||
			'side-slide-menu': () => import('@/components/SideSlideMenuComponent.vue'),
 | 
			
		||||
			'simple-attachment-note': () => import('@/components/SimpleAttachmentNoteComponent.vue'),
 | 
			
		||||
			'share-note-component': () => import('@/components/ShareNoteComponent.vue'),
 | 
			
		||||
 | 
			
		||||
			'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
 | 
			
		||||
		},
 | 
			
		||||
		data(){
 | 
			
		||||
			return {
 | 
			
		||||
@@ -116,6 +203,8 @@
 | 
			
		||||
				loadingMessage: 'Loading Note',
 | 
			
		||||
				currentNoteId: 0,
 | 
			
		||||
				noteText: '',
 | 
			
		||||
				rawTextId: 0,
 | 
			
		||||
				shareUsername: null,
 | 
			
		||||
				diffNoteText: '',
 | 
			
		||||
				statusText: 'Saved',
 | 
			
		||||
				lastNoteHash: null,
 | 
			
		||||
@@ -130,7 +219,7 @@
 | 
			
		||||
				styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
 | 
			
		||||
 | 
			
		||||
				sizeDown: false, //Used to animate close state
 | 
			
		||||
				colorPickerVisible: false, 
 | 
			
		||||
				
 | 
			
		||||
				colorPickerLocation: null,
 | 
			
		||||
 | 
			
		||||
                tinymce: null, //Initialized editor instance
 | 
			
		||||
@@ -145,6 +234,10 @@
 | 
			
		||||
                usersOnNote: 0,
 | 
			
		||||
 | 
			
		||||
                extraToolbarsVisible: true,
 | 
			
		||||
                showTagSlideMenu: false,
 | 
			
		||||
                colorPickerVisible: false, 
 | 
			
		||||
                showFilesSideMenu: false,
 | 
			
		||||
                showNoteOptions: false,
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		watch: {
 | 
			
		||||
@@ -168,12 +261,13 @@
 | 
			
		||||
				if(this.noteid == noteId && this.editor){
 | 
			
		||||
					this.editor.moveCursorToEnd()
 | 
			
		||||
					this.editor.insertHTML(imageCode)
 | 
			
		||||
					this.save()
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		beforeDestroy(){
 | 
			
		||||
 | 
			
		||||
			this.$io.emit('leave_room', this.noteid)
 | 
			
		||||
			this.$io.emit('leave_room', this.rawTextId)
 | 
			
		||||
 | 
			
		||||
			document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
 | 
			
		||||
 | 
			
		||||
@@ -189,24 +283,7 @@
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
 | 
			
		||||
				this.loadNote(this.noteid)
 | 
			
		||||
 | 
			
		||||
				//Tell server to push this note into a room
 | 
			
		||||
				this.$io.emit('join_room', this.noteid)
 | 
			
		||||
 | 
			
		||||
				this.$io.on('update_user_count', userCount => {
 | 
			
		||||
					this.usersOnNote = userCount
 | 
			
		||||
			})
 | 
			
		||||
				
 | 
			
		||||
				//Server will hand deliver diffs from other notes to this one
 | 
			
		||||
				this.$io.on('incoming_diff', incomingDiffData => {
 | 
			
		||||
					this.patchText(incomingDiffData)
 | 
			
		||||
				})
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
		},
 | 
			
		||||
		methods: {
 | 
			
		||||
			initSquire(){
 | 
			
		||||
@@ -215,7 +292,7 @@
 | 
			
		||||
				this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
 | 
			
		||||
				this.setText(this.noteText)
 | 
			
		||||
 | 
			
		||||
				//Open links when clicked in editor
 | 
			
		||||
				//Click Event - Open links when clicked in editor or toggle checks
 | 
			
		||||
				this.editor.addEventListener('click', e => {
 | 
			
		||||
 | 
			
		||||
					//Link clicked in editor - open link
 | 
			
		||||
@@ -284,6 +361,7 @@
 | 
			
		||||
			},
 | 
			
		||||
			//If nothing is selected, select the entire line
 | 
			
		||||
			selectLineIfNoSelect(){
 | 
			
		||||
 | 
			
		||||
				//Select entire line if range is not set 
 | 
			
		||||
				let selection = this.editor.getSelection()
 | 
			
		||||
 | 
			
		||||
@@ -297,6 +375,7 @@
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			modifyFont(inSize){
 | 
			
		||||
 | 
			
		||||
				this.selectLineIfNoSelect()
 | 
			
		||||
 | 
			
		||||
				let fontInfo = this.editor.getFontInfo()
 | 
			
		||||
@@ -338,6 +417,149 @@
 | 
			
		||||
					this.editor.italic()
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			undoCustom(){
 | 
			
		||||
				//The same as pressing CTRL + Z 
 | 
			
		||||
				// this.editor.focus()
 | 
			
		||||
				// document.execCommand("undo", false, null)
 | 
			
		||||
				this.editor.undo()
 | 
			
		||||
			},
 | 
			
		||||
			uncheckAllListItems(){
 | 
			
		||||
				//
 | 
			
		||||
				// Uncheck All List Items
 | 
			
		||||
				//
 | 
			
		||||
 | 
			
		||||
				//Close menu if user is on mobile, then sort list
 | 
			
		||||
				if(this.$store.getters.getIsUserOnMobile){
 | 
			
		||||
					this.showNoteOptions = false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				//Fetch the container
 | 
			
		||||
				let container = document.getElementById('squire-id')
 | 
			
		||||
 | 
			
		||||
				Array.from( container.getElementsByClassName('active') ).forEach(item => {
 | 
			
		||||
					item.classList.remove('active');
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			deleteCompletedListItems(){
 | 
			
		||||
				//
 | 
			
		||||
				// Delete Completed List Items
 | 
			
		||||
				//
 | 
			
		||||
 | 
			
		||||
				//Close menu if user is on mobile, then sort list
 | 
			
		||||
				if(this.$store.getters.getIsUserOnMobile){
 | 
			
		||||
					this.showNoteOptions = false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				//Fetch the container
 | 
			
		||||
				let container = document.getElementById('squire-id')
 | 
			
		||||
 | 
			
		||||
				//Go through each item, on first level, look for Unordered Lists
 | 
			
		||||
				container.childNodes.forEach( (node) => {
 | 
			
		||||
					if(node.nodeName == 'UL'){
 | 
			
		||||
 | 
			
		||||
						//Create two categories, done and not done list items
 | 
			
		||||
						let undoneElements = document.createDocumentFragment()
 | 
			
		||||
 | 
			
		||||
						//Go through each item in each list we found
 | 
			
		||||
						node.childNodes.forEach( (checkListItem, index) => {
 | 
			
		||||
 | 
			
		||||
							//Skip Embedded lists, they are handled with the list item above them. Keep lists with intented items together
 | 
			
		||||
							if(checkListItem.nodeName == 'UL'){
 | 
			
		||||
								return
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							//Check if list item has active class
 | 
			
		||||
							const checkedItem = checkListItem.classList.contains('active')
 | 
			
		||||
 | 
			
		||||
							//Check if the next item is a list, Keep lists with intented items together
 | 
			
		||||
							let sublist = null
 | 
			
		||||
							if(node.childNodes[index+1] && node.childNodes[index+1].nodeName == 'UL'){
 | 
			
		||||
								sublist = node.childNodes[index+1]
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							//Push checked items and their sub lists to the done set
 | 
			
		||||
							if(!checkedItem){
 | 
			
		||||
 | 
			
		||||
								undoneElements.appendChild( checkListItem.cloneNode(true) )
 | 
			
		||||
								if(sublist){
 | 
			
		||||
									undoneElements.appendChild( sublist.cloneNode(true) )
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
						})
 | 
			
		||||
 | 
			
		||||
						//Remove all HTML from node, push unfinished items, then finished below them
 | 
			
		||||
						node.innerHTML = null
 | 
			
		||||
						node.appendChild(undoneElements)
 | 
			
		||||
						
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			sortList(){
 | 
			
		||||
				//
 | 
			
		||||
				// Sort list, checked at the bottom, unchecked at the top
 | 
			
		||||
				//
 | 
			
		||||
 | 
			
		||||
				//Close menu if user is on mobile, then sort list
 | 
			
		||||
				if(this.$store.getters.getIsUserOnMobile){
 | 
			
		||||
					this.showNoteOptions = false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				//Fetch the container
 | 
			
		||||
				let container = document.getElementById('squire-id')
 | 
			
		||||
 | 
			
		||||
				//Go through each item, on first level, look for Unordered Lists
 | 
			
		||||
				container.childNodes.forEach( (node) => {
 | 
			
		||||
					if(node.nodeName == 'UL'){
 | 
			
		||||
 | 
			
		||||
						//Create two categories, done and not done list items
 | 
			
		||||
						let doneElements = document.createDocumentFragment()
 | 
			
		||||
						let undoneElements = document.createDocumentFragment()
 | 
			
		||||
 | 
			
		||||
						//Go through each item in each list we found
 | 
			
		||||
						node.childNodes.forEach( (checkListItem, index) => {
 | 
			
		||||
 | 
			
		||||
							//Skip Embedded lists, they are handled with the list item above them. Keep lists with intented items together
 | 
			
		||||
							if(checkListItem.nodeName == 'UL'){
 | 
			
		||||
								return
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							//Check if list item has active class
 | 
			
		||||
							const checkedItem = checkListItem.classList.contains('active')
 | 
			
		||||
 | 
			
		||||
							//Check if the next item is a list, Keep lists with intented items together
 | 
			
		||||
							let sublist = null
 | 
			
		||||
							if(node.childNodes[index+1] && node.childNodes[index+1].nodeName == 'UL'){
 | 
			
		||||
								sublist = node.childNodes[index+1]
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							//Push checked items and their sub lists to the done set
 | 
			
		||||
							if(checkedItem){
 | 
			
		||||
 | 
			
		||||
								doneElements.appendChild( checkListItem.cloneNode(true) )
 | 
			
		||||
								if(sublist){
 | 
			
		||||
									doneElements.appendChild( sublist.cloneNode(true) )
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
							} else {
 | 
			
		||||
 | 
			
		||||
								undoneElements.appendChild( checkListItem.cloneNode(true) )
 | 
			
		||||
								if(sublist){
 | 
			
		||||
									undoneElements.appendChild( sublist.cloneNode(true) )
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
						})
 | 
			
		||||
 | 
			
		||||
						//Remove all HTML from node, push unfinished items, then finished below them
 | 
			
		||||
						node.innerHTML = null
 | 
			
		||||
						node.appendChild(undoneElements)
 | 
			
		||||
						node.appendChild(doneElements)
 | 
			
		||||
						
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			setText(inText){
 | 
			
		||||
 | 
			
		||||
				this.editor.setHTML(inText)
 | 
			
		||||
@@ -349,13 +571,16 @@
 | 
			
		||||
				return this.editor.getHTML()
 | 
			
		||||
			},
 | 
			
		||||
			showColorPicker(event){
 | 
			
		||||
 | 
			
		||||
				this.colorPickerVisible = !this.colorPickerVisible
 | 
			
		||||
				this.colorPickerLocation = {'x':event.clientX, 'y':event.clientY}
 | 
			
		||||
			},
 | 
			
		||||
			openEditAttachment(){
 | 
			
		||||
 | 
			
		||||
				this.$router.push('/attachments/note/'+this.currentNoteId)
 | 
			
		||||
			},
 | 
			
		||||
			onTogglePinned(){
 | 
			
		||||
 | 
			
		||||
				if(this.pinned == 0){
 | 
			
		||||
					this.pinned = 1
 | 
			
		||||
				} else {
 | 
			
		||||
@@ -366,6 +591,7 @@
 | 
			
		||||
				this.save()
 | 
			
		||||
			},
 | 
			
		||||
			onToggleArchived(){
 | 
			
		||||
 | 
			
		||||
				if(this.archived == 0){
 | 
			
		||||
					this.archived = 1
 | 
			
		||||
				} else {
 | 
			
		||||
@@ -376,6 +602,7 @@
 | 
			
		||||
				this.save()
 | 
			
		||||
			},
 | 
			
		||||
			onCloseColorChanger(){
 | 
			
		||||
 | 
			
		||||
				this.colorPickerVisible = false
 | 
			
		||||
			},
 | 
			
		||||
			onChangeColor(newStyleObject){
 | 
			
		||||
@@ -403,6 +630,8 @@
 | 
			
		||||
 | 
			
		||||
						//Set up local data
 | 
			
		||||
						vm.currentNoteId = noteId
 | 
			
		||||
						this.rawTextId = response.data.rawTextId
 | 
			
		||||
						this.shareUsername = response.data.shareUsername
 | 
			
		||||
 | 
			
		||||
						vm.noteText = response.data.text
 | 
			
		||||
						vm.diffNoteText = response.data.text
 | 
			
		||||
@@ -423,7 +652,8 @@
 | 
			
		||||
						this.loading = false
 | 
			
		||||
 | 
			
		||||
						vm.$nextTick(() => {
 | 
			
		||||
							// this.initTinyMce()
 | 
			
		||||
							
 | 
			
		||||
							this.setupWebSockets()
 | 
			
		||||
							this.initSquire()
 | 
			
		||||
						})
 | 
			
		||||
 | 
			
		||||
@@ -473,8 +703,8 @@
 | 
			
		||||
 | 
			
		||||
  					// console.log(patch_text)
 | 
			
		||||
					this.$io.emit('note_diff', {
 | 
			
		||||
						id:this.currentNoteId,
 | 
			
		||||
						diff:patch_text
 | 
			
		||||
						id: this.rawTextId,
 | 
			
		||||
						diff: patch_text
 | 
			
		||||
					})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -575,9 +805,6 @@
 | 
			
		||||
 | 
			
		||||
					}, 20)
 | 
			
		||||
				// }
 | 
			
		||||
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
			},
 | 
			
		||||
			xpath(el) {
 | 
			
		||||
				//Skip things we can't use
 | 
			
		||||
@@ -592,7 +819,8 @@
 | 
			
		||||
				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) { 
 | 
			
		||||
			getElementByXPath(xpath){
 | 
			
		||||
 | 
			
		||||
				return new XPathEvaluator()
 | 
			
		||||
					.createExpression(xpath)
 | 
			
		||||
					.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE) .singleNodeValue 
 | 
			
		||||
@@ -661,7 +889,6 @@
 | 
			
		||||
						})
 | 
			
		||||
					// }, 300)
 | 
			
		||||
				})
 | 
			
		||||
				
 | 
			
		||||
			},
 | 
			
		||||
			checkForUpdatedNote(){
 | 
			
		||||
 | 
			
		||||
@@ -669,7 +896,7 @@
 | 
			
		||||
 | 
			
		||||
				//If user leaves page then returns to page, reload the first batch
 | 
			
		||||
				if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
 | 
			
		||||
					console.log('Checking for note updates after visibility change.')
 | 
			
		||||
					// console.log('Checking for note updates after visibility change.')
 | 
			
		||||
 | 
			
		||||
					const postData = { 
 | 
			
		||||
						noteId:this.currentNoteId, 
 | 
			
		||||
@@ -726,7 +953,19 @@
 | 
			
		||||
						return
 | 
			
		||||
					}, 300)
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			setupWebSockets(){
 | 
			
		||||
				//Tell server to push this note into a room
 | 
			
		||||
				this.$io.emit('join_room', this.rawTextId)
 | 
			
		||||
 | 
			
		||||
				this.$io.on('update_user_count', userCount => {
 | 
			
		||||
					this.usersOnNote = userCount
 | 
			
		||||
				})
 | 
			
		||||
				
 | 
			
		||||
				//Server will hand deliver diffs from other notes to this one
 | 
			
		||||
				this.$io.on('incoming_diff', incomingDiffData => {
 | 
			
		||||
					this.patchText(incomingDiffData)
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -734,38 +973,6 @@
 | 
			
		||||
 | 
			
		||||
<style type="text/css" scoped>
 | 
			
		||||
 | 
			
		||||
	/* squire note menu button */
 | 
			
		||||
 | 
			
		||||
	.note-menu {
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		display: block;
 | 
			
		||||
		background: #221f2b;
 | 
			
		||||
		color: white;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
		border: 1px solid #534c68;
 | 
			
		||||
	}
 | 
			
		||||
	.note-menu > .nm-button {
 | 
			
		||||
		padding: 10px 10px;
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
		text-align: center;
 | 
			
		||||
		box-sizing: border-box;
 | 
			
		||||
		font-size: 1.1em;
 | 
			
		||||
		vertical-align: middle;
 | 
			
		||||
		/*height: 40px;*/
 | 
			
		||||
		display: table-cell;
 | 
			
		||||
	}
 | 
			
		||||
	.nm-button > i {
 | 
			
		||||
		font-size: 1.1em;
 | 
			
		||||
		margin: 0;
 | 
			
		||||
	}
 | 
			
		||||
	.nm-button:hover {
 | 
			
		||||
		background-color: #534c68;
 | 
			
		||||
		color: white;
 | 
			
		||||
	}
 | 
			
		||||
	.nm-button + .nm-button {
 | 
			
		||||
		border-left: 1px solid #534c68;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* squire styles */
 | 
			
		||||
 | 
			
		||||
	/*Settings manager styles */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								client/src/components/NoteMenuButtonComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								client/src/components/NoteMenuButtonComponent.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="nm-button" :class="moreClass" :data-tooltip="tip" data-inverted>
 | 
			
		||||
		<!-- Display Icon and text  -->
 | 
			
		||||
		<i v-if="icon" :class="`${icon} icon`"></i>
 | 
			
		||||
		<span v-if="(text && mobile) || (text && showText)">{{text}}</span>
 | 
			
		||||
 | 
			
		||||
		<slot></slot>
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
	/*
 | 
			
		||||
		Menu button
 | 
			
		||||
			Single Icon View
 | 
			
		||||
			Single Icon With small text on mobile
 | 
			
		||||
			Tooltips on desktop
 | 
			
		||||
				Tooltip above or below
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	export default {
 | 
			
		||||
		name: 'NoteMenuButtonComponent',
 | 
			
		||||
		props: [ 'icon', 'text', 'tooltip', 'moreClass', 'showText', 'tip'],
 | 
			
		||||
		data () {
 | 
			
		||||
			return {
 | 
			
		||||
				files: [],
 | 
			
		||||
				mobile: false,
 | 
			
		||||
				showTooltip: false,
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		beforeMount(){
 | 
			
		||||
			this.mobile = this.$store.getters.getIsUserOnMobile
 | 
			
		||||
		},
 | 
			
		||||
		mounted(){
 | 
			
		||||
			// console.log('Im a button')
 | 
			
		||||
		},
 | 
			
		||||
		methods: {
 | 
			
		||||
			onFileClick(file){
 | 
			
		||||
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,40 +1,35 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<div v-on:click="fullTagEdit = true">
 | 
			
		||||
 | 
			
		||||
		<!-- simple string view  -->
 | 
			
		||||
		<!-- class="ui basic segment" -->
 | 
			
		||||
		<div v-if="!fullTagEdit"> 
 | 
			
		||||
			<div class="simple-tag-display">
 | 
			
		||||
 | 
			
		||||
				<!-- Show Loading  -->
 | 
			
		||||
				<span v-if="!loaded">Loading Tags...</span>
 | 
			
		||||
 | 
			
		||||
				<!-- Default count  -->
 | 
			
		||||
				<span v-if="loaded">
 | 
			
		||||
					<i class="tags icon"></i> <b>{{tags.length}} Tag<span v-if="tags.length > 1">s</span></b> 
 | 
			
		||||
				</span>
 | 
			
		||||
				
 | 
			
		||||
				<!-- No tags default text  -->
 | 
			
		||||
				<span v-if="tags.length == 0 && loaded" class="ui small compact basic green button">
 | 
			
		||||
					<i class="plus icon"></i> Add a tag
 | 
			
		||||
				</span>
 | 
			
		||||
 | 
			
		||||
				<!-- display tags in comma delimited list -->
 | 
			
		||||
				<span v-if="tags.length > 0">
 | 
			
		||||
					<span v-for="(tag, i) in tags"><span v-if="i > 0">, </span>{{ucWords(tag.text)}}</span>
 | 
			
		||||
				</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	<div>
 | 
			
		||||
 | 
			
		||||
		<!-- hover over view -->
 | 
			
		||||
		<div v-if="fullTagEdit" class="full-tag-area fade-in-fwd" v-on:mouseleave="fullTagEdit = false; clearSuggestions()">
 | 
			
		||||
		<div v-if="fullTagEdit" class="full-tag-area fade-in-fwd">
 | 
			
		||||
 | 
			
		||||
			<!-- existing tags -->
 | 
			
		||||
			<div class="delete-tag-display" v-if="tags.length > 0">
 | 
			
		||||
				<div class="ui icon large label" v-for="tag in tags" :class="{ 'green':(newTagInput == tag.text) }">
 | 
			
		||||
					 {{ucWords(tag.text)}} <i class="delete icon" v-on:click="removeTag(tag.id)"></i>
 | 
			
		||||
			<div class="ui grid">
 | 
			
		||||
 | 
			
		||||
				<div class="sixteen wide column">
 | 
			
		||||
					<h2><i class="green tags icon"></i>Edit Tags</h2>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="sixteen wide column">
 | 
			
		||||
					<h3>All Tags</h3>
 | 
			
		||||
					<div v-if="allTags.length > 0">
 | 
			
		||||
						<div class="ui icon large label" v-for="tag in allTags" :class="{ 'green':isTagOnNote(tag.id) }">
 | 
			
		||||
							 {{ ucWords(tag.text) }}
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="sixteen wide column">
 | 
			
		||||
					<h3>Tags on Note</h3>
 | 
			
		||||
					<div v-if="allTags.length > 0 && noteTagIds.length > 0">
 | 
			
		||||
						<div class="ui icon large label" v-for="tag in noteTagIds">
 | 
			
		||||
							 {{ getTagTextById(tag['tagId']) }} <i class="delete icon" v-on:click="removeTag(tag['entryId'])"></i>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			<!-- tag input and suggestion popup -->
 | 
			
		||||
			<div class="ui form">
 | 
			
		||||
@@ -70,9 +65,11 @@
 | 
			
		||||
				newTagInput: '',
 | 
			
		||||
				typeDebounce: null,
 | 
			
		||||
 | 
			
		||||
				allTags: [],
 | 
			
		||||
				noteTagIds: [],
 | 
			
		||||
				suggestions: [],
 | 
			
		||||
				selection: 0,
 | 
			
		||||
				fullTagEdit: false,
 | 
			
		||||
				fullTagEdit: true,
 | 
			
		||||
				loaded: false,
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
@@ -85,14 +82,37 @@
 | 
			
		||||
		methods: {
 | 
			
		||||
			getTags(){
 | 
			
		||||
				//Get Note Tags -> /api/tags/get
 | 
			
		||||
				let vm = this
 | 
			
		||||
				axios.post('/api/tag/get', {'noteId': this.noteId})
 | 
			
		||||
				.then(response => {
 | 
			
		||||
					vm.loaded = true
 | 
			
		||||
					//Set up local data
 | 
			
		||||
					vm.tags = response.data
 | 
			
		||||
				.then( ({data}) => {
 | 
			
		||||
 | 
			
		||||
					this.loaded = true
 | 
			
		||||
 | 
			
		||||
					this.allTags = data.allTags
 | 
			
		||||
					this.noteTagIds = data.noteTagIds
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			isTagOnNote(id){
 | 
			
		||||
				for (let i = 0; i < this.noteTagIds.length; i++) {
 | 
			
		||||
					const current = this.noteTagIds[i]
 | 
			
		||||
					if(current && current['tagId'] == id){
 | 
			
		||||
						return true
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			getTagTextById(id){
 | 
			
		||||
				let tag = this.getTagById(id)
 | 
			
		||||
				if(tag && tag.text){
 | 
			
		||||
					return this.ucWords( tag.text )
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			getTagById(id){
 | 
			
		||||
				for (let i = 0; i < this.allTags.length; i++) {
 | 
			
		||||
					const current = this.allTags[i]
 | 
			
		||||
					if(current && current['id'] == id){
 | 
			
		||||
						return current
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			tagInput(event){
 | 
			
		||||
				let vm = this
 | 
			
		||||
 | 
			
		||||
@@ -177,6 +197,7 @@
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			onFocus(){
 | 
			
		||||
				return
 | 
			
		||||
				//Show suggested tags
 | 
			
		||||
				let vm = this
 | 
			
		||||
				let postData = {
 | 
			
		||||
@@ -211,10 +232,10 @@
 | 
			
		||||
					'tagId':tagId,
 | 
			
		||||
					'noteId':this.noteId
 | 
			
		||||
				}
 | 
			
		||||
				let vm = this
 | 
			
		||||
 | 
			
		||||
				axios.post('/api/tag/removefromnote', postData)
 | 
			
		||||
				.then(response => {
 | 
			
		||||
					vm.getTags()
 | 
			
		||||
					this.getTags()
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			clearSuggestions(){
 | 
			
		||||
 
 | 
			
		||||
@@ -40,37 +40,40 @@
 | 
			
		||||
				
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
				
 | 
			
		||||
			<!-- Toolbar on the bottom  -->
 | 
			
		||||
			<div class="bottom aligned row icon-bar" @click.self="onClick(note.id)">
 | 
			
		||||
				<div class="six wide column clickable" @click="onClick(note.id)">
 | 
			
		||||
					{{$helpers.timeAgo(note.updated)}} 
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="ten wide right aligned column">
 | 
			
		||||
					
 | 
			
		||||
					<!-- ALways show delete button on mobile -->
 | 
			
		||||
					<delete-button :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }" :note-id="note.id" />
 | 
			
		||||
			<div class="bottom aligned row" @click.self="onClick(note.id)">
 | 
			
		||||
				<div class="sixteen wide column">
 | 
			
		||||
					<div class="ui grid reduced-padding">
 | 
			
		||||
 | 
			
		||||
						<div class="thirteen wide column clickable icon-bar" @click="onClick(note.id)">
 | 
			
		||||
							<!-- {{$helpers.timeAgo(note.updated)}}  -->
 | 
			
		||||
							<span v-if="note.tags">
 | 
			
		||||
								<span v-for="tag in (note.tags.split(','))" class="little-tag">{{ tag }}</span>
 | 
			
		||||
							</span>
 | 
			
		||||
							<span v-if="note.pinned == 1" data-position="top right" data-tooltip="Pinned" data-inverted="">
 | 
			
		||||
								<i class="green pin icon"></i>
 | 
			
		||||
							</span>
 | 
			
		||||
							<span v-if="note.archived == 1" data-position="top right" data-tooltip="Archived" data-inverted="">
 | 
			
		||||
								<i class="green archive icon"></i>
 | 
			
		||||
							</span>
 | 
			
		||||
					<span v-if="note.attachment_count > 0" v-on:click.stop="openEditAttachment" class="clickable">
 | 
			
		||||
						<i class="linkify icon"></i> {{note.attachment_count}}
 | 
			
		||||
					</span>
 | 
			
		||||
					<span v-if="note.tag_count == 1" data-position="top right" data-tooltip="Note has 1 tag" data-inverted="">
 | 
			
		||||
						<i class="tags icon"></i> {{note.tag_count}}
 | 
			
		||||
					</span>
 | 
			
		||||
					<span v-if="note.tag_count > 1" :data-tooltip="`Note has ${note.tag_count} tags`" data-position="top right" data-inverted="">
 | 
			
		||||
						<i class="tags icon"></i> {{note.tag_count}}
 | 
			
		||||
					</span>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="three wide right aligned column">
 | 
			
		||||
							<delete-button :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }" :note-id="note.id" />
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
						<div class="row" v-if="note.thumbs">
 | 
			
		||||
							<div class="tiny-thumb-box" v-on:click="openEditAttachment">
 | 
			
		||||
								<img v-for="thumb in note.thumbs.split(',').reverse()" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -145,13 +148,16 @@
 | 
			
		||||
 | 
			
		||||
	/*Strict font sizes for card display*/
 | 
			
		||||
	.small-text, .small-text > p, .small-text > h1, .small-text > h2 {
 | 
			
		||||
		font-size: 1.0em !important;
 | 
			
		||||
		/*font-size: 1.0em !important;*/
 | 
			
		||||
		font-size: 15px !important;
 | 
			
		||||
	}
 | 
			
		||||
	.small-text > p, , .small-text > h1, .small-text > h2 {
 | 
			
		||||
		margin-bottom: 0.5em;
 | 
			
		||||
	}
 | 
			
		||||
	.big-text, .big-text > p, .big-text > h1, .big-text > h2 {
 | 
			
		||||
		font-size: 1.3em !important;
 | 
			
		||||
		/*font-size: 1.3em !important;*/
 | 
			
		||||
		font-size: 16px !important;
 | 
			
		||||
		font-weight: bold;
 | 
			
		||||
	}
 | 
			
		||||
	.big-text > p, .big-text > h1, .big-text > h2 {
 | 
			
		||||
		margin-bottom: 0.3em;
 | 
			
		||||
@@ -186,7 +192,7 @@
 | 
			
		||||
		/*box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);*/
 | 
			
		||||
		box-shadow: 0 0px 5px 1px rgba(34,36,38,0);
 | 
			
		||||
		margin: 5px;
 | 
			
		||||
		padding: 1em 1.5em;
 | 
			
		||||
		padding: 0.7em 1em;
 | 
			
		||||
		border-radius: .28571429rem;
 | 
			
		||||
		border: 1px solid;
 | 
			
		||||
		border-color: var(--border_color);
 | 
			
		||||
@@ -205,11 +211,42 @@
 | 
			
		||||
	}
 | 
			
		||||
	.icon-bar {
 | 
			
		||||
		opacity: 0.8;
 | 
			
		||||
		margin-top: -2.2rem;
 | 
			
		||||
		/*margin-top: -2.2rem;*/
 | 
			
		||||
	}
 | 
			
		||||
	.hover-hide {
 | 
			
		||||
		opacity: 0.0;
 | 
			
		||||
	}
 | 
			
		||||
	.little-tag {
 | 
			
		||||
		font-size: 0.7em;
 | 
			
		||||
		padding: 5px 5px;
 | 
			
		||||
		border: 1px solid var(--border_color);
 | 
			
		||||
		margin: 5px 3px 0 0;
 | 
			
		||||
		border-radius: 3px;
 | 
			
		||||
		white-space: nowrap;
 | 
			
		||||
		max-width: 100px;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
		line-height: 0.8em;
 | 
			
		||||
		text-overflow: ellipsis;
 | 
			
		||||
	}
 | 
			
		||||
	.tiny-thumb-box {
 | 
			
		||||
		max-height: 70px;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
		width: 100%;
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
		background-color: rgba(200, 200, 200, 0.2);
 | 
			
		||||
		white-space: nowrap;
 | 
			
		||||
		overflow-x: scroll;
 | 
			
		||||
		border: 1px solid var(--border_color);
 | 
			
		||||
		border-left: none;
 | 
			
		||||
		border-right: none;
 | 
			
		||||
		text-align: center;
 | 
			
		||||
		scrollbar-width: none;
 | 
			
		||||
	}
 | 
			
		||||
	.tiny-thumb {
 | 
			
		||||
		max-height: 70px;
 | 
			
		||||
		display: inline-block;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.note-title-display-card:hover .icon-bar {
 | 
			
		||||
		opacity: 1;
 | 
			
		||||
@@ -262,7 +299,6 @@
 | 
			
		||||
		.note-title-display-card {
 | 
			
		||||
			width: calc(100% + 10px);
 | 
			
		||||
			margin: 0px -5px 10px -5px;
 | 
			
		||||
			padding: 15px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
	<div class="ui form">
 | 
			
		||||
		<div class="fields">
 | 
			
		||||
			<div class="sixteen wide field">
 | 
			
		||||
				<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
 | 
			
		||||
				<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes and Files" />
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										112
									
								
								client/src/components/ShareNoteComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								client/src/components/ShareNoteComponent.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
<style type="text/css" scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
 | 
			
		||||
		<div class="ui grid" v-if="this.shareUsername == null">
 | 
			
		||||
 | 
			
		||||
			<div class="row">
 | 
			
		||||
				<div class="eight wide column">
 | 
			
		||||
					<div class="ui form">
 | 
			
		||||
						<div class="field">
 | 
			
		||||
							<input type="text" placeholder="Share with someone" v-model="shareUserInput" v-on:keyup="onKeyup">
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="eight wide column">
 | 
			
		||||
					<div class="ui disabled button" v-if="shareUserInput.length == 0">
 | 
			
		||||
						Share
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui green button" v-if="shareUserInput.length > 0" v-on:click="onSubmitClick">
 | 
			
		||||
						Share
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="sixteen wide column" v-if="sharedWithUsers.length > 0">
 | 
			
		||||
				<h3>Users who can edit this note</h3>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="row" v-for="item in sharedWithUsers">
 | 
			
		||||
				<div class="eight wide middle aligned column">
 | 
			
		||||
					<h3><i class="green user circle icon"></i>{{item.username}}</h3>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="eight wide column">
 | 
			
		||||
					<div class="ui basic compact button" v-on:click="onRevokeAccess(item.noteId)">Remove Access</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="ui grid" v-if="this.shareUsername != null">
 | 
			
		||||
			<div class="sixteen wide column">
 | 
			
		||||
				Shared with you by <h3><i class="green user circle icon"></i>{{shareUsername}}</h3>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
	import axios from 'axios'
 | 
			
		||||
 | 
			
		||||
	export default {
 | 
			
		||||
		name: 'ShareNoteComponent',
 | 
			
		||||
		props: [ 'noteId', 'rawTextId', 'shareUsername' ],
 | 
			
		||||
		data () {
 | 
			
		||||
			return {
 | 
			
		||||
				sharedWithUsers: [],
 | 
			
		||||
				shareUserInput: '',
 | 
			
		||||
				debounce: null,
 | 
			
		||||
				enableSubmitShare: false,
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		beforeMount(){
 | 
			
		||||
 | 
			
		||||
		},
 | 
			
		||||
		mounted(){
 | 
			
		||||
 | 
			
		||||
			if(this.shareUsername == null){
 | 
			
		||||
				this.loadShareList()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		},
 | 
			
		||||
		methods: {
 | 
			
		||||
			loadShareList(){
 | 
			
		||||
				axios.post('/api/note/getshareusers', {'rawTextId':this.rawTextId })
 | 
			
		||||
				.then( ({data}) => {
 | 
			
		||||
					this.sharedWithUsers = data
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			onRevokeAccess(noteId){
 | 
			
		||||
				axios.post('/api/note/shareremoveuser', {'noteId':noteId})
 | 
			
		||||
				.then( ({data}) => {
 | 
			
		||||
					console.log(data)
 | 
			
		||||
					if(data == true){
 | 
			
		||||
						this.loadShareList()
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
			},
 | 
			
		||||
			onKeyup(event){
 | 
			
		||||
				if(event.keyCode == 13){
 | 
			
		||||
					this.onSubmitClick()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			onSubmitClick(){
 | 
			
		||||
 | 
			
		||||
				axios.post('/api/note/shareadduser', {'noteId':this.noteId, 'rawTextId':this.rawTextId, 'username':this.shareUserInput })
 | 
			
		||||
				.then( ({data}) => {
 | 
			
		||||
					if(data == true){
 | 
			
		||||
						this.shareUserInput = ''
 | 
			
		||||
						this.loadShareList()
 | 
			
		||||
					} else {
 | 
			
		||||
						this.$bus.$emit('notification', 'User not found')
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										133
									
								
								client/src/components/SideSlideMenuComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								client/src/components/SideSlideMenuComponent.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
<style type="text/css" scoped>
 | 
			
		||||
	.slide-container {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
		right: 55%;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		z-index: 400;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
		height: 100%;
 | 
			
		||||
	}
 | 
			
		||||
	.slide-content {
 | 
			
		||||
		box-sizing: border-box;
 | 
			
		||||
		/*padding: 1em 1.5em;*/
 | 
			
		||||
		height: calc(100% - 43px);
 | 
			
		||||
		border-right: 1px solid var(--menu-border);
 | 
			
		||||
		background-color: var(--background_color);
 | 
			
		||||
		overflow-x: scroll;
 | 
			
		||||
	}
 | 
			
		||||
	.slide-shadow {
 | 
			
		||||
		position: fixed;
 | 
			
		||||
		top: 0;
 | 
			
		||||
		left: 0;
 | 
			
		||||
		right: 50%;
 | 
			
		||||
		bottom: 0;
 | 
			
		||||
		color: red;
 | 
			
		||||
		background-color: rgba(0,0,0,0.5);
 | 
			
		||||
		/*background: linear-gradient(90deg, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 55%);*/
 | 
			
		||||
		z-index: 399;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
	}
 | 
			
		||||
	.note-menu {
 | 
			
		||||
		height: 43px;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@media only screen and (max-width: 740px) {
 | 
			
		||||
		.slide-shadow {
 | 
			
		||||
			background-color: rgba(0,0,0,0.5);
 | 
			
		||||
		}
 | 
			
		||||
		.slide-content {
 | 
			
		||||
			height: calc(100% - 55px);
 | 
			
		||||
		}
 | 
			
		||||
		.slide-container {
 | 
			
		||||
			left: 0;
 | 
			
		||||
			right: 0;
 | 
			
		||||
			top: 0;
 | 
			
		||||
			bottom: 0;
 | 
			
		||||
		}
 | 
			
		||||
		.note-menu {
 | 
			
		||||
			height: 55px;
 | 
			
		||||
			padding: 0 30px;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.modal-fade-enter,
 | 
			
		||||
	.modal-fade-leave-active {
 | 
			
		||||
		opacity: 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.modal-fade-enter-active,
 | 
			
		||||
	.modal-fade-leave-active {
 | 
			
		||||
		transition: opacity .5s ease;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<transition name="modal-fade">
 | 
			
		||||
		<div>
 | 
			
		||||
 | 
			
		||||
			<div class="slide-container">
 | 
			
		||||
 | 
			
		||||
				<!-- content of the editor  -->
 | 
			
		||||
				<div class="slide-content">
 | 
			
		||||
					<slot></slot>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- close menu on bottom  -->
 | 
			
		||||
				<div class="note-menu">
 | 
			
		||||
					<nm-button more-class="right" icon="close" text="close" :show-text="true" v-on:click.native="close" />
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
			</div>
 | 
			
		||||
			
 | 
			
		||||
			<div class="slide-shadow" v-on:click="close"></div>
 | 
			
		||||
			
 | 
			
		||||
		</div>
 | 
			
		||||
	</transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
	export default {
 | 
			
		||||
		name: 'SideSlideMenu',
 | 
			
		||||
		props: [ 'name' ],
 | 
			
		||||
		components: {
 | 
			
		||||
			'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
 | 
			
		||||
		},
 | 
			
		||||
		data () {
 | 
			
		||||
			return {
 | 
			
		||||
				items: []
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		beforeMount(){
 | 
			
		||||
			
 | 
			
		||||
			//Other panels will tell this one to close
 | 
			
		||||
			this.$bus.$on('destroy_all_other_side_panels', (name) => {
 | 
			
		||||
				if(this.name != name){
 | 
			
		||||
					this.close()
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		beforeDestroy(){
 | 
			
		||||
		},
 | 
			
		||||
		mounted(){
 | 
			
		||||
			
 | 
			
		||||
			//Close all other panels that are not this one
 | 
			
		||||
			this.$nextTick( () => {
 | 
			
		||||
				this.$bus.$emit('destroy_all_other_side_panels', this.name)
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		methods: {
 | 
			
		||||
			onClickTag(index){
 | 
			
		||||
				console.log('yup')
 | 
			
		||||
			},
 | 
			
		||||
			close() {
 | 
			
		||||
				this.$emit('close');
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										119
									
								
								client/src/components/SimpleAttachmentNoteComponent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								client/src/components/SimpleAttachmentNoteComponent.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
<style type="text/css" scoped>
 | 
			
		||||
 | 
			
		||||
	.img-container {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		flex-wrap: wrap;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.img-row {
 | 
			
		||||
		height: 30vh;
 | 
			
		||||
		flex-grow: 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.img-row:last-child {
 | 
			
		||||
		/* There's no science in using "10" here. In all my testing, this delivered the best results. */
 | 
			
		||||
		flex-grow: 10;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.img-row > img {
 | 
			
		||||
		max-height: calc(100% - 10px);
 | 
			
		||||
		min-width: calc(100% - 10px);
 | 
			
		||||
		max-width: calc(100% - 10px);
 | 
			
		||||
		object-fit: cover;
 | 
			
		||||
		vertical-align: bottom;
 | 
			
		||||
		/*padding: 5px;*/
 | 
			
		||||
		box-shadow: 0px 2px 2px 1px rgba(34,36,38,0.3);
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<div>
 | 
			
		||||
 | 
			
		||||
		<div v-if="uploadedToNote.length > 0">
 | 
			
		||||
			<h2>Images Uploaded to Note</h2>
 | 
			
		||||
			<div class="ui fluid green button" v-on:click="$router.push('/attachments/note/'+noteId)">
 | 
			
		||||
				Manage Files on this Note
 | 
			
		||||
				<i class="chevron circle right icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<p></p>
 | 
			
		||||
			<div class="img-container">
 | 
			
		||||
				<div v-for="file in uploadedToNote" class="img-row" v-on:click="onFileClick(file)">
 | 
			
		||||
					<img :src="`/api/static/thumb_${file.file_location}`">
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- extra row helps it display properly -->
 | 
			
		||||
				<div class="img-row"></div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		<!-- Add images to note  -->
 | 
			
		||||
		<div v-if="files.length > 0">
 | 
			
		||||
			<h2>All other Images</h2>
 | 
			
		||||
			<div class="ui fluid green button" v-on:click="$router.push('/attachments')">
 | 
			
		||||
				Manage All Files
 | 
			
		||||
				<i class="chevron circle right icon"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<p></p>
 | 
			
		||||
			<div class="img-container">
 | 
			
		||||
				<div v-for="file in files" class="img-row" v-on:click="onFileClick(file)">
 | 
			
		||||
					<img :src="`/api/static/thumb_${file.file_location}`">
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<!-- extra row helps it display properly -->
 | 
			
		||||
				<div class="img-row"></div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		
 | 
			
		||||
	</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
	import axios from 'axios'
 | 
			
		||||
 | 
			
		||||
	export default {
 | 
			
		||||
		name: 'SimpleAttachmentNoteComponent',
 | 
			
		||||
		props: [ 'noteId', 'squireEditor' ],
 | 
			
		||||
		data () {
 | 
			
		||||
			return {
 | 
			
		||||
				files: [],
 | 
			
		||||
				uploadedToNote: [],
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		beforeMount(){
 | 
			
		||||
 | 
			
		||||
		},
 | 
			
		||||
		mounted(){
 | 
			
		||||
			axios.post('/api/attachment/search', {'attachmentType':2})
 | 
			
		||||
			.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)
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		methods: {
 | 
			
		||||
			onFileClick(file){
 | 
			
		||||
 | 
			
		||||
				const imageCode = `<img alt="image" src="/api/static/thumb_${file.file_location}">`
 | 
			
		||||
 | 
			
		||||
				this.$bus.$emit('new_file_upload', {noteId: this.noteId, imageCode})
 | 
			
		||||
 | 
			
		||||
				if(this.$store.getters.getIsUserOnMobile){
 | 
			
		||||
					this.close()
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			close() {
 | 
			
		||||
				this.$emit('close');
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -71,11 +71,12 @@
 | 
			
		||||
						//Redirect user to notes section after login
 | 
			
		||||
						vm.$router.push('/notes')
 | 
			
		||||
					} else {
 | 
			
		||||
						this.$bus.$emit('notification', 'Incorrect Username or Password')
 | 
			
		||||
						vm.$store.commit('destroyLoginToken')
 | 
			
		||||
					}
 | 
			
		||||
				})
 | 
			
		||||
				.catch(error => {
 | 
			
		||||
					console.log('There was an error with log in request')
 | 
			
		||||
					this.$bus.$emit('notification', 'Incorrect Username or Password')
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,12 @@
 | 
			
		||||
						<div class="fields">
 | 
			
		||||
							<div class="ten wide field">
 | 
			
		||||
								<search-input></search-input>
 | 
			
		||||
								<!-- <input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" /> -->
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="six wide field">
 | 
			
		||||
								<span class="ui fluid green button" 
 | 
			
		||||
									v-if="showClear"
 | 
			
		||||
									@click="reset">
 | 
			
		||||
									<i class="undo icon"></i>Reset Filters
 | 
			
		||||
									<i class="undo icon"></i>Back to All Notes
 | 
			
		||||
								</span>
 | 
			
		||||
								<!-- <fast-filters /> -->
 | 
			
		||||
							</div>
 | 
			
		||||
@@ -36,6 +35,7 @@
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div v-if="commonTags.length > 0" class="sixteen wide column">
 | 
			
		||||
				<h4><i class="green tags icon"></i>Tags</h4>
 | 
			
		||||
				<span v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
 | 
			
		||||
					<span class="ui clickable basic label" :class="{ 'green':(searchTags.includes(tag.id)) }">
 | 
			
		||||
					{{ucWords(tag.text)}} <span class="detail">{{tag.usages}}</span>
 | 
			
		||||
@@ -388,6 +388,7 @@
 | 
			
		||||
			},
 | 
			
		||||
			//Try to close notes on URL hash change /notes/open/123 to /notes - parse 123, close note id 123
 | 
			
		||||
			hashChangeAction(event){
 | 
			
		||||
 | 
			
		||||
				//Clean up path of hash change
 | 
			
		||||
				let path = window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.hash
 | 
			
		||||
				let newPath = event.newURL.replace(path,'')
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ Attachment.textSearch = (userId, searchTerm) => {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Attachment.search = (userId, noteId) => {
 | 
			
		||||
Attachment.search = (userId, noteId, attachmentType) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
		let params = [userId]
 | 
			
		||||
@@ -54,6 +54,11 @@ Attachment.search = (userId, noteId) => {
 | 
			
		||||
			params.push(noteId)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(Number.isInteger(attachmentType)){
 | 
			
		||||
			query += 'AND attachment_type = ? '
 | 
			
		||||
			params.push(attachmentType)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		query += 'ORDER BY last_indexed DESC '
 | 
			
		||||
 | 
			
		||||
		db.promise()
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,35 @@ let Note = module.exports = {}
 | 
			
		||||
 | 
			
		||||
const gm = require('gm')
 | 
			
		||||
 | 
			
		||||
// --------------
 | 
			
		||||
 | 
			
		||||
Note.migrateNoteTextToNewTable = () => {
 | 
			
		||||
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query('SELECT id, text FROM note WHERE note_raw_text_id IS NULL')
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			rows[0].forEach( ({id, text}) => {
 | 
			
		||||
 | 
			
		||||
				db.promise()
 | 
			
		||||
				.query('INSERT INTO note_raw_text (text) VALUES (?)', [text])
 | 
			
		||||
				.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
					db.promise()
 | 
			
		||||
					.query(`UPDATE note SET note_raw_text_id = ? WHERE (id = ?)`, [rows[0].insertId, id])
 | 
			
		||||
					.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
						return 'Nice'
 | 
			
		||||
					})
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			resolve('Its probably running... :-D')
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Note.fixAttachmentThumbnails = () => {
 | 
			
		||||
	const filePath = '../staticFiles/'
 | 
			
		||||
	db.promise()
 | 
			
		||||
@@ -66,6 +95,8 @@ Note.stressTest = () => {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --------------
 | 
			
		||||
 | 
			
		||||
Note.create = (userId, noteText, quickNote = 0) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
@@ -74,39 +105,23 @@ Note.create = (userId, noteText, quickNote = 0) => {
 | 
			
		||||
		const created = Math.round((+new Date)/1000)
 | 
			
		||||
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query('INSERT INTO note (user_id, text, updated, created, quick_note) VALUES (?,?,?,?,?)', 
 | 
			
		||||
			[userId, noteText, created, created, quickNote])
 | 
			
		||||
		.query(`INSERT INTO note_raw_text (text) VALUE ('')`)
 | 
			
		||||
		.then( (rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			const rawTextId = rows[0].insertId
 | 
			
		||||
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('INSERT INTO note (user_id, note_raw_text_id, updated, created, quick_note) VALUES (?,?,?,?,?)', 
 | 
			
		||||
			[userId, rawTextId, created, created, quickNote])
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			// New notes are empty, don't add to solr index
 | 
			
		||||
			// Note.reindex(userId, rows[0].insertId)
 | 
			
		||||
			// Indexing is done on save
 | 
			
		||||
			resolve(rows[0].insertId) //Only return the new note ID when creating a new note
 | 
			
		||||
		})
 | 
			
		||||
		.catch(console.log)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Note.reindexAll = () => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query(`
 | 
			
		||||
		
 | 
			
		||||
			SELECT id, user_id FROM note;
 | 
			
		||||
 | 
			
		||||
		`)
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			console.log()
 | 
			
		||||
 | 
			
		||||
			rows[0].forEach(item => {
 | 
			
		||||
				Note.reindex(item.user_id, item.id)
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			resolve(true)
 | 
			
		||||
		})
 | 
			
		||||
		.catch(console.log)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Note.reindex = (userId, noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
@@ -154,8 +169,23 @@ Note.update = (userId, noteId, noteText, color, pinned, archived) => {
 | 
			
		||||
		const now = Math.round((+new Date)/1000)
 | 
			
		||||
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query('UPDATE note SET text = ?, pinned = ?, archived = ?, updated = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1', 
 | 
			
		||||
			[noteText, pinned, archived, now, color, noteId, userId])
 | 
			
		||||
		.query('SELECT note_raw_text_id FROM note WHERE id = ? AND user_id = ?', [noteId, userId])
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			const textId = rows[0][0]['note_raw_text_id']
 | 
			
		||||
 | 
			
		||||
			//Update Note text
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('UPDATE note_raw_text SET text = ? WHERE id = ?', [noteText, textId])
 | 
			
		||||
		})
 | 
			
		||||
		.then( (rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			//Update other note attributes
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('UPDATE note SET pinned = ?, archived = ?, updated = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1', 
 | 
			
		||||
			[pinned, archived, now, color, noteId, userId])
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			//Async solr note reindex
 | 
			
		||||
@@ -171,7 +201,6 @@ Note.update = (userId, noteId, noteText, color, pinned, archived) => {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Delete a note and all its remaining parts
 | 
			
		||||
//
 | 
			
		||||
@@ -179,16 +208,54 @@ Note.delete = (userId, noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
		//
 | 
			
		||||
		// Delete, note, text index, tags 
 | 
			
		||||
		// Delete, note, text, search index and associated tags 
 | 
			
		||||
		// Leave the attachments, they can be deleted on their own
 | 
			
		||||
		//
 | 
			
		||||
		// Leave Tags, their text is shared
 | 
			
		||||
 | 
			
		||||
		db.promise().query('DELETE FROM note WHERE note.id = ? AND note.user_id = ?', [noteId,userId])
 | 
			
		||||
		let rawTextId = null
 | 
			
		||||
		let noteTextCount = 0
 | 
			
		||||
 | 
			
		||||
		// Lookup the note text ID, we need this to count usages
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query('SELECT note_raw_text_id FROM note WHERE id = ? AND user_id = ?', [noteId, userId])
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			return db.promise().query('DELETE FROM note_text_index WHERE note_text_index.note_id = ? AND note_text_index.user_id = ?', [noteId,userId])	
 | 
			
		||||
 | 
			
		||||
			//Save the raw text ID
 | 
			
		||||
			rawTextId = rows[0][0]['note_raw_text_id']
 | 
			
		||||
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('SELECT count(id) as count FROM note WHERE note_raw_text_id = ?', [rawTextId])
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			return db.promise().query('DELETE FROM note_tag WHERE note_tag.note_id = ? AND note_tag.user_id = ?', [noteId,userId])	
 | 
			
		||||
 | 
			
		||||
			//Save the number of times the note is used
 | 
			
		||||
			noteTextCount = rows[0][0]['count']
 | 
			
		||||
 | 
			
		||||
			//Don't delete text if its shared
 | 
			
		||||
			if(noteTextCount == 1){
 | 
			
		||||
				//If text is only used on one note, delete it (its not shared)
 | 
			
		||||
				return db.promise()
 | 
			
		||||
					.query('SELECT count(id) as count FROM note WHERE note_raw_text_id = ?', [rawTextId])
 | 
			
		||||
			} else {
 | 
			
		||||
				return new Promise((resolve, reject) => {
 | 
			
		||||
					resolve(true)
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
		.then( results => {
 | 
			
		||||
			// Delete Note entry for this user.
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('DELETE FROM note WHERE note.id = ? AND note.user_id = ?', [noteId, userId])
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			// Delete search index
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('DELETE FROM note_text_index WHERE note_text_index.note_id = ? AND note_text_index.user_id = ?', [noteId,userId])	
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			// delete tags
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('DELETE FROM note_tag WHERE note_tag.note_id = ? AND note_tag.user_id = ?', [noteId,userId])	
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
			resolve(true)
 | 
			
		||||
@@ -251,9 +318,19 @@ Note.get = (userId, noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query(`
 | 
			
		||||
			SELECT note.text, note.updated, note.pinned, note.archived, note.color, count(distinct attachment.id) as attachment_count
 | 
			
		||||
			SELECT 
 | 
			
		||||
				note_raw_text.text, 
 | 
			
		||||
				note.updated,
 | 
			
		||||
				note.pinned,
 | 
			
		||||
				note.archived,
 | 
			
		||||
				note.color,
 | 
			
		||||
				count(distinct attachment.id) as attachment_count,
 | 
			
		||||
				note.note_raw_text_id as rawTextId,
 | 
			
		||||
				user.username as shareUsername
 | 
			
		||||
			FROM note 
 | 
			
		||||
			JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
 | 
			
		||||
			LEFT JOIN attachment ON (note.id = attachment.note_id)
 | 
			
		||||
			LEFT JOIN user ON (note.share_user_id = user.id)
 | 
			
		||||
			WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId])
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
@@ -265,6 +342,7 @@ Note.get = (userId, noteId) => {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//Public note share action -> may not be used
 | 
			
		||||
Note.getShared = (noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		db.promise()
 | 
			
		||||
@@ -356,20 +434,33 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
 | 
			
		||||
 | 
			
		||||
			// Base of the query, modified with fastFilters
 | 
			
		||||
			// Add to query for character counts -> CHAR_LENGTH(note.text) as chars
 | 
			
		||||
			let searchParams = [userId]
 | 
			
		||||
			let noteSearchQuery = `
 | 
			
		||||
				SELECT note.id, 
 | 
			
		||||
					SUBSTRING(note.text, 1, 1500) as text, 
 | 
			
		||||
					SUBSTRING(note_raw_text.text, 1, 1500) as text, 
 | 
			
		||||
					updated, 
 | 
			
		||||
					color, 
 | 
			
		||||
					count(distinct note_tag.id) as tag_count, 
 | 
			
		||||
					count(distinct attachment.id) as attachment_count,
 | 
			
		||||
					note.pinned,
 | 
			
		||||
					note.archived
 | 
			
		||||
					note.archived,
 | 
			
		||||
					GROUP_CONCAT(DISTINCT tag.text) as tags,
 | 
			
		||||
					GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs,
 | 
			
		||||
					shareUser.username as username
 | 
			
		||||
				FROM note 
 | 
			
		||||
				JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
 | 
			
		||||
				LEFT JOIN note_tag ON (note.id = note_tag.note_id)
 | 
			
		||||
				LEFT JOIN tag ON (tag.id = note_tag.tag_id)
 | 
			
		||||
				LEFT JOIN attachment ON (note.id = attachment.note_id)
 | 
			
		||||
				LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
 | 
			
		||||
				WHERE note.user_id = ?`
 | 
			
		||||
			let searchParams = [userId]
 | 
			
		||||
			
 | 
			
		||||
			//Show shared notes
 | 
			
		||||
			if(fastFilters.onlyShowSharedNotes == 1){
 | 
			
		||||
				noteSearchQuery += ' AND note.share_user_id IS NOT NULL' //Show Archived
 | 
			
		||||
			} else {
 | 
			
		||||
				noteSearchQuery += ' AND note.share_user_id IS NULL'
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//If text search returned results, limit search to those ids			
 | 
			
		||||
			if(textSearchIds.length > 0){
 | 
			
		||||
@@ -418,15 +509,15 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
 | 
			
		||||
			// Always prioritize pinned notes in searches.
 | 
			
		||||
 | 
			
		||||
			//Default Sort, order by last updated
 | 
			
		||||
			let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, created DESC, opened DESC, id DESC'
 | 
			
		||||
			let defaultOrderBy = ' ORDER BY note.pinned DESC, note.updated DESC, note.created DESC, note.opened DESC, id DESC'
 | 
			
		||||
 | 
			
		||||
			//Order by Last Created Date
 | 
			
		||||
			if(fastFilters.lastCreated == 1){
 | 
			
		||||
				defaultOrderBy = ' ORDER BY note.pinned DESC, created DESC, updated DESC, opened DESC, id DESC'
 | 
			
		||||
				defaultOrderBy = ' ORDER BY note.pinned DESC, note.created DESC, note.updated DESC, note.opened DESC, id DESC'
 | 
			
		||||
			}
 | 
			
		||||
			//Order by last Opened Date
 | 
			
		||||
			if(fastFilters.lastOpened == 1){
 | 
			
		||||
				defaultOrderBy = ' ORDER BY note.pinned DESC, opened DESC, updated DESC, created DESC, id DESC'
 | 
			
		||||
				defaultOrderBy = ' ORDER BY note.pinned DESC, opened DESC, note.updated DESC, note.created DESC, id DESC'
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//Append Order by to query
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										127
									
								
								server/models/ShareNote.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								server/models/ShareNote.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
//
 | 
			
		||||
// All actions in noteController.js
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const db = require('@config/database')
 | 
			
		||||
 | 
			
		||||
const Note = require('@models/Note')
 | 
			
		||||
 | 
			
		||||
let ShareNote = module.exports = {}
 | 
			
		||||
 | 
			
		||||
// Share a note with a user, given the correct username
 | 
			
		||||
ShareNote.addUser = (userId, noteId, rawTextId, username) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
		let shareUserId = null
 | 
			
		||||
		let newNoteShare = null
 | 
			
		||||
 | 
			
		||||
		//Check that user actually exists
 | 
			
		||||
		db.promise().query(`SELECT id FROM user WHERE username = ?`, [username])
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			if(rows[0].length == 0){
 | 
			
		||||
				throw new Error('User Does Not Exist')
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			shareUserId = rows[0][0]['id']
 | 
			
		||||
 | 
			
		||||
			//Check if note has already been added for user
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query('SELECT id FROM note WHERE user_id = ? AND note_raw_text_id = ?', [shareUserId, rawTextId])
 | 
			
		||||
			
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			if(rows[0].length != 0){
 | 
			
		||||
				throw new Error('User Already Has Note')
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//Lookup note to share with user, clone this data to create users new note
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query(`SELECT * FROM note WHERE id = ? LIMIT 1`, [noteId])
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			newNoteShare = rows[0][0]
 | 
			
		||||
 | 
			
		||||
			//Modify note with the share attributes we want
 | 
			
		||||
			delete newNoteShare['id']
 | 
			
		||||
			delete newNoteShare['opened']
 | 
			
		||||
			newNoteShare['share_user_id'] = userId //User who shared the note 
 | 
			
		||||
			newNoteShare['user_id'] = shareUserId //User who gets note
 | 
			
		||||
 | 
			
		||||
			//Setup db colums, db values and number of '?' to put into prepared statement
 | 
			
		||||
			let dbColumns = []
 | 
			
		||||
			let dbValues = []
 | 
			
		||||
			let escapeChars = []
 | 
			
		||||
 | 
			
		||||
			//Pull out all the data we need from object to create prepared statemnt
 | 
			
		||||
			Object.keys(newNoteShare).forEach( key => {
 | 
			
		||||
				escapeChars.push('?')
 | 
			
		||||
				dbColumns.push(key)
 | 
			
		||||
				dbValues.push(newNoteShare[key])
 | 
			
		||||
			})
 | 
			
		||||
		
 | 
			
		||||
			//Stick all the note value back into query, insert updated note
 | 
			
		||||
			return db.promise()
 | 
			
		||||
			.query(`INSERT INTO note (${dbColumns.join()}) VALUES (${escapeChars.join()})`, dbValues)
 | 
			
		||||
		})
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			//Success!
 | 
			
		||||
			return resolve(true)
 | 
			
		||||
		})
 | 
			
		||||
		.catch(error => {
 | 
			
		||||
			// console.log(error)
 | 
			
		||||
			resolve(false)
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get users who see a shared note
 | 
			
		||||
ShareNote.getUsers = (userId, rawTextId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query(`
 | 
			
		||||
			SELECT username, note.id as noteId
 | 
			
		||||
			FROM note 
 | 
			
		||||
			JOIN user ON (user.id = note.user_id)
 | 
			
		||||
			WHERE note_raw_text_id = ?
 | 
			
		||||
			AND share_user_id = ?
 | 
			
		||||
			AND user_id != ?
 | 
			
		||||
			`, [rawTextId, userId, userId])
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			//Return a list of user names
 | 
			
		||||
			return resolve (rows[0])
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove a user from a shared note
 | 
			
		||||
ShareNote.removeUser = (userId, noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		
 | 
			
		||||
		//note.id = noteId, share_user_id = userId
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query('SELECT user_id FROM note WHERE id = ? AND share_user_id = ?', [noteId, userId])
 | 
			
		||||
		.then( (rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			//User has shared this note, with this user
 | 
			
		||||
			if(rows[0].length == 1 && Number.isInteger(rows[0][0]['user_id'])){
 | 
			
		||||
 | 
			
		||||
				Note.delete(rows[0][0]['user_id'], noteId)
 | 
			
		||||
				.then( result => {
 | 
			
		||||
					resolve(result)
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
			} else {
 | 
			
		||||
				return resolve(false)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,7 @@ Tag.userTags = (userId) => {
 | 
			
		||||
			JOIN note_tag ON tag.id = note_tag.tag_id
 | 
			
		||||
			WHERE note_tag.user_id = ?
 | 
			
		||||
			GROUP BY tag.id
 | 
			
		||||
			ORDER BY usages DESC
 | 
			
		||||
			ORDER BY id DESC
 | 
			
		||||
		`, [userId])
 | 
			
		||||
		.then( (rows, fields) => {
 | 
			
		||||
			resolve(rows[0])
 | 
			
		||||
@@ -98,39 +98,50 @@ Tag.add = (tagText) => {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Get all tags AND tags associated to note
 | 
			
		||||
//
 | 
			
		||||
Tag.get = (userId, noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
 | 
			
		||||
		//Update last opened date of note
 | 
			
		||||
		const now = Math.round((+new Date)/1000)
 | 
			
		||||
		Tag.userTags(userId).then(userTags => {
 | 
			
		||||
			db.promise()
 | 
			
		||||
		.query('UPDATE note SET opened = ? WHERE id = ? AND user_id = ? LIMIT 1', [now, noteId, userId])
 | 
			
		||||
		.then((rows, fields) => {})
 | 
			
		||||
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query(`SELECT note_tag.id, tag.text FROM note_tag
 | 
			
		||||
				JOIN tag ON (tag.id = note_tag.tag_id)
 | 
			
		||||
			.query(`SELECT tag_id as tagId, id as entryId
 | 
			
		||||
					FROM note_tag
 | 
			
		||||
					WHERE user_id = ? AND note_id = ?;`, [userId, noteId])
 | 
			
		||||
			.then((rows, fields) => {
 | 
			
		||||
			resolve(rows[0]) //Return all tags found by query
 | 
			
		||||
 | 
			
		||||
				//pull IDs out of returned results
 | 
			
		||||
				// let ids = rows[0].map( item => {})
 | 
			
		||||
 | 
			
		||||
				resolve({'noteTagIds':rows[0], 'allTags':userTags }) //Return all tags found by query
 | 
			
		||||
			})
 | 
			
		||||
			.catch(console.log)
 | 
			
		||||
		})
 | 
			
		||||
		
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Get all tags for a note and concatinate into a string 'all, tags, like, this'
 | 
			
		||||
//
 | 
			
		||||
Tag.string = (userId, noteId) => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		Tag.get(userId, noteId).then(tagArray => {
 | 
			
		||||
		db.promise()
 | 
			
		||||
		.query(`SELECT GROUP_CONCAT(DISTINCT tag.text SEPARATOR ', ') as text
 | 
			
		||||
				FROM note_tag
 | 
			
		||||
				JOIN tag ON note_tag.tag_id = tag.id
 | 
			
		||||
				WHERE user_id = ? AND note_id = ?;`, [userId, noteId])
 | 
			
		||||
		.then((rows, fields) => {
 | 
			
		||||
 | 
			
		||||
			let tagString = ''
 | 
			
		||||
			tagArray.forEach( (tag, i) => {
 | 
			
		||||
				if(i > 0){ tagString += ',' }
 | 
			
		||||
				tagString += tag.text
 | 
			
		||||
			})
 | 
			
		||||
			//Output comma delimited list of tag strings
 | 
			
		||||
			resolve(tagString)
 | 
			
		||||
			let finalText = rows[0][0]['text']
 | 
			
		||||
			if(finalText == null){
 | 
			
		||||
				finalText = ''
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return resolve(finalText) //Return all tags found by query
 | 
			
		||||
		})
 | 
			
		||||
		.catch(console.log)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ router.use(function setUserId (req, res, next) {
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
router.post('/search', function (req, res) {
 | 
			
		||||
	Attachment.search(userId, req.body.noteId)
 | 
			
		||||
	Attachment.search(userId, req.body.noteId, req.body.attachmentType)
 | 
			
		||||
	.then( data => res.send(data) )
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ var express = require('express')
 | 
			
		||||
var router = express.Router()
 | 
			
		||||
 | 
			
		||||
let Notes = require('@models/Note');
 | 
			
		||||
let ShareNote = require('@models/ShareNote');
 | 
			
		||||
 | 
			
		||||
let userId = null
 | 
			
		||||
let socket = null
 | 
			
		||||
@@ -18,6 +19,9 @@ router.use(function setUserId (req, res, next) {
 | 
			
		||||
	next()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Note actions
 | 
			
		||||
//
 | 
			
		||||
router.post('/get', function (req, res) {
 | 
			
		||||
	req.io.emit('welcome_homie', 'Welcome, dont poop from excitement')
 | 
			
		||||
	Notes.get(userId, req.body.noteId)
 | 
			
		||||
@@ -58,15 +62,35 @@ router.post('/difftext', function (req, res) {
 | 
			
		||||
	})
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Share Note Actions
 | 
			
		||||
//
 | 
			
		||||
router.post('/getshareusers', function (req, res) {
 | 
			
		||||
	ShareNote.getUsers(userId, req.body.rawTextId)
 | 
			
		||||
	.then(results => res.send(results))
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
router.post('/shareadduser', function (req, res) {
 | 
			
		||||
	ShareNote.addUser(userId, req.body.noteId, req.body.rawTextId, req.body.username)
 | 
			
		||||
	.then(results => res.send(results))
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
router.post('/shareremoveuser', function (req, res) {
 | 
			
		||||
	ShareNote.removeUser(userId, req.body.noteId)
 | 
			
		||||
	.then(results => res.send(results))
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Testing Action
 | 
			
		||||
//
 | 
			
		||||
//Reindex all notes. Not a very good function, not public
 | 
			
		||||
router.get('/reindex5yu43prchuj903mrc', function (req, res) {
 | 
			
		||||
 | 
			
		||||
	Notes.fixAttachmentThumbnails()
 | 
			
		||||
	res.send('A whole mess is going on in the background')
 | 
			
		||||
	Notes.migrateNoteTextToNewTable().then(status => {
 | 
			
		||||
		return res.send(status)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Notes.stressTest().then( i => {
 | 
			
		||||
	// 	// Notes.reindexAll().then( result => res.send('Welcome to reindex...oh god'))
 | 
			
		||||
	// })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								staticFiles/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								staticFiles/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
*
 | 
			
		||||
*/
 | 
			
		||||
!.gitignore
 | 
			
		||||
!assets
 | 
			
		||||
		Reference in New Issue
	
	Block a user