From 148b822d499710e790ae7e33779a5da8432cacc5 Mon Sep 17 00:00:00 2001
From: Max G <admin@internet.com>
Date: Thu, 27 Jan 2022 04:48:19 +0000
Subject: [PATCH] Tons of littele interface changes and cleanups Massive update
 to image scraper with much better image getter Lots of little ui updates for
 mobile

---
 client/src/Helpers.js                         |   2 +-
 client/src/assets/semantic-helper.css         |   2 +-
 client/src/assets/squire.js                   |   9 +-
 client/src/components/ColorPicker.vue         |  95 +++++----
 client/src/components/NoteInputPanel.vue      | 199 +++++++-----------
 .../src/components/NoteTitleDisplayCard.vue   | 151 ++++++++-----
 client/src/pages/HomePage.vue                 |  34 +--
 client/src/pages/NotesPage.vue                |  15 +-
 dontSync.txt                                  |   3 +-
 server/helpers/Auth.js                        |   3 +-
 server/helpers/SiteScrape.js                  |  93 ++++++--
 server/index.js                               |   2 -
 server/models/Attachment.js                   |  25 ++-
 server/models/Note.js                         |   5 +-
 server/models/Tag.js                          |  27 +++
 server/models/User.js                         |   2 +-
 server/routes/tagController.js                |   6 +
 17 files changed, 400 insertions(+), 273 deletions(-)

diff --git a/client/src/Helpers.js b/client/src/Helpers.js
index 0b22d71..2e45431 100644
--- a/client/src/Helpers.js
+++ b/client/src/Helpers.js
@@ -53,7 +53,7 @@ helpers.timeAgo = (time) => {
 			if (typeof format[2] == 'string') {
 				return format[list_choice]
 			} else {
-				return Math.floor(seconds / format[2]) + ' ' + format[1]// + ' ' + token
+				return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token
 			}
 		}
 	}
diff --git a/client/src/assets/semantic-helper.css b/client/src/assets/semantic-helper.css
index 8d8a050..37b5c88 100644
--- a/client/src/assets/semantic-helper.css
+++ b/client/src/assets/semantic-helper.css
@@ -43,7 +43,7 @@ html {
 	height:100%;
 	padding: 0;
 	margin: 0;
-	background: none;
+	background: var(--body_bg_color);
 }
 a:hover {
 	text-decoration: underline;
diff --git a/client/src/assets/squire.js b/client/src/assets/squire.js
index 29529ae..c5c8f82 100644
--- a/client/src/assets/squire.js
+++ b/client/src/assets/squire.js
@@ -2117,7 +2117,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) {
                             break;
                         }
                     }
-                    data = data.replace( /^[ \t\r\n]+/g, sibling ? ' ' : '' );
+                    data = data.replace( /^[ \r\n]+/g, sibling ? ' ' : '' );
                 }
                 if ( endsWithWS ) {
                     walker.currentNode = child;
@@ -2132,7 +2132,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) {
                             break;
                         }
                     }
-                    data = data.replace( /[ \t\r\n]+$/g, sibling ? ' ' : '' );
+                    data = data.replace( /[ \r\n]+$/g, sibling ? ' ' : '' );
                 }
                 if ( data ) {
                     child.data = data;
@@ -2693,7 +2693,8 @@ var sanitizeToDOMFragment = function ( html, isPaste, self ) {
         ALLOW_UNKNOWN_PROTOCOLS: true,
         WHOLE_DOCUMENT: false,
         RETURN_DOM: true,
-        RETURN_DOM_FRAGMENT: true
+        RETURN_DOM_FRAGMENT: true,
+        FORCE_BODY: false
     }) : null;
     return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment();
 };
@@ -5011,4 +5012,4 @@ if ( typeof exports === 'object' ) {
     }
 }
 
-}( document ) );
\ No newline at end of file
+}( document ) );
diff --git a/client/src/components/ColorPicker.vue b/client/src/components/ColorPicker.vue
index c6aef64..612af09 100644
--- a/client/src/components/ColorPicker.vue
+++ b/client/src/components/ColorPicker.vue
@@ -1,54 +1,59 @@
 <template>
 		
 	
-	<div :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
-		<div class="ui basic segment">
+	<div>
+		
 		<div class="ui grid">
 
-			<div class="ui sixteen wide center aligned column">
-				<div class="ui fluid button" v-on:click="clearStyles">
+			<div class="ui sixteen wide column">
+				<div class="ui dividing header">
+					Reset Background Color and Icon
+				</div>
+				<div class="ui labeled basic icon button" v-on:click="clearStyles">
 					<i class="refresh icon"></i>
-					Clear All Styles
+					Reset
 				</div>
 			</div>
 
