Compare commits
	
		
			3 Commits
		
	
	
		
			848c86327a
			...
			c972430ef4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c972430ef4 | ||
|  | 6d0187ee0a | ||
|  | 00500ecc33 | 
| @@ -12,6 +12,7 @@ echo '-------' | ||||
| BACKUPDIR="/home/mab/databaseBackupSolidScribe" | ||||
| #DEVDBPASS="Crama!Lama*Jamma###88383!!!!!345345956245i" | ||||
| DEVDBPASS="RootPass1234!" | ||||
| # LazaLinga&33Can't!Do!That34 | ||||
|  | ||||
| cd $BACKUPDIR | ||||
|  | ||||
| @@ -28,8 +29,12 @@ gunzip -dkv $LASTZIPPEDFILE | ||||
| BACKUPFILE=$(ls -At *.sql | head -n1) | ||||
|  | ||||
| #Fix to replace incompatible DB type | ||||
| echo "Updating table name in $BACKUPFILE" | ||||
| sed -i $BACKUPFILE -e 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' | ||||
| echo "Updating table name in -> $BACKUPFILE" | ||||
| #sed -i $BACKUPFILE -e 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' | ||||
|  | ||||
| #Fix encoding for dev DB and exclude system tables | ||||
| sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' $BACKUPFILE | ||||
| sed -r '/INSERT INTO `(sys|mysql)`/d' $BACKUPFILE > $BACKUPFILE | ||||
|  | ||||
| echo "Removing and syncing static files" | ||||
| rm -r /home/mab/ss/staticFiles/* | ||||
| @@ -38,8 +43,20 @@ rsync -e 'ssh -p 13328' -hazC --update mab@solidscribe.com:/home/mab/pi/staticFi | ||||
| echo "Updating Database" | ||||
| mysql -u root --password="$DEVDBPASS" < $BACKUPFILE | ||||
|  | ||||
| ## Optimize Database Tables | ||||
| # mysqlcheck --all-databases | ||||
| mysqlcheck --all-databases -o -u root --password="$DEVDBPASS" --silent | ||||
| # mysqlcheck --all-databases --auto-repair | ||||
| # mysqlcheck --all-databases --analyze | ||||
|  | ||||
| # Fix an issues with DB after messing around with it | ||||
| mysql_upgrade -u root --password="$DEVDBPASS" | ||||
|  | ||||
| #clean up extracted and modified SQL dumps | ||||
| rm *.sql | ||||
|  | ||||
|  | ||||
|  | ||||
| echo '-------' | ||||
| echo "Applied Prod database to Dev. LastFile: $BACKUPFILE" | ||||
| echo '-------' | ||||
| @@ -200,6 +200,11 @@ export default { | ||||
| 			this.blockUntilNextRequest = true | ||||
| 		}) | ||||
|  | ||||
| 		//Track users active sessions | ||||
| 		this.$io.on('update_active_user_count', countData => { | ||||
| 			this.$store.commit('setActiveSessions', countData) | ||||
| 		}) | ||||
|  | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		loggedIn () { | ||||
|   | ||||
| @@ -330,9 +330,14 @@ i.green.icon.icon.icon.icon { | ||||
| 		background-color: rgba(255, 255, 255, 0.2); | ||||
| 	} | ||||
|  | ||||
| 	.note-card-text code,  | ||||
| 	.squire-box code, | ||||
| 	.note-card-text pre,  | ||||
| 	.squire-box pre { | ||||
| 		/*word-wrap: break-word;*/ | ||||
| 		display: inline-block; | ||||
| 		border-left: 2px solid var(--main-accent); | ||||
| 		padding-left: 15px; | ||||
| 	} | ||||
| 	.note-card-text p, | ||||
| 	.squire-box p { | ||||
| @@ -880,6 +885,14 @@ i.green.icon.icon.icon.icon { | ||||
|   -webkit-transform-origin: left center; | ||||
|           transform-origin: left center; | ||||
| } | ||||
| @media only screen and (max-width: 740px) { | ||||
| 	/*hide tooltips on mobile*/ | ||||
| 	[data-tooltip]:hover:before, | ||||
| 	[data-tooltip]:hover:after { | ||||
| 	  visibility: visible; | ||||
| 	  opacity: 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| .glint:after { | ||||
|   | ||||
| @@ -154,16 +154,20 @@ | ||||
| <style type="text/css" scoped> | ||||
| 	.icon-button, .color-button { | ||||
| 		height: 40px; | ||||
| 		width: calc(10% - 7px); | ||||
| 		width: calc(15% - 1px); | ||||
| 		display: inline-block; | ||||
| 		cursor: pointer; | ||||
| 		font-size: 1.3em; | ||||
| 		border: 1px solid grey; | ||||
| 		text-align: center; | ||||
| 		padding: 5px 0 0; | ||||
| 		padding: 5px 0px 0 0; | ||||
| 		border-radius: 4px; | ||||
| 		box-shadow: 0px 1px 3px 0px #3e3e3e; | ||||
| 		margin: 7px 7px 0 0; | ||||
| 		margin: 2px 2px 0 0; | ||||
| 		box-sizing: border-box; | ||||
| 	} | ||||
| 	.color-button { | ||||
| 		width: calc(10% - 4px); | ||||
| 	} | ||||
| 	.rounded { | ||||
| 		border-radius: 5px; | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| <style scoped> | ||||
| 	.slotholder { | ||||
| 		height: 100vh; | ||||
| 		width: 155px; | ||||
| 		width: 180px; | ||||
| 		display: block; | ||||
| 		float: left; | ||||
| 	} | ||||
| 	.global-menu { | ||||
| 		width: 155px; | ||||
| 		width: 180px; | ||||
| 		/* background: #221f2b; */ | ||||
| 		background: #221f2b; | ||||
| 		margin: 0; | ||||
| @@ -21,7 +21,7 @@ | ||||
| 	} | ||||
| 	.menu-logo-display { | ||||
| 		width: 27px; | ||||
| 		margin: 5px 0 0 41px; | ||||
| 		margin: 5px 0 0 55px; | ||||
| 		display: inline-block; | ||||
| 		height: auto; | ||||
| 	} | ||||
| @@ -54,9 +54,6 @@ | ||||
| 			text-decoration: none; | ||||
| 		} | ||||
|  | ||||
| 		.router-link-active i { | ||||
| 			/*color: #16ab39;*/ | ||||
| 		} | ||||
| 		.router-link-active { | ||||
| 			background-color: #534c68; | ||||
| 		} | ||||
| @@ -141,6 +138,17 @@ | ||||
| 		.mobile-button.active { | ||||
| 			background-color: transparent; | ||||
| 		} | ||||
| 		.single-line-text { | ||||
| 			width: calc(100%); | ||||
| 			/*margin: 5px 10px;*/ | ||||
| 			white-space: nowrap; | ||||
| 			overflow: hidden; | ||||
| 			text-overflow: ellipsis; | ||||
| 			display: inline-block; | ||||
| 		} | ||||
| 		.faded { | ||||
| 			color: var(--dark_border_color); | ||||
| 		} | ||||
|  | ||||
| </style> | ||||
|  | ||||
| @@ -170,7 +178,6 @@ | ||||
| 				</span> | ||||
| 			</div> | ||||
|  | ||||
|  | ||||
| 			<!-- open straight to note --> | ||||
| 			<router-link  | ||||
| 				v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"  | ||||
| @@ -198,8 +205,8 @@ | ||||
| 			</router-link> | ||||
|  | ||||
| 			<!-- menu --> | ||||
| 			<div class="mobile-button"> | ||||
| 				<i class="green link bars icon" v-on:click="collapseMenu"></i> | ||||
| 			<div class="mobile-button" v-on:click="collapseMenu"> | ||||
| 				<i class="green link bars icon" ></i> | ||||
| 				Menu | ||||
| 			</div> | ||||
|  | ||||
| @@ -220,12 +227,12 @@ | ||||
|  | ||||
| 			<div class="menu-section" v-if="loggedIn"> | ||||
| 				<div v-if="!disableNewNote" @click="createNote" class="menu-item menu-item menu-button"> | ||||
| 					<div class="ui green button"> | ||||
| 					<div class="ui green fluid compact button"> | ||||
| 						<i class="plus icon"></i>New Note | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<div v-if="disableNewNote" class="menu-item menu-item menu-button"> | ||||
| 					<div class="ui basic button"> | ||||
| 					<div class="ui basic fluid compact button"> | ||||
| 						<i class="plus loading icon"></i>New Note | ||||
| 					</div> | ||||
| 				</div> | ||||
| @@ -238,14 +245,19 @@ | ||||
| 				</router-link> | ||||
| 				<div> | ||||
| 					<div class="menu-item menu-button sub" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && ($store.getters.totals['sharedToNotes'] > 0 || $store.getters.totals['sharedFromNotes'] > 0)"> | ||||
| 						<i class="grey mail outline icon"></i>Inbox  | ||||
| 						<i class="grey paper plane outline icon"></i>Shared | ||||
|  | ||||
| 						<counter v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']" class="float-right" number-id="sharedToNotes" /> | ||||
| 					</div> | ||||
| 					<div class="menu-item menu-button sub" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0"> | ||||
| 							<i class="grey archive icon"></i>Archived | ||||
| 							<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> --> | ||||
| 							 | ||||
| 							<counter v-if="$store.getters.totals && $store.getters.totals['archivedNotes']" class="float-right" number-id="archivedNotes" /> | ||||
| 						</div> | ||||
| 					<div class="menu-item menu-button sub" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0"> | ||||
| 							<i class="grey trash alternate outline icon"></i>Trashed | ||||
|  | ||||
| 							<counter v-if="$store.getters.totals && $store.getters.totals['trashedNotes']" class="float-right" number-id="trashedNotes" /> | ||||
| 						</div> | ||||
| 					<!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> --> | ||||
| 					<!-- <div v-on:click="updateFastFilters(0)" class="menu-item menu-button sub"><i class="grey linkify icon"></i>Links</div> --> | ||||
| @@ -320,6 +332,25 @@ | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<!-- Tags --> | ||||
| 			<div class="menu-section" v-if="gotTags()"> | ||||
| 				<div class="menu-item"> | ||||
| 					<i class="green tags icon"></i> | ||||
| 					Tags | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div v-if="gotTags()"> | ||||
| 				<div class="menu-section"  | ||||
| 					v-for="(data, tag) in $store.getters.totals['tags']"> | ||||
| 					<router-link class="menu-item menu-button" :to="`/search/tags/${tag}`"> | ||||
| 						<span class="single-line-text"> | ||||
| 						<!-- <i class="small grey tag icon"></i> --> | ||||
| 						<span class="float-right">{{ data.uses }}</span> | ||||
| 						<span class="faded"> #</span> {{ tag }}</span> | ||||
| 					</router-link> | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<div v-on:click="reloadPage" class="version-display" v-if="version != 0" > | ||||
| 				<i :class="`${getVersionIcon()} icon`"></i> {{ version }} | ||||
| 			</div> | ||||
| @@ -379,6 +410,16 @@ | ||||
| 			}, | ||||
| 		}, | ||||
| 		methods: { | ||||
| 			gotTags(){ | ||||
|  | ||||
| 				if(this.loggedIn && this.$store.getters.totals && this.$store.getters.totals.tags | ||||
| 					&& Object.keys(this.$store.getters.totals.tags).length | ||||
| 				){ | ||||
|  | ||||
| 					return true | ||||
| 				} | ||||
| 				return false | ||||
| 			}, | ||||
| 			logout() { | ||||
| 				 | ||||
| 				this.$router.push('/') | ||||
|   | ||||
| @@ -39,9 +39,9 @@ | ||||
| 	.loading-container { | ||||
| 		text-align: center; | ||||
| 		width: 100%; | ||||
| 		min-height: 100px; | ||||
| 		/*min-height: 100px;*/ | ||||
| 		margin: 20px 0; | ||||
| 		padding: 40px; | ||||
| 		/*padding: 40px;*/ | ||||
| 		border-radius: 7px; | ||||
| 		background-color: var(--small_element_bg_color); | ||||
| 	} | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
|  | ||||
| <template> | ||||
|  | ||||
| <div v-on:keyup.enter="login()"> | ||||
| <div> | ||||
|  | ||||
| 	<!-- thicc form display  --> | ||||
| 	<div v-if="!thin" class="ui large form"> | ||||
| 	<div v-if="!thin" class="ui large form" v-on:keyup.enter="register()"> | ||||
| 		<div class="field"> | ||||
| 			<div class="ui input"> | ||||
| 				<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail"> | ||||
| @@ -24,14 +24,14 @@ | ||||
| 			<div class="ui fluid buttons"> | ||||
| 				 | ||||
|  | ||||
| 				<div v-on:click="register()" class="ui button"> | ||||
| 				<div v-on:click="register()" class="ui green button" :class="{ 'disabled':(username.length == 0 || password.length == 0)}"> | ||||
| 					<i class="plug icon"></i> | ||||
| 					Sign Up | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="or"></div> | ||||
|  | ||||
| 				<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui green button"> | ||||
| 				<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui button"> | ||||
| 					<i class="power icon"></i> | ||||
| 					Login | ||||
| 				</div> | ||||
| @@ -49,7 +49,7 @@ | ||||
| 	</div> | ||||
|  | ||||
| 	<!-- Thin form display  --> | ||||
| 	<div v-if="thin" class="ui small form"> | ||||
| 	<div v-if="thin" class="ui small form" v-on:keyup.enter="login()"> | ||||
|  | ||||
| 		<div v-if="!require2FA" class="field"><!-- hide this field if someone is logging in with 2FA --> | ||||
| 			<div class="ui grid"> | ||||
| @@ -228,7 +228,6 @@ | ||||
| <style type="text/css" scoped="true"> | ||||
| 	.small-terms { | ||||
| 		display: inline-block; | ||||
| 		text-align: right; | ||||
| 		width: 100%; | ||||
| 		font-size: 0.9em; | ||||
| 	} | ||||
|   | ||||
| @@ -7,38 +7,16 @@ | ||||
| 	> | ||||
|  | ||||
| 		<!-- Giant Edit Note Menu  --> | ||||
| 		<div class="edit-menu glint" :class="{ 'slide-out-top':(sizeDown == true) }"> | ||||
| 		<div class="edit-menu" :class="{ 'slide-out-top':(sizeDown == true) }"> | ||||
|  | ||||
| 			<!-- edit spacer is disabled, it is helpful if menu gets bigger. It adds a left margin, starting the icons at the edge of the note  --> | ||||
| 			<div class="edit-spacer"></div> | ||||
|  | ||||
| 			<div class="menu-top-half"> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click=" hash=0; save() " :data-tooltip="`Save\n(CTRL + S)`" data-position="bottom center"> | ||||
| 					<i class="icons"> | ||||
| 						<i class="grey save outline icon"></i> | ||||
| 						<i v-if="statusText == 'saved'" class="green small bottom left corner check icon"></i> | ||||
| 						<i v-if="statusText == 'saving'" class="small purple bottom left corner double angle up icon"></i> | ||||
| 					</i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="toggleList('ul')" :data-tooltip="`Task List\n(CTRL + SHIFT + 8)`" data-position="bottom center" :class="{'edit-active':activeToDo}"> | ||||
| 					<i class="tasks icon"></i> | ||||
| 				</div> | ||||
| 				<div class="edit-button" v-on:click="toggleList('ol')" :data-tooltip="`Ordered List\n(CTRL + SHIFT + 9)`" data-position="bottom center" :class="{'edit-active':activeList}"> | ||||
| 					<i class="list ol icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center"> | ||||
| 					<i class="font icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div v-if="lastUsedColor" class="edit-button" v-on:click="applyLastUsedColor()" data-tooltip="Last Color" data-position="bottom center" :style="{'color':lastUsedColor}"> | ||||
| 					<i class="eye dropper icon"></i> | ||||
| 					<div class="font-color-bar" :style="{'background':lastUsedColor}"></div> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="toggleBold()" :data-tooltip="`Bold\n(CTRL + b)`" data-position="bottom center" :class="{'edit-active':activeBold}"> | ||||
| @@ -53,10 +31,22 @@ | ||||
| 					<i class="underline icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="modifyCode('1.4em')" data-tooltip="Quote" data-position="bottom center" :class="{'edit-active':activeCode}"> | ||||
| 					<i class="quote right icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="modifyFont('0.9em')" data-tooltip="Sub Title" data-position="bottom center" :class="{'edit-active':activeSubTitle}"> | ||||
| 					<i class="small text height icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" :class="{'edit-active':activeTitle}"> | ||||
| 					<i class="text height icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center"> | ||||
| 					<i class="remove format icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="editor.increaseQuoteLevel()" :data-tooltip="`Indent\n(TAB)`" data-position="bottom center"> | ||||
| @@ -75,17 +65,21 @@ | ||||
| 					<i class="border all icon"></i> | ||||
| 				</div> --> | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="toggleList('ul')" :data-tooltip="`Task List\n(CTRL + SHIFT + 8)`" data-position="bottom center" :class="{'edit-active':activeToDo}"> | ||||
| 					<i class="tasks icon"></i> | ||||
| 				</div> | ||||
| 				<div class="edit-button" v-on:click="toggleList('ol')" :data-tooltip="`Ordered List\n(CTRL + SHIFT + 9)`" data-position="bottom center" :class="{'edit-active':activeList}"> | ||||
| 					<i class="list ol icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
| 				 | ||||
| 				<div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center"> | ||||
| 					<i class="grip lines icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center"> | ||||
| 					<i class="remove format icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="bottom center"> | ||||
| 					<i class="undo icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
|  | ||||
| @@ -114,28 +108,19 @@ | ||||
|  | ||||
| 				<div class="edit-divide"></div> | ||||
|  | ||||
|  | ||||
| 				<div class="edit-button" v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="bottom center"> | ||||
| 					<i class="green eye icon"></i> {{ usersOnNote }} | ||||
| 				<div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="bottom center"> | ||||
| 					<i class="reply icon"></i> | ||||
| 				</div> | ||||
|  | ||||
| 				<!-- | ||||
|  				<div class="edit-button" v-on:click="simulateTyping()"> | ||||
| 					<i class="purple bolt icon"></i> | ||||
| 				</div> --> | ||||
|  | ||||
| 				<div class="edit-button" v-if="diffsApplied > 0" :data-tooltip="`Unsaved Changes`" data-position="bottom center"> | ||||
| 					+{{ diffsApplied }} | ||||
| 				</div> | ||||
|  | ||||
| 				<div class="edit-button ui" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center"> | ||||
| 					<i class="green close icon"></i> | ||||
| 				<div class="edit-button done-button" 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> | ||||
| 		</div> | ||||
|  | ||||
| 		<!-- invisible menu item, creates BG for bottom menu --> | ||||
| 		<div class="bottom-edit-menu"></div> | ||||
|  | ||||
| 		<div class="input-container-wrapper" | ||||
| @@ -146,7 +131,7 @@ | ||||
|  | ||||
| 				<!-- Loading indicator  --> | ||||
| 				<transition name="fade"> | ||||
| 					<div v-if="loading || forceShowLoading" class="loading-note" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"> | ||||
| 					<div v-if="loading && forceShowLoading" class="loading-note" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"> | ||||
| 						<div class="loading-text"> | ||||
| 							<loading-icon :message="loadingMessage" /> | ||||
| 						</div> | ||||
| @@ -173,6 +158,12 @@ | ||||
| 				<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> | ||||
| 						{{ lastVisibilityState }} | ||||
| 					</span> | ||||
|  | ||||
| 					<span class="add-mini-tag" v-if="allTags.length == 0"> | ||||
| 						<i class="tags icon"></i>Add Tags | ||||
| 					</span> | ||||
| @@ -183,6 +174,28 @@ | ||||
| 						+ | ||||
| 					</span> | ||||
|  | ||||
| 					<span class="status-menu" v-on:click=" hash=0; save()"> | ||||
|  | ||||
| 						<span v-if="diffsApplied > 0"> | ||||
| 							+{{ diffsApplied }} Unsaved Changes | ||||
| 						</span> | ||||
|  | ||||
| 						<span v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="left center"> | ||||
| 							<i class="green eye icon"></i> {{ usersOnNote }} | ||||
| 						</span> | ||||
|  | ||||
| 						<span v-if="statusText == 'modified'" data-position="left center" data-tooltip="Modified"> | ||||
| 							<i class="grey asterisk icon"></i> | ||||
| 						</span> | ||||
| 						<span v-if="statusText == 'saving'" data-position="left center" data-tooltip="Saving"> | ||||
| 							<i class="grey upload icon"></i> | ||||
| 						</span> | ||||
| 						<span v-if="statusText == 'saved'" data-position="left center" data-tooltip="Saved"> | ||||
| 							<i class="grey check icon"></i> | ||||
| 						</span> | ||||
|  | ||||
| 					</span> | ||||
| 					 | ||||
| 				</div> | ||||
|  | ||||
| 				<!-- Squire Box --> | ||||
| @@ -202,6 +215,7 @@ | ||||
| 		<!-- color picker --> | ||||
| 		<color-tooltip  | ||||
| 			v-if="colorpicker"  | ||||
| 			:last-used-color="lastUsedColor" | ||||
| 			v-on:color="color => modifyColor(color)" | ||||
| 			v-on:close="colorpicker = false" | ||||
| 		/> | ||||
| @@ -234,7 +248,7 @@ | ||||
|  | ||||
| 		<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="ui compact stackable grid"> | ||||
|  | ||||
| 					<div class="eight wide column"> | ||||
| 						<div class="ui dividing header"> | ||||
| @@ -370,7 +384,7 @@ | ||||
| 		data(){ | ||||
| 			return { | ||||
| 				loading: true, | ||||
| 				forceShowLoading: true, | ||||
| 				forceShowLoading: false, | ||||
| 				loadingMessage: 'Loading Note', | ||||
| 				currentNoteId: 0, | ||||
| 				modified: false, | ||||
| @@ -385,6 +399,7 @@ | ||||
| 				lastNoteHash: null, | ||||
| 				saveDebounce: null, //Prevent save from being called numerous times quickly | ||||
| 				updated: 'Never', | ||||
| 				lastInteractionTimestamp:null, //Tracks when note was loaded and last saved/refreshed | ||||
| 				editDebounce: null, | ||||
| 				textChangedDebounce: null, | ||||
| 				keyPressesCounter: 0, //Determine keys pressed between saves | ||||
| @@ -478,12 +493,12 @@ | ||||
| 		}, | ||||
| 		mounted: function() { | ||||
|  | ||||
| 			//Show loading for a minimum time | ||||
| 			//Show loading if note has not loaded in 500ms | ||||
| 			setTimeout(()=>{ | ||||
| 				this.forceShowLoading = false | ||||
| 			}, 100) | ||||
| 				this.forceShowLoading = true | ||||
| 			}, 500) | ||||
|  | ||||
| 			// document.addEventListener('visibilitychange', this.checkForUpdatedNote) | ||||
| 			document.addEventListener('visibilitychange', this.checkForUpdatedNote) | ||||
|  | ||||
| 			//Init squire as early as possible | ||||
| 			if(this.editor && this.editor.destroy){ | ||||
| @@ -561,7 +576,6 @@ | ||||
| 				if(!this.$store.getters.getIsUserOnMobile){ | ||||
| 					this.editor.focus() | ||||
| 					this.editor.moveCursorToEnd() | ||||
|  | ||||
| 				} | ||||
|  | ||||
| 				//Set up websockets after squire is set up | ||||
| @@ -596,10 +610,6 @@ | ||||
|  | ||||
| 						//If the offset is triggered with a negative offset, it means the before element was clicked | ||||
| 						if(e.offsetX < -5){ | ||||
| 							//Will hide keyboard if clicked on mobile | ||||
| 							if(this.$store.getters.getIsUserOnMobile){ | ||||
| 								this.editor.blur() | ||||
| 							} | ||||
|  | ||||
| 							//Area before element was clicked, they clicked the checkbox | ||||
| 							if (el.className === 'active'){ | ||||
| @@ -607,6 +617,21 @@ | ||||
| 							} else { | ||||
| 								el.className = 'active'; | ||||
| 							} | ||||
| 							//Trigger keyup event to save list changes | ||||
| 							this.onKeyup(e) | ||||
|  | ||||
| 							//Will hide keyboard if clicked on mobile | ||||
| 							if(this.$store.getters.getIsUserOnMobile){ | ||||
|  | ||||
| 								setTimeout(() => { | ||||
|  | ||||
| 									document.activeElement.blur() | ||||
| 									e.preventDefault() | ||||
| 									return | ||||
|  | ||||
| 								}, 25) | ||||
|  | ||||
| 							} | ||||
| 						} | ||||
|  | ||||
| 						 | ||||
| @@ -660,10 +685,10 @@ | ||||
| 				}) | ||||
|  | ||||
| 				//Show and hide additional toolbars | ||||
| 				this.editor.addEventListener('focus', e => { | ||||
| 				}) | ||||
| 				this.editor.addEventListener('blur',  e => { | ||||
| 				}) | ||||
| 				// this.editor.addEventListener('focus', e => { | ||||
| 				// }) | ||||
| 				// this.editor.addEventListener('blur',  e => { | ||||
| 				// }) | ||||
| 			}, | ||||
| 			openEditAttachment(){ | ||||
|  | ||||
| @@ -723,35 +748,8 @@ | ||||
| 							return | ||||
| 						} | ||||
|  | ||||
| 						//Set up local data | ||||
| 						this.currentNoteId = this.noteid | ||||
| 						this.rawTextId = response.data.rawTextId | ||||
| 						this.shareUsername = response.data.shareUsername | ||||
|  | ||||
| 						this.created = response.data.created | ||||
| 						this.updated = response.data.updated | ||||
| 						this.noteTitle = '' | ||||
| 						if(response.data.title){ | ||||
| 							this.noteTitle = response.data.title | ||||
| 						} | ||||
|  | ||||
| 						this.noteText = response.data.text | ||||
| 						this.lastNoteHash = this.hashString( response.data.text ) | ||||
| 						// this.diffNoteText = response.data.text | ||||
|  | ||||
| 						//Setup note tags | ||||
| 						this.allTags = response.data.tags ? response.data.tags.split(','):[] | ||||
|  | ||||
| 						//Set up note colors | ||||
| 						if(response.data.color){ | ||||
| 							this.styleObject = JSON.parse(response.data.color) | ||||
| 						} | ||||
| 						 | ||||
| 						if(response.data.pinned != null){ | ||||
| 							this.pinned = response.data.pinned | ||||
| 						} | ||||
| 						this.archived = response.data.archived | ||||
| 						this.attachmentCount = response.data.attachment_count | ||||
| 						//Setup all responsive vue data  | ||||
| 						this.setupLoadedNoteData(response) | ||||
|  | ||||
| 						this.loading = false | ||||
|  | ||||
| @@ -768,6 +766,43 @@ | ||||
| 					console.log('Could not fetch note') | ||||
| 				} | ||||
| 			}, | ||||
| 			setupLoadedNoteData(response){ | ||||
| 				//All the data returned by the server, setup locally in vue component | ||||
|  | ||||
| 				//Set up local data | ||||
| 				this.currentNoteId = this.noteid | ||||
| 				this.rawTextId = response.data.rawTextId | ||||
| 				this.shareUsername = response.data.shareUsername | ||||
|  | ||||
| 				this.created = response.data.created | ||||
| 				this.updated = response.data.updated | ||||
| 				this.lastInteractionTimestamp = +new Date | ||||
| 				this.noteTitle = '' | ||||
| 				if(response.data.title){ | ||||
| 					this.noteTitle = response.data.title | ||||
| 				} | ||||
|  | ||||
| 				this.noteText = response.data.text | ||||
| 				this.lastNoteHash = this.hashString( response.data.text ) | ||||
| 				// this.diffNoteText = response.data.text | ||||
|  | ||||
| 				//Setup note tags | ||||
| 				this.allTags = response.data.tags ? response.data.tags.split(','):[] | ||||
|  | ||||
| 				//Set up note colors | ||||
| 				if(response.data.color){ | ||||
| 					this.styleObject = JSON.parse(response.data.color) | ||||
| 				} | ||||
| 				 | ||||
| 				if(response.data.pinned != null){ | ||||
| 					this.pinned = response.data.pinned | ||||
| 				} | ||||
| 				this.archived = response.data.archived | ||||
| 				this.attachmentCount = response.data.attachment_count | ||||
|  | ||||
| 				return true | ||||
|  | ||||
| 			}, | ||||
| 			//Called on squire event for keyup | ||||
| 			diffText(event){ | ||||
|  | ||||
| @@ -846,10 +881,11 @@ | ||||
|  | ||||
| 				//Save after x keystrokes | ||||
| 				this.keyPressesCounter = (this.keyPressesCounter + 1) | ||||
| 				if(this.keyPressesCounter > 25){ | ||||
| 				if(this.keyPressesCounter > 60){ | ||||
| 					this.keyPressesCounter = 0 | ||||
| 					this.save() | ||||
| 				} | ||||
|  | ||||
| 			}, | ||||
| 			save(force = false){ | ||||
| 				return new Promise((resolve, reject) => { | ||||
| @@ -890,6 +926,7 @@ | ||||
| 					axios.post('/api/note/update', postData).then( response => { | ||||
| 						this.statusText = 'saved' | ||||
| 						this.updated = +new Date | ||||
| 						this.lastInteractionTimestamp = +new Date | ||||
| 						this.modified = true | ||||
| 						this.diffsApplied = 0 | ||||
|  | ||||
| @@ -902,27 +939,35 @@ | ||||
| 			}, | ||||
| 			checkForUpdatedNote(){ | ||||
|  | ||||
| 				//Ignore visibility changes, handle this with socket IO | ||||
| 				//Just keep it always up to date if user is on note | ||||
| 				return | ||||
| 				const now = +new Date | ||||
| 				//Only check every 3 seconds | ||||
| 				const checkForUpdateTimeout = now - this.lastInteractionTimestamp > (2 * 1000) | ||||
|  | ||||
| 				//If user leaves page then returns to page, reload the first batch | ||||
| 				if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){ | ||||
| 					// console.log('Checking for note updates after visibility change.') | ||||
| 				if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible' && checkForUpdateTimeout){ | ||||
| 					 | ||||
| 					const postData = {  | ||||
| 						noteId:this.currentNoteId,  | ||||
| 						text:this.getText(), | ||||
| 						updated: this.updated | ||||
| 					} | ||||
| 					//Focus Regained on Note, check for update | ||||
| 					axios.post('/api/note/get', { 'noteId': this.noteid }) | ||||
| 					.then(response => { | ||||
|  | ||||
| 						const serverTextHash = this.hashString( response.data.text ) | ||||
|  | ||||
| 						if(this.lastNoteHash != serverTextHash){ | ||||
| 							// console.log('note was changed UPDATE THAT BITCH!!!!') | ||||
| 							this.setupLoadedNoteData(response) | ||||
|  | ||||
| 							//Manually set squire text to show | ||||
| 							this.setText(this.noteText) | ||||
| 						} | ||||
|  | ||||
| 					}) | ||||
|  | ||||
| 					console.log('Focus regained with note open.') | ||||
| 					console.log('Attempting to fix diff text. fix this. Search spleen') | ||||
| 					return | ||||
| 				} | ||||
|  | ||||
| 				//Track visibility state  | ||||
| 				//Keep track of visibility change and last interaction time | ||||
| 				this.lastVisibilityState = document.visibilityState | ||||
| 				this.lastInteractionTimestamp = +new Date | ||||
| 				 | ||||
| 			}, | ||||
| 			hashString(inText){ | ||||
|  | ||||
| @@ -1040,6 +1085,33 @@ | ||||
|  | ||||
| <style type="text/css" scoped> | ||||
|  | ||||
| 	.status-menu { | ||||
| 		position: absolute; | ||||
| 		right: 10px; | ||||
| 		z-index: 1019; | ||||
| 		text-align: right; | ||||
| 	} | ||||
|  | ||||
| 	.font-color-bar { | ||||
| 		/*width: calc(100% - 8px);*/ | ||||
| 		height: 3px; | ||||
| 		position: absolute; | ||||
| 		bottom: 2px; | ||||
| 		right: 4px; | ||||
| 		left: 4px; | ||||
| 		z-index: 0; | ||||
| 		background: linear-gradient( | ||||
| 	        45deg, | ||||
| 	        rgba(255, 0, 0, 1) 0%, | ||||
| 	        rgba(208, 222, 33, 1) 20%, | ||||
| 	        rgba(63, 218, 216, 1) 40%, | ||||
| 	        rgba(28, 127, 238, 1) 60%, | ||||
| 	        rgba(186, 12, 248, 1) 80%, | ||||
| 	        rgba(255, 0, 0, 1) 100% | ||||
| 	    ); | ||||
| 	    overflow: hidden; | ||||
| 	} | ||||
|  | ||||
| 	.large-close-button	{ | ||||
| 		position: absolute; | ||||
| 		min-height: 55px; | ||||
| @@ -1094,6 +1166,7 @@ | ||||
| 			margin-left: auto; | ||||
| 			margin-right: auto; | ||||
| 			max-width: 1100px; | ||||
| 			position: relative; | ||||
| 		} | ||||
| 		.add-mini-tag { | ||||
| 			color: var(--border_color); | ||||
| @@ -1128,7 +1201,7 @@ | ||||
| 			display: block; | ||||
| 			background-color: var(--small_element_bg_color); | ||||
| 			z-index: 1019; | ||||
| 			padding: 3px 5px; | ||||
| 			padding: 5px 0; | ||||
| 			border: none; | ||||
| 			border-bottom: 1px solid var(--menu-accent); | ||||
| 			text-align: center; | ||||
| @@ -1140,10 +1213,24 @@ | ||||
| 			background-color: var(--small_element_bg_color); | ||||
| 			color: var(--menu-text); | ||||
| 			display: inline-block; | ||||
| 			font-size: 1.4em; | ||||
| 			padding: 4px 1px 5px 4px; | ||||
| 			border-radius: 3px; | ||||
| 			cursor: pointer; | ||||
| 			font-size: 1.4em; | ||||
| 			box-shadow: 0 0 1px 0 #c4c4c4; | ||||
| 			margin: 0 3px 0; | ||||
| 			padding: 6px 0 0 0; | ||||
| 			text-align: center; | ||||
| 			min-width: 32px; | ||||
| 			min-height: 32px; | ||||
|  | ||||
| 		} | ||||
| 		.edit-button > i { | ||||
| 			font-size: 1em; | ||||
| 			padding: 0; | ||||
| 			margin: 0; | ||||
| 		} | ||||
| 		.edit-button > span { | ||||
| 			padding: 0 8px; | ||||
| 		} | ||||
| 		.edit-button:hover { | ||||
| 			background-color: var(--menu-accent); | ||||
| @@ -1157,7 +1244,7 @@ | ||||
| 			background-color: var(--menu-accent); | ||||
| 			height: 15px; | ||||
| 			width: 1px; | ||||
| 			margin: 0 1px; | ||||
| 			margin: 0 8px; | ||||
| 			padding: 0; | ||||
| 		} | ||||
| 		@media only screen and (max-width: 740px) { | ||||
| @@ -1253,7 +1340,7 @@ | ||||
| 			left: 0; | ||||
| 			right: 0; | ||||
| 			top: 35px; | ||||
| 			bottom: 40px; | ||||
| 			bottom: 37px; | ||||
| 			background-color: var(--small_element_bg_color); | ||||
| 		} | ||||
| 		.note-wrapper { | ||||
| @@ -1263,6 +1350,9 @@ | ||||
| 		.shade1, .shade2 { | ||||
| 			right: 150%; | ||||
| 		} | ||||
| 		.edit-menu { | ||||
| 			background-color: var(--dark_border_color); | ||||
| 		} | ||||
| 		/*menu overwrites */ | ||||
| 		.bottom-edit-menu { | ||||
| 			position: fixed; | ||||
| @@ -1271,14 +1361,19 @@ | ||||
| 			right: 0; | ||||
| 			width: 100%; | ||||
| 			display: block; | ||||
| 			background-color: var(--small_element_bg_color); | ||||
| 			/*background-color: var(--small_element_bg_color);*/ | ||||
| 			background-color: var(--dark_border_color); | ||||
| 			z-index: 1012; | ||||
| 			border: none; | ||||
| 			border-top: 1px solid var(--menu-accent); | ||||
| 			min-height: 40px; | ||||
| 			min-height: 37px; | ||||
| 		} | ||||
| 		.edit-menu { | ||||
| 			text-align: center; | ||||
| 		.menu-top-half, .menu-bottom-half { | ||||
| 			display: flex; | ||||
| 			justify-content: space-around; | ||||
| 		} | ||||
| 		.edit-divide { | ||||
| 			display: none; | ||||
| 		} | ||||
| 		.menu-bottom-half { | ||||
| 			z-index: 1005; | ||||
| @@ -1288,6 +1383,14 @@ | ||||
| 			right: 0; | ||||
| 			text-align: center; | ||||
| 		} | ||||
| 		.edit-button { | ||||
| 			flex-basis: 100%; | ||||
| 			margin: 0 2px; | ||||
| 		} | ||||
| 		.done-button { | ||||
| 			flex-basis: 150%; | ||||
| 			font-size: 13px; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| <template> | ||||
| 	<div class="note-title-display-card"  | ||||
| 		:style="{'background-color':color, 'color':fontColor, 'border-color':color }" | ||||
| 		:class="{'currently-open':(currentlyOpen || showWorking), 'bgboy':triggerClosedAnimation, 'title-view':titleView }" | ||||
| 	> | ||||
| 		:class="{ | ||||
| 			'currently-open':(currentlyOpen || showWorking),  | ||||
| 			'ring':triggerClosedAnimation,  | ||||
| 			'title-view':titleView  | ||||
| 		}"> | ||||
|  | ||||
|  | ||||
| 			<!-- Show title and snippet below it --> | ||||
| @@ -75,8 +78,15 @@ | ||||
| 				<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> | ||||
| 				<span class="thick-sub" v-if="note.subtext.length > 0 && note.title.length == 0"> | ||||
| 					{{ removeHtml(note.subtext) }} | ||||
| 				</span> | ||||
| 				<span class="thin-sub" v-else-if="note.subtext.length > 0"> | ||||
| 					{{ removeHtml(note.subtext) }} | ||||
| 				</span> | ||||
| 				<span v-else-if="note.title.length == 0 && removeHtml(note.subtext).length == 0"> | ||||
| 					Empty Note | ||||
| 				</span> | ||||
| 			 | ||||
| 				<!-- tags --> | ||||
| 				<span v-if="note.tags" class="thin-tags" > | ||||
| @@ -280,7 +290,7 @@ | ||||
| 					setTimeout(()=>{ | ||||
| 						//After 3 seconds, hide it | ||||
| 						this.triggerClosedAnimation = false | ||||
| 					}, 3000) | ||||
| 					}, 1500) | ||||
|  | ||||
| 				}, 500) | ||||
| 				 | ||||
| @@ -472,7 +482,7 @@ | ||||
| 		max-width: none; | ||||
| 		padding: 10px; | ||||
| 		margin: 0; | ||||
| 		overflow: hidden; | ||||
| 		/*overflow: hidden;*/ | ||||
| 		border-radius: 0; | ||||
| 		border: none; | ||||
| 		/*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/ | ||||
| @@ -498,8 +508,17 @@ | ||||
| 		overflow: hidden; | ||||
| 		text-overflow: ellipsis; | ||||
| 		display: -webkit-box; | ||||
| 		-webkit-line-clamp: 2; | ||||
| 		line-clamp: 2;  | ||||
| 		-webkit-line-clamp: 1; | ||||
| 		line-clamp: 1;  | ||||
| 		-webkit-box-orient: vertical; | ||||
| 		opacity: 0.85; | ||||
| 	} | ||||
| 	.thin-container .thick-sub { | ||||
| 		overflow: hidden; | ||||
| 		text-overflow: ellipsis; | ||||
| 		display: -webkit-box; | ||||
| 		-webkit-line-clamp: 3; | ||||
| 		line-clamp: 3;  | ||||
| 		-webkit-box-orient: vertical; | ||||
| 		opacity: 0.85; | ||||
| 	} | ||||
| @@ -700,4 +719,36 @@ | ||||
|     animation: bgin 4s cubic-bezier(0.19, 1, 0.22, 1) 1; | ||||
| } | ||||
|  | ||||
| /*switch between ring or BG boy to change save animation*/ | ||||
|  | ||||
| .ring { | ||||
| 	position: relative; | ||||
| } | ||||
| .ring::after { | ||||
|   content: ''; | ||||
|   width: 10px;  | ||||
|   height: 10px; | ||||
|   border-radius: 100%; | ||||
|   border: 6px solid #00FFCB; | ||||
|   position: absolute; | ||||
|   z-index: 800; | ||||
|   top: 50%; | ||||
|   left: 50%; | ||||
|   transform: translate(-50%, -50%); | ||||
|   animation: ring 1.5s 1; | ||||
| } | ||||
|  | ||||
| @keyframes ring { | ||||
|   0% { | ||||
|     width: 10px; | ||||
|     height: 10px; | ||||
|     opacity: 1; | ||||
|   } | ||||
|   100% { | ||||
|     width: 420px; | ||||
|     height: 420px; | ||||
|     opacity: 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| </style> | ||||
| @@ -2,22 +2,43 @@ | ||||
| 	.colors { | ||||
| 		position: fixed; | ||||
| 		z-index: 1023; | ||||
| 		top: 5px; | ||||
| 		top: 35px; | ||||
| 		/*height: 100px;*/ | ||||
| 		width: 400px; | ||||
| 		left: 20%; | ||||
| 	} | ||||
| 	.colors-container { | ||||
| 		max-width: 370px; | ||||
| 		/*max-width: 360px;*/ | ||||
| 		display: flex; | ||||
| 		/*flex-direction: column;*/ | ||||
| 		flex-wrap: wrap; | ||||
| 		justify-content: center; | ||||
| 		align-items: stretch; | ||||
| 		align-content: stretch; | ||||
|  | ||||
| 		height: 250px; | ||||
| 		width: 100%; | ||||
| 	} | ||||
| 	.dot { | ||||
| 		display: inline-block; | ||||
| 		width: 30px; | ||||
| 		height: 30px; | ||||
| 		/*display: inline-block;*/ | ||||
|  | ||||
| 		border-radius: 30px; | ||||
| 		box-shadow: 0px 1px 3px 0px #3e3e3e; | ||||
| 		margin: 7px 7px 0 0; | ||||
| 		box-shadow: 0px 0px 0px 1px inset #3e3e3e; | ||||
| 		margin: 0 0 2px 2px; | ||||
| 		cursor: pointer; | ||||
| 		flex-basis: 9%; | ||||
| 		height: 30px; | ||||
| 		text-align: center; | ||||
| 	} | ||||
| 	.dot > i { | ||||
| 		margin: 9px 0 0 0; | ||||
| 		color: white; | ||||
| 		text-shadow:  | ||||
| 			1px 1px 2px #3e3e3e, | ||||
| 			1px -1px 2px #3e3e3e, | ||||
| 			-1px 1px 2px #3e3e3e, | ||||
| 			-1px -1px 2px #3e3e3e | ||||
| 		; | ||||
| 	} | ||||
| 	.shade { | ||||
| 		position: fixed; | ||||
| @@ -30,12 +51,16 @@ | ||||
| 		width: 100vw; | ||||
| 		height: 100vh; | ||||
| 	} | ||||
| 	.big-shadow { | ||||
| 		box-shadow: 0px 4px 5px 1px #a8a8a8; | ||||
| 	} | ||||
| 	@media only screen and (max-width: 740px) { | ||||
| 		.colors { | ||||
| 			position: fixed; | ||||
| 			left: 0; | ||||
| 			right: 0; | ||||
| 			top: 0; | ||||
| 			left: 5px; | ||||
| 			right: -5px; | ||||
| 			top: 5px; | ||||
| 			width: 95%; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
| @@ -43,13 +68,15 @@ | ||||
| <template> | ||||
| 	<div> | ||||
| 		<div class="colors"> | ||||
| 			<div class="ui raised segment"> | ||||
| 			<div class="ui segment big-shadow"> | ||||
| 				<h3>Select Text Color</h3> | ||||
| 				<div class="colors-container"> | ||||
| 					<span  | ||||
| 						v-for="(color,index) in colors"  | ||||
| 						class="dot" | ||||
| 						v-on:click="onColorClick(index)" | ||||
| 						:style="`background-color: ${color};`"> | ||||
| 						<i v-if="lastUsedColor == color" class="check icon"></i> | ||||
| 					</span> | ||||
| 				</div> | ||||
| 			</div> | ||||
| @@ -65,6 +92,7 @@ | ||||
| 		components:{ | ||||
| 			'nm-button':require('@/components/NoteMenuButtonComponent.vue').default | ||||
| 		}, | ||||
| 		props: [ 'lastUsedColor' ], | ||||
| 		data: function(){  | ||||
| 			return { | ||||
| 				hover: false, | ||||
|   | ||||
| @@ -9,6 +9,8 @@ const SquireButtonFunctions = { | ||||
|             activeList: false, | ||||
|             activeToDo: false, | ||||
|             activeColor: null, | ||||
|             activeCode: false, | ||||
|             activeSubTitle: false, | ||||
|             // | ||||
|             lastUsedColor: null, | ||||
| 		} | ||||
| @@ -28,6 +30,8 @@ const SquireButtonFunctions = { | ||||
| 			this.activeToDo = false | ||||
| 			this.activeColor = null | ||||
| 			this.activeUnderline = false | ||||
| 			this.activeCode = false | ||||
|             this.activeSubTitle = false | ||||
|  | ||||
| 			if(e.path.indexOf('>U>') > -1 || e.path.search(/U$/) > -1){ | ||||
| 				this.activeUnderline = true | ||||
| @@ -38,15 +42,21 @@ const SquireButtonFunctions = { | ||||
| 			if(e.path.indexOf('>I') > -1){ | ||||
| 				this.activeItalics = true | ||||
| 			} | ||||
| 			if(e.path.indexOf('fontSize') > -1){ | ||||
| 			if(e.path.indexOf('fontSize=1.4em') > -1){ | ||||
| 				this.activeTitle = true | ||||
| 			} | ||||
| 			if(e.path.indexOf('fontSize=0.9em') > -1){ | ||||
| 				this.activeSubTitle = true | ||||
| 			} | ||||
| 			if(e.path.indexOf('OL>LI') > -1){ | ||||
| 				this.activeList = true | ||||
| 			} | ||||
| 			if(e.path.indexOf('UL>LI') > -1){ | ||||
| 				this.activeToDo = true | ||||
| 			} | ||||
| 			if(e.path.indexOf('CODE') > -1){ | ||||
| 				this.activeCode= true | ||||
| 			} | ||||
| 			const colorIndex = e.path.indexOf('color=') | ||||
| 			if(colorIndex > -1){ | ||||
| 				//Get all digigs after color index, then limit to 3 | ||||
| @@ -143,6 +153,12 @@ const SquireButtonFunctions = { | ||||
| 				this.editor.italic() | ||||
| 			} | ||||
| 		}, | ||||
| 		modifyCode(){ | ||||
|  | ||||
| 			this.selectLineIfNoSelect() | ||||
|  | ||||
| 			this.editor.toggleCode() | ||||
| 		}, | ||||
| 		undoCustom(){ | ||||
| 			//The same as pressing CTRL + Z  | ||||
| 			// this.editor.focus() | ||||
|   | ||||
| @@ -27,6 +27,10 @@ | ||||
| 							v-on:tagClick="tagId => toggleTagFilter(tagId)" | ||||
| 						/> | ||||
|  | ||||
| 						<span> | ||||
| 							Active Sessions {{ $store.getters.getActiveSessions }} | ||||
| 						</span> | ||||
| 						 | ||||
| 						<div class="ui right floated basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0"> | ||||
| 							<span v-if="titleView"> | ||||
| 								<i class="th icon"></i> Tiles | ||||
| @@ -51,7 +55,7 @@ | ||||
|  | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="sixteen wide column" v-if="searchTerm.length > 0 && !loadingInProgress"> | ||||
| 			<div class="sixteen wide column" v-if="searchTerm.length > 0 && !showLoading"> | ||||
| 				<h2 class="ui header"> | ||||
| 					<div class="content"> | ||||
| 						{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}" | ||||
| @@ -63,11 +67,15 @@ | ||||
| 			</div> | ||||
|  | ||||
| 			<div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column"> | ||||
| 				<h2>Archived Notes</h2> | ||||
| 				<h2> | ||||
| 					<i class="green archive icon"></i> | ||||
| 					Archived Notes</h2> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1"> | ||||
| 				<h2>Trash | ||||
| 				<h2> | ||||
| 					<i class="green trash alternate outline icon"></i> | ||||
| 					Trashed Notes | ||||
| 					<span>({{ $store.getters.totals['trashedNotes'] }})</span> | ||||
| 					<div class="ui right floated basic button" data-tooltip="This doesn't work yet"> | ||||
| 						<i class="poo storm icon"></i> | ||||
| @@ -77,7 +85,8 @@ | ||||
| 			</div> | ||||
| 			 | ||||
| 			<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1"> | ||||
| 				<h2>Shared Notes</h2> | ||||
| 				<h2><i class="green paper plane outline icon"></i> | ||||
| 					Shared Notes</h2> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="sixteen wide column" v-if="tagSuggestions.length > 0"> | ||||
| @@ -88,17 +97,6 @@ | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<!-- found attachments  --> | ||||
| 			<div class="sixteen wide column" v-if="foundAttachments.length > 0"> | ||||
| 				<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5> | ||||
| 				<attachment-display  | ||||
| 					v-for="item in foundAttachments"  | ||||
| 					:item="item" | ||||
| 					:key="item.id" | ||||
| 					:search-params="{}" | ||||
| 				/> | ||||
| 			</div> | ||||
|  | ||||
| 			<!-- Note title card display  --> | ||||
| 			<div class="sixteen wide column"> | ||||
|  | ||||
| @@ -130,11 +128,25 @@ | ||||
| 					</div> | ||||
|  | ||||
|  | ||||
| 					<loading-icon v-if="loadingInProgress" message="Decrypting Notes" /> | ||||
| 					<div class="loading-section" v-if="showLoading"> | ||||
| 						<loading-icon message="Decrypting Notes" /> | ||||
| 					</div> | ||||
|  | ||||
|  | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<!-- found attachments  --> | ||||
| 			<div class="sixteen wide column" v-if="foundAttachments.length > 0"> | ||||
| 				<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5> | ||||
| 				<attachment-display  | ||||
| 					v-for="item in foundAttachments"  | ||||
| 					:item="item" | ||||
| 					:key="item.id" | ||||
| 					:search-params="{}" | ||||
| 				/> | ||||
| 			</div> | ||||
|  | ||||
| 		</div> | ||||
|  | ||||
| 		 | ||||
| @@ -180,10 +192,10 @@ | ||||
|  | ||||
| 				//Load up notes in batches | ||||
| 				firstLoadBatchSize: 10, //First set of rapidly loaded notes | ||||
| 				batchSize: 25, //Size of batch loaded when user scrolls through current batch | ||||
| 				batchSize: 20, //Size of batch loaded when user scrolls through current batch | ||||
| 				batchOffset: 0, //Tracks the current batch that has been loaded | ||||
| 				loadingBatchTimeout: null, //Limit how quickly batches can be loaded | ||||
| 				loadingInProgress: false, | ||||
| 				showLoading: false, | ||||
| 				scrollLoadEnabled: true, | ||||
|  | ||||
| 				//Clear button is not visible  | ||||
| @@ -329,7 +341,7 @@ | ||||
|  | ||||
| 			//Reload page content - don't trigger if load is in progress | ||||
| 			this.$bus.$on('note_reload', () => { | ||||
| 				if(!this.loadingInProgress){ | ||||
| 				if(!this.showLoading){ | ||||
| 					this.reset() | ||||
| 				} | ||||
| 			}) | ||||
| @@ -342,6 +354,8 @@ | ||||
| 			//update note on visibility change | ||||
| 			// document.addEventListener('visibilitychange', this.visibiltyChangeAction); | ||||
|  | ||||
| 			//Find previously stored notes, cache for 20 hours, load them and compare  | ||||
|  | ||||
| 		}, | ||||
| 		beforeDestroy(){ | ||||
| 			window.removeEventListener('scroll', this.onScroll) | ||||
| @@ -373,12 +387,29 @@ | ||||
| 			'$route.params.id': function(id){ | ||||
| 				//Open note on ID, null id will close note | ||||
| 				this.activeNoteId1 = id | ||||
| 			}, | ||||
| 			'$route' (to, from) { | ||||
|  | ||||
|  | ||||
| 				// Reload the notes if returning to this page | ||||
| 				if(to.fullPath == '/notes' && !from.fullPath.includes('/notes/open/')){ | ||||
|  | ||||
| 					this.reset() | ||||
| 				} | ||||
|  | ||||
| 				//Lookup tags set in URL | ||||
| 				if(to.params.tag && this.$store.getters.totals && this.$store.getters.totals['tags'][to.params.tag]){ | ||||
|  | ||||
| 					//Lookup tag in store by string | ||||
| 					const tagObject = this.$store.getters.totals['tags'][to.params.tag] | ||||
|  | ||||
| 					//Pull key out of string and load tags for that key | ||||
| 					this.toggleTagFilter(tagObject.id) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		methods: { | ||||
| 			onScroll(e){ | ||||
| 			console.log('Scroll') | ||||
| 		}, | ||||
| 			toggleTitleView(){ | ||||
| 				this.titleView = !this.titleView | ||||
| 			}, | ||||
| @@ -418,6 +449,10 @@ | ||||
| 			}, | ||||
| 			onScroll(e){ | ||||
|  | ||||
| 				if(!this.scrollLoadEnabled){ | ||||
| 					return | ||||
| 				} | ||||
|  | ||||
| 				clearTimeout(this.loadingBatchTimeout) | ||||
| 				this.loadingBatchTimeout = setTimeout(() => { | ||||
|  | ||||
| @@ -427,12 +462,12 @@ | ||||
| 					const height = document.getElementById('app').scrollHeight | ||||
|  | ||||
| 					//Load if less than 500px from the bottom | ||||
| 					if(((height - scrolledDown) < 500) && this.scrollLoadEnabled && !this.loadingInProgress){ | ||||
| 					if(((height - scrolledDown) < 500) && this.scrollLoadEnabled){ | ||||
| 						 | ||||
| 						this.search(false, this.batchSize, true) | ||||
| 						this.search(true, this.batchSize, true) | ||||
| 					} | ||||
|  | ||||
| 				}, 30) | ||||
| 				}, 50) | ||||
|  | ||||
| 				 | ||||
| 				return | ||||
| @@ -553,19 +588,14 @@ | ||||
| 				return new Promise((resolve, reject) => { | ||||
|  | ||||
| 					//Don't double load note batches | ||||
| 					if(this.loadingInProgress){ | ||||
| 					if(this.showLoading){ | ||||
| 						console.log('Loading already in progress') | ||||
| 						return resolve(false) | ||||
| 					} | ||||
|  | ||||
| 					//Reset a lot of stuff if we are not merging batches | ||||
| 					if(!mergeExisting){ | ||||
| 						Object.keys(this.noteSections).forEach( key => { | ||||
| 							this.noteSections[key] = [] | ||||
| 						}) | ||||
| 						this.batchOffset = 0 // Reset batch offset if we are not merging note batches | ||||
| 						this.batchOffset = 0 // Reset batch offset if we are not merging note batches or new set will be offset from current and overwrite current set with second batch | ||||
| 					} | ||||
| 					this.searchResultsCount = 0 | ||||
|  | ||||
| 					//Remove all filter limits from previous queries | ||||
| 					delete this.fastFilters.limitSize | ||||
| @@ -593,25 +623,40 @@ | ||||
| 					} | ||||
|  | ||||
| 					//Perform search - or die | ||||
| 					this.loadingInProgress = true | ||||
| 					this.showLoading = showLoading | ||||
| 					this.scrollLoadEnabled = false | ||||
| 					axios.post('/api/note/search', postData) | ||||
| 					.then(response => { | ||||
|  | ||||
| 						//Reset a lot of stuff if we are not merging batches | ||||
| 						if(!mergeExisting){ | ||||
| 							Object.keys(this.noteSections).forEach( key => { | ||||
| 								this.noteSections[key] = [] | ||||
| 							}) | ||||
| 						} | ||||
| 						this.searchResultsCount = 0 | ||||
|  | ||||
| 						// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad) | ||||
|  | ||||
| 						//Save the number of notes just loaded | ||||
| 						this.batchOffset += response.data.notes.length | ||||
|  | ||||
| 						//Enable or disable scroll loading | ||||
| 						//Enable scroll loading if endpoint retured notes | ||||
| 						this.scrollLoadEnabled = response.data.notes.length > 0 | ||||
|  | ||||
| 						if(response.data.total > 0){ | ||||
| 							this.searchResultsCount = response.data.total | ||||
| 						} | ||||
| 						 | ||||
| 						this.loadingInProgress = false | ||||
| 						this.showLoading = false | ||||
| 						this.generateNoteCategories(response.data.notes, mergeExisting) | ||||
|  | ||||
| 						//cache initial notes for faster reloads | ||||
| 						if(!mergeExisting && this.showClear == false){ | ||||
| 							const cachedNotesJson = JSON.stringify(response.data.notes) | ||||
| 							localStorage.setItem('snippetCache', cachedNotesJson) | ||||
| 						} | ||||
|  | ||||
| 						return resolve(true) | ||||
| 					}) | ||||
| 					.catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') }) | ||||
| @@ -726,7 +771,7 @@ | ||||
| 				//clear out tags | ||||
| 				this.searchTags = [] | ||||
| 				this.tagSuggestions = [] | ||||
| 				this.loadingInProgress = false | ||||
| 				this.showLoading = false | ||||
| 				this.searchTerm = '' | ||||
| 				this.$bus.$emit('reset_fast_filters') //Clear out search | ||||
|  | ||||
| @@ -743,9 +788,21 @@ | ||||
| 				filter[options[index]] = 1 | ||||
|  | ||||
| 				this.fastFilters = filter | ||||
|  | ||||
| 				//If notes exist in cache, load them up | ||||
| 				let showLoading = true | ||||
| 				const cachedNotesJson = localStorage.getItem('snippetCache') | ||||
| 				const cachedNotes = JSON.parse(cachedNotesJson) | ||||
| 				if(cachedNotes && cachedNotes.length > 0 && !this.showClear){ | ||||
|  | ||||
| 					//Load cache. do not merge existing | ||||
| 					this.generateNoteCategories(cachedNotes, false) | ||||
| 					showLoading = false | ||||
| 				} | ||||
|  | ||||
| 				//Fetch First batch of notes with new filter | ||||
| 				this.search(true, this.firstLoadBatchSize, false) | ||||
| 				.then( r => this.search(false, this.batchSize, true)) | ||||
| 				this.search(showLoading, this.batchSize, false) | ||||
| 				// .then( r => this.search(false, this.batchSize, true)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -769,4 +826,35 @@ | ||||
| 	.note-card-section + .note-card-section { | ||||
| 		padding: 15px 0 0; | ||||
| 	} | ||||
| 	.loading-section { | ||||
| 		position: fixed; | ||||
| 		bottom: 40px; | ||||
| 		padding: 0 10px; | ||||
| 		right: 5px; | ||||
| 		box-shadow: 0 1px 3px 0 #656565; | ||||
| 		border-radius: 6px; | ||||
| 		background-color: var(--small_element_bg_color); | ||||
| 		opacity: 0.9; | ||||
| 		font-size: 0.7em; | ||||
| 	} | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| /*html, body { | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| .wrap { | ||||
|   height: 100%; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| }*/ | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| </style> | ||||
| @@ -43,6 +43,12 @@ export default new Router({ | ||||
|       meta: {title: 'Open Note'}, | ||||
|       component: NotesPage, | ||||
|     }, | ||||
|     { | ||||
|       path: '/search/tags/:tag', | ||||
|       name: 'Search Notes', | ||||
|       meta: {title: 'Search Notes'}, | ||||
|       component: NotesPage, | ||||
|     }, | ||||
|     { | ||||
|       path: '/notes/open/:id/menu/:openMenu', | ||||
|       name: 'Open Note Menu', | ||||
|   | ||||
| @@ -10,6 +10,7 @@ export default new Vuex.Store({ | ||||
| 		nightMode: false, | ||||
| 		isUserOnMobile: false, | ||||
| 		userTotals: null, | ||||
| 		activeSessions: 0, | ||||
| 	}, | ||||
| 	mutations: { | ||||
| 		setUsername(state, username){ | ||||
| @@ -24,6 +25,7 @@ export default new Vuex.Store({ | ||||
| 			localStorage.removeItem('loginToken') | ||||
| 			localStorage.removeItem('username') | ||||
| 			localStorage.removeItem('currentVersion') | ||||
| 			localStorage.removeItem('snippetCache') | ||||
| 			delete axios.defaults.headers.common['authorizationtoken'] | ||||
| 			state.username = null | ||||
| 			state.userTotals = null | ||||
| @@ -119,6 +121,10 @@ export default new Vuex.Store({ | ||||
| 			// Object.keys(totalsObject).forEach( key => { | ||||
| 			// 	console.log(key + ' -- ' + totalsObject[key]) | ||||
| 			// }) | ||||
| 		}, | ||||
| 		setActiveSessions(state, countData){ | ||||
| 			//Count of the number of active socket.io sessions for this user | ||||
| 			state.activeSessions = countData | ||||
| 		} | ||||
| 	}, | ||||
| 	getters: { | ||||
| @@ -144,6 +150,9 @@ export default new Vuex.Store({ | ||||
| 		totals: state => { | ||||
| 			return state.userTotals | ||||
| 		}, | ||||
| 		getActiveSessions: state => { | ||||
| 			return state.activeSessions | ||||
| 		} | ||||
| 	}, | ||||
| 	actions: { | ||||
| 		fetchAndUpdateUserTotals ({ commit }) { | ||||
|   | ||||
| @@ -51,12 +51,31 @@ io.on('connection', function(socket){ | ||||
| 		Auth.decodeToken(token) | ||||
| 		.then(userData => { | ||||
| 			socket.join(userData.userId) | ||||
|  | ||||
| 			//Track active logged in user accounts | ||||
| 			const usersInRoom = io.sockets.adapter.rooms[userData.userId] | ||||
| 			io.to(userData.userId).emit('update_active_user_count', usersInRoom.length) | ||||
|  | ||||
| 		}).catch(error => { | ||||
| 			//Don't add user to room if they are not logged in | ||||
| 			// console.log(error) | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	socket.on('get_active_user_count', token => { | ||||
| 		Auth.decodeToken(token) | ||||
| 		.then(userData => { | ||||
| 			socket.join(userData.userId) | ||||
|  | ||||
| 			//Track active logged in user accounts | ||||
| 			const usersInRoom = io.sockets.adapter.rooms[userData.userId] | ||||
| 			io.to(userData.userId).emit('update_active_user_count', usersInRoom.length) | ||||
|  | ||||
| 		}).catch(error => { | ||||
| 			// console.log(error) | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	//Renew Session tokens when users request a new one | ||||
| 	socket.on('renew_session_token', token => { | ||||
|  | ||||
| @@ -205,7 +224,7 @@ io.on('connection', function(socket){ | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	socket.on('disconnect', function(){ | ||||
| 	socket.on('disconnect', function(socket){ | ||||
| 		// console.log('user disconnected'); | ||||
| 	}); | ||||
| }); | ||||
|   | ||||
| @@ -9,7 +9,7 @@ const speakeasy = require('speakeasy') | ||||
|  | ||||
| let User = module.exports = {} | ||||
|  | ||||
| const version = '3.3.3' | ||||
| const version = '3.4.2' | ||||
|  | ||||
| //Login a user, if that user does not exist create them | ||||
| //Issues login token | ||||
| @@ -196,7 +196,9 @@ User.register = (username, password) => { | ||||
| User.getCounts = (userId) => { | ||||
| 	return new Promise((resolve, reject) => { | ||||
|  | ||||
| 		let countTotals = {} | ||||
| 		let countTotals = { | ||||
| 			tags: {} | ||||
| 		} | ||||
| 		const userHash = cs.hash(String(userId)).toString('base64') | ||||
|  | ||||
| 		db.promise().query( | ||||
| @@ -244,12 +246,37 @@ User.getCounts = (userId) => { | ||||
|  | ||||
| 			Object.assign(countTotals, rows[0][0]) //combine results | ||||
|  | ||||
| 			//Count usages of user tags, sort by most popular | ||||
| 			return db.promise().query(` | ||||
| 				SELECT  | ||||
| 					tag.text, COUNT(tag_id) AS uses, tag.id | ||||
| 				FROM note_tag | ||||
| 					JOIN tag ON (tag.id = note_tag.tag_id) | ||||
| 				WHERE user_id = ? | ||||
| 				GROUP BY tag_id | ||||
| 				ORDER BY uses DESC | ||||
| 				LIMIT 5 | ||||
| 			`, [userId]) | ||||
|  | ||||
| 		}).then( (rows, fields) => { | ||||
|  | ||||
| 			 | ||||
|  | ||||
| 			//Convert everything to an int or 0 | ||||
| 			Object.keys(countTotals).forEach( key => { | ||||
| 				const count = parseInt(countTotals[key]) | ||||
| 				countTotals[key] = count ? count : 0 | ||||
| 			}) | ||||
|  | ||||
| 			//Build out tags object | ||||
| 			let tagsObject = {} | ||||
| 			rows[0].forEach(tagRow => { | ||||
| 				tagsObject[tagRow['text']] = {'id':tagRow.id, 'uses':tagRow.uses} | ||||
| 			}) | ||||
|  | ||||
| 			//Assign after counts are updated | ||||
| 			countTotals['tags'] = tagsObject | ||||
|  | ||||
| 			countTotals['currentVersion'] = version | ||||
|  | ||||
| 			resolve(countTotals) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user