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 />
 | 
							<router-view />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<global-notification />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,6 +17,7 @@
 | 
				
			|||||||
export default {
 | 
					export default {
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		'global-site-menu': require('@/components/GlobalSiteMenu.vue').default,
 | 
							'global-site-menu': require('@/components/GlobalSiteMenu.vue').default,
 | 
				
			||||||
 | 
							'global-notification':require('@/components/GlobalNotificationComponent.vue').default
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	data: function(){ 
 | 
						data: function(){ 
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,14 @@
 | 
				
			|||||||
	--text_color: #3d3d3d;
 | 
						--text_color: #3d3d3d;
 | 
				
			||||||
	--outline_color: rgba(34,36,38,.15);
 | 
						--outline_color: rgba(34,36,38,.15);
 | 
				
			||||||
	--border_color: rgba(34,36,38,.20);
 | 
						--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 {
 | 
					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*/
 | 
					/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
 | 
				
			||||||
body{
 | 
					body {
 | 
				
			||||||
	color: var(--text_color);
 | 
						color: var(--text_color);
 | 
				
			||||||
	background-color: var(--background_color);
 | 
						background-color: var(--background_color);
 | 
				
			||||||
	font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
 | 
						font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
 | 
				
			||||||
@@ -101,6 +109,77 @@ a:hover {
 | 
				
			|||||||
	text-decoration: underline;
 | 
						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 {
 | 
					.note-status-indicator {
 | 
				
			||||||
	position: absolute;
 | 
						position: absolute;
 | 
				
			||||||
	width: 100px;
 | 
						width: 100px;
 | 
				
			||||||
@@ -117,16 +196,17 @@ a:hover {
 | 
				
			|||||||
/* squire text styles */
 | 
					/* squire text styles */
 | 
				
			||||||
	.squire-box {
 | 
						.squire-box {
 | 
				
			||||||
		border: none;
 | 
							border: none;
 | 
				
			||||||
		height: calc(100% - 60px);
 | 
							height: calc(100% - 69px);
 | 
				
			||||||
		box-sizing: border-box;
 | 
							box-sizing: border-box;
 | 
				
			||||||
		padding: 10px 15px 40px;
 | 
							padding: 10px 15px 10px;
 | 
				
			||||||
		background: transparent;
 | 
							background: transparent;
 | 
				
			||||||
		overflow-x: scroll; 
 | 
							overflow-x: scroll; 
 | 
				
			||||||
		/*color: var(--text_color);*/
 | 
							/*color: var(--text_color);*/
 | 
				
			||||||
		font-size: 1.2em;
 | 
							font-size: 1.2em;
 | 
				
			||||||
		line-height: 1.5em;
 | 
							line-height: 1.5em;
 | 
				
			||||||
		word-wrap: break-word;
 | 
							word-wrap: break-word;
 | 
				
			||||||
		border-bottom: 1px solid #ccc;
 | 
							/*border-bottom: 1px solid #ccc;*/
 | 
				
			||||||
 | 
							scrollbar-width: none;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	/*Makes the first line real big */
 | 
						/*Makes the first line real big */
 | 
				
			||||||
	.squire-box p:first-child {
 | 
						.squire-box p:first-child {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,16 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	<div class="color-picker" :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
 | 
						<div :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
 | 
				
			||||||
 | 
					 | 
				
			||||||
		<div class="floating-buttons">
 | 
					 | 
				
			||||||
		<div class="ui basic segment">
 | 
							<div class="ui basic segment">
 | 
				
			||||||
				<div class="ui fluid buttons">
 | 
							<div class="ui grid">
 | 
				
			||||||
					<div class="ui compact button" v-on:click="closeThisBitch">
 | 
					
 | 
				
			||||||
						<i class="close icon"></i>
 | 
								<div class="ui sixteen wide center aligned column">
 | 
				
			||||||
						Close
 | 
									<div class="ui fluid button" v-on:click="clearStyles">
 | 
				
			||||||
					</div>
 | 
					 | 
				
			||||||
					<div class="ui compact button" v-on:click="clearStyles">
 | 
					 | 
				
			||||||
					<i class="refresh icon"></i>
 | 
										<i class="refresh icon"></i>
 | 
				
			||||||
					Clear All Styles
 | 
										Clear All Styles
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		<div class="ui basic segment">
 | 
					 | 
				
			||||||
		<div class="ui grid">
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="row">
 | 
								<div class="row">
 | 
				
			||||||
				<div class="sixteen wide column">
 | 
									<div class="sixteen wide column">
 | 
				
			||||||
@@ -62,8 +52,7 @@
 | 
				
			|||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
		<div class="shade-boy" v-on:click="closeThisBitch"></div>
 | 
					
 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
@@ -157,43 +146,6 @@
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<style type="text/css" scoped>
 | 
					<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 {
 | 
						.icon-button {
 | 
				
			||||||
		height: 40px;
 | 
							height: 40px;
 | 
				
			||||||
		width: 14.2%;
 | 
							width: 14.2%;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,9 @@
 | 
				
			|||||||
</style>
 | 
					</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
	<form>
 | 
						<form data-tooltip="Upload File" data-inverted>
 | 
				
			||||||
		<label :for="`upfile-${noteId}`" class="clickable">
 | 
							<label :for="`upfile-${noteId}`" class="clickable">
 | 
				
			||||||
			<i class="upload icon"></i>
 | 
								<nm-button icon="upload" :text="uploadStatusText"/>
 | 
				
			||||||
			<span v-if="uploadPercentage != 0">{{uploadPercentage}}%</span>
 | 
					 | 
				
			||||||
			<span v-else>Upload</span>
 | 
					 | 
				
			||||||
		</label>
 | 
							</label>
 | 
				
			||||||
		<input class="hidden-up" type="file" :id="`upfile-${noteId}`" ref="file" v-on:change="handleFileUpload()" />
 | 
							<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> -->
 | 
							<!-- <button v-if="file" v-on:click="uploadFileToServer()">Submit</button> -->
 | 
				
			||||||
@@ -23,10 +21,13 @@
 | 
				
			|||||||
	export default {
 | 
						export default {
 | 
				
			||||||
		name: 'FileUploadButton',
 | 
							name: 'FileUploadButton',
 | 
				
			||||||
		props: [ 'noteId' ],
 | 
							props: [ 'noteId' ],
 | 
				
			||||||
 | 
							components: {
 | 
				
			||||||
 | 
								'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		data () {
 | 
							data () {
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
				file: null,
 | 
									file: null,
 | 
				
			||||||
				uploadPercentage: 0,
 | 
									uploadStatusText: 'Upload',
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		mounted(){
 | 
							mounted(){
 | 
				
			||||||
@@ -44,12 +45,12 @@
 | 
				
			|||||||
					formData, {
 | 
										formData, {
 | 
				
			||||||
						headers: { 'Content-Type': 'multipart/form-data' },
 | 
											headers: { 'Content-Type': 'multipart/form-data' },
 | 
				
			||||||
						onUploadProgress: ( progressEvent ) => {
 | 
											onUploadProgress: ( progressEvent ) => {
 | 
				
			||||||
					        this.uploadPercentage = parseInt( 
 | 
										        this.uploadStatusText = parseInt( 
 | 
				
			||||||
					        	Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) )
 | 
										        	Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) )
 | 
				
			||||||
					      }
 | 
										      }
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				).then(results => {
 | 
									).then(results => {
 | 
				
			||||||
					this.uploadPercentage = 'Working'
 | 
										this.uploadStatusText = 'Working'
 | 
				
			||||||
					this.file = null
 | 
										this.file = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// console.log('File upload results')
 | 
										// console.log('File upload results')
 | 
				
			||||||
@@ -58,17 +59,19 @@
 | 
				
			|||||||
					const name = results.data.fileName
 | 
										const name = results.data.fileName
 | 
				
			||||||
					const location = results.data.goodFileName
 | 
										const location = results.data.goodFileName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										this.$bus.$emit('notification', 'Processing Upload')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if(name && location){
 | 
										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})
 | 
											this.$bus.$emit('new_file_upload', {noteId: this.noteId, imageCode})
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
				.catch(results => {
 | 
									.catch(results => {
 | 
				
			||||||
					this.uploadPercentage = 0
 | 
										this.uploadStatusText = 0
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			handleFileUpload() {
 | 
								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>
 | 
								</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 class="menu-section" v-if="loggedIn">
 | 
				
			||||||
				<div v-on:click="updateFastFilters(2)" class="menu-item menu-button">
 | 
									<div v-on:click="updateFastFilters(2)" class="menu-item menu-button">
 | 
				
			||||||
					<i class="archive icon"></i>Archived
 | 
										<i class="archive icon"></i>Archived
 | 
				
			||||||
@@ -239,6 +245,7 @@
 | 
				
			|||||||
			if(this.mobile){
 | 
								if(this.mobile){
 | 
				
			||||||
				this.menuOpen = false
 | 
									this.menuOpen = false
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		computed: {
 | 
							computed: {
 | 
				
			||||||
			loggedIn () {
 | 
								loggedIn () {
 | 
				
			||||||
@@ -279,6 +286,7 @@
 | 
				
			|||||||
				})
 | 
									})
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			destroyLoginToken() {
 | 
								destroyLoginToken() {
 | 
				
			||||||
 | 
									this.$bus.$emit('notification', 'Logged Out')
 | 
				
			||||||
				this.$store.commit('destroyLoginToken')
 | 
									this.$store.commit('destroyLoginToken')
 | 
				
			||||||
				this.$router.push('/')
 | 
									this.$router.push('/')
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -309,6 +317,7 @@
 | 
				
			|||||||
					'withLinks', // 'Only Show Notes with Links'
 | 
										'withLinks', // 'Only Show Notes with Links'
 | 
				
			||||||
					'withTags', // 'Only Show Notes with Tags'
 | 
										'withTags', // 'Only Show Notes with Tags'
 | 
				
			||||||
					'onlyArchived', //'Only Show Archived Notes'
 | 
										'onlyArchived', //'Only Show Archived Notes'
 | 
				
			||||||
 | 
										'onlyShowSharedNotes', //Only show shared notes
 | 
				
			||||||
				]
 | 
									]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				let filter = {}
 | 
									let filter = {}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,30 +17,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		<div class="note-menu">
 | 
							<div class="note-menu">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="nm-button" v-on:click="close">
 | 
								<nm-button v-on:click.native="close" icon="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>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="nm-button" v-on:click="toggleItalic()">
 | 
								<nm-button v-on:click.native="toggleList('ol')" icon="list ol" />
 | 
				
			||||||
				<i class="quote left icon"></i>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			<div class="nm-button" v-on:click="modifyFont('2.286em') ">
 | 
								<nm-button v-on:click.native="toggleList('ul')" icon="tasks" />
 | 
				
			||||||
				<i class="text height icon"></i>
 | 
					 | 
				
			||||||
			</div>
 | 
					 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			<div v-if="usersOnNote > 1" class="nm-button">
 | 
								<nm-button v-on:click.native="toggleBold()" icon="bold" />
 | 
				
			||||||
				<i class="green user circle icon"></i>{{usersOnNote}}
 | 
								
 | 
				
			||||||
			</div>
 | 
								<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>
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,47 +44,136 @@
 | 
				
			|||||||
			<div class="ui green button">{{statusText}}</div>
 | 
								<div class="ui green button">{{statusText}}</div>
 | 
				
			||||||
		</span>
 | 
							</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
 | 
								<color-picker
 | 
				
			||||||
			v-if="colorPickerVisible" 
 | 
					 | 
				
			||||||
			:location="colorPickerLocation" 
 | 
					 | 
				
			||||||
				@changeColor="onChangeColor"
 | 
									@changeColor="onChangeColor"
 | 
				
			||||||
				@close="onCloseColorChanger"
 | 
									@close="onCloseColorChanger"
 | 
				
			||||||
				:style-object="styleObject"
 | 
									:style-object="styleObject"
 | 
				
			||||||
			/>
 | 
								/>
 | 
				
			||||||
 | 
							</side-slide-menu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<!-- Note options on the bottom of note -->
 | 
							<side-slide-menu v-if="showTagSlideMenu" v-on:close="showTagSlideMenu = false" name="tags">
 | 
				
			||||||
		<div class="all-settings" :class="{ 'low-settings':!extraToolbarsVisible }">
 | 
								<div class="ui basic segment">
 | 
				
			||||||
 | 
									<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
 | 
				
			||||||
			<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'}}
 | 
					 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
				<!-- archive button -->
 | 
							</side-slide-menu>
 | 
				
			||||||
				<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>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<!-- attachment button -->
 | 
							<side-slide-menu v-if="showFilesSideMenu" v-on:close="showFilesSideMenu = false" name="images">
 | 
				
			||||||
				<div class="nm-button" v-on:click="openEditAttachment">
 | 
								<div class="ui basic segment">
 | 
				
			||||||
					<i class="folder icon"></i> Files
 | 
									<simple-attachment-note
 | 
				
			||||||
 | 
										v-on:close="showFilesSideMenu = false"
 | 
				
			||||||
 | 
										:note-id="noteid" 
 | 
				
			||||||
 | 
										:squire-editor="editor">
 | 
				
			||||||
 | 
									</simple-attachment-note>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
				<!-- file upload button  -->
 | 
							</side-slide-menu>
 | 
				
			||||||
				<file-upload-button class="nm-button" :noteId="noteid" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<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>
 | 
										</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>
 | 
						</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -105,10 +187,15 @@
 | 
				
			|||||||
	name: 'InputNotes',
 | 
						name: 'InputNotes',
 | 
				
			||||||
		props: [ 'noteid', 'position' ],
 | 
							props: [ 'noteid', 'position' ],
 | 
				
			||||||
		components:{
 | 
							components:{
 | 
				
			||||||
			'note-tag-edit': require('@/components/NoteTagEdit.vue').default,
 | 
								'note-tag-edit': () => import('@/components/NoteTagEdit.vue'),
 | 
				
			||||||
			'color-picker': require('@/components/ColorPicker.vue').default,
 | 
								'color-picker': () => import('@/components/ColorPicker.vue'),
 | 
				
			||||||
			'file-upload-button': require('@/components/FileUploadButton.vue').default,
 | 
								'file-upload-button': () => import('@/components/FileUploadButton.vue'),
 | 
				
			||||||
			'delete-button': require('@/components/NoteDeleteButtonComponent.vue').default,
 | 
								// '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(){
 | 
							data(){
 | 
				
			||||||
			return {
 | 
								return {
 | 
				
			||||||
@@ -116,6 +203,8 @@
 | 
				
			|||||||
				loadingMessage: 'Loading Note',
 | 
									loadingMessage: 'Loading Note',
 | 
				
			||||||
				currentNoteId: 0,
 | 
									currentNoteId: 0,
 | 
				
			||||||
				noteText: '',
 | 
									noteText: '',
 | 
				
			||||||
 | 
									rawTextId: 0,
 | 
				
			||||||
 | 
									shareUsername: null,
 | 
				
			||||||
				diffNoteText: '',
 | 
									diffNoteText: '',
 | 
				
			||||||
				statusText: 'Saved',
 | 
									statusText: 'Saved',
 | 
				
			||||||
				lastNoteHash: null,
 | 
									lastNoteHash: null,
 | 
				
			||||||
@@ -130,7 +219,7 @@
 | 
				
			|||||||
				styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
 | 
									styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				sizeDown: false, //Used to animate close state
 | 
									sizeDown: false, //Used to animate close state
 | 
				
			||||||
				colorPickerVisible: false, 
 | 
									
 | 
				
			||||||
				colorPickerLocation: null,
 | 
									colorPickerLocation: null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                tinymce: null, //Initialized editor instance
 | 
					                tinymce: null, //Initialized editor instance
 | 
				
			||||||
@@ -145,6 +234,10 @@
 | 
				
			|||||||
                usersOnNote: 0,
 | 
					                usersOnNote: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                extraToolbarsVisible: true,
 | 
					                extraToolbarsVisible: true,
 | 
				
			||||||
 | 
					                showTagSlideMenu: false,
 | 
				
			||||||
 | 
					                colorPickerVisible: false, 
 | 
				
			||||||
 | 
					                showFilesSideMenu: false,
 | 
				
			||||||
 | 
					                showNoteOptions: false,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		watch: {
 | 
							watch: {
 | 
				
			||||||
@@ -168,12 +261,13 @@
 | 
				
			|||||||
				if(this.noteid == noteId && this.editor){
 | 
									if(this.noteid == noteId && this.editor){
 | 
				
			||||||
					this.editor.moveCursorToEnd()
 | 
										this.editor.moveCursorToEnd()
 | 
				
			||||||
					this.editor.insertHTML(imageCode)
 | 
										this.editor.insertHTML(imageCode)
 | 
				
			||||||
 | 
										this.save()
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		beforeDestroy(){
 | 
							beforeDestroy(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.$io.emit('leave_room', this.noteid)
 | 
								this.$io.emit('leave_room', this.rawTextId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
 | 
								document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -189,24 +283,7 @@
 | 
				
			|||||||
			this.$nextTick(() => {
 | 
								this.$nextTick(() => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.loadNote(this.noteid)
 | 
									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: {
 | 
							methods: {
 | 
				
			||||||
			initSquire(){
 | 
								initSquire(){
 | 
				
			||||||
@@ -215,7 +292,7 @@
 | 
				
			|||||||
				this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
 | 
									this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
 | 
				
			||||||
				this.setText(this.noteText)
 | 
									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 => {
 | 
									this.editor.addEventListener('click', e => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					//Link clicked in editor - open link
 | 
										//Link clicked in editor - open link
 | 
				
			||||||
@@ -284,6 +361,7 @@
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			//If nothing is selected, select the entire line
 | 
								//If nothing is selected, select the entire line
 | 
				
			||||||
			selectLineIfNoSelect(){
 | 
								selectLineIfNoSelect(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				//Select entire line if range is not set 
 | 
									//Select entire line if range is not set 
 | 
				
			||||||
				let selection = this.editor.getSelection()
 | 
									let selection = this.editor.getSelection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -297,6 +375,7 @@
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			modifyFont(inSize){
 | 
								modifyFont(inSize){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.selectLineIfNoSelect()
 | 
									this.selectLineIfNoSelect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				let fontInfo = this.editor.getFontInfo()
 | 
									let fontInfo = this.editor.getFontInfo()
 | 
				
			||||||
@@ -338,6 +417,149 @@
 | 
				
			|||||||
					this.editor.italic()
 | 
										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){
 | 
								setText(inText){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.editor.setHTML(inText)
 | 
									this.editor.setHTML(inText)
 | 
				
			||||||
@@ -349,13 +571,16 @@
 | 
				
			|||||||
				return this.editor.getHTML()
 | 
									return this.editor.getHTML()
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			showColorPicker(event){
 | 
								showColorPicker(event){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.colorPickerVisible = !this.colorPickerVisible
 | 
									this.colorPickerVisible = !this.colorPickerVisible
 | 
				
			||||||
				this.colorPickerLocation = {'x':event.clientX, 'y':event.clientY}
 | 
									this.colorPickerLocation = {'x':event.clientX, 'y':event.clientY}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			openEditAttachment(){
 | 
								openEditAttachment(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.$router.push('/attachments/note/'+this.currentNoteId)
 | 
									this.$router.push('/attachments/note/'+this.currentNoteId)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			onTogglePinned(){
 | 
								onTogglePinned(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if(this.pinned == 0){
 | 
									if(this.pinned == 0){
 | 
				
			||||||
					this.pinned = 1
 | 
										this.pinned = 1
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
@@ -366,6 +591,7 @@
 | 
				
			|||||||
				this.save()
 | 
									this.save()
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			onToggleArchived(){
 | 
								onToggleArchived(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if(this.archived == 0){
 | 
									if(this.archived == 0){
 | 
				
			||||||
					this.archived = 1
 | 
										this.archived = 1
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
@@ -376,6 +602,7 @@
 | 
				
			|||||||
				this.save()
 | 
									this.save()
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			onCloseColorChanger(){
 | 
								onCloseColorChanger(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.colorPickerVisible = false
 | 
									this.colorPickerVisible = false
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			onChangeColor(newStyleObject){
 | 
								onChangeColor(newStyleObject){
 | 
				
			||||||
@@ -403,6 +630,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
						//Set up local data
 | 
											//Set up local data
 | 
				
			||||||
						vm.currentNoteId = noteId
 | 
											vm.currentNoteId = noteId
 | 
				
			||||||
 | 
											this.rawTextId = response.data.rawTextId
 | 
				
			||||||
 | 
											this.shareUsername = response.data.shareUsername
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						vm.noteText = response.data.text
 | 
											vm.noteText = response.data.text
 | 
				
			||||||
						vm.diffNoteText = response.data.text
 | 
											vm.diffNoteText = response.data.text
 | 
				
			||||||
@@ -423,7 +652,8 @@
 | 
				
			|||||||
						this.loading = false
 | 
											this.loading = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						vm.$nextTick(() => {
 | 
											vm.$nextTick(() => {
 | 
				
			||||||
							// this.initTinyMce()
 | 
												
 | 
				
			||||||
 | 
												this.setupWebSockets()
 | 
				
			||||||
							this.initSquire()
 | 
												this.initSquire()
 | 
				
			||||||
						})
 | 
											})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -473,8 +703,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  					// console.log(patch_text)
 | 
					  					// console.log(patch_text)
 | 
				
			||||||
					this.$io.emit('note_diff', {
 | 
										this.$io.emit('note_diff', {
 | 
				
			||||||
						id:this.currentNoteId,
 | 
											id: this.rawTextId,
 | 
				
			||||||
						diff:patch_text
 | 
											diff: patch_text
 | 
				
			||||||
					})
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -575,9 +805,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
					}, 20)
 | 
										}, 20)
 | 
				
			||||||
				// }
 | 
									// }
 | 
				
			||||||
 | 
					 | 
				
			||||||
				
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			xpath(el) {
 | 
								xpath(el) {
 | 
				
			||||||
				//Skip things we can't use
 | 
									//Skip things we can't use
 | 
				
			||||||
@@ -592,7 +819,8 @@
 | 
				
			|||||||
				const sames = [].filter.call(el.parentNode.children, function (x) { return x.tagName == el.tagName })
 | 
									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)+']' : '')
 | 
									return this.xpath(el.parentNode) + '/' + el.tagName.toLowerCase() + (sames.length > 1 ? '['+([].indexOf.call(sames, el)+1)+']' : '')
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			getElementByXPath(xpath) { 
 | 
								getElementByXPath(xpath){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return new XPathEvaluator()
 | 
									return new XPathEvaluator()
 | 
				
			||||||
					.createExpression(xpath)
 | 
										.createExpression(xpath)
 | 
				
			||||||
					.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE) .singleNodeValue 
 | 
										.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE) .singleNodeValue 
 | 
				
			||||||
@@ -661,7 +889,6 @@
 | 
				
			|||||||
						})
 | 
											})
 | 
				
			||||||
					// }, 300)
 | 
										// }, 300)
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
				
 | 
					 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			checkForUpdatedNote(){
 | 
								checkForUpdatedNote(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -669,7 +896,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				//If user leaves page then returns to page, reload the first batch
 | 
									//If user leaves page then returns to page, reload the first batch
 | 
				
			||||||
				if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
 | 
									if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
 | 
				
			||||||
					console.log('Checking for note updates after visibility change.')
 | 
										// console.log('Checking for note updates after visibility change.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					const postData = { 
 | 
										const postData = { 
 | 
				
			||||||
						noteId:this.currentNoteId, 
 | 
											noteId:this.currentNoteId, 
 | 
				
			||||||
@@ -726,7 +953,19 @@
 | 
				
			|||||||
						return
 | 
											return
 | 
				
			||||||
					}, 300)
 | 
										}, 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>
 | 
					<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 */
 | 
						/* squire styles */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*Settings manager 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>
 | 
					<template>
 | 
				
			||||||
	<div v-on:click="fullTagEdit = true">
 | 
						<div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
		<!-- 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>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<!-- hover over view -->
 | 
							<!-- 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="ui grid">
 | 
				
			||||||
			<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) }">
 | 
									<div class="sixteen wide column">
 | 
				
			||||||
					 {{ucWords(tag.text)}} <i class="delete icon" v-on:click="removeTag(tag.id)"></i>
 | 
										<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>
 | 
				
			||||||
 | 
									</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 -->
 | 
								<!-- tag input and suggestion popup -->
 | 
				
			||||||
			<div class="ui form">
 | 
								<div class="ui form">
 | 
				
			||||||
@@ -70,9 +65,11 @@
 | 
				
			|||||||
				newTagInput: '',
 | 
									newTagInput: '',
 | 
				
			||||||
				typeDebounce: null,
 | 
									typeDebounce: null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									allTags: [],
 | 
				
			||||||
 | 
									noteTagIds: [],
 | 
				
			||||||
				suggestions: [],
 | 
									suggestions: [],
 | 
				
			||||||
				selection: 0,
 | 
									selection: 0,
 | 
				
			||||||
				fullTagEdit: false,
 | 
									fullTagEdit: true,
 | 
				
			||||||
				loaded: false,
 | 
									loaded: false,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@@ -85,14 +82,37 @@
 | 
				
			|||||||
		methods: {
 | 
							methods: {
 | 
				
			||||||
			getTags(){
 | 
								getTags(){
 | 
				
			||||||
				//Get Note Tags -> /api/tags/get
 | 
									//Get Note Tags -> /api/tags/get
 | 
				
			||||||
				let vm = this
 | 
					 | 
				
			||||||
				axios.post('/api/tag/get', {'noteId': this.noteId})
 | 
									axios.post('/api/tag/get', {'noteId': this.noteId})
 | 
				
			||||||
				.then(response => {
 | 
									.then( ({data}) => {
 | 
				
			||||||
					vm.loaded = true
 | 
					
 | 
				
			||||||
					//Set up local data
 | 
										this.loaded = true
 | 
				
			||||||
					vm.tags = response.data
 | 
					
 | 
				
			||||||
 | 
										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){
 | 
								tagInput(event){
 | 
				
			||||||
				let vm = this
 | 
									let vm = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -177,6 +197,7 @@
 | 
				
			|||||||
				})
 | 
									})
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			onFocus(){
 | 
								onFocus(){
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
				//Show suggested tags
 | 
									//Show suggested tags
 | 
				
			||||||
				let vm = this
 | 
									let vm = this
 | 
				
			||||||
				let postData = {
 | 
									let postData = {
 | 
				
			||||||
@@ -211,10 +232,10 @@
 | 
				
			|||||||
					'tagId':tagId,
 | 
										'tagId':tagId,
 | 
				
			||||||
					'noteId':this.noteId
 | 
										'noteId':this.noteId
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				let vm = this
 | 
					
 | 
				
			||||||
				axios.post('/api/tag/removefromnote', postData)
 | 
									axios.post('/api/tag/removefromnote', postData)
 | 
				
			||||||
				.then(response => {
 | 
									.then(response => {
 | 
				
			||||||
					vm.getTags()
 | 
										this.getTags()
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			clearSuggestions(){
 | 
								clearSuggestions(){
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,37 +40,40 @@
 | 
				
			|||||||
				
 | 
									
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									
 | 
				
			||||||
			<!-- Toolbar on the bottom  -->
 | 
								<!-- Toolbar on the bottom  -->
 | 
				
			||||||
			<div class="bottom aligned row icon-bar" @click.self="onClick(note.id)">
 | 
								<div class="bottom aligned row" @click.self="onClick(note.id)">
 | 
				
			||||||
				<div class="six wide column clickable" @click="onClick(note.id)">
 | 
									<div class="sixteen wide column">
 | 
				
			||||||
					{{$helpers.timeAgo(note.updated)}} 
 | 
										<div class="ui grid reduced-padding">
 | 
				
			||||||
				</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="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="">
 | 
												<span v-if="note.pinned == 1" data-position="top right" data-tooltip="Pinned" data-inverted="">
 | 
				
			||||||
								<i class="green pin icon"></i>
 | 
													<i class="green pin icon"></i>
 | 
				
			||||||
							</span>
 | 
												</span>
 | 
				
			||||||
							<span v-if="note.archived == 1" data-position="top right" data-tooltip="Archived" data-inverted="">
 | 
												<span v-if="note.archived == 1" data-position="top right" data-tooltip="Archived" data-inverted="">
 | 
				
			||||||
								<i class="green archive icon"></i>
 | 
													<i class="green archive icon"></i>
 | 
				
			||||||
							</span>
 | 
												</span>
 | 
				
			||||||
					<span v-if="note.attachment_count > 0" v-on:click.stop="openEditAttachment" class="clickable">
 | 
											</div>
 | 
				
			||||||
						<i class="linkify icon"></i> {{note.attachment_count}}
 | 
											<div class="three wide right aligned column">
 | 
				
			||||||
					</span>
 | 
												<delete-button :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }" :note-id="note.id" />
 | 
				
			||||||
					<span v-if="note.tag_count == 1" data-position="top right" data-tooltip="Note has 1 tag" data-inverted="">
 | 
											</div>
 | 
				
			||||||
						<i class="tags icon"></i> {{note.tag_count}}
 | 
					
 | 
				
			||||||
					</span>
 | 
											<div class="row" v-if="note.thumbs">
 | 
				
			||||||
					<span v-if="note.tag_count > 1" :data-tooltip="`Note has ${note.tag_count} tags`" data-position="top right" data-inverted="">
 | 
												<div class="tiny-thumb-box" v-on:click="openEditAttachment">
 | 
				
			||||||
						<i class="tags icon"></i> {{note.tag_count}}
 | 
													<img v-for="thumb in note.thumbs.split(',').reverse()" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
 | 
				
			||||||
					</span>
 | 
					 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
@@ -145,13 +148,16 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/*Strict font sizes for card display*/
 | 
						/*Strict font sizes for card display*/
 | 
				
			||||||
	.small-text, .small-text > p, .small-text > h1, .small-text > h2 {
 | 
						.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 {
 | 
						.small-text > p, , .small-text > h1, .small-text > h2 {
 | 
				
			||||||
		margin-bottom: 0.5em;
 | 
							margin-bottom: 0.5em;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	.big-text, .big-text > p, .big-text > h1, .big-text > h2 {
 | 
						.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 {
 | 
						.big-text > p, .big-text > h1, .big-text > h2 {
 | 
				
			||||||
		margin-bottom: 0.3em;
 | 
							margin-bottom: 0.3em;
 | 
				
			||||||
@@ -186,7 +192,7 @@
 | 
				
			|||||||
		/*box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);*/
 | 
							/*box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);*/
 | 
				
			||||||
		box-shadow: 0 0px 5px 1px rgba(34,36,38,0);
 | 
							box-shadow: 0 0px 5px 1px rgba(34,36,38,0);
 | 
				
			||||||
		margin: 5px;
 | 
							margin: 5px;
 | 
				
			||||||
		padding: 1em 1.5em;
 | 
							padding: 0.7em 1em;
 | 
				
			||||||
		border-radius: .28571429rem;
 | 
							border-radius: .28571429rem;
 | 
				
			||||||
		border: 1px solid;
 | 
							border: 1px solid;
 | 
				
			||||||
		border-color: var(--border_color);
 | 
							border-color: var(--border_color);
 | 
				
			||||||
@@ -205,11 +211,42 @@
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	.icon-bar {
 | 
						.icon-bar {
 | 
				
			||||||
		opacity: 0.8;
 | 
							opacity: 0.8;
 | 
				
			||||||
		margin-top: -2.2rem;
 | 
							/*margin-top: -2.2rem;*/
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	.hover-hide {
 | 
						.hover-hide {
 | 
				
			||||||
		opacity: 0.0;
 | 
							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 {
 | 
						.note-title-display-card:hover .icon-bar {
 | 
				
			||||||
		opacity: 1;
 | 
							opacity: 1;
 | 
				
			||||||
@@ -262,7 +299,6 @@
 | 
				
			|||||||
		.note-title-display-card {
 | 
							.note-title-display-card {
 | 
				
			||||||
			width: calc(100% + 10px);
 | 
								width: calc(100% + 10px);
 | 
				
			||||||
			margin: 0px -5px 10px -5px;
 | 
								margin: 0px -5px 10px -5px;
 | 
				
			||||||
			padding: 15px;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
	<div class="ui form">
 | 
						<div class="ui form">
 | 
				
			||||||
		<div class="fields">
 | 
							<div class="fields">
 | 
				
			||||||
			<div class="sixteen wide field">
 | 
								<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>
 | 
							</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
 | 
											//Redirect user to notes section after login
 | 
				
			||||||
						vm.$router.push('/notes')
 | 
											vm.$router.push('/notes')
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
 | 
											this.$bus.$emit('notification', 'Incorrect Username or Password')
 | 
				
			||||||
						vm.$store.commit('destroyLoginToken')
 | 
											vm.$store.commit('destroyLoginToken')
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
				.catch(error => {
 | 
									.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="fields">
 | 
				
			||||||
							<div class="ten wide field">
 | 
												<div class="ten wide field">
 | 
				
			||||||
								<search-input></search-input>
 | 
													<search-input></search-input>
 | 
				
			||||||
								<!-- <input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" /> -->
 | 
					 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
							<div class="six wide field">
 | 
												<div class="six wide field">
 | 
				
			||||||
								<span class="ui fluid green button" 
 | 
													<span class="ui fluid green button" 
 | 
				
			||||||
									v-if="showClear"
 | 
														v-if="showClear"
 | 
				
			||||||
									@click="reset">
 | 
														@click="reset">
 | 
				
			||||||
									<i class="undo icon"></i>Reset Filters
 | 
														<i class="undo icon"></i>Back to All Notes
 | 
				
			||||||
								</span>
 | 
													</span>
 | 
				
			||||||
								<!-- <fast-filters /> -->
 | 
													<!-- <fast-filters /> -->
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
@@ -36,6 +35,7 @@
 | 
				
			|||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div v-if="commonTags.length > 0" class="sixteen wide column">
 | 
								<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 v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
 | 
				
			||||||
					<span class="ui clickable basic label" :class="{ 'green':(searchTags.includes(tag.id)) }">
 | 
										<span class="ui clickable basic label" :class="{ 'green':(searchTags.includes(tag.id)) }">
 | 
				
			||||||
					{{ucWords(tag.text)}} <span class="detail">{{tag.usages}}</span>
 | 
										{{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
 | 
								//Try to close notes on URL hash change /notes/open/123 to /notes - parse 123, close note id 123
 | 
				
			||||||
			hashChangeAction(event){
 | 
								hashChangeAction(event){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				//Clean up path of hash change
 | 
									//Clean up path of hash change
 | 
				
			||||||
				let path = window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.hash
 | 
									let path = window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.hash
 | 
				
			||||||
				let newPath = event.newURL.replace(path,'')
 | 
									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) => {
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let params = [userId]
 | 
							let params = [userId]
 | 
				
			||||||
@@ -54,6 +54,11 @@ Attachment.search = (userId, noteId) => {
 | 
				
			|||||||
			params.push(noteId)
 | 
								params.push(noteId)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(Number.isInteger(attachmentType)){
 | 
				
			||||||
 | 
								query += 'AND attachment_type = ? '
 | 
				
			||||||
 | 
								params.push(attachmentType)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		query += 'ORDER BY last_indexed DESC '
 | 
							query += 'ORDER BY last_indexed DESC '
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		db.promise()
 | 
							db.promise()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,35 @@ let Note = module.exports = {}
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const gm = require('gm')
 | 
					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 = () => {
 | 
					Note.fixAttachmentThumbnails = () => {
 | 
				
			||||||
	const filePath = '../staticFiles/'
 | 
						const filePath = '../staticFiles/'
 | 
				
			||||||
	db.promise()
 | 
						db.promise()
 | 
				
			||||||
@@ -66,6 +95,8 @@ Note.stressTest = () => {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note.create = (userId, noteText, quickNote = 0) => {
 | 
					Note.create = (userId, noteText, quickNote = 0) => {
 | 
				
			||||||
	return new Promise((resolve, reject) => {
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,39 +105,23 @@ Note.create = (userId, noteText, quickNote = 0) => {
 | 
				
			|||||||
		const created = Math.round((+new Date)/1000)
 | 
							const created = Math.round((+new Date)/1000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		db.promise()
 | 
							db.promise()
 | 
				
			||||||
		.query('INSERT INTO note (user_id, text, updated, created, quick_note) VALUES (?,?,?,?,?)', 
 | 
							.query(`INSERT INTO note_raw_text (text) VALUE ('')`)
 | 
				
			||||||
			[userId, noteText, created, created, quickNote])
 | 
							.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) => {
 | 
							.then((rows, fields) => {
 | 
				
			||||||
			// New notes are empty, don't add to solr index
 | 
								// Indexing is done on save
 | 
				
			||||||
			// Note.reindex(userId, rows[0].insertId)
 | 
					 | 
				
			||||||
			resolve(rows[0].insertId) //Only return the new note ID when creating a new note
 | 
								resolve(rows[0].insertId) //Only return the new note ID when creating a new note
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		.catch(console.log)
 | 
							.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) => {
 | 
					Note.reindex = (userId, noteId) => {
 | 
				
			||||||
	return new Promise((resolve, reject) => {
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -154,8 +169,23 @@ Note.update = (userId, noteId, noteText, color, pinned, archived) => {
 | 
				
			|||||||
		const now = Math.round((+new Date)/1000)
 | 
							const now = Math.round((+new Date)/1000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		db.promise()
 | 
							db.promise()
 | 
				
			||||||
		.query('UPDATE note SET text = ?, pinned = ?, archived = ?, updated = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1', 
 | 
							.query('SELECT note_raw_text_id FROM note WHERE id = ? AND user_id = ?', [noteId, userId])
 | 
				
			||||||
			[noteText, pinned, archived, now, color, 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) => {
 | 
							.then((rows, fields) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			//Async solr note reindex
 | 
								//Async solr note reindex
 | 
				
			||||||
@@ -171,7 +201,6 @@ Note.update = (userId, noteId, noteText, color, pinned, archived) => {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// Delete a note and all its remaining parts
 | 
					// Delete a note and all its remaining parts
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
@@ -179,16 +208,54 @@ Note.delete = (userId, noteId) => {
 | 
				
			|||||||
	return new Promise((resolve, reject) => {
 | 
						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 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) => {
 | 
							.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) => {
 | 
							.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) => {
 | 
							.then((rows, fields) => {
 | 
				
			||||||
			resolve(true)
 | 
								resolve(true)
 | 
				
			||||||
@@ -251,9 +318,19 @@ Note.get = (userId, noteId) => {
 | 
				
			|||||||
	return new Promise((resolve, reject) => {
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
		db.promise()
 | 
							db.promise()
 | 
				
			||||||
		.query(`
 | 
							.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 
 | 
								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 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])
 | 
								WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId,noteId])
 | 
				
			||||||
		.then((rows, fields) => {
 | 
							.then((rows, fields) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -265,6 +342,7 @@ Note.get = (userId, noteId) => {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Public note share action -> may not be used
 | 
				
			||||||
Note.getShared = (noteId) => {
 | 
					Note.getShared = (noteId) => {
 | 
				
			||||||
	return new Promise((resolve, reject) => {
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
		db.promise()
 | 
							db.promise()
 | 
				
			||||||
@@ -356,20 +434,33 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			// Base of the query, modified with fastFilters
 | 
								// Base of the query, modified with fastFilters
 | 
				
			||||||
			// Add to query for character counts -> CHAR_LENGTH(note.text) as chars
 | 
								// Add to query for character counts -> CHAR_LENGTH(note.text) as chars
 | 
				
			||||||
 | 
								let searchParams = [userId]
 | 
				
			||||||
			let noteSearchQuery = `
 | 
								let noteSearchQuery = `
 | 
				
			||||||
				SELECT note.id, 
 | 
									SELECT note.id, 
 | 
				
			||||||
					SUBSTRING(note.text, 1, 1500) as text, 
 | 
										SUBSTRING(note_raw_text.text, 1, 1500) as text, 
 | 
				
			||||||
					updated, 
 | 
										updated, 
 | 
				
			||||||
					color, 
 | 
										color, 
 | 
				
			||||||
					count(distinct note_tag.id) as tag_count, 
 | 
										count(distinct note_tag.id) as tag_count, 
 | 
				
			||||||
					count(distinct attachment.id) as attachment_count,
 | 
										count(distinct attachment.id) as attachment_count,
 | 
				
			||||||
					note.pinned,
 | 
										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 
 | 
									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 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 attachment ON (note.id = attachment.note_id)
 | 
				
			||||||
 | 
									LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
 | 
				
			||||||
				WHERE note.user_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 text search returned results, limit search to those ids			
 | 
				
			||||||
			if(textSearchIds.length > 0){
 | 
								if(textSearchIds.length > 0){
 | 
				
			||||||
@@ -418,15 +509,15 @@ Note.search = (userId, searchQuery, searchTags, fastFilters) => {
 | 
				
			|||||||
			// Always prioritize pinned notes in searches.
 | 
								// Always prioritize pinned notes in searches.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			//Default Sort, order by last updated
 | 
								//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
 | 
								//Order by Last Created Date
 | 
				
			||||||
			if(fastFilters.lastCreated == 1){
 | 
								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
 | 
								//Order by last Opened Date
 | 
				
			||||||
			if(fastFilters.lastOpened == 1){
 | 
								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
 | 
								//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
 | 
								JOIN note_tag ON tag.id = note_tag.tag_id
 | 
				
			||||||
			WHERE note_tag.user_id = ?
 | 
								WHERE note_tag.user_id = ?
 | 
				
			||||||
			GROUP BY tag.id
 | 
								GROUP BY tag.id
 | 
				
			||||||
			ORDER BY usages DESC
 | 
								ORDER BY id DESC
 | 
				
			||||||
		`, [userId])
 | 
							`, [userId])
 | 
				
			||||||
		.then( (rows, fields) => {
 | 
							.then( (rows, fields) => {
 | 
				
			||||||
			resolve(rows[0])
 | 
								resolve(rows[0])
 | 
				
			||||||
@@ -98,39 +98,50 @@ Tag.add = (tagText) => {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Get all tags AND tags associated to note
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
Tag.get = (userId, noteId) => {
 | 
					Tag.get = (userId, noteId) => {
 | 
				
			||||||
	return new Promise((resolve, reject) => {
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//Update last opened date of note
 | 
							Tag.userTags(userId).then(userTags => {
 | 
				
			||||||
		const now = Math.round((+new Date)/1000)
 | 
					 | 
				
			||||||
			db.promise()
 | 
								db.promise()
 | 
				
			||||||
		.query('UPDATE note SET opened = ? WHERE id = ? AND user_id = ? LIMIT 1', [now, noteId, userId])
 | 
								.query(`SELECT tag_id as tagId, id as entryId
 | 
				
			||||||
		.then((rows, fields) => {})
 | 
										FROM note_tag
 | 
				
			||||||
 | 
					 | 
				
			||||||
		db.promise()
 | 
					 | 
				
			||||||
		.query(`SELECT note_tag.id, tag.text FROM note_tag
 | 
					 | 
				
			||||||
				JOIN tag ON (tag.id = note_tag.tag_id)
 | 
					 | 
				
			||||||
					WHERE user_id = ? AND note_id = ?;`, [userId, noteId])
 | 
										WHERE user_id = ? AND note_id = ?;`, [userId, noteId])
 | 
				
			||||||
			.then((rows, fields) => {
 | 
								.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)
 | 
								.catch(console.log)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Get all tags for a note and concatinate into a string 'all, tags, like, this'
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
Tag.string = (userId, noteId) => {
 | 
					Tag.string = (userId, noteId) => {
 | 
				
			||||||
	return new Promise((resolve, reject) => {
 | 
						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 = ''
 | 
								let finalText = rows[0][0]['text']
 | 
				
			||||||
			tagArray.forEach( (tag, i) => {
 | 
								if(finalText == null){
 | 
				
			||||||
				if(i > 0){ tagString += ',' }
 | 
									finalText = ''
 | 
				
			||||||
				tagString += tag.text
 | 
								}
 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
			//Output comma delimited list of tag strings
 | 
					 | 
				
			||||||
			resolve(tagString)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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) {
 | 
					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) )
 | 
						.then( data => res.send(data) )
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ var express = require('express')
 | 
				
			|||||||
var router = express.Router()
 | 
					var router = express.Router()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let Notes = require('@models/Note');
 | 
					let Notes = require('@models/Note');
 | 
				
			||||||
 | 
					let ShareNote = require('@models/ShareNote');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let userId = null
 | 
					let userId = null
 | 
				
			||||||
let socket = null
 | 
					let socket = null
 | 
				
			||||||
@@ -18,6 +19,9 @@ router.use(function setUserId (req, res, next) {
 | 
				
			|||||||
	next()
 | 
						next()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Note actions
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
router.post('/get', function (req, res) {
 | 
					router.post('/get', function (req, res) {
 | 
				
			||||||
	req.io.emit('welcome_homie', 'Welcome, dont poop from excitement')
 | 
						req.io.emit('welcome_homie', 'Welcome, dont poop from excitement')
 | 
				
			||||||
	Notes.get(userId, req.body.noteId)
 | 
						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
 | 
					//Reindex all notes. Not a very good function, not public
 | 
				
			||||||
router.get('/reindex5yu43prchuj903mrc', function (req, res) {
 | 
					router.get('/reindex5yu43prchuj903mrc', function (req, res) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Notes.fixAttachmentThumbnails()
 | 
						Notes.migrateNoteTextToNewTable().then(status => {
 | 
				
			||||||
	res.send('A whole mess is going on in the background')
 | 
							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