-			<div class="row">
-				<div class="sixteen wide column">
-					<br>
-					<p>Note Color</p>
-					<div v-for="color in colors" 
-						class="color-button" 
-						:style="{ backgroundColor:color }"
-						v-on:click="chosenColor(color)"
-					></div>
+			<div class="sixteen wide column rounded" :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
+				<div class="ui dividing header" :style="{ 'color':allStyles['noteText']}">
+					<i class="fill drip icon"></i>
+					Background Color
+				</div>
+				<div v-for="color in colors" 
+					class="color-button" 
+					:style="{ backgroundColor:color }"
+					v-on:click="chosenColor(color)"
+				></div>
+			</div>
+
+			<div class="sixteen wide column">
+				<div class="ui dividing header">
+					<span v-if="allStyles.noteIcon" >
+						<i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
+					</span>
+					Note Icon
+				</div>
+				<div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" >
+					<i :class="`large ${icon} icon`"></i>		
 				</div>
 			</div>
 
-			<div class="row">
-				<div class="sixteen wide column">
-					<p>Note Icon
-						<span v-if="allStyles.noteIcon" >
-							<i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
-						</span>
-					</p>
-					<div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" >
-						<i :class="`large ${icon} icon`" :style="{ 'color':allStyles.iconColor }"></i>		
-					</div>
+			<div class="sixteen wide column">
+				<div class="ui dividing header">
+					<span v-if="allStyles.noteIcon" >
+						<i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
+					</span>
+					Icon Color
+				</div>
+				<div v-for="color in getReducedColors()" 
+					class="color-button" 
+					:style="{ backgroundColor:color }"
+					v-on:click="chooseIconColor(color)"
+				>
 				</div>
 			</div>
-
-			<div class="row">
-				<div class="sixteen wide column">
-					<p>Icon Color</p>
-					<div v-for="color in getReducedColors()" 
-						class="color-button" 
-						:style="{ backgroundColor:color }"
-						v-on:click="chooseIconColor(color)"
-					>
-					</div>
-				</div>
-			</div>
-		</div>
+		
 		</div>
 		
 	</div>
@@ -147,20 +152,20 @@
 	}
 </script>
 <style type="text/css" scoped>
