* Added fake site warning

* Fixed a bunch of style bugs for chrome browsers
* Improved check box styles on desktop and mobile
* Touch up tool tip styles. Only dark now.
* Created a separate terms page
* Added 2FA auth token options to login
* Added tool tip displays to some buttons on editor
* Added pinned and archived options to overflow menu
* Changed shared note styles
* Disabled Scroll into view
* Made image display smaller when adding images to notes
* Added a last used color option
* Updated help page
* Fixed spelling error on terms page
* Added a big ass green label on the new note icon
* Scratch pad now opens a note, which is the scratch pad
* Added better 2fa guide
* Added change password option
* Added log out and log out all active sessions option
* Added strict rate limiting on login and register actions
* Added middleware to routes that force authentication to be accessed
* Fixed bug that was causing shared notes to appear empty
* Updated option now appears on shared notes after they are actually updated
This commit is contained in:
Max G 2020-07-23 05:00:20 +00:00
parent a8a966866c
commit b34a62e114
24 changed files with 560 additions and 484 deletions

View File

@ -1,9 +1,22 @@
<template> <template>
<div id="app" :class="{ 'night-mode':($store.getters.getIsNightMode == 2) }"> <div id="app" :class="{ 'night-mode':($store.getters.getIsNightMode == 2) }">
<global-site-menu /> <div class="ui container" v-if="showFakeSite">
<div class="ui basic very padded segment">
<div class="ui inverted red segment">
<h1>WARNING - False site detected</h1>
<h2>The Domain for this website is not correct.</h2>
<h2>Only trust <a class="ui button" href="https://www.solidscribe.com">https://www.solidscribe.com</a></h2>
<h2>Do not any enter any personal information into this website.</h2>
<h2>You will be redirected to the correct domain in {{redirectSeconds}}</h2>
</div>
</div>
</div>
<router-view />
<global-site-menu v-if="!showFakeSite" />
<router-view v-if="!showFakeSite" />
<global-notification /> <global-notification />
@ -22,7 +35,8 @@ export default {
}, },
data: function(){ data: function(){
return { return {
// loggedIn: showFakeSite:false, //Incorrect domain detection
redirectSeconds: 15,
fetchingInProgress: false, //Prevent start getting token while fetch is in progress fetchingInProgress: false, //Prevent start getting token while fetch is in progress
blockUntilNextRequest: false //If token was just renewed, don't fetch more until next request blockUntilNextRequest: false //If token was just renewed, don't fetch more until next request
} }
@ -112,6 +126,17 @@ export default {
}, },
mounted: function(){ mounted: function(){
const isDev = process.env['NODE_ENV'] == 'development'
if(window.location.hostname.toLowerCase() != "www.solidscribe.com" && !isDev){
this.showFakeSite = true
setInterval(() => {
this.redirectSeconds--
if(this.redirectSeconds == 0){
window.location.href = 'https://www.solidscribe.com'
}
}, 1000)
}
//Update totals for entire app on event //Update totals for entire app on event
this.$io.on('update_counts', () => { this.$io.on('update_counts', () => {
console.log('Got event, update totals') console.log('Got event, update totals')

View File

@ -116,6 +116,9 @@ body {
background-color: transparent; background-color: transparent;
border-color: var(--dark_border_color); border-color: var(--dark_border_color);
} }
.ui.dividing.header {
border-bottom-color: var(--dark_border_color);
}
.ui.icon.input > i.icon { .ui.icon.input > i.icon {
color: var(--text_color); color: var(--text_color);
} }
@ -251,6 +254,13 @@ i.green.icon.icon.icon.icon {
z-index: 100; z-index: 100;
cursor: pointer; cursor: pointer;
} }
.text-container {
max-width: 1000px;
display: block;
margin-left: auto;
margin-right: auto;
background-color: var(--small_element_bg_color) !important;
}
/* squire text styles */ /* squire text styles */
.squire-box { .squire-box {
@ -264,7 +274,7 @@ i.green.icon.icon.icon.icon {
box-sizing: border-box; box-sizing: border-box;
padding: 10px 15px 10px; padding: 10px 15px 10px;
/*background: transparent;*/ /*background: transparent;*/
overflow-x: scroll; overflow: hidden;
font-size: 1.2em; font-size: 1.2em;
line-height: 1.8em; line-height: 1.8em;
word-wrap: break-word; word-wrap: break-word;
@ -295,7 +305,7 @@ i.green.icon.icon.icon.icon {
.note-card-text pre, .note-card-text pre,
.squire-box pre { .squire-box pre {
word-wrap: break-word; /*word-wrap: break-word;*/
} }
.note-card-text p, .note-card-text p,
.squire-box p { .squire-box p {
@ -307,6 +317,10 @@ i.green.icon.icon.icon.icon {
margin: 0; margin: 0;
padding: 0 0 0 2.5em; padding: 0 0 0 2.5em;
} }
.note-card-text u,
.squire-box u {
text-decoration-color: var(--main-accent);
}
.note-card-text img { .note-card-text img {
max-width:100%; max-width:100%;
height: auto; height: auto;
@ -322,7 +336,12 @@ i.green.icon.icon.icon.icon {
.squire-box li > p { .squire-box li > p {
margin-bottom: 0; margin-bottom: 0;
} }
.note-card-text ol,
.squire-box ol,
.note-card-text ul,
.squire-box ul {
margin: 8px 0 0 0;
}
.note-card-text ul > li, .note-card-text ul > li,
.squire-box ul > li { .squire-box ul > li {
position: relative; position: relative;
@ -331,35 +350,53 @@ i.green.icon.icon.icon.icon {
.note-card-text ul > li:before, .note-card-text ul > li:before,
.squire-box ul > li:before { .squire-box ul > li:before {
/*filled circle */
content: "\f111"; content: "\f111";
/*empty square*/
/*content: "\F0C8";*/
font-family: 'Icons'; font-family: 'Icons';
/*font-family: 'outline-icons';*/
backface-visibility: hidden; backface-visibility: hidden;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
text-decoration: inherit; text-decoration: inherit;
text-align: center; text-align: center;
line-height: 1.4em; font-size: 0.25em;
font-size: 0.75em;
height: 17px; height: 100%;
width: 17px; width: 20px;
display: inline-block; display: inline-block;
position: absolute; position: absolute;
left: -30px; left: -25px;
/*border: 2px solid #444;*/ /*border: 2px solid #444;*/
/*border-radius: 4px;*/ /*border-radius: 4px;*/
bottom: 0; bottom: 0;
top: 4px; top: 0;
cursor: pointer; cursor: pointer;
opacity: 0.7; opacity: 0.7;
color: var(--text_color);
text-align: center;
} }
/* filled in check circle */
ul > li.active:before { ul > li.active:before {
font-family: 'Icons'; font-family: 'Icons';
content: "\f058"; content: "\F14A";
color: var(--main-accent); color: var(--main-accent);
opacity: 1; opacity: 1;
font-size: 1em;
}
/* hover - transparent icon */
.squire-box ul > li:hover:not(.active):before {
font-family: 'outline-icons';
content: "\f14a";
opacity: 0.4;
font-size: 1em;
} }
.note-title-display-card .divide, .note-title-display-card .divide,
@ -447,27 +484,28 @@ i.green.icon.icon.icon.icon {
min-height: 30px; min-height: 30px;
} }
/*empty check box*/
.note-card-text ul > li:before, .note-card-text ul > li:before,
.squire-box ul > li:before { .squire-box ul > li:before,
.squire-box ul > li:hover:not(.active):before {
/*empty checkmark*/
/*font-family: 'Icons';*/
/*content: "\f058";*/
content: "\f111"; content: "\F0C8";
font-family: outline-icons; font-family: 'outline-icons';
height: 24px;
width: 24px;
left: -40px; left: -40px;
bottom: 0;
top: 0px;
cursor: pointer;
line-height: 0.9em;
font-size: 1.4em; font-size: 1.4em;
opacity: 0.2;
} }
/*Filled check box */
ul > li.active:before { ul > li.active:before {
font-family: 'Icons'; font-family: 'Icons';
content: "\f058"; content: "\F14A";
color: var(--main-accent); color: var(--main-accent);
opacity: 1; opacity: 1;
} }
@ -539,36 +577,34 @@ i.green.icon.icon.icon.icon {
position: absolute; position: absolute;
content: ''; content: '';
font-size: 1rem; font-size: 1rem;
width: 0.71428571em; width: 10px;
height: 0.71428571em; height: 10px;
background: #FFFFFF; background: #1B1C1D;
-webkit-transform: rotate(45deg); -webkit-transform: rotate(45deg);
transform: rotate(45deg); transform: rotate(45deg);
z-index: 1901; z-index: 1901;
-webkit-box-shadow: 1px 1px 0 0 #bababc;
box-shadow: 1px 1px 0 0 #bababc;
} }
/* Popup */ /* Popup */
[data-tooltip]:after { [data-tooltip]:after {
min-width: 40px;
pointer-events: none; pointer-events: none;
content: attr(data-tooltip); content: attr(data-tooltip);
position: absolute; position: absolute;
text-transform: none; text-transform: none;
text-align: left; text-align: center;
white-space: nowrap; white-space: pre;
font-size: 1rem; font-size: 1rem;
border: 1px solid #D4D4D5; border: 1px solid #D4D4D5;
line-height: 1.4285em; line-height: 1.4285em;
max-width: none; max-width: none;
background: #FFFFFF; background: #1B1C1D;
padding: 0.833em 1em; padding: 0.5em;
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
color: rgba(0, 0, 0, 0.87); /*color: var(--main-accent);*/
color: white;
border-radius: 0.28571429rem; border-radius: 0.28571429rem;
-webkit-box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15);
z-index: 1900; z-index: 1900;
} }
@ -578,7 +614,7 @@ i.green.icon.icon.icon.icon {
right: auto; right: auto;
bottom: 100%; bottom: 100%;
left: 50%; left: 50%;
background: #FFFFFF; background: #1B1C1D;
margin-left: -0.07142857rem; margin-left: -0.07142857rem;
margin-bottom: 0.14285714rem; margin-bottom: 0.14285714rem;
} }
@ -596,7 +632,7 @@ i.green.icon.icon.icon.icon {
pointer-events: none; pointer-events: none;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
transition: opacity 0.2s ease; /*transition: opacity 0.2s ease;*/
} }
[data-tooltip]:before { [data-tooltip]:before {
-webkit-transform: rotate(45deg) scale(0) !important; -webkit-transform: rotate(45deg) scale(0) !important;
@ -619,93 +655,13 @@ i.green.icon.icon.icon.icon {
transform: rotate(45deg) scale(1) !important; transform: rotate(45deg) scale(1) !important;
} }
/* Animation Position */
[data-tooltip]:after,
[data-tooltip][data-position="top center"]:after,
[data-tooltip][data-position="bottom center"]:after {
-webkit-transform: translateX(-50%) scale(0) !important;
transform: translateX(-50%) scale(0) !important;
}
[data-tooltip]:hover:after,
[data-tooltip][data-position="bottom center"]:hover:after {
-webkit-transform: translateX(-50%) scale(1) !important;
transform: translateX(-50%) scale(1) !important;
}
[data-tooltip][data-position="left center"]:after,
[data-tooltip][data-position="right center"]:after {
-webkit-transform: translateY(-50%) scale(0) !important;
transform: translateY(-50%) scale(0) !important;
}
[data-tooltip][data-position="left center"]:hover:after,
[data-tooltip][data-position="right center"]:hover:after {
-webkit-transform: translateY(-50%) scale(1) !important;
transform: translateY(-50%) scale(1) !important;
}
[data-tooltip][data-position="top left"]:after,
[data-tooltip][data-position="top right"]:after,
[data-tooltip][data-position="bottom left"]:after,
[data-tooltip][data-position="bottom right"]:after {
-webkit-transform: scale(0) !important;
transform: scale(0) !important;
}
[data-tooltip][data-position="top left"]:hover:after,
[data-tooltip][data-position="top right"]:hover:after,
[data-tooltip][data-position="bottom left"]:hover:after,
[data-tooltip][data-position="bottom right"]:hover:after {
-webkit-transform: scale(1) !important;
transform: scale(1) !important;
}
[data-tooltip][data-variation~="fixed"]:after {
white-space: normal;
width: 250px;
}
[data-tooltip][data-variation*="wide fixed"]:after {
width: 350px;
}
[data-tooltip][data-variation*="very wide fixed"]:after {
width: 550px;
}
@media only screen and (max-width: 767.98px) {
[data-tooltip][data-variation~="fixed"]:after {
width: 250px;
}
}
/*--------------
Inverted
---------------*/
/* Arrow */
[data-tooltip][data-inverted]:before {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
/* Arrow Position */
[data-tooltip][data-inverted]:before {
background: #1B1C1D;
}
/* Popup */
[data-tooltip][data-inverted]:after {
background: #1B1C1D;
color: #FFFFFF;
border: none;
-webkit-box-shadow: none;
box-shadow: none;
}
[data-tooltip][data-inverted]:after .header {
background: none;
color: #FFFFFF;
}
/*-------------- /*--------------
Position Position
---------------*/ ---------------*/
[data-position~="top"][data-tooltip]:before { [data-position~="top"][data-tooltip]:before {
background: #FFFFFF; background: #1B1C1D;
} }
/* Top Center */ /* Top Center */
@ -723,7 +679,7 @@ i.green.icon.icon.icon.icon {
right: auto; right: auto;
bottom: 100%; bottom: 100%;
left: 50%; left: 50%;
background: #FFFFFF; background: #1B1C1D;
margin-left: -0.07142857rem; margin-left: -0.07142857rem;
margin-bottom: 0.14285714rem; margin-bottom: 0.14285714rem;
} }
@ -762,7 +718,7 @@ i.green.icon.icon.icon.icon {
margin-bottom: 0.14285714rem; margin-bottom: 0.14285714rem;
} }
[data-position~="bottom"][data-tooltip]:before { [data-position~="bottom"][data-tooltip]:before {
background: #FFFFFF; background: #1B1C1D;
-webkit-box-shadow: -1px -1px 0 0 #bababc; -webkit-box-shadow: -1px -1px 0 0 #bababc;
box-shadow: -1px -1px 0 0 #bababc; box-shadow: -1px -1px 0 0 #bababc;
} }
@ -781,7 +737,7 @@ i.green.icon.icon.icon.icon {
bottom: auto; bottom: auto;
right: auto; right: auto;
top: 100%; top: 100%;
left: 50%; left: 30%;
margin-left: -0.07142857rem; margin-left: -0.07142857rem;
margin-top: 0.14285714rem; margin-top: 0.14285714rem;
} }
@ -829,9 +785,7 @@ i.green.icon.icon.icon.icon {
top: 50%; top: 50%;
margin-top: -0.14285714rem; margin-top: -0.14285714rem;
margin-right: -0.07142857rem; margin-right: -0.07142857rem;
background: #FFFFFF; background: #1B1C1D;
-webkit-box-shadow: 1px -1px 0 0 #bababc;
box-shadow: 1px -1px 0 0 #bababc;
} }
/* Right Center */ /* Right Center */
@ -847,30 +801,9 @@ i.green.icon.icon.icon.icon {
top: 50%; top: 50%;
margin-top: -0.07142857rem; margin-top: -0.07142857rem;
margin-left: 0.14285714rem; margin-left: 0.14285714rem;
background: #FFFFFF; background: #1B1C1D;
-webkit-box-shadow: -1px 1px 0 0 #bababc;
box-shadow: -1px 1px 0 0 #bababc;
} }
/* Inverted Arrow Color */
[data-inverted][data-position~="bottom"][data-tooltip]:before {
background: #1B1C1D;
-webkit-box-shadow: -1px -1px 0 0 #bababc;
box-shadow: -1px -1px 0 0 #bababc;
}
[data-inverted][data-position="left center"][data-tooltip]:before {
background: #1B1C1D;
-webkit-box-shadow: 1px -1px 0 0 #bababc;
box-shadow: 1px -1px 0 0 #bababc;
}
[data-inverted][data-position="right center"][data-tooltip]:before {
background: #1B1C1D;
-webkit-box-shadow: -1px 1px 0 0 #bababc;
box-shadow: -1px 1px 0 0 #bababc;
}
[data-inverted][data-position~="top"][data-tooltip]:before {
background: #1B1C1D;
}
[data-position~="bottom"][data-tooltip]:before { [data-position~="bottom"][data-tooltip]:before {
-webkit-transform-origin: center bottom; -webkit-transform-origin: center bottom;
transform-origin: center bottom; transform-origin: center bottom;
@ -894,12 +827,4 @@ i.green.icon.icon.icon.icon {
[data-position="right center"][data-tooltip]:after { [data-position="right center"][data-tooltip]:after {
-webkit-transform-origin: left center; -webkit-transform-origin: left center;
transform-origin: left center; transform-origin: left center;
}
/*--------------
Basic
---------------*/
[data-tooltip][data-variation~="basic"]:before {
display: none;
} }

View File

@ -7,7 +7,7 @@
min-height: 50px; min-height: 50px;
min-width: 200px; min-width: 200px;
max-width: calc(100% - 30px); max-width: calc(100% - 30px);
z-index: 1002; z-index: 1020;
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1); box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
border-radius: 4px; border-radius: 4px;

View File

@ -86,7 +86,7 @@
} }
.place-holder { .place-holder {
width: 100%; width: 100%;
height: 50px; height: 40px;
} }
.logo-display { .logo-display {
width: 27px; width: 27px;
@ -273,11 +273,17 @@
<div class="menu-section"> <div class="menu-section">
<router-link class="menu-item menu-button" exact-active-class="active" to="/help"> <router-link class="menu-item menu-button" exact-active-class="active" to="/help">
<i class="question circle outline icon"></i>Help | Terms <i class="question circle outline icon"></i>Help
</router-link> </router-link>
</div> </div>
<div class="menu-section" v-if="loggedIn" :data-tooltip="`Settings for ${this.$store.getters.getUsername}`" data-inverted="" data-position="right center"> <div class="menu-section">
<router-link class="menu-item menu-button" exact-active-class="active" to="/terms">
<i class="info circle icon"></i>Terms
</router-link>
</div>
<div class="menu-section" v-if="loggedIn">
<router-link class="menu-item menu-button" exact-active-class="active" to="/settings"> <router-link class="menu-item menu-button" exact-active-class="active" to="/settings">
<i v-if="userIcon" class="cog icon"></i>{{ usernameDisplay }} <i v-if="userIcon" class="cog icon"></i>{{ usernameDisplay }}
</router-link> </router-link>
@ -365,7 +371,6 @@
axios.post('/api/quick-note/get') axios.post('/api/quick-note/get')
.then( ({data}) => { .then( ({data}) => {
console.log(data)
this.$router.push({'path':'/notes/open/'+data.noteId}) this.$router.push({'path':'/notes/open/'+data.noteId})
}) })

View File

@ -36,7 +36,7 @@
<div class="sixteen wide column"> <div class="sixteen wide column">
<span class="small-terms"> <span class="small-terms">
By signing up you agree to Solid Scribe's By signing up you agree to Solid Scribe's
<router-link to="/help"> <router-link to="/terms">
Terms of Use Terms of Use
</router-link> </router-link>
</span> </span>
@ -77,7 +77,7 @@
</div> </div>
<span class="small-terms"> <span class="small-terms">
By signing up you agree to Solid Scribe's By signing up you agree to Solid Scribe's
<router-link to="/help"> <router-link to="/terms">
Terms of Use Terms of Use
</router-link> </router-link>
</span> </span>
@ -147,7 +147,7 @@
return return
} }
axios.post('/api/user/register', {'username': this.username, 'password': this.password}) axios.post('/api/public/register', {'username': this.username, 'password': this.password})
.then(({data}) => { .then(({data}) => {
if(data == false){ if(data == false){
@ -167,7 +167,7 @@
return return
} }
axios.post('/api/user/login', {'username': this.username, 'password': this.password, 'authToken':this.authToken }) axios.post('/api/public/login', {'username': this.username, 'password': this.password, 'authToken':this.authToken })
.then(({data}) => { .then(({data}) => {
//Enable 2FA on form //Enable 2FA on form
@ -193,7 +193,7 @@
} }
}) })
.catch(error => { .catch(error => {
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password') this.$bus.$emit('notification', error)
}) })
} }
} }

View File

@ -2,7 +2,7 @@
<!-- 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 -->
<div <div
id="InputNotes" id="InputNotes"
class="master-note-edit full-focus position-0" class="master-note-edit"
@keyup.esc="closeButtonAction()" @keyup.esc="closeButtonAction()"
> >
@ -13,47 +13,51 @@
<div class="edit-spacer"></div> <div class="edit-spacer"></div>
<div class="menu-top-half"> <div class="menu-top-half">
<div class="edit-button" v-on:click="closeButtonAction()" data-tooltip="Close" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center">
<i class="close icon"></i> <i class="close icon"></i>
</div> </div>
<div class="edit-divide"></div> <div class="edit-divide"></div>
<div class="edit-button" v-on:click="toggleList('ul')" data-tooltip="Task List" data-position="bottom center" data-inverted :class="{'edit-active':activeToDo}"> <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> <i class="tasks icon"></i>
</div> </div>
<div class="edit-button" v-on:click="toggleList('ol')" data-tooltip="Numbered List" data-position="bottom center" data-inverted :class="{'edit-active':activeList}"> <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> <i class="list ol icon"></i>
</div> </div>
<div class="edit-divide"></div> <div class="edit-divide"></div>
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center" data-inverted :style="{'color':activeColor}"> <div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center">
<i class="font icon"></i> <i class="font icon"></i>
</div> </div>
<div class="edit-button" v-on:click="toggleBold()" data-tooltip="Bold" data-position="bottom center" data-inverted :class="{'edit-active':activeBold}"> <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>
<div class="edit-button" v-on:click="toggleBold()" :data-tooltip="`Bold\n(CTRL + b)`" data-position="bottom center" :class="{'edit-active':activeBold}">
<i class="bold icon"></i> <i class="bold icon"></i>
</div> </div>
<div class="edit-button" v-on:click="toggleItalic()" data-tooltip="Italic" data-position="bottom center" data-inverted :class="{'edit-active':activeItalics}"> <div class="edit-button" v-on:click="toggleItalic()" :data-tooltip="`Italic\n(CRTL + i)`" data-position="bottom center" :class="{'edit-active':activeItalics}">
<i class="italic icon"></i> <i class="italic icon"></i>
</div> </div>
<div class="edit-button" v-on:click="toggleUnderline()" data-tooltip="Underline" data-position="bottom center" data-inverted :class="{'edit-active':activeUnderline}"> <div class="edit-button" v-on:click="toggleUnderline()" :data-tooltip="`Underline\n(CRTL + u)`" data-position="bottom center" :class="{'edit-active':activeUnderline}">
<i class="underline icon"></i> <i class="underline icon"></i>
</div> </div>
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" data-inverted :class="{'edit-active':activeTitle}"> <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> <i class="text height icon"></i>
</div> </div>
<div class="edit-divide"></div> <div class="edit-divide"></div>
<div class="edit-button" v-on:click="editor.increaseQuoteLevel()" data-tooltip="Indent" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="editor.increaseQuoteLevel()" :data-tooltip="`Indent\n(TAB)`" data-position="bottom center">
<i class="indent icon"></i> <i class="indent icon"></i>
</div> </div>
<div class="edit-button" v-on:click="editor.decreaseQuoteLevel()" data-tooltip="Outdent" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="editor.decreaseQuoteLevel()" :data-tooltip="`Un-Indent\n(SHIFT + TAB)`" data-position="bottom center">
<i class="outdent icon"></i> <i class="outdent icon"></i>
</div> </div>
@ -61,60 +65,49 @@
<div class="menu-bottom-half"> <div class="menu-bottom-half">
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/table`)" data-tooltip="Insert Table" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/table`)" data-tooltip="Insert Table" data-position="bottom center">
<i class="border all icon"></i> <i class="border all icon"></i>
</div> </div>
<div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center">
<i class="grip lines icon"></i> <i class="grip lines icon"></i>
</div> </div>
<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center">
<i class="remove format icon"></i> <i class="remove format icon"></i>
</div> </div>
<div class="edit-button" v-on:click="undoCustom()" data-tooltip="Undo" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="bottom center">
<i class="undo icon"></i> <i class="undo icon"></i>
</div> </div>
<div class="edit-divide"></div> <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" data-inverted :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> <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" data-inverted> <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> <i class="tags icon"></i>
</div> </div>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/images`)" data-tooltip="Images" data-position="bottom center" data-inverted> <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/images`)" data-tooltip="Images" data-position="bottom center">
<i class="image icon"></i> <i class="image icon"></i>
</div> </div>
<file-upload-button <file-upload-button
data-tooltip="Upload File" data-position="bottom center" data-inverted data-tooltip="Upload File" data-position="bottom center"
class="edit-button" class="edit-button"
:noteId="noteid" /> :noteId="noteid" />
<div class="edit-divide"></div> <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" data-inverted> <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/options`)" data-tooltip="More Options" data-position="bottom center">
<i class="ellipsis horizontal icon"></i> <i class="ellipsis horizontal icon"></i>
</div> </div>
<div class="edit-divide"></div> <div class="edit-divide"></div>
<!--
<div class="edit-button" v-on:click="onToggleArchived()" :data-tooltip="archived == 1?'Move to main list':'Move to Archive'" data-position="bottom center" data-inverted>
<span v-if="archived == 1"><i class="green archive icon"></i></span>
<span v-if="archived != 1"><i class="archive icon"></i></span>
</div>
<div class="edit-button" v-on:click="onTogglePinned" :data-tooltip="pinned == 1?'Un-pin from top':'Pin to top'" data-position="bottom center" data-inverted> <div class="edit-button" v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="bottom center">
<span v-if="pinned == 1"><i class="green pin icon"></i></span>
<span v-if="pinned != 1"><i class="pin icon"></i></span>
</div> -->
<div class="edit-button" v-if="usersOnNote > 1">
<i class="green eye icon"></i> {{ usersOnNote }} <i class="green eye icon"></i> {{ usersOnNote }}
</div> </div>
@ -123,7 +116,7 @@
<i class="purple bolt icon"></i> <i class="purple bolt icon"></i>
</div> --> </div> -->
<div class="edit-button" v-on:click=" hash=0; save() "> <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="icons">
<i class="grey save outline icon"></i> <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 == 'saved'" class="green small bottom left corner check icon"></i>
@ -131,7 +124,7 @@
</i> </i>
</div> </div>
<div class="edit-button" v-if="diffsApplied > 0"> <div class="edit-button" v-if="diffsApplied > 0" :data-tooltip="`Unsaved Changes`" data-position="bottom center">
+{{ diffsApplied }} +{{ diffsApplied }}
</div> </div>
@ -172,8 +165,8 @@
</div> </div>
<!-- little tags on the side --> <!-- little tags on the side, only show on desktop -->
<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }"> <div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }" v-if="!$store.getters.getIsUserOnMobile">
<span v-for="tag in allTags" class="subtle-tag active-mini-tag" v-if="isTagOnNote(tag.id)" v-on:click="removeTag(tag.id)"> <span v-for="tag in allTags" class="subtle-tag active-mini-tag" v-if="isTagOnNote(tag.id)" v-on:click="removeTag(tag.id)">
<i class="tag icon"></i> <i class="tag icon"></i>
{{ tag.text }} {{ tag.text }}
@ -222,7 +215,24 @@
<div class="ui basic padded segment"> <div class="ui basic padded segment">
<div class="ui grid"> <div class="ui grid">
<div class="sixteen wide column"> <div class="sixteen wide column">
<h2>Additional Note Options</h2> <h3>Note Options</h3>
</div>
<div class="eight wide column">
<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>
<div class="sixteen wide column"> <div class="sixteen wide column">
<div class="ui labeled icon fluid basic button" v-on:click="sortList"> <div class="ui labeled icon fluid basic button" v-on:click="sortList">
@ -242,6 +252,9 @@
Uncheck All Uncheck All
</div> </div>
</div> </div>
<div class="sixteen wide column">
<h3>Misc Options</h3>
</div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="calculateMath" data-tooltip="Calculates algebra before '='"> <div class="ui labeled icon fluid basic button" v-on:click="calculateMath" data-tooltip="Calculates algebra before '='">
<i class="calculator icon"></i> <i class="calculator icon"></i>
@ -258,7 +271,7 @@
</div> </div>
<div class="sixteen wide column" v-if="rawTextId > 0"> <div class="sixteen wide column" v-if="rawTextId > 0">
<h2>Share Note</h2> <h3>Share Note</h3>
<share-note-component <share-note-component
:note-id="noteid" :note-id="noteid"
:raw-text-id="rawTextId" :raw-text-id="rawTextId"
@ -287,7 +300,7 @@
<div class="full-focus-shade shade1" <div class="full-focus-shade shade1"
:class="{ 'slide-out-left':sizeDown }" :class="{ 'slide-out-left':sizeDown }"
v-on:click="closeButtonAction()"></div> v-on:click="closeButtonAction()"></div>
<div class="full-focus-shade shade2" <div class="full-focus-shade shade2"
:class="{ 'slide-out-right':sizeDown }" :class="{ 'slide-out-right':sizeDown }"
v-on:click="closeButtonAction()"></div> v-on:click="closeButtonAction()"></div>
@ -404,6 +417,7 @@
} }
}, },
beforeMount(){ beforeMount(){
this.$bus.$on('new_file_upload', ({noteId, imageCode}) => { this.$bus.$on('new_file_upload', ({noteId, imageCode}) => {
if(this.noteid == noteId && this.editor){ if(this.noteid == noteId && this.editor){
this.editor.moveCursorToEnd() this.editor.moveCursorToEnd()
@ -438,6 +452,9 @@
// document.addEventListener('visibilitychange', this.checkForUpdatedNote) // document.addEventListener('visibilitychange', this.checkForUpdatedNote)
//Init squire as early as possible //Init squire as early as possible
if(this.editor && this.editor.destroy){
this.editor.destroy()
}
this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' }) this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
this.$nextTick(() => { this.$nextTick(() => {
@ -1052,10 +1069,11 @@
titleResize(){ titleResize(){
//Resize the title field //Resize the title field
let element = this.$refs.titleTextarea let element = this.$refs.titleTextarea
let padding = 0 if(element){
element.style.height = 'auto' element.style.height = 'auto'
element.style.height = (element.scrollHeight + padding) +'px' element.style.height = (element.scrollHeight) +'px'
}
}, },
} }
} }
@ -1072,7 +1090,7 @@
background-color: var(--menu-accent); background-color: var(--menu-accent);
z-index: 999; z-index: 999;
cursor: pointer; cursor: pointer;
opacity: 0.8; opacity: 0.88;
} }
.shade1 { .shade1 {
left: 50%; left: 50%;
@ -1089,7 +1107,8 @@
left: 15%; left: 15%;
right: 15%; right: 15%;
z-index: 1005; z-index: 1005;
overflow-x: scroll; overflow-y: scroll;
overflow-x: hidden;
scrollbar-width: none; scrollbar-width: none;
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
} }
@ -1248,20 +1267,20 @@
} }
/*End Settings manager styles */ /*End Settings manager styles */
/* container styles change based on mobile and number of open screens */ /* container styles change based on mobile and number of open screens */
.master-note-edit { /* .master-note-edit {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
height: 100vh; height: 100vh;
z-index: 1001; z-index: 1001;
left: 15%;
right: 15%;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
scrollbar-width: none; scrollbar-width: none;
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
} }*/
.loading-note { .loading-note {
position: absolute; position: absolute;
top: 0; top: 0;
@ -1282,19 +1301,18 @@
} }
/* One note open, in the middle of the screen */ /* One note open, in the middle of the screen */
.master-note-edit.position-0 {
left: 50%;
right: 0;
}
.master-note-edit.full-focus {
left: 15%;
right: 15%;
}
.side-menu-open { .side-menu-open {
left: calc(50% + 10px) !important; left: calc(50% + 10px) !important;
right: calc(0% + 10px) !important; right: calc(0% + 10px) !important;
} }
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.master-note-edit {
left: 0;
right: 0;
}
.input-container-wrapper { .input-container-wrapper {
left: 0; left: 0;
right: 0; right: 0;
@ -1338,27 +1356,6 @@
/* Two Notes Open, each takes up 50% of the space */
.master-note-edit.position-1 {
left: 50%;
right: 0%;
}
.master-note-edit.position-2 {
left: 0%;
right: 50%;
}
.master-note-edit.position-3 {
display: inline-block;
position: inherit;
width: 100%;
min-height: 200px;
height: auto;
box-shadow: none;
}
/* animations START */ /* animations START */
.slide-out-top { .slide-out-top {

View File

@ -8,17 +8,6 @@
<!-- Show title and snippet below it --> <!-- Show title and snippet below it -->
<div class="overflow-hidden note-card-text" @click="cardClicked" v-if="!titleView"> <div class="overflow-hidden note-card-text" @click="cardClicked" v-if="!titleView">
<span class="subtext" v-if="note.shareUsername">
Shared by {{ note.shareUsername }}
<span v-if="note.opened == null && !beenClicked" class="ui tiny green compact right floated button">
New
</span>
<span v-else-if="note.updated > note.opened && !beenClicked" class="ui tiny green compact right floated basic button">
Updated
</span>
</span>
<span v-if="note.title == '' && note.subtext == ''"> <span v-if="note.title == '' && note.subtext == ''">
Empty Note Empty Note
</span> </span>
@ -42,9 +31,22 @@
Locked Locked
</div> </div>
<!-- Shared Details -->
<span class="subtext" v-if="note.shared == 2"> <span class="subtext" v-if="note.shared == 2">
You Shared this note <i class="green paper plane outline icon"></i> Shared
<span v-if="note.updated > note.opened && !beenClicked" class="ui tiny green compact right floated basic button"> <span v-if="note.updated/1000 > note.opened && !beenClicked" class="ui tiny green compact right floated basic button">
Updated
</span>
</span>
<span class="subtext" v-if="note.shareUsername">
<i class="green paper plane outline icon"></i> Shared by {{ note.shareUsername }}
<span v-if="note.opened == null && !beenClicked" class="ui tiny green compact right floated button">
New
</span>
<span v-else-if="note.updated/1000 > note.opened && !beenClicked" class="ui tiny green compact right floated basic button">
Updated Updated
</span> </span>
</span> </span>
@ -235,11 +237,11 @@
justClosed(){ justClosed(){
// Scroll note into view // Scroll note into view
this.$el.scrollIntoView({ // this.$el.scrollIntoView({
behavior: 'smooth', // behavior: 'smooth',
block: 'center', // block: 'center',
inline: 'center' // inline: 'center'
}) // })
//After scroll, trigger green outline animation //After scroll, trigger green outline animation
setTimeout(() => { setTimeout(() => {

View File

@ -6,7 +6,7 @@
} }
.img-row { .img-row {
height: 30vh; height: 20vh;
flex-grow: 1; flex-grow: 1;
} }

View File

@ -2,10 +2,10 @@
.colors { .colors {
position: absolute; position: absolute;
z-index: 1023; z-index: 1023;
top: 42px; top: 5px;
/*height: 100px;*/ /*height: 100px;*/
/*width: 415px;*/ width: 400px;
left: 0; left: 20%;
} }
.colors-container { .colors-container {
max-width: 370px; max-width: 370px;

View File

@ -9,6 +9,8 @@ const SquireButtonFunctions = {
activeList: false, activeList: false,
activeToDo: false, activeToDo: false,
activeColor: null, activeColor: null,
//
lastUsedColor: null,
} }
}, },
methods: { methods: {
@ -49,7 +51,10 @@ const SquireButtonFunctions = {
if(colorIndex > -1){ if(colorIndex > -1){
//Get all digigs after color index, then limit to 3 //Get all digigs after color index, then limit to 3
let colors = e.path.substring(colorIndex).match(/\d+/g).slice(0,3) let colors = e.path.substring(colorIndex).match(/\d+/g).slice(0,3)
this.activeColor=`rgb(${colors.join(',')})`
const lastColor = `rgb(${colors.join(',')})`
this.activeColor = lastColor
this.lastUsedColor = lastColor
} }
}, },
@ -93,6 +98,11 @@ const SquireButtonFunctions = {
this.selectLineIfNoSelect() this.selectLineIfNoSelect()
//Set color of font //Set color of font
this.editor.setTextColour(color) this.editor.setTextColour(color)
this.lastUsedColor = color
},
applyLastUsedColor(){
this.modifyColor(this.lastUsedColor)
}, },
toggleList(type){ toggleList(type){

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@
v-if="$store.getters.totals && ($store.getters.totals['youGotMailCount'] > 0)" v-if="$store.getters.totals && ($store.getters.totals['youGotMailCount'] > 0)"
style="position: relative;"> style="position: relative;">
<i class="green mail icon"></i>Inbox <i class="green mail icon"></i>Inbox
+{{ $store.getters.totals['youGotMailCount'] }} <span class="tiny circular floating ui green label">+{{ $store.getters.totals['youGotMailCount'] }}</span>
</div> </div>
<tag-display <tag-display

View File

@ -1,129 +1,162 @@
<template> <template>
<div class="ui grid"> <div class="squire-box">
<div class="row"></div> <div class="">
<!-- spacer column -->
<div class="sixteen wide column">
<h2 class="ui dividing header">
<i class="cog icon"></i>
Settings
</h2>
<div class="ui segment"> <h3 class="ui dividing header">
<h3>Change Password</h3> <i class="green cog icon"></i>
<p>Create a new scratch pad. Old scratch pad will turn into a normal note.</p> Settings
<div class="ui compact basic button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true"> </h3>
<i class="sync alternate reload icon"></i>
New Scratch Pad
</div>
<div v-if="showNewNoteConfirm" class="ui compact basic button shrinking" v-on:click="showNewNoteConfirm = false">
<i class="close icon"></i>
Cancel
</div>
<div v-if="showNewNoteConfirm" class="ui compact basic button shrinking" v-on:click="newQuickNote()">
<i class="green thumbs up icon"></i>
Confirm
</div>
</div>
<!-- Accent Color --> <h4>New Scratch Pad</h4>
<div class="ui segment"> <div class="ui segment">
<div class="ui grid"> <p>Create a new scratch pad. Old scratch pad will turn into a normal note.</p>
<div class="sixteen wide column"> <div class="ui compact basic button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true">
<h3 class="ui header"> <i class="sync alternate reload icon"></i>
Accent Color New Scratch Pad
</h3> </div>
<div <div v-if="showNewNoteConfirm" class="ui compact basic button shrinking" v-on:click="showNewNoteConfirm = false">
v-for="color in themeColors" <i class="close icon"></i>
class="ui compact basic button" Cancel
:style="`background: linear-gradient(0deg, ${color} 4%, rgba(0,0,0,0) 5%);`" </div>
v-on:click="setAccentColor(color)"> <div v-if="showNewNoteConfirm" class="ui compact basic button shrinking" v-on:click="newQuickNote()">
<logo style="width: 33px; height: auto;" :color="color" /> <i class="green thumbs up icon"></i>
</div> Confirm
</div>
</div>
</div>
<!-- Enable Two Factor -->
<div class="ui segment">
<h3>Two Factor Authentication</h3>
<div class="ui stackable grid">
<div class="four wide column">
<p>1. Enter Password and get QR</p>
<div class="ui fluid action input">
<input type="password" placeholder="Current Password" v-model="password">
<div v-if="password.length == 0" class="ui disabled button">
Get QR code
</div>
<div v-if="password.length > 0" class="ui green button" v-on:click="getQrCode()">
Get QR code
</div>
</div>
</div>
<div class="five wide column">
<p>2. Scan QR Code</p>
<p v-if="qrCode == ''">QR Code Will appear here.</p>
<img v-if="qrCode != ''" :src="qrCode" alt="QR Code">
</div>
<div class="four wide column">
<p>3. Verify with code</p>
<div class="ui input" v-if="qrCode != ''">
<input type="text" placeholder="Verification Code" v-model="verificationToken" v-on:keyup.enter="verifyQrCode()">
</div>
</div>
</div>
</div>
<!-- change password -->
<div class="ui segment">
<h3>Change Password</h3>
<div class="ui grid">
<div class="five wide column">
<label>Current Password</label>
<div class="ui fluid input">
<input v-model="change1" type="password" placeholder="Current Password">
</div>
</div>
<div class="five wide column">
<label>New Password</label>
<div class="ui fluid input">
<input v-model="change2" type="password" placeholder="New Password">
</div>
</div>
<div class="six wide column">
<label>Rereat New Password</label>
<div class="ui fluid action input">
<input v-model="change3" type="password" placeholder="Repeat Password">
<div v-on:click="passwordChange()" class="ui button" :class="{'green':(change1.length > 0 && change2 == change3)}">
Change it!
</div>
</div>
</div>
</div>
</div>
<!-- log out -->
<div class="ui segment">
<div class="ui grid">
<div class="sixteen wide column">
<h3>Log Out</h3>
</div>
<div class="eight wide column">
<div class="ui button" v-on:click="logout()">
Log Out this browser
</div>
</div>
<div class="eight wide column">
<div class="ui button" v-on:click="revokeAllSessions()">
Log Out all other browsers
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- Accent Color -->
<h4 class="ui header">
Accent Color
</h4>
<div class="ui segment">
<div class="ui doubling grid">
<div class="sixteen wide column">
<p>Theme changes are only saved to this browser.</p>
<div
v-for="color in themeColors"
class="ui compact basic button"
:style="`background: linear-gradient(0deg, ${color} 4%, rgba(0,0,0,0) 5%);`"
v-on:click="setAccentColor(color)">
<logo style="width: 33px; height: auto;" :color="color" />
</div>
</div>
</div>
</div>
<!-- Enable Two Factor -->
<h4>Two Factor Authentication</h4>
<div class="ui segment">
<div class="ui stackable grid">
<div class="six wide column">
<p>1. Enter Password and get QR</p>
<div class="ui fluid action input">
<input type="password" placeholder="Current Password" v-model="password">
<div v-if="password.length == 0" class="ui disabled button">
Get QR code
</div>
<div v-if="password.length > 0" class="ui green button" v-on:click="getQrCode()">
Get QR code
</div>
</div>
</div>
<div class="four wide column">
<p>2. Scan QR Code</p>
<p v-if="qrCode == ''">(QR Code will appear here)</p>
<img v-if="qrCode != ''" :src="qrCode" alt="QR Code">
</div>
<div class="six wide column">
<p>3. Verify with code</p>
<div class="ui fluid action input" v-if="qrCode != ''">
<input type="text" placeholder="Verification Code" v-model="verificationToken" v-on:keyup.enter="verifyQrCode()">
<div class="ui green button">
Verify!
</div>
</div>
<div class="ui fluid action input" v-if="qrCode == ''">
<input type="text" placeholder="Verification Code" >
<div class="ui disabled button">
Verify!
</div>
</div>
</div>
</div>
</div>
<!-- change password -->
<h4>Change Password</h4>
<div class="ui segment">
<div class="ui stackable grid">
<div class="five wide column">
<p>Current Password</p>
<div class="ui fluid input">
<input v-model="change1" type="password" placeholder="Current Password">
</div>
</div>
<div class="five wide column">
<p>New Password</p>
<div class="ui fluid input">
<input v-model="change2" type="password" placeholder="New Password">
</div>
</div>
<div class="six wide column">
<p>Rereat New Password</p>
<div class="ui fluid action input">
<input v-model="change3" type="password" placeholder="Repeat Password">
<div v-on:click="passwordChange()" class="ui button" :class="{'green':(change1.length > 0 && change2 == change3)}">
Change it!
</div>
</div>
</div>
</div>
</div>
<!-- log out -->
<h4>Log Out</h4>
<div class="ui segment">
<div class="ui stackable grid">
<div class="eight wide column">
<div class="ui button" v-on:click="logout()">
<i class="power off icon"></i>
Log Out on this browser
</div>
</div>
<div class="eight wide column">
<div class="ui button" v-on:click="revokeAllSessions()">
<i class="sign out icon"></i>
Log Out all other browsers
</div>
</div>
</div>
</div>
<h4>Export All Data (In Development)</h4>
<div class="ui segment">
<p>Download all files and notes in raw text or html</p>
<div class="ui button">Export all Data</div>
</div>
<h4>Delete Account (In Development)</h4>
<div class="ui segment">
<div class="ui stackable grid">
<div class="eight wide column">
<p>Delete all data. This can not be undone.</p>
<div class="ui fluid input">
<input type="password" placeholder="Current Password" v-model="password">
</div>
</div>
<div class="four wide bottom aligned column">
<div class="ui fluid button">Verify</div>
</div>
<div class="four wide bottom aligned column">
<div class="ui disabled fluid button">Delete Account</div>
</div>
</div>
</div>
</div>
</div>
</template> </template>
<script> <script>

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,7 @@ import Router from 'vue-router'
const HomePage = () => import(/* webpackChunkName: "HomePage" */ '@/pages/HomePage') const HomePage = () => import(/* webpackChunkName: "HomePage" */ '@/pages/HomePage')
const LoginPage = () => import(/* webpackChunkName: "LoginPage" */ '@/pages/LoginPage') const LoginPage = () => import(/* webpackChunkName: "LoginPage" */ '@/pages/LoginPage')
const HelpPage = () => import(/* webpackChunkName: "HelpPage" */ '@/pages/HelpPage') const HelpPage = () => import(/* webpackChunkName: "HelpPage" */ '@/pages/HelpPage')
const TermsPage = () => import(/* webpackChunkName: "TermsPage" */ '@/pages/TermsPage')
const SettingsPage = () => import(/* webpackChunkName: "SettingsPage" */ '@/pages/SettingsPage') const SettingsPage = () => import(/* webpackChunkName: "SettingsPage" */ '@/pages/SettingsPage')
const SharePage = () => import(/* webpackChunkName: "SharePage" */ '@/pages/SharePage') const SharePage = () => import(/* webpackChunkName: "SharePage" */ '@/pages/SharePage')
const NotesPage = () => import(/* webpackChunkName: "NotesPage" */ '@/pages/NotesPage') const NotesPage = () => import(/* webpackChunkName: "NotesPage" */ '@/pages/NotesPage')
@ -53,7 +54,13 @@ export default new Router({
meta: {title:'Help'}, meta: {title:'Help'},
component: HelpPage component: HelpPage
}, },
{ {
path: '/terms',
name: 'Terms',
meta: {title:'Terms'},
component: TermsPage
},
{
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
meta: {title:'Settings'}, meta: {title:'Settings'},

View File

@ -21,17 +21,17 @@ const port = 3000
// //
// Request Rate Limiter // Request Rate Limiter
// //
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit')
//Limiter for the entire app
const limiter = rateLimit({ const limiter = rateLimit({
windowMs: 10 * 60 * 1000, // minutes windowMs: 10 * 60 * 1000, // 10 minutes
max: 1000 // limit each IP to 100 requests per windowMs max: 1000 // limit each IP to 1000 requests per windowMs
}); })
// apply to all requests // apply to all requests
app.use(limiter); app.use(limiter);
var http = require('http').createServer(app); var http = require('http').createServer(app);
var io = require('socket.io')(http, { var io = require('socket.io')(http, {
path:'/socket' path:'/socket'
@ -222,6 +222,7 @@ app.use(express.json({limit: '5mb'}))
app.use(function(req, res, next){ app.use(function(req, res, next){
//Always null out master key, never allow it set from outside //Always null out master key, never allow it set from outside
req.headers.userId = null
req.headers.masterKey = null req.headers.masterKey = null
req.headers.sessionId = null req.headers.sessionId = null
@ -243,10 +244,7 @@ app.use(function(req, res, next){
}) })
.catch(error => { .catch(error => {
console.log(error) next('Unauthorized')
res.statusMessage = error //Throw 400 error if token is bad
res.status(400).end()
}) })
} else { } else {
next() //No token. Move along. next() //No token. Move along.
@ -260,13 +258,12 @@ let UserTest = require('@models/User')
let NoteTest = require('@models/Note') let NoteTest = require('@models/Note')
let AuthTest = require('@helpers/Auth') let AuthTest = require('@helpers/Auth')
Auth.testTwoFactor()
Auth.test() Auth.test()
UserTest.keyPairTest('genMan23', '1', printResults) UserTest.keyPairTest('genMan25', '1', printResults)
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults)) .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
.then( message => { .then( message => {
if(printResults) console.log(message) if(printResults) console.log(message)
Auth.testTwoFactor()
}) })
@ -303,4 +300,16 @@ app.use('/api/quick-note', quickNote)
//Output running status //Output running status
app.listen(port, () => { app.listen(port, () => {
// console.log(`Listening on port ${port}!`) // console.log(`Listening on port ${port}!`)
})
//
//Error handlers
//
//Default error handler just say unauthorized for everything
app.use(function (err, req, res, next) {
if (err) {
res.status(401).send('Unauthorized')
return
}
next()
}) })

View File

@ -449,8 +449,7 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
//Shared notes use encrypted key - decrypt key then decrypt note //Shared notes use encrypted key - decrypt key then decrypt note
const encryptedShareKey = rows[0][0].encrypted_share_password_key const encryptedShareKey = rows[0][0].encrypted_share_password_key
if(encryptedShareKey != null){ if(encryptedShareKey != null){
masterKey = crypto.privateDecrypt(userPrivateKey, masterKey = crypto.privateDecrypt(userPrivateKey, Buffer.from(encryptedShareKey, 'base64') )
Buffer.from(encryptedShareKey, 'base64') )
} }
let encryptedNoteText = '' let encryptedNoteText = ''
@ -475,10 +474,14 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
for (var i = 0; i < rows[0].length; i++) { for (var i = 0; i < rows[0].length; i++) {
const otherNote = rows[0][i] const otherNote = rows[0][i]
//Re-encrypt for other user //Re-encrypt for other user
const updatedSnippet = cs.encrypt(masterKey, otherNote.snippet_salt, snippet) let updatedSnippet = '' //Default to no snippet
if(noteText.length > 500){
updatedSnippet = cs.encrypt(masterKey, otherNote.snippet_salt, snippet)
}
db.promise().query('UPDATE note SET snippet = ? WHERE id = ?', [updatedSnippet, otherNote.id]) db.promise().query('UPDATE note SET snippet = ? WHERE id = ?', [updatedSnippet, otherNote.id])
.then((rows, fields) => {
SocketIo.to(otherNote['user_id']).emit('new_note_text_saved', {'noteId':otherNote.id, hash}) SocketIo.to(otherNote['user_id']).emit('new_note_text_saved', {'noteId':otherNote.id, hash})
})
} }
}) })
@ -489,10 +492,13 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
}) })
.then( (rows, fields) => { .then( (rows, fields) => {
//Set openend time to a minute ago
const theFuture = Math.round((+new Date)/1000) + 10
//Update other note attributes //Update other note attributes
return db.promise() return db.promise()
.query('UPDATE note SET pinned = ?, archived = ?, color = ?, snippet = ?, indexed = 0 WHERE id = ? AND user_id = ? LIMIT 1', .query('UPDATE note SET pinned = ?, archived = ?, color = ?, snippet = ?, indexed = 0, opened = ? WHERE id = ? AND user_id = ? LIMIT 1',
[pinned, archived, color, noteSnippet, noteId, userId]) [pinned, archived, color, noteSnippet, theFuture, noteId, userId])
}) })
.then((rows, fields) => { .then((rows, fields) => {
@ -750,7 +756,6 @@ Note.get = (userId, noteId, masterKey) => {
}) })
.then((rows, fields) => { .then((rows, fields) => {
const nowTime = Math.round((+new Date)/1000)
let noteLockedOut = false let noteLockedOut = false
let noteData = rows[0][0] let noteData = rows[0][0]
// const rawTextId = noteData['rawTextId'] // const rawTextId = noteData['rawTextId']
@ -776,6 +781,7 @@ Note.get = (userId, noteId, masterKey) => {
noteData.title = textObject[0] noteData.title = textObject[0]
noteData.text = textObject[1] noteData.text = textObject[1]
const nowTime = Math.round((+new Date)/1000)
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId]) db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
//Return note data //Return note data
@ -1122,7 +1128,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
} }
} }
} catch(err) { } catch(err) {
console.log('Error opening note id -> ', note.id) console.log('Error opening note id -> '+note.id+' for userId -> '+userId)
console.log(err) console.log(err)
} }

View File

@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
let User = module.exports = {} let User = module.exports = {}
const version = '3.1.5' const version = '3.1.6'
//Login a user, if that user does not exist create them //Login a user, if that user does not exist create them
//Issues login token //Issues login token
@ -498,6 +498,7 @@ User.deleteUser = (userId, password) => {
let deletePromises = [] let deletePromises = []
//Delete all notes and raw text
let noteDelete = db.promise().query(` let noteDelete = db.promise().query(`
DELETE note, note_raw_text DELETE note, note_raw_text
FROM note FROM note
@ -506,12 +507,14 @@ User.deleteUser = (userId, password) => {
`,[userId]) `,[userId])
deletePromises.push(noteDelete) deletePromises.push(noteDelete)
//Delete user entry
let userDelete = db.promise().query(` let userDelete = db.promise().query(`
DELETE FROM user WHERE id = ? DELETE FROM user WHERE id = ?
`,[userId]) `,[userId])
deletePromises.push(userDelete) deletePromises.push(userDelete)
let tables = ['user_key', 'user_encrypted_search_index', 'attachment'] //Delete user_key, encrypted search index
let tables = ['user_key', 'user_encrypted_search_index']
tables.forEach(tableName => { tables.forEach(tableName => {
const query = `DELETE FROM ${tableName} WHERE user_id = ?` const query = `DELETE FROM ${tableName} WHERE user_id = ?`
@ -519,6 +522,8 @@ User.deleteUser = (userId, password) => {
deletePromises.push(deleteQuery) deletePromises.push(deleteQuery)
}) })
//Remove all note attachments and files
return Promise.all(deletePromises) return Promise.all(deletePromises)
} }

View File

@ -6,16 +6,23 @@ let router = express.Router()
let Attachment = require('@models/Attachment') let Attachment = require('@models/Attachment')
let Note = require('@models/Note') let Note = require('@models/Note')
let userId = null let userId = null
let masterKey = null
// middleware that is specific to this router // middleware that is specific to this router
router.use(function setUserId (req, res, next) { router.use(function setUserId (req, res, next) {
if(userId = req.headers.userId){
//Session key is required to continue
if(!req.headers.sessionId){
next('Unauthorized')
}
if(req.headers.userId){
userId = req.headers.userId userId = req.headers.userId
masterKey = req.headers.masterKey masterKey = req.headers.masterKey
next()
} }
next()
}) })
router.post('/search', function (req, res) { router.post('/search', function (req, res) {

View File

@ -10,12 +10,17 @@ let masterKey = null
// middleware that is specific to this router // middleware that is specific to this router
router.use(function setUserId (req, res, next) { router.use(function setUserId (req, res, next) {
//Session key is required to continue
if(!req.headers.sessionId){
next('Unauthorized')
}
if(req.headers.userId){ if(req.headers.userId){
userId = req.headers.userId userId = req.headers.userId
masterKey = req.headers.masterKey masterKey = req.headers.masterKey
next()
} }
next()
}) })
// //

View File

@ -1,17 +1,60 @@
var express = require('express') var express = require('express')
var router = express.Router() var router = express.Router()
const rateLimit = require('express-rate-limit')
const Note = require('@models/Note')
const User = require('@models/User')
let Note = require('@models/Note')
// //
// Public Note action // Public Note action
// //
router.post('/opensharednote', function (req, res) { const sharedNoteLimiter = rateLimit({
windowMs: 30 * 60 * 1000, //30 min window
max: 50, // start blocking after 50 requests
message:'Unable to open that many shared notes'
})
router.post('/opensharednote', sharedNoteLimiter, function (req, res) {
Note.getShared(req.body.noteId, req.body.sharedKey) Note.getShared(req.body.noteId, req.body.sharedKey)
.then(results => res.send(results)) .then(results => res.send(results))
}) })
//
// Login User
//
const loginLimiter = rateLimit({
windowMs: 30 * 60 * 1000, // 30 min window
max: 25, // start blocking after 25 requests
message:'Please try to login again later'
})
router.post('/login', loginLimiter, function (req, res) {
User.login(req.body.username, req.body.password, req.body.authToken)
.then( returnData => {
res.send(returnData)
})
})
//
// Register User
//
const registerLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour window
max: 5, // start blocking after 5 requests
message:'Please try again to create an acount in an hour'
})
router.post('/register', registerLimiter, function (req, res) {
User.register(req.body.username, req.body.password)
.then( returnData => {
res.send(returnData)
})
})

View File

@ -6,13 +6,19 @@ let QuickNote = require('@models/QuickNote');
let userId = null let userId = null
let masterKey = null let masterKey = null
// middleware that is specific to this router
router.use(function setUserId (req, res, next) { router.use(function setUserId (req, res, next) {
//Session key is required to continue
if(!req.headers.sessionId){
next('Unauthorized')
}
if(req.headers.userId){ if(req.headers.userId){
userId = req.headers.userId userId = req.headers.userId
masterKey = req.headers.masterKey masterKey = req.headers.masterKey
next()
} }
next()
}) })
//Get quick note text //Get quick note text

View File

@ -1,16 +1,24 @@
var express = require('express') var express = require('express')
var router = express.Router() var router = express.Router()
let Tags = require('@models/Tag'); let Tags = require('@models/Tag')
let userId = null let userId = null
let masterKey = null
// middleware that is specific to this router // middleware that is specific to this router
router.use(function setUserId (req, res, next) { router.use(function setUserId (req, res, next) {
if(userId = req.headers.userId){
userId = req.headers.userId //Session key is required to continue
if(!req.headers.sessionId){
next('Unauthorized')
}
if(req.headers.userId){
userId = req.headers.userId
masterKey = req.headers.masterKey
next()
} }
next()
}) })
//Get the latest notes the user has created //Get the latest notes the user has created

View File

@ -5,20 +5,24 @@ const User = require('@models/User')
const Auth = require('@helpers/Auth') const Auth = require('@helpers/Auth')
const cs = require('@helpers/CryptoString') const cs = require('@helpers/CryptoString')
let userId = null
let masterKey = null
// middleware that is specific to this router // middleware that is specific to this router
router.use(function timeLog (req, res, next) { router.use(function setUserId (req, res, next) {
// console.log('Time: ', Date.now())
next() //Session key is required to continue
if(!req.headers.sessionId){
next('Unauthorized')
}
if(req.headers.userId){
userId = req.headers.userId
masterKey = req.headers.masterKey
next()
}
}) })
// Login User
router.post('/login', function (req, res) {
User.login(req.body.username, req.body.password, req.body.authToken)
.then( returnData => {
res.send(returnData)
})
})
// Logout User // Logout User
router.post('/logout', function (req, res) { router.post('/logout', function (req, res) {
@ -28,19 +32,6 @@ router.post('/logout', function (req, res) {
}) })
}) })
// Register User
router.post('/register', function (req, res) {
User.register(req.body.username, req.body.password)
.then( returnData => {
res.send(returnData)
})
.catch(e => {
res.send(false)
})
})
// change password // change password
router.post('/changepassword', function (req, res) { router.post('/changepassword', function (req, res) {