* Added an auth screen that isn't integrated at all or working

* Force logout of user with authorization error
* Wrong site blocker doesn't trigger on the solid scribe domain
* Added log out button to main side bar making it easier to find
* Improved icon set for notes
* Colored notes display better on mobile, fixed text color based on color brightness
* Moved terms of use link to the bottom of a few pages
* Updated feature sections on home page, make them clearer and easier to process
* Tweaked color themes
* Deleted links no longer show up in search
* Updated search to use multiple key words
* Updated tests to do a multi word search
* Tweaked a bunch of styles to look better on chrome and browsers
This commit is contained in:
Max G 2020-08-03 02:40:27 +00:00
parent 3447b2e0e6
commit 1d891ea734
17 changed files with 399 additions and 193 deletions

View File

@ -13,6 +13,37 @@
</div> </div>
</div> </div>
<div class="auth-block" v-if="requireAuth">
<div class="ui raised inverted segment">
<div class="ui centered header">
Authentication Required
</div>
<div class="ui small inverted centered header" v-if="$store.getters.getUsername">
<i class="green user outline icon"></i>
{{ $store.getters.getUsername }}
</div>
<div class="ui large form">
<div class="field">
<div class="ui small inverted header">Password</div>
<div class="ui input">
<input type="password" v-model="password" placeholder="Password">
</div>
</div>
<div class="field">
<div class="ui small inverted header">One Time Password</div>
<div class="ui input">
<input type="password" v-model="otp" placeholder="One Time Password">
</div>
</div>
<div class="ui fluid inverted black button">
<i class="unlock icon"></i>
Submit</div>
</div>
</div>
</div>
<global-site-menu v-if="!showFakeSite" /> <global-site-menu v-if="!showFakeSite" />
@ -31,14 +62,18 @@ import axios from 'axios'
export default { export default {
components: { components: {
'global-site-menu': require('@/components/GlobalSiteMenu.vue').default, 'global-site-menu': require('@/components/GlobalSiteMenu.vue').default,
'global-notification':require('@/components/GlobalNotificationComponent.vue').default 'global-notification':require('@/components/GlobalNotificationComponent.vue').default,
}, },
data: function(){ data: function(){
return { return {
showFakeSite:false, //Incorrect domain detection showFakeSite:false, //Incorrect domain detection
redirectSeconds: 15, 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
requireAuth: false,
password: '',
otp: '',
} }
}, },
@ -81,6 +116,16 @@ export default {
return response return response
}, },
(error) => { (error) => {
//Catch all authorization errors, log user out if we encounter one
if(error.response && error.response.status == 401){
this.$router.push('/')
this.$store.commit('destroyLoginToken')
this.$bus.$emit('notification', 'Error: You have been logged out.')
}
return Promise.reject(error) return Promise.reject(error)
} }
) )
@ -127,7 +172,7 @@ export default {
mounted: function(){ mounted: function(){
const isDev = process.env['NODE_ENV'] == 'development' const isDev = process.env['NODE_ENV'] == 'development'
if(window.location.hostname.toLowerCase() != "www.solidscribe.com" && !isDev){ if(window.location.hostname.toLowerCase().replace('www.','') != "solidscribe.com" && !isDev){
this.showFakeSite = true this.showFakeSite = true
setInterval(() => { setInterval(() => {
this.redirectSeconds-- this.redirectSeconds--
@ -162,16 +207,23 @@ export default {
} }
}, },
methods: { methods: {
destroyLoginToken() {
this.$store.commit('destroyLoginToken')
},
loginGateway() { loginGateway() {
if(!this.loggedIn){ if(!this.loggedIn){
console.log('This user is not logged in') console.log('This user is not logged in')
this.$router.push({'path':'/login'}) this.$router.push({'path':'/login'})
return return
} }
} },
logout() {
this.$router.push('/')
axios.post('/api/user/logout')
setTimeout(() => {
this.$store.commit('destroyLoginToken')
this.$bus.$emit('notification', 'Logged Out')
}, 200)
},
} }
} }
</script> </script>

View File

@ -39,6 +39,10 @@
html { html {
/*scrollbar-width: none;*/ /*scrollbar-width: none;*/
width: 100%;
height:100%;
padding: 0;
margin: 0;
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
@ -47,6 +51,15 @@ div.ui.basic.segment.no-fluf-segment {
margin-top: 0px; margin-top: 0px;
} }
.page-container {
/*width: 100%;*/
display: block;
margin: 0;
padding: 0.5rem;
box-sizing: border-box;
overflow: hidden;
}
/* Night mode modifiers */ /* Night mode modifiers */
/*Make images sepia in night mode */ /*Make images sepia in night mode */
@ -268,7 +281,7 @@ i.green.icon.icon.icon.icon {
/*height: calc(100% - 69px);*/ /*height: calc(100% - 69px);*/
min-height: 500px; min-height: 500px;
background-color: rgba(255,200,0,0.0); background-color: var(--small_element_bg_color);
/*margin-bottom: 15px;*/ /*margin-bottom: 15px;*/
box-sizing: border-box; box-sizing: border-box;
@ -282,6 +295,10 @@ i.green.icon.icon.icon.icon {
scrollbar-width: none; scrollbar-width: none;
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
caret-color: var(--main-accent); caret-color: var(--main-accent);
margin-left: auto;
margin-right: auto;
max-width: 1100px;
} }
.squire-box::selection, .squire-box::selection,
.squire-box::-moz-selection { .squire-box::-moz-selection {
@ -469,7 +486,7 @@ i.green.icon.icon.icon.icon {
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.squire-box { .squire-box {
min-height: calc(100vh - 120px); min-height: calc(100vh - 122px);
} }
.ui.button.shrinking { .ui.button.shrinking {
@ -549,6 +566,27 @@ i.green.icon.icon.icon.icon {
animation: fade-in-fwd 0.8s both; animation: fade-in-fwd 0.8s both;
} }
/* div that comes up, blocking interaction annd requiring authentication */
.auth-block {
background-color: rgba(0,0,0,0.9);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100vh;
width: 100%;
z-index: 200;
}
.auth-block > div {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -40%);
width: 300px;
}
/** /**
* ---------------------------------------- * ----------------------------------------
* animation fade-in-fwd * animation fade-in-fwd

View File

@ -16,7 +16,7 @@
<div class="sixteen wide column"> <div class="sixteen wide column">
<br> <br>
<p>Note Color</p> <p>Note Color</p>
<div v-for="color in getReducedColors()" <div v-for="color in colors"
class="color-button" class="color-button"
:style="{ backgroundColor:color }" :style="{ backgroundColor:color }"
v-on:click="chosenColor(color)" v-on:click="chosenColor(color)"
@ -66,7 +66,7 @@
blankStyle:{ 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, blankStyle:{ 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null },
colors: [null, colors: [null,
'rgb(67,67,67)','rgb(102,102,102)','rgb(153,153,153)','rgb(183,183,183)','rgb(204,204,204)','rgb(217,217,217)','rgb(239,239,239)','rgb(243,243,243)','rgb(255,255,255)','rgb(152,0,0)','rgb(255,0,0)','rgb(255,153,0)','rgb(255,255,0)','rgb(0,255,0)','rgb(0,255,255)','rgb(74,134,232)','rgb(0,0,255)','rgb(153,0,255)','rgb(255,0,255)','rgb(230,184,175)','rgb(244,204,204)','rgb(252,229,205)','rgb(255,242,204)','rgb(217,234,211)','rgb(208,224,227)','rgb(201,218,248)','rgb(207,226,243)','rgb(217,210,233)','rgb(234,209,220)','rgb(221,126,107)','rgb(234,153,153)','rgb(249,203,156)','rgb(255,229,153)','rgb(182,215,168)','rgb(162,196,201)','rgb(164,194,244)','rgb(159,197,232)','rgb(180,167,214)','rgb(213,166,189)','rgb(204,65,37)','rgb(224,102,102)','rgb(246,178,107)','rgb(255,217,102)','rgb(147,196,125)','rgb(118,165,175)','rgb(109,158,235)','rgb(111,168,220)','rgb(142,124,195)','rgb(194,123,160)','rgb(166,28,0)','rgb(204,0,0)','rgb(230,145,56)','rgb(241,194,50)','rgb(106,168,79)','rgb(69,129,142)','rgb(60,120,216)','rgb(61,133,198)','rgb(103,78,167)','rgb(166,77,121)','rgb(133,32,12)','rgb(153,0,0)','rgb(180,95,6)','rgb(191,144,0)','rgb(56,118,29)','rgb(19,79,92)','rgb(17,85,204)','rgb(11,83,148)','rgb(53,28,117)','rgb(116,27,71)','rgb(91,15,0)','rgb(102,0,0)','rgb(120,63,4)','rgb(127,96,0)','rgb(39,78,19)','rgb(12,52,61)','rgb(28,69,135)','rgb(7,55,99)','rgb(32,18,77)','rgb(76,17,48)'], 'rgb(67,67,67)','rgb(102,102,102)','rgb(153,153,153)','rgb(183,183,183)','rgb(204,204,204)','rgb(217,217,217)','rgb(239,239,239)','rgb(243,243,243)','rgb(255,255,255)','rgb(152,0,0)','rgb(255,0,0)','rgb(255,153,0)','rgb(255,255,0)','rgb(0,255,0)','rgb(0,255,255)','rgb(74,134,232)','rgb(0,0,255)','rgb(153,0,255)','rgb(255,0,255)','rgb(230,184,175)','rgb(244,204,204)','rgb(252,229,205)','rgb(255,242,204)','rgb(217,234,211)','rgb(208,224,227)','rgb(201,218,248)','rgb(207,226,243)','rgb(217,210,233)','rgb(234,209,220)','rgb(221,126,107)','rgb(234,153,153)','rgb(249,203,156)','rgb(255,229,153)','rgb(182,215,168)','rgb(162,196,201)','rgb(164,194,244)','rgb(159,197,232)','rgb(180,167,214)','rgb(213,166,189)','rgb(204,65,37)','rgb(224,102,102)','rgb(246,178,107)','rgb(255,217,102)','rgb(147,196,125)','rgb(118,165,175)','rgb(109,158,235)','rgb(111,168,220)','rgb(142,124,195)','rgb(194,123,160)','rgb(166,28,0)','rgb(204,0,0)','rgb(230,145,56)','rgb(241,194,50)','rgb(106,168,79)','rgb(69,129,142)','rgb(60,120,216)','rgb(61,133,198)','rgb(103,78,167)','rgb(166,77,121)','rgb(133,32,12)','rgb(153,0,0)','rgb(180,95,6)','rgb(191,144,0)','rgb(56,118,29)','rgb(19,79,92)','rgb(17,85,204)','rgb(11,83,148)','rgb(53,28,117)','rgb(116,27,71)','rgb(91,15,0)','rgb(102,0,0)','rgb(120,63,4)','rgb(127,96,0)','rgb(39,78,19)','rgb(12,52,61)','rgb(28,69,135)','rgb(7,55,99)','rgb(32,18,77)','rgb(76,17,48)'],
icons: ['ambulance','anchor','balance scale','bath','bed','beer','bell','bell slash','bell slash outline','bicycle','binoculars','birthday cake','blind','bomb','book','bookmark','briefcase','building','car','coffee','crosshairs','dollar sign','eye','eye slash','fighter jet','fire','fire extinguisher','flag','flag checkered','flask','gamepad','gavel','gift','glass martini','globe','graduation cap','h square','heart','heart outline','heartbeat','home','hospital','hospital outline','image','image outline','images','images outline','industry','info','info circle','key','leaf','lemon','lemon outline','life ring','life ring outline','lightbulb','lightbulb outline','location arrow','low vision','magnet','male','map','map outline','map marker','map marker alternate','map pin','map signs','medkit','money bill alternate','money bill alternate outline','motorcycle','music','newspaper','newspaper outline','paw','phone','phone square','phone volume','plane','plug','plus','plus square','plus square outline','print','recycle','road','rocket','search','search minus','search plus','ship','shopping bag','shopping basket','shopping cart','shower','street view','subway','suitcase','tag','tags','taxi','thumbtack','ticket alternate','tint','train','tree','trophy','truck','tty','umbrella','university','utensil spoon','utensils','wheelchair','wifi','wrench'] icons: ['cat','crow','dog','dove','dragon','feather','feather alternate','fish','frog','hippo','horse','horse head','kiwi bird','otter','paw','spider','video','headphones','motorcycle','truck','monster truck','campground','cloud sun','drumstick bite','football ball','fruit-apple','hiking','mountain','tractor','tree','wind','wine bottle','coffee','flask','glass cheers','glass martini','beer','toilet paper','gift','globe','hand holding heart','comment','graduation cap','hat cowboy','hat wizard','mitten','user tie','laptop code','microchip','shield alternate','mouse','plug','power off','satellite','hammer','wrench','bell','eye','marker','paperclip','atom','award','theater masks','music','grin alternate','grin tongue squint outline','laugh wink','fire','fire alternate','poop','sun','money bill alternate','piggy bank','heart outline','heartbeat','running','walking','bacon','bone','bread slice','candy cane','carrot','cheese','cloud meatball','cookie','egg','hamburger','hotdog','ice cream','lemon','lemon outline','pepper hot','pizza slice','seedling','stroopwafel','leaf','book dead','broom','cloud moon','ghost','mask','skull crossbones','certificate','check','check circle','joint','cannabis','bong','gem','futbol','brain','dna','hand spock','hand spock outline','meteor','moon','moon outline','robot','rocket','satellite dish','space shuttle','user astronaut','fingerprint','thumbs up','thumbs down']
} }
}, },
watch:{ watch:{
@ -83,17 +83,11 @@
let reduced = [] let reduced = []
this.colors.forEach((color,i) => { this.colors.forEach((color,i) => {
if(i < 20 || i > 69){
let mod = (i % 10)+1 //1 - 10
let lines = [3, 5, 8, 9, 10]
// if(lines.includes(mod)){
reduced.push(color) reduced.push(color)
// } }
}) })
reduced.push("#000")
return reduced return reduced
}, },
clearStyles(){ clearStyles(){
@ -114,12 +108,17 @@
//Automatically select note text color //Automatically select note text color
//If you are using hex colors, use this
// Convert hex color to RGB - http://gist.github.com/983661 // Convert hex color to RGB - http://gist.github.com/983661
let color = +("0x" + inColor.slice(1).replace(inColor.length < 5 && /./g, '$&$&')); // let color = +("0x" + inColor.slice(1).replace(inColor.length < 5 && /./g, '$&$&'));
// let r = color >> 16;
// let g = color >> 8 & 255;
// let b = color & 255;
let r = color >> 16; const set = inColor.match(/\d+/g)
let g = color >> 8 & 255; const r = parseInt(set[0])
let b = color & 255; const g = parseInt(set[1])
const b = parseInt(set[2])
//Convert RGB to HSP //Convert RGB to HSP
const hsp = Math.sqrt( 0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b) ); const hsp = Math.sqrt( 0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b) );

View File

@ -83,6 +83,7 @@
/*padding: 5px 1rem 5px;*/ /*padding: 5px 1rem 5px;*/
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
width: 100vw;
} }
.place-holder { .place-holder {
width: 100%; width: 100%;
@ -277,20 +278,18 @@
</router-link> </router-link>
</div> </div>
<div class="menu-section"> <div class="menu-section" v-if="loggedIn">
<router-link class="menu-item menu-button" exact-active-class="active" to="/terms"> <router-link class="menu-item menu-button" exact-active-class="active" to="/settings">
<i class="info circle icon"></i>Terms <i class="cog icon"></i>Settings
</router-link> </router-link>
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<router-link class="menu-item menu-button" exact-active-class="active" to="/settings"> <div class="menu-item menu-button" v-on:click="logout()">
<i v-if="userIcon" class="cog icon"></i>{{ usernameDisplay }} <i class="log out icon"></i>Log Out
</router-link> </div>
</div> </div>
<div v-on:click="reloadPage" class="version-display" v-if="version != 0" > <div v-on:click="reloadPage" class="version-display" v-if="version != 0" >
<i :class="`${getVersionIcon()} icon`"></i> {{ version }} <i :class="`${getVersionIcon()} icon`"></i> {{ version }}
</div> </div>
@ -348,24 +347,18 @@
//Map logged in from state //Map logged in from state
return this.$store.getters.getLoggedIn return this.$store.getters.getLoggedIn
}, },
usernameDisplay() {
//Remove Emails from username, limit length to 16 chars
let name = this.$store.getters.getUsername
let splitName = name.split('@')
if(splitName.length > 1){
name = splitName.shift()
this.userIcon = false
}
if(name.length > 16){
this.userIcon = false
}
return this.ucWords(name.substring(0, 16))
},
}, },
methods: { methods: {
logout() {
this.$router.push('/')
axios.post('/api/user/logout')
setTimeout(() => {
this.$store.commit('destroyLoginToken')
this.$bus.$emit('notification', 'Logged Out')
}, 200)
},
newQuickNote(){ newQuickNote(){
axios.post('/api/quick-note/get') axios.post('/api/quick-note/get')

View File

@ -73,8 +73,10 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.darken-accent { .darken-accent {
filter: brightness(62%); filter: brightness(62%);
-webkit-filter: brightness(62%);
} }
.brighten-accent { .brighten-accent {
filter: saturate(145%); filter: saturate(145%);
-webkit-filter: saturate(145%);
} }
</style> </style>

View File

@ -133,10 +133,11 @@
<div class="bottom-edit-menu"></div> <div class="bottom-edit-menu"></div>
<div class="input-container-wrapper" :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}"> <div class="input-container-wrapper"
:class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}">
<!-- Squire box grows --> <!-- Squire box grows -->
<div id="text-box-container" class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"> <div id="text-box-container" class="note-wrapper">
<!-- Loading indicator --> <!-- Loading indicator -->
<transition name="fade"> <transition name="fade">
@ -154,12 +155,17 @@
v-on:keydown="titleResize" v-on:keydown="titleResize"
@keydown.enter.exact.prevent="editor.focus(); editor.moveCursorToEnd()" @keydown.enter.exact.prevent="editor.focus(); editor.moveCursorToEnd()"
rows="1" rows="1"
:style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText'] }"
v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input"> v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input">
</textarea> </textarea>
<!-- Squire Box --> <!-- Squire Box -->
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Type Note Here"></div> <div
id="squire-id"
class="squire-box"
ref="squirebox"
:style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText'] }"
placeholder="Type Note Here"></div>
</div> </div>
@ -1248,14 +1254,20 @@
.stealth-input { .stealth-input {
width: 100%; width: 100%;
padding: 10px 15px 5px; padding: 10px 15px 5px;
background-color: rgba(255,255,255,0.1); background-color: var(--small_element_bg_color );
border: none; border: none;
font-size: 1.7em; font-size: 1.7em;
color: var(--text_color); color: var(--text_color);
caret-color: var(--main-accent);
resize: none; resize: none;
overflow: hidden; overflow: hidden;
margin: 0; /*margin: 0;*/
outline: none; outline: none;
display: block;
margin-left: auto;
margin-right: auto;
max-width: 1100px;
} }

View File

@ -26,6 +26,12 @@
v-html="note.subtext"></span> v-html="note.subtext"></span>
<!-- Not indexed warning -->
<!-- <span v-if="note.indexed != 1">
<span class="green label">Not Indexed</span>
</span> -->
<div class="ui fluid basic button" v-if="note.encrypted == 1"> <div class="ui fluid basic button" v-if="note.encrypted == 1">
<i class="green lock icon"></i> <i class="green lock icon"></i>
Locked Locked
@ -410,6 +416,7 @@
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
/*The subtle shadow*/ /*The subtle shadow*/
/*box-shadow: 0px 1px 2px 1px rgba(210, 211, 211, 0.46);*/ /*box-shadow: 0px 1px 2px 1px rgba(210, 211, 211, 0.46);*/
box-shadow: 2px 2px 6px 0 rgba(0,0,0,.15);
transition: box-shadow ease 0.5s, transform linear 0.1s; transition: box-shadow ease 0.5s, transform linear 0.1s;
margin: 5px; margin: 5px;
/*padding: 0.7em 1em;*/ /*padding: 0.7em 1em;*/
@ -562,7 +569,7 @@
@media only screen and (max-width: 700px) { @media only screen and (max-width: 700px) {
.note-title-display-card { .note-title-display-card {
width: calc(100% + 10px); width: calc(100% + 10px);
margin: 0px -5px 10px -5px; /*margin: 0px -5px 10px -5px;*/
} }
} }
@media only screen and (min-width: 700px) and (max-width: 900px) { @media only screen and (min-width: 700px) and (max-width: 900px) {

View File

@ -1,6 +1,6 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.colors { .colors {
position: absolute; position: fixed;
z-index: 1023; z-index: 1023;
top: 5px; top: 5px;
/*height: 100px;*/ /*height: 100px;*/

File diff suppressed because one or more lines are too long

View File

@ -167,8 +167,10 @@
<!-- Overview --> <!-- Overview -->
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<div class="six wide column"> <div class="six wide column">
<h2>Solid Scribe focuses on powerful text editing and user privacy</h2> <h2>Powerful text editing and privacy</h2>
<h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> <h3>Easily edit, share and organize thousands of notes.</h3>
<h3>Feel safe knowing no one can read your notes but you.</h3>
<!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> -->
</div> </div>
<div class="four wide column"> <div class="four wide column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas"> <img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
@ -176,52 +178,21 @@
</div> </div>
<!-- features list --> <!-- features list -->
<div class="middle aligned centered row"> <div class="top aligned centered row">
<div class="sixteen wide column">
<h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>Features</h1> <!-- note features -->
</div>
<div class="six wide column"> <div class="six wide column">
<h2 class="ui dividing header">
<div class="content"> <h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>App Features</h1>
<i class="icons">
<i class="grey lock icon"></i>
<i class="bottom left corner yellow key icon"></i>
</i>
Privacy Focused
<div class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div>
</div>
</h2>
<h2 class="ui dividing header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
<i class="grey list icon"></i> <i class="grey sticky note icon"></i>
<i class="bottom left corner green check icon"></i> <i class="bottom left corner teal plus icon"></i>
</i> </i>
To Do Lists Create as many notes as you want
<div class="sub header">Create To Do lists that are always synced, work on mobile and can be sorted.</div> <div class="sub header">Create unlimited notes up to 5,000,000 characters long.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey file icon"></i>
<i class="bottom left corner blue pen icon"></i>
</i>
Document Editing Tools
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey images icon"></i>
<i class="bottom left corner teal paperclip icon"></i>
</i>
Add Images
<div class="sub header">Upload images to notes, add search text to the images to find them later.</div>
</div> </div>
</h2> </h2>
@ -232,40 +203,7 @@
<i class="bottom left corner purple plus icon"></i> <i class="bottom left corner purple plus icon"></i>
</i> </i>
Tag Notes Tag Notes
<div class="sub header">Easily add and edit tags on notes then sort notes by tag.</div> <div class="sub header">Easily add and edit tags on notes then search or sort by tag.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey cloud moon icon"></i>
<i class="bottom left corner red eye icon"></i>
</i>
Night Mode
<div class="sub header">Pure black night theme with an even darker flux theme.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey share alternate icon"></i>
<i class="bottom left corner share icon"></i>
</i>
Share Encrypted Notes
<div class="sub header">Share encrypted notes with friends without compromising security.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey users icon"></i>
<i class="bottom left corner olive exchange icon"></i>
</i>
Collaborative Note Editing
<div class="sub header">Notes instantly update in real time everywhere the note is open.</div>
</div> </div>
</h2> </h2>
@ -275,8 +213,8 @@
<i class="grey search icon"></i> <i class="grey search icon"></i>
<i class="bottom left corner orange font icon"></i> <i class="bottom left corner orange font icon"></i>
</i> </i>
Keyword Search Search Note Text
<div class="sub header">Easily search all notes. Encrypted search index ensures privacy and convenience.</div> <div class="sub header">Easily search all notes, files, links and tags.</div>
</div> </div>
</h2> </h2>
@ -291,6 +229,109 @@
</div> </div>
</h2> </h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey cloud moon icon"></i>
<i class="bottom left corner red eye icon"></i>
</i>
Night Mode
<div class="sub header">Pure black night theme with an even darker flux theme.</div>
</div>
</h2>
</div>
<!-- editing features -->
<div class="six wide column">
<h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>Editing Features</h1>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey list icon"></i>
<i class="bottom left corner green check icon"></i>
</i>
Create To Do Lists
<div class="sub header">Create To Do lists that are always synced, work on mobile and can be sorted.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey file icon"></i>
<i class="bottom left corner blue pen icon"></i>
</i>
Formatting Tools
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey file icon"></i>
<i class="bottom left corner orange paint brush icon"></i>
</i>
Customized Colorful Notes
<div class="sub header">Color the background of notes and add colored icons to make them stand out.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey images icon"></i>
<i class="bottom left corner teal paperclip icon"></i>
</i>
Add Images
<div class="sub header">Upload images to notes, add search text to the images to find them later.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey users icon"></i>
<i class="bottom left corner olive exchange icon"></i>
</i>
Collaborative Note Editing
<div class="sub header">Notes instantly update in real time everywhere its open and anywhere its shared.</div>
</div>
</h2>
</div>
</div>
<div class="middle aligned centered row">
<!-- privacy features -->
<div class="six wide column">
<h1 class="ui center aligned header"><i class="sliders horizontal icon"></i>Privacy Features</h1>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey lock icon"></i>
<i class="bottom left corner yellow key icon"></i>
</i>
All Note Text is Encrypted
<div class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey search icon"></i>
<i class="bottom left corner orange font icon"></i>
</i>
Note Search is Encrypted
<div class="sub header">Easily search the contents of all your notes without compromising security.</div>
</div>
</h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey share alternate icon"></i>
<i class="bottom left corner share icon"></i>
</i>
Encrypted Note Sharing
<div class="sub header">Shared notes are still encrypted, only readable by you and the shared users.</div>
</div>
</h2>
<h2 class="ui dividing header"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
@ -301,11 +342,12 @@
<div class="sub header">Enable two factor authentication for added peace of mind.</div> <div class="sub header">Enable two factor authentication for added peace of mind.</div>
</div> </div>
</h2> </h2>
</div> </div>
<div class="four wide column">
<div class="six wide column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/onboarding.svg" alt=""> <img loading="lazy" width="100%" src="/api/static/assets/marketing/onboarding.svg" alt="">
</div> </div>
</div> </div>
@ -347,7 +389,7 @@
<h2>Leave your Ad Blockers turned on</h2> <h2>Leave your Ad Blockers turned on</h2>
<h3>SolidScribe doesn't load any trackers or ads. It was designed to run on <h3>SolidScribe doesn't load any trackers or ads. It was designed to run on
<a href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Firefox</a>, with <a href="https://www.mozilla.org/en-US/firefox/new/" target="_blank">Firefox</a>, with
<a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/" target="_blank">uBlock Origin</a> and a <a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/" target="_blank">an Ad Blocker</a> turned on. It even works with a
<a href="https://pi-hole.net/" target="_blank">Pi-hole</a> on the network.</h3> <a href="https://pi-hole.net/" target="_blank">Pi-hole</a> on the network.</h3>
</div> </div>
<div class="four wide column"> <div class="four wide column">
@ -423,6 +465,10 @@
</div> </div>
</div> </div>
<div class="center aligned sixteen wide column">
<router-link to="/terms"></i>Solid Scribe Terms of Use</router-link>
</div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="ui basic segment no-fluf-segment"> <div class="page-container">
<div class="ui grid" ref="content"> <div class="ui grid" ref="content">

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="squire-box"> <div class="squire-box">
<div class=""> <div>
<h3 class="ui dividing header"> <h3 class="ui dividing header">
<i class="green cog icon"></i> <i class="inline green cog icon"></i>
Settings Settings for {{ $store.getters.getUsername }}
</h3> </h3>
<h4>New Scratch Pad</h4> <h4>New Scratch Pad</h4>
@ -113,19 +113,14 @@
</div> </div>
<!-- log out --> <!-- log out -->
<h4>Log Out</h4> <h4>Revoke Other Active Sessions</h4>
<div class="ui segment"> <div class="ui segment">
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="eight wide column"> <div class="sixteen wide column">
<div class="ui button" v-on:click="logout()"> <p>Revoke access on any logged in device, except for the one you are currently using.<br><br></p>
<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()"> <div class="ui button" v-on:click="revokeAllSessions()">
<i class="sign out icon"></i> <i class="sign out icon"></i>
Log Out all other browsers Log Out all other devices
</div> </div>
</div> </div>
</div> </div>
@ -155,6 +150,12 @@
</div> </div>
</div> </div>
<div class="ui grid">
<div class="center aligned sixteen wide column">
<router-link to="/terms"></i>Solid Scribe Terms of Use</router-link>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -208,14 +209,6 @@
}) })
}, },
logout() {
this.$store.commit('destroyLoginToken')
this.$router.push('/')
axios.post('/api/user/logout')
setTimeout(() => {
this.$bus.$emit('notification', 'Logged Out')
}, 200)
},
setAccentColor(color){ setAccentColor(color){
let root = document.documentElement let root = document.documentElement

File diff suppressed because one or more lines are too long

View File

@ -26,24 +26,25 @@ export default new Vuex.Store({
localStorage.removeItem('currentVersion') localStorage.removeItem('currentVersion')
delete axios.defaults.headers.common['authorizationtoken'] delete axios.defaults.headers.common['authorizationtoken']
state.username = null state.username = null
state.userTotals = null
}, },
toggleNightMode(state, pastTheme){ toggleNightMode(state, pastTheme){
const themes = { const themes = {
'white':{ 'white':{
'body_bg_color': '#f5f6f7', 'body_bg_color': '#f1f1f1',//'#f5f6f7',
'small_element_bg_color': '#fff', 'small_element_bg_color': '#fff',
'text_color': '#3d3d3d', 'text_color': '#3d3d3d',
'dark_border_color': '#DFE1E6', 'dark_border_color': '#d9d9d9',//'#DFE1E6',
'border_color': '#DFE1E6', 'border_color': '#DFE1E6',
'menu-accent': '#cecece', 'menu-accent': '#cecece',
'menu-text': '#5e6268', 'menu-text': '#5e6268',
}, },
'black':{ 'black':{
'body_bg_color': '#000', 'body_bg_color': '#0f0f0f',//'#000',
'small_element_bg_color': '#000', 'small_element_bg_color': '#000',
'text_color': '#FFF', 'text_color': '#FFF',
'dark_border_color': '#ACACAC', //Lighter color to accent elemnts user can interact with 'dark_border_color': '#555',//'#ACACAC', //Lighter color to accent elemnts user can interact with
'border_color': '#555', 'border_color': '#555',
'menu-accent': '#626262', 'menu-accent': '#626262',
'menu-text': '#d9d9d9', 'menu-text': '#d9d9d9',
@ -52,7 +53,7 @@ export default new Vuex.Store({
'body_bg_color': '#000', 'body_bg_color': '#000',
'small_element_bg_color': '#000', 'small_element_bg_color': '#000',
'text_color': '#a98457', 'text_color': '#a98457',
'dark_border_color': '#a98457', 'dark_border_color': '#555',
'border_color': '#555', 'border_color': '#555',
'menu-accent': '#626262', 'menu-accent': '#626262',
'menu-text': '#a69682', 'menu-text': '#a69682',
@ -98,10 +99,6 @@ export default new Vuex.Store({
} }
})(navigator.userAgent||navigator.vendor||window.opera, state); })(navigator.userAgent||navigator.vendor||window.opera, state);
}, },
toggleNoteSettingsPane(state){
state.isNoteSettingsOpen = !state.isNoteSettingsOpen
},
setSocketIoSocket(state, socket){ setSocketIoSocket(state, socket){
//Put socket id in axios headers //Put socket id in axios headers

View File

@ -259,7 +259,7 @@ let NoteTest = require('@models/Note')
let AuthTest = require('@helpers/Auth') let AuthTest = require('@helpers/Auth')
Auth.test() Auth.test()
UserTest.keyPairTest('genMan25', '1', printResults) UserTest.keyPairTest('genMan30', '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)

View File

@ -32,6 +32,7 @@ Attachment.textSearch = (userId, searchTerm) => {
) as snippet ) as snippet
FROM attachment FROM attachment
WHERE user_id = ? WHERE user_id = ?
AND visible != 0
AND MATCH(text) AND MATCH(text)
AGAINST(? IN NATURAL LANGUAGE MODE) AGAINST(? IN NATURAL LANGUAGE MODE)
LIMIT 1000` LIMIT 1000`

View File

@ -42,7 +42,7 @@ Note.test = (userId, masterKey, printResults) => {
testNoteId = newNoteId testNoteId = newNoteId
return Note.update return Note.update
(userId, testNoteId, 'Note text', 'Test Note beans Title', 0, 0, 0, 'hash', masterKey) (userId, testNoteId, 'Note text', 'Test Note beans barns Title', 0, 0, 0, 'hash', masterKey)
}) })
.then(() => { .then(() => {
@ -63,14 +63,14 @@ Note.test = (userId, masterKey, printResults) => {
if(printResults) console.log('Test: Reindex normal note - Pass') if(printResults) console.log('Test: Reindex normal note - Pass')
return Note.encryptedIndexSearch(userId, 'beans', null, masterKey) return Note.encryptedIndexSearch(userId, 'beans barns', null, masterKey)
}) })
.then(textSearchResults => { .then(textSearchResults => {
if(textSearchResults['ids'] && textSearchResults['ids'].length >= 1){ if(textSearchResults['ids'] && textSearchResults['ids'].length >= 1){
if(printResults) console.log('Test: Normal Note Search Index - Pass') if(printResults) console.log('Test: Normal Note Search Index - Pass')
} else { console.log('Test: Search Index - Fail') } } else { console.log('Test: Search Index - Fail-------------> 🥱') }
return ShareNote.addUserToSharedNote(userId, testNoteId, shareUserId, masterKey) return ShareNote.addUserToSharedNote(userId, testNoteId, shareUserId, masterKey)
}) })
@ -868,39 +868,98 @@ Note.encryptedIndexSearch = (userId, searchQuery, searchTags, masterKey) => {
const decipheredSearchIndex = cs.decrypt(masterKey, row.salt, row.index) const decipheredSearchIndex = cs.decrypt(masterKey, row.salt, row.index)
const searchIndex = JSON.parse(decipheredSearchIndex) const searchIndex = JSON.parse(decipheredSearchIndex)
//Clean up search word //Clean up search word, leave in spaces, split to array
const word = searchQuery.toLowerCase().replace(/[^a-z0-9]/g, '') const words = searchQuery.toLowerCase().replace(/[^a-z0-9 ]/g, '').split(' ')
let noteIds = [] let wordSearchCount = 0;
let partials = []
Object.keys(searchIndex).forEach(wordIndex => {
if( wordIndex.indexOf(word) != -1 && wordIndex != word){ let partialWords = [] //For debugging
partials.push(wordIndex) let exactWords = [] //For debugging
noteIds.push(...searchIndex[wordIndex])
let exactWordIdSets = []
let partialMatchNoteIds = []
words.forEach(word => {
//Skip short words
if(word.length <= 2){
return
} }
//count all words being searched
wordSearchCount++
//Save all exact match sets if found
if(searchIndex[word]){
// exactWords.push(word) //Words for debugging
exactWordIdSets.push(...searchIndex[word])
}
//Find all partial word matches in index
Object.keys(searchIndex).forEach(wordIndex => {
if( wordIndex.indexOf(word) != -1 && wordIndex != word){
// partialWords.push(wordIndex) //partialWords for debugging
partialMatchNoteIds.push(...searchIndex[wordIndex])
}
})
}) })
const exactArray = searchIndex[word] ? searchIndex[word] : [] //If more than one work was searched, remove notes that don't contain both
if(words.length > 1 && exactWordIdSets.length > 0){
let searchData = { //Find ids that appear more than once, this means there was an exact match in more than one note
'word':word, let overlappingIds = exactWordIdSets.filter((e, i, a) => a.indexOf(e) !== i)
'exact': exactArray, overlappingIds = [...new Set(overlappingIds)]
'partials': partials,
'partial': [...new Set(noteIds) ], //If there are notes that appear
if(overlappingIds.length > 0){
exactWordIdSets = overlappingIds
}
//If note appears in partial and exact, show only that set
const partialIntersect = exactWordIdSets.filter(x => partialMatchNoteIds.includes(x))
if(partialIntersect.length > 0){
exactWordIdSets = partialIntersect
partialMatchNoteIds = []
}
} }
//Remove duplicates from final id sets
let finalExact = [ ...new Set(exactWordIdSets) ]
let finalPartial = [ ...new Set(partialMatchNoteIds) ]
//Remove exact matches from partials set if there is overlap //Remove exact matches from partials set if there is overlap
if(searchData['exact'].length > 0 && searchData['partial'].length > 0){ if(finalExact.length > 0 && finalPartial.length > 0){
searchData['partial'] = searchData['partial'] finalPartial = finalPartial
.filter( ( el ) => !searchData['exact'].includes( el ) ) .filter( ( el ) => !finalExact.includes( el ) )
} }
searchData['ids'] = searchData['exact'].concat(searchData['partial']) //Combine the two filtered sets
searchData['total'] = searchData['ids'].length let finalIdSearchSet = finalExact.concat(finalPartial)
// console.log(searchData['total']) // let searchData = {
// 'query':searchQuery,
// 'words_count': words.length,
// 'exact_matches': exactWordIdSets.length,
// 'word_search_count': wordSearchCount,
// 'exactWords': exactWords,
// 'exact': finalExact,
// 'partialWords': partialWords,
// 'partial': finalPartial,
// }
return resolve({ 'ids':searchData['ids'] }) // //Lump all found note ids into one array
// searchData['ids'] = finalIdSearchSet
// searchData['total'] = searchData['ids'].length
// console.log('-----------------')
// console.log(searchData)
// console.log('-----------------')
return resolve({ 'ids':finalIdSearchSet })
} else { } else {
@ -971,7 +1030,8 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs, GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs,
shareUser.username as shareUsername, shareUser.username as shareUsername,
note.shared, note.shared,
note.encrypted_share_password_key note.encrypted_share_password_key,
note.indexed
FROM note FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id) JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
LEFT JOIN note_tag ON (note.id = note_tag.note_id) LEFT JOIN note_tag ON (note.id = note_tag.note_id)