-	.icon-button {
+	.icon-button, .color-button {
 		height: 40px;
 		width: calc(10% - 7px);
 		display: inline-block;
 		cursor: pointer;
 		font-size: 1.3em;
-	}
-	.color-button {
-		display: inline-block;
-		width: calc(10% - 7px);
-		height: 30px;
-		border-radius: 30px;
+		border: 1px solid grey;
+		text-align: center;
+		padding: 5px 0 0;
+		border-radius: 4px;
 		box-shadow: 0px 1px 3px 0px #3e3e3e;
 		margin: 7px 7px 0 0;
-		cursor: pointer;
+	}
+	.rounded {
+		border-radius: 5px;
 	}
 </style>
\ No newline at end of file
diff --git a/client/src/components/NoteInputPanel.vue b/client/src/components/NoteInputPanel.vue
index f748149..563e243 100644
--- a/client/src/components/NoteInputPanel.vue
+++ b/client/src/components/NoteInputPanel.vue
@@ -1,9 +1,9 @@
 <template>
-	<!-- change class to .master-note-edit to have it popup on the screen  -->
+	<!-- change class to .master-note-edit to have it popup on the screen. 
+		@keyup.esc="closeButtonAction()"  -->
 	<div 
 		id="InputNotes" 
-		class="master-note-edit" 
-		@keyup.esc="closeButtonAction()"
+		class="master-note-edit"
 	>
 
 		<!-- Giant Edit Note Menu  -->
@@ -89,9 +89,9 @@
 
 				<div class="edit-divide"></div>
 
-				<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="bottom center" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
+<!-- 				<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="bottom center" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
 					<i class="paint brush icon"></i>
-				</div>
+				</div> -->
 				<!-- <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" data-tooltip="Tags" data-position="bottom center">
 					<i class="tags icon"></i>
 				</div> -->
@@ -107,7 +107,9 @@
 				<div class="edit-divide"></div>
 
 				<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/options`)" data-tooltip="More Options" data-position="bottom center">
+					&nbsp;&nbsp;
 					<i class="ellipsis horizontal icon"></i>
+					&nbsp;&nbsp;
 				</div>
 
 				<div class="edit-divide"></div>
@@ -128,6 +130,7 @@
 
 				<div class="edit-button ui" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center">
 					<i class="green close icon"></i>
+					<span class="ui green text">Done</span>
 				</div>
 				
 			</div>
@@ -139,7 +142,7 @@
 			:class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}">
 
 			<!-- Squire box grows -->
-			<div id="text-box-container" class="note-wrapper">
+			<div id="text-box-container" class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground']}">
 
 				<!-- Loading indicator  -->
 				<transition name="fade">
@@ -161,19 +164,22 @@
 					v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input glint">
 				</textarea>
 
-				<div class="large-close-button glint" v-on:click="closeButtonAction()">
+				<!-- close button giant -->
+				<div v-if="!$store.getters.getIsUserOnMobile" class="large-close-button" v-on:click="closeButtonAction()">
 					<i class="fitted green close icon"></i>
 				</div>
 
-				<!-- little tags on the side, only show on desktop -->
-				<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)">
-					<span class="add-mini-tag" v-if="noteTags.length == 0">
+				<!-- tags on the side, only show on desktop -->
+				<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }"
+					v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" 
+					:style="{ 'background-color':styleObject['noteBackground'] }">
+					<span class="add-mini-tag" v-if="allTags.length == 0">
 						<i class="tags icon"></i>Add Tags
 					</span>
-					<span v-for="tag in allTags" class="active-mini-tag" v-if="isTagOnNote(tag.id)">
-						#{{ tag.text }}
+					<span v-for="tag in allTags" class="active-mini-tag">
+						#{{ tag }}
 					</span>
-					<span class="active-mini-tag" v-if="noteTags.length > 0">
+					<span class="active-mini-tag" v-if="allTags.length > 0">
 						+
 					</span>
 					
@@ -201,21 +207,23 @@
 		/>
 
 		<!-- Side slide menus for colors, tags, images and other options -->
-		<side-slide-menu v-if="colors" v-on:close="colors = false" name="colors">
+<!-- 		<side-slide-menu v-if="colors" v-on:close="colors = false" name="colors">
 			<color-picker
 				@changeColor="onChangeColor"
 				@close="colors = false; $router.go(-1)"
 				:style-object="styleObject"
 			/>
-		</side-slide-menu>
+		</side-slide-menu> -->
 
-		<side-slide-menu v-if="tags" v-on:close="tags = false; fetchNoteTags()" name="tags" :style-object="styleObject">
+		<!-- tag edit menu -->
+		<side-slide-menu v-if="tags" v-on:close="tags = false; fetchNoteTags()" name="tags">
 			<div class="ui basic segment">
 				<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
 			</div>
 		</side-slide-menu>
 
-		<side-slide-menu v-if="images" v-on:close="images = false" name="images" :style-object="styleObject">
+		<!-- images menu -->
+		<side-slide-menu v-if="images" v-on:close="images = false" name="images">
 			<div class="ui basic segment">
 				<simple-attachment-note
 					:note-id="noteid" 
@@ -224,67 +232,81 @@
 			</div>
 		</side-slide-menu>
 
-		<side-slide-menu v-if="options" v-on:close="options = false" name="note-options" :style-object="styleObject">
+		<side-slide-menu v-if="options" v-on:close="options = false" name="note-options">
 			<div class="ui basic padded segment">
 				<div class="ui grid">
-					<div class="sixteen wide column">
-						<h3>Note Options</h3>
-					</div>
+
 					<div class="eight wide column">
+						<div class="ui dividing header">
+							Note Options
+						</div>
 		 				<div class="ui labeled icon fluid basic button" v-on:click="onToggleArchived()">
 		 					<i class="archive icon" :class="{'green':(archived == 1)}"></i>
 							<span v-if="archived == 1">Un-Archive Note</span>
 							<span v-if="archived != 1">Archive Note</span>
 						</div>
-					</div>
-					<div class="eight wide column">
 						<div class="ui labeled icon fluid basic button" v-on:click="onTogglePinned">
 							<i class="pin icon" :class="{'green':(pinned == 1)}"></i>
 							<span v-if="pinned == 1">Un-Pin Note</span>
 							<span v-if="pinned != 1">Pin Note</span>
 						</div>
 					</div>
-					<div class="sixteen wide column">
-						<h3>List Options</h3>
-					</div>
-					<div class="sixteen wide column">
+
+					<div class="eight wide column">
+						<div class="ui dividing header">
+							List Options
+						</div>
 						<div class="ui labeled icon fluid basic button" v-on:click="sortList">
 							<i class="sort amount up icon"></i>
-							Sort List
+							Sort List (Complete to bottom)
+						</div>
+						<div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems">
+							<i class="list ul icon"></i>
+							Uncheck All
 						</div>
-					</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
 						</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
+						<div class="ui dividing header">
+							Calculate Line
 						</div>
-					</div>
-					<div class="sixteen wide column">
-						<h3>Misc Options</h3>
-					</div>
-					<div class="eight wide column">
-						<div class="ui labeled icon fluid basic button" v-on:click="calculateMath" data-tooltip="Calculates algebra before '='">
+						<p>
+							Calculates algebra before '='
+						</p>
+						<div class="ui labeled icon fluid basic button" v-on:click="calculateMath">
 							<i class="calculator icon"></i>
-							Simple Math
-						</div>
-					</div>
-					<div class="eight wide column">
-						<!-- data-tooltip="Files on note" -->
-						<div v-on:click="openEditAttachment"  class="ui labeled icon fluid basic button">
-							<i class="folder icon"></i>
-							Note Files
-							{{ attachmentCount }}
+							Calculate Simple Math
 						</div>
 					</div>
 
+					<div class="eight wide column">
+						<!-- data-tooltip="Files on note" -->
+						<div class="ui dividing header">
+							Note Attachments & Links
+						</div>
+						<p>
+							Attachment & Link Count {{ attachmentCount }}
+						</p>
+						<div v-on:click="openEditAttachment"  class="ui labeled icon fluid basic button">
+							<i class="folder icon"></i>
+							View all Attachments & Links 
+						</div>
+					</div>
+
+					<color-picker
+						@changeColor="onChangeColor"
+						@close="colors = false; $router.go(-1)"
+						:style-object="styleObject"
+					/>
+
 					<div class="sixteen wide column" v-if="rawTextId > 0">
-						<h3>Share Note</h3>
+						<div class="ui dividing header">
+							Share Note
+						</div>
 						<share-note-component 
 							:note-id="noteid"
 							:raw-text-id="rawTextId"
@@ -296,7 +318,7 @@
 		</side-slide-menu>
 
 		<!-- create table option  -->
-		<side-slide-menu v-if="table" v-on:close="table = false; fetchNoteTags()" name="table" :style-object="styleObject">
+		<side-slide-menu v-if="table" v-on:close="table = false;" name="table" :style-object="styleObject">
 			<div class="ui basic segment">
 				<h2>Insert Table</h2>
 				<div class="table-tic-table">
@@ -521,77 +543,15 @@
 
 				}, totalTime + 40)
 			},
-			removeTag(tagId){
-
-				this.allTags = []
-				let entryId = 0
-
-				//Find fucking note tag for removal
-				this.noteTags.forEach(noteTag => {
-					if(noteTag['tagId'] == tagId){
-						entryId = noteTag['entryId']
-					}
-				})
-
-				let postData = {
-					'tagId':entryId,
-					'noteId':this.noteid
-				}
-
-				axios.post('/api/tag/removefromnote', postData)
-				.then(response => {
-					this.fetchNoteTags()
-				})
-				.catch(error => { this.$bus.$emit('notification', 'Failed to Remove Tag') })
-			},
-			addTag(tagText){
-
-				this.allTags = []
-
-				let postData = {
-					'tagText':tagText,
-					'noteId':this.noteid
-				}
-
-				axios.post('/api/tag/addtonote', postData)
-				.then(response => {
-					this.fetchNoteTags()
-				})
-				.catch(error => { this.$bus.$emit('notification', 'Failed to Add Tag') })
-			},
 			fetchNoteTags(){
-				axios.post('/api/tag/get', {'noteId': this.noteid})
+				axios.post('/api/tag/fornote', {'noteId': this.noteid})
 				.then(({data}) => {
-					this.allTags = data.allTags
-					this.noteTags = data.noteTagIds
 
-					//Stick used tags at top.
-					if(this.noteTags.length > 0){
-
-						let frontTags = []
-
-						for (var i = this.allTags.length - 1; i >= 0; i--) {
-							this.noteTags.forEach(noteTag => {
-								if(this.allTags[i]['id'] == noteTag['tagId']){
-									frontTags.push(this.allTags[i])
-									this.allTags.splice(i,1)
-								}
-							})
-						}
-
-						this.allTags.unshift(...frontTags)
-					}
+					//Setup note tags from string
+					this.allTags = data.tags ? data.tags.split(',') : []
+					
 				})
 			},
-			isTagOnNote(id){
-				for (let i = 0; i < this.noteTags.length; i++) {
-					const current = this.noteTags[i]
-					if(current && current['tagId'] == id){
-						return true
-					}
-				}
-				return false
-			},
 			initSquire(){
 				
 				//Set up squire and load note text
@@ -604,10 +564,6 @@
 
 				}
 
-				//Load tags on mobile
-				this.fetchNoteTags()
-				
-
 				//Set up websockets after squire is set up
 				setTimeout(() => {
 					this.setupWebSockets()
@@ -783,6 +739,9 @@
 						this.lastNoteHash = this.hashString( response.data.text )
 						// this.diffNoteText = response.data.text
 
+						//Setup note tags
+						this.allTags = response.data.tags ? response.data.tags.split(','):[]
+
 						//Set up note colors
 						if(response.data.color){
 							this.styleObject = JSON.parse(response.data.color)
diff --git a/client/src/components/NoteTitleDisplayCard.vue b/client/src/components/NoteTitleDisplayCard.vue
index a2fae45..cf95566 100644
--- a/client/src/components/NoteTitleDisplayCard.vue
+++ b/client/src/components/NoteTitleDisplayCard.vue
@@ -21,27 +21,9 @@
 					class="big-text"><p>{{ note.title }}</p></span>
 
 				<span class="tags" v-if="note.tags">
-						<span  v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span>
-						<br>
-					</span>
-
-				<!-- Sub text display -->
-				<span v-if="note.subtext.length > 0"
-					class="small-text"
-					v-html="note.subtext"></span>
-
-
-				<!-- Not indexed warning -->
-<!-- 				<span v-if="note.indexed != 1">
-					<span class="green label">Not Indexed</span>
-				</span> -->
-
-				
-				<div class="ui fluid basic button" v-if="note.encrypted == 1">
-					<i class="green lock icon"></i>
-					Locked
-				</div>
-
+					<span  v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span>
+					<br>
+				</span>
 
 				<!-- Shared Details -->
 				<span class="subtext" v-if="note.shared == 2">
@@ -62,23 +44,71 @@
 					</span>
 				</span>
 
-			</div>
+				<!-- Sub text display -->
+				<span v-if="note.subtext.length > 0"
+					class="small-text"
+					v-html="note.subtext"></span>
+
+
+				<!-- Not indexed warning -->
+<!-- 				<span v-if="note.indexed != 1">
+					<span class="green label">Not Indexed</span>
+				</span> -->
+
 				
-			<div v-if="titleView" class="single-line-text" @click="cardClicked">
-				<span class="title-line" v-if="note.title.length > 0">{{ note.title }}<br></span>
-				<span class="sub-line" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span>
-				<span v-if="note.title.length == 0 && note.title.length == 0">Empty Note</span>
+<!-- 				<div class="ui fluid basic button" v-if="note.encrypted == 1">
+					<i class="green lock icon"></i>
+					Locked
+				</div> -->
+
+			</div>
+			
+			<!-- slim card view  -->
+			<div v-if="titleView" class="thin-container" @click="cardClicked">
+					
+				<!-- icon -->
+				<span v-if="noteIcon" class="thin-icon">
+					<i :class="`${noteIcon} icon`" :style="{ 'color':iconColor }"></i>
+				</span>
+				
+				<!-- title -->
+				<span class="thin-title" v-if="note.title.length > 0">{{ note.title }}</span>
+				
+				<!-- snippet  -->
+				<span class="thin-sub" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span>
+				<span v-if="note.title.length == 0 && removeHtml(note.subtext).length == 0">Empty Note</span>
+			
+				<!-- tags -->
+				<span v-if="note.tags" class="thin-tags" >
+					<span  v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}
+					</span>
+				</span>
+
+				<!-- edited -->
+				<span class="thin-right">
+					{{$helpers.timeAgo( note.updated )}}
+
+					<i class="green link ellipsis vertical icon"></i>
+				</span>
+
 			</div>
 				
 			<!-- Toolbar on the bottom  -->
 			<div class="tool-bar" @click.self="cardClicked" v-if="!titleView">
-				<div class="icon-bar">
 
-					<span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
+				<div v-if="getThumbs.length > 0">
+					<div class="tiny-thumb-box" v-on:click="openEditAttachment">
+						<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
+					</div>
+				</div>
+
+				<div class="icon-bar" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
+
+					<span class="time-ago-display">
 						{{$helpers.timeAgo( note.updated )}}
 					</span>
 
-					<span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
+					<span class="teeny-buttons">
 
 						<span v-if="!note.trashed">
 
@@ -115,19 +145,13 @@
 							</i>
 							<delete-button class="teeny-button" :note-id="note.id" />
 						</span>
-
-						
-
 					</span>
 				</div>
 
-				<div v-if="getThumbs.length > 0">
-					<div class="tiny-thumb-box" v-on:click="openEditAttachment">
-						<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
-					</div>
-				</div>
+				
 			</div>
 
+			<!-- tag edit menu -->
 			<side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true">
 				<div class="ui basic segment">
 					<note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/>
@@ -333,13 +357,11 @@
 
 	.teeny-buttons {
 		float: right;
-		width: 65%;
 		text-align: right;
 	}
 	.time-ago-display {
-		width: 35%;
-		float: left;
-		text-align: center;
+		font-size: 11px;
+		font-weight: bold;
 	}
 	.tags {
 		width: 100%;
@@ -364,9 +386,7 @@
 
 	/*Strict font sizes for card display*/
 	.small-text {
-		max-height: 267px;
 		width: 100%;
-		overflow: hidden;
 		display: inline-block;
 	}
 	.small-text, .small-text > p, .small-text > h1, .small-text > h2 {
@@ -426,7 +446,7 @@
 		/*width: calc(33.333% - 10px);*/
 		width: calc(25% - 10px);
 		/*min-width: 190px;*/
-		min-height: 130px;
+		/*min-height: 130px;*/
 		/*transition: box-shadow 0.3s;*/
 		box-sizing: border-box;
 		cursor: pointer;
@@ -435,7 +455,11 @@
 		letter-spacing: 0.05rem;
 		display: flex;
 		flex-direction: column;
+		align-items: stretch;
 		text-align: left;
+
+		min-height: 100px;
+		max-height: 450px;
 	}
 	.note-title-display-card:hover {
 		/*box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);*/
@@ -446,21 +470,49 @@
 		width: 100%;
 		min-height: 20px;
 		max-width: none;
+		padding: 10px;
+		margin: 0;
+		overflow: hidden;
+		border-radius: 0;
+		border: none;
 		/*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/
 	}
-		
-	.single-line-text {
+	.title-view + .title-view {
+		border-top: 1px solid var(--border_color);
+	}
+
+	.thin-container.single-line-text {
 		width: calc(100% - 25px);
-		margin: 5px 10px;
+		/*margin: 5px 10px;*/
 		white-space: nowrap;
 		overflow: hidden;
 		text-overflow: ellipsis;
 		box-sizing: border-box;
 	}
-	.title-line {
+
+	.thin-container .thin-title {
 		font-weight: bold;
 		font-size: 1.2em;
-		padding: 0 20px 0 0;
+	}
+	.thin-container .thin-sub {
+		overflow: hidden;
+		text-overflow: ellipsis;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		line-clamp: 2; 
+		-webkit-box-orient: vertical;
+		opacity: 0.85;
+	}
+	.thin-container .thin-tags {
+		float: left;
+		margin-top: 3px;
+	}
+	.thin-container .thin-right {
+		float: right;
+		color: var(--dark_border_color);
+	}
+	.thin-container .thin-icon {
+		float: right;
 	}
 
 	.icon-bar {
@@ -468,6 +520,7 @@
 		padding: 5px 10px 0;
 		opacity: 1;
 		width: 100%;
+		background-color: rgba(200, 200, 200, 0.2);
 	}
 	.hover-hide {
 		opacity: 0.0;
diff --git a/client/src/pages/HomePage.vue b/client/src/pages/HomePage.vue
index a5ab63a..b2b420e 100644
--- a/client/src/pages/HomePage.vue
+++ b/client/src/pages/HomePage.vue
@@ -191,11 +191,24 @@
 				</div>
 			</div>
 
+			<!-- Overview -->
+			<div class="middle aligned centered row">
+				<div class="six wide column">
+					<h2 class="ui dividing header">Powerful text editing and privacy</h2>
+					<h3>Easily edit, share and organize thousands of notes.</h3>
+					<h3>Feel safe knowing no one can read your notes but you.</h3>
+					<!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> -->
+				</div>
+				<div class="four wide column">
+					<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
+				</div>
+			</div>
+
 			<!-- theme selector -->
 			<div class="ui white row">
 				<div class="sixteen wide middle aligned column">
 					<div class="ui container">
-						<h2>
+						<h2 style="color: var(--main-accent);">
 							Pick your theme
 						</h2>
 						<h3 v-if="$parent.loggedIn">Go to settings to change theme</h3>
@@ -211,19 +224,6 @@
 				</div>
 			</div>
 
-			<!-- Overview -->
-			<div class="middle aligned centered row">
-				<div class="six wide column">
-					<h2 class="ui dividing header">Powerful text editing and privacy</h2>
-					<h3>Easily edit, share and organize thousands of notes.</h3>
-					<h3>Feel safe knowing no one can read your notes but you.</h3>
-					<!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> -->
-				</div>
-				<div class="four wide column">
-					<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
-				</div>
-			</div>
-
 			<!-- features list -->
 			<div class="top aligned centered row">
 
@@ -355,7 +355,7 @@
 								<i class="grey lock icon"></i> 
 								<i class="bottom left corner yellow key icon"></i> 
 							</i>
-							All Note Text is Encrypted
+							Secure Notes
 							<div class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div>
 						</div>
 					</h2>
@@ -365,7 +365,7 @@
 								<i class="grey search icon"></i> 
 								<i class="bottom left corner orange font icon"></i> 
 							</i>
-							Note Search is Encrypted
+							Private Search
 							<div class="sub header">Search the contents of all your notes without compromising security.</div>
 						</div>
 					</h2>
@@ -375,7 +375,7 @@
 								<i class="grey share alternate icon"></i> 
 								<i class="bottom left corner share icon"></i> 
 							</i>
-							Encrypted Note Sharing
+							Encrypted Sharing
 							<div class="sub header">Shared notes are still encrypted, only readable by you and the shared users.</div>
 						</div>
 					</h2>
diff --git a/client/src/pages/NotesPage.vue b/client/src/pages/NotesPage.vue
index a2d4d64..508011f 100644
--- a/client/src/pages/NotesPage.vue
+++ b/client/src/pages/NotesPage.vue
@@ -27,9 +27,15 @@
 							v-on:tagClick="tagId => toggleTagFilter(tagId)"
 						/>
 						
-						<div class="ui basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
-							<i v-if="titleView" class="th icon"></i>
-							<i v-if="!titleView" class="bars icon"></i>
+						<div class="ui right floated basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
+							<span v-if="titleView">
+								<i class="th icon"></i> Tiles
+							</span>
+							<span v-if="!titleView">
+								<i  class="list icon"></i> List
+							</span>
+							
+							
 						</div>
 						
 					</div>
@@ -223,6 +229,9 @@
 
 			this.$parent.loginGateway()
 
+			//If user is on title view, 
+			this.titleView = this.$store.getters.getIsUserOnMobile
+
 			this.$io.on('new_note_created', noteId => {
 
 				//Do not update note if its open
diff --git a/dontSync.txt b/dontSync.txt
index 6db8a5f..6007d6b 100644
--- a/dontSync.txt
+++ b/dontSync.txt
@@ -11,4 +11,5 @@ common.js
 bundle.*
 client/dist*
 server/public/*
-client/dist*
\ No newline at end of file
+client/dist*
+*_scrape*
\ No newline at end of file
diff --git a/server/helpers/Auth.js b/server/helpers/Auth.js
index 7367c48..d49ada3 100644
--- a/server/helpers/Auth.js
+++ b/server/helpers/Auth.js
@@ -6,6 +6,7 @@ const speakeasy = require('speakeasy')
 let Auth = {}
 
 const tokenSecretKey = process.env.JSON_KEY
+const sessionTokenUses = 300 //Defines number of uses each session token has before being refreshed
 
 //Creates session token 
 Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => {
@@ -26,7 +27,7 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) =>
 
 			return db.promise().query(
 			'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)', 
-			[salt, encryptedMasterPass, created, 40, userHash, sessionId])
+			[salt, encryptedMasterPass, created, sessionTokenUses, userHash, sessionId])
 
 		})
 		.then((r,f) => {
diff --git a/server/helpers/SiteScrape.js b/server/helpers/SiteScrape.js
index 39472a4..295036b 100644
--- a/server/helpers/SiteScrape.js
+++ b/server/helpers/SiteScrape.js
@@ -54,7 +54,7 @@ SiteScrape.getCleanUrls = (textBlock) => {
 SiteScrape.getHostName = (url) => {
 
 	var hostname = 'https://'+(new URL(url)).hostname;
-	console.log('hostname', hostname)
+	// console.log('hostname', hostname)
 	return hostname
 }
 
@@ -63,36 +63,95 @@ SiteScrape.getDisplayImage = ($, url) => {
 
 	const hostname = SiteScrape.getHostName(url)
 
-	let metaImg = $('meta[property="og:image"]')
-	let shortcutIcon = $('link[rel="shortcut icon"]')
-	let favicon = $('link[rel="icon"]')
+	let metaImg = $('[property="og:image"]')
+	let shortcutIcon = $('[rel="shortcut icon"]')
+	let favicon = $('[rel="icon"]')
 	let randomImg = $('img')
 
-	console.log('----')
+	//Set of images we may want gathered from various places in source
+	let imagesWeWant = []
+	let thumbnail = ''
 
 	//Scrape metadata for page image
-	//Grab the first random image we find
-	if(randomImg && randomImg[0] && randomImg[0].attribs){
-		thumbnail = hostname + randomImg[0].attribs.src
-		console.log('random img '+thumbnail)
+	if(randomImg && randomImg.length > 0){
+
+		let imgSrcs = []
+		for (let i = 0; i < randomImg.length; i++) {
+			imgSrcs.push( randomImg[i].attribs.src )
+		}
+
+		const half = Math.ceil(imgSrcs.length / 2)
+		imagesWeWant = [...imgSrcs.slice(-half), ...imgSrcs.slice(0,half) ]
+
 	}
-	//Grab the favicon of the site
+	//Grab the shortcut icon
 	if(favicon && favicon[0] && favicon[0].attribs){
-		thumbnail = hostname + favicon[0].attribs.href
-		console.log('favicon '+thumbnail)
+		imagesWeWant.push(favicon[0].attribs.href)
 	}
 	//Grab the shortcut icon
 	if(shortcutIcon && shortcutIcon[0] && shortcutIcon[0].attribs){
-		thumbnail = hostname + shortcutIcon[0].attribs.href
-		console.log('shortcut '+thumbnail)
+		imagesWeWant.push(shortcutIcon[0].attribs.href)
 	}
 	//Grab the presentation image for the site
 	if(metaImg && metaImg[0] && metaImg[0].attribs){
-		thumbnail = metaImg[0].attribs.content
-		console.log('ogImg '+thumbnail)
+		imagesWeWant.unshift(metaImg[0].attribs.content)
+	}
+
+	// console.log(imagesWeWant)
+
+	//Remove everything that isn't an accepted file format
+	for (let i = imagesWeWant.length - 1; i >= 0; i--) {
+
+		let img = String(imagesWeWant[i])
+
+		if(
+			!img.includes('.jpg') && 
+			!img.includes('.jpeg') && 
+			!img.includes('.png') && 
+			!img.includes('.gif')
+		){
+			imagesWeWant.splice(i,1)
+		}
+	}
+
+	//Find if we have absolute thumbnails or not
+	let foundAbsolute = false
+	for (let i = imagesWeWant.length - 1; i >= 0; i--) {
+
+		let img = imagesWeWant[i]
+
+		//Add host name if its not included
+		if(String(img).includes('//') || String(img).includes('http')){
+			foundAbsolute = true
+			break
+		}
+	}
+
+	//Go through all found images. Grab the one closest to the top. Closer is better
+	for (let i = imagesWeWant.length - 1; i >= 0; i--) {
+		
+		let img = imagesWeWant[i]
+
+		if(!String(img).includes('//') && foundAbsolute){
+			continue;
+		}
+
+		//Only add host to images if no absolute images were found
+		if(!String(img).includes('//') ){
+			if(img.indexOf('/') != 0){
+				img = '/' + img
+			}
+			img = hostname + img
+		}
+
+		if(img.indexOf('//') == 0){
+			img = 'https:' + img //Scrape breaks without protocol 
+		}
+			
+		thumbnail = img
+		
 	}
 
-	console.log('-----')
 	return thumbnail
 }
 
diff --git a/server/index.js b/server/index.js
index 677fa1a..b8c64ea 100644
--- a/server/index.js
+++ b/server/index.js
@@ -257,7 +257,6 @@ const printResults = true
 let UserTest = require('@models/User')
 let NoteTest = require('@models/Note')
 let AuthTest = require('@helpers/Auth')
-
 Auth.test()
 UserTest.keyPairTest('genMan30', '1', printResults)
 .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
@@ -266,7 +265,6 @@ UserTest.keyPairTest('genMan30', '1', printResults)
 	Auth.testTwoFactor()
 })
 
-
 //Test 
 app.get('/api', (req, res) => res.send('Solidscribe API is up and running'))
 
diff --git a/server/models/Attachment.js b/server/models/Attachment.js
index ded35d5..e7b1134 100644
--- a/server/models/Attachment.js
+++ b/server/models/Attachment.js
@@ -325,14 +325,14 @@ Attachment.downloadFileFromUrl = (url) => {
 
 	return new Promise((resolve, reject) => {
 
-			if(url == null){
+			if(url == null || url == undefined || url == ''){
 				resolve(null)
 			}
 
 			const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
-			const extension = '.'+url.split('.').pop() //This is throwing an error
-			let fileName = random+'_scrape'+extension
-			const thumbPath = 'thumb_'+fileName
+			let extension = ''
+			let fileName = random+'_scrape'
+			let thumbPath = 'thumb_'+fileName
 
 			console.log('Scraping image url')
 			console.log(url)
@@ -347,6 +347,8 @@ Attachment.downloadFileFromUrl = (url) => {
 				.on('response', res => {
 					console.log(res.statusCode)
 					console.log(res.headers['content-type'])
+					//Get mime type from header content type
+					// extension = '.'+String(res.headers['content-type']).split('/').pop()
 				})
 				.pipe(fs.createWriteStream(filePath+thumbPath))
 				.on('close', () => {
@@ -354,14 +356,17 @@ Attachment.downloadFileFromUrl = (url) => {
 					//resize image if its real big
 					gm(filePath+thumbPath)
 					.resize(550) //Resize to width of 550 px 
-					.quality(75) //compression level 0 - 100 (best)
+					.quality(85) //compression level 0 - 100 (best)
 					.write(filePath+thumbPath, function (err) {
-						if(err){ console.log(err) }
+						if(err){ 
+							console.log(err) 
+							return resolve(null)
+						}
+
+						console.log('Saved Image')
+						return resolve(fileName)
 					})
 
-
-					console.log('Saved Image')
-					resolve(fileName)
 				})
 	})
 }
@@ -396,7 +401,7 @@ Attachment.processUrl = (userId, noteId, url) => {
 		.query(`INSERT INTO attachment 
 			(note_id, user_id, attachment_type, text, url, last_indexed, file_location) 
 			VALUES (?, ?, ?, ?, ?, ?, ?)`, 
-			[noteId, userId, 1, 'Processing...', url, created, null])
+			[noteId, userId, 1, url, url, created, null])
 		.then((rows, fields) => {
 			//Set two bigger variables then return request for processing
 			request = rp(options)
diff --git a/server/models/Note.js b/server/models/Note.js
index 9367b3d..8c5a422 100644
--- a/server/models/Note.js
+++ b/server/models/Note.js
@@ -681,6 +681,7 @@ Note.get = (userId, noteId, masterKey) => {
 					note_raw_text.text, 
 					note_raw_text.salt, 
 					note_raw_text.updated as updated,
+					GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags,
 					note.id,
 					note.user_id,
 					note.created,
@@ -697,7 +698,9 @@ Note.get = (userId, noteId, masterKey) => {
 				JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
 				LEFT JOIN attachment ON (note.id = attachment.note_id)
 				LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id)
-				WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, noteId])
+				LEFT JOIN note_tag ON (note.id = note_tag.note_id AND note_tag.user_id = ?)
+				LEFT JOIN tag ON (note_tag.tag_id = tag.id)
+				WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, userId, noteId])
 
 		})
 		.then((rows, fields) => {
diff --git a/server/models/Tag.js b/server/models/Tag.js
index b502db4..deedc51 100644
--- a/server/models/Tag.js
+++ b/server/models/Tag.js
@@ -138,6 +138,33 @@ Tag.get = (userId, noteId) => {
 	})
 }
 
+//
+// Get just tag string for note
+//
+Tag.fornote = (userId, noteId) => {
+	return new Promise((resolve, reject) => {
+
+		
+			db.promise()
+			.query(`SELECT GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags 
+					FROM note_tag
+					LEFT JOIN tag ON (note_tag.tag_id = tag.id)
+					WHERE note_tag.note_id = ?
+					AND user_id = ?;
+					`, [noteId,userId])
+			.then((rows, fields) => {
+
+				//pull IDs out of returned results
+				// let ids = rows[0].map( item => {})
+
+				resolve( rows[0][0] ) //Return all tags found by query
+			})
+			.catch(console.log)
+		
+		
+	})
+}
+
 //
 // Get all tags for a note and concatinate into a string 'all, tags, like, this'
 //
diff --git a/server/models/User.js b/server/models/User.js
index f82edd0..17d254f 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
 
 let User = module.exports = {}
 
-const version = '3.3.1'
+const version = '3.3.3'
 
 //Login a user, if that user does not exist create them
 //Issues login token
diff --git a/server/routes/tagController.js b/server/routes/tagController.js
index 5e444d1..7157221 100644
--- a/server/routes/tagController.js
+++ b/server/routes/tagController.js
@@ -50,6 +50,12 @@ router.post('/get', function (req, res) {
 	.then( data => res.send(data) )
 })
 
+//Get the latest notes the user has created
+router.post('/fornote', function (req, res) {
+	Tags.fornote(userId, req.body.noteId)
+	.then( data => res.send(data) )
+})
+
 //Get all the tags for this user in order of usage
 router.post('/usertags', function (req, res) {
 	Tags.userTags(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters)