Compare commits

..

No commits in common. "e7d1cc7bc988c1f8972ff72e0376484072b2d5d0" and "cca89a60d86c41861844315f2025be70428ffec1" have entirely different histories.

35 changed files with 420 additions and 1767 deletions

BIN
client/build/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -97,12 +97,6 @@ export default {
//Detect if user is on a mobile browser and set a flag in store //Detect if user is on a mobile browser and set a flag in store
this.$store.commit('detectIsUserOnMobile') this.$store.commit('detectIsUserOnMobile')
//Set Main theme color
const accentColor = localStorage.getItem('main-accent')
if(accentColor){
document.documentElement.style.setProperty('--main-accent', accentColor)
}
//Set color theme based on local storage //Set color theme based on local storage
const themeNumber = localStorage.getItem('nightMode') const themeNumber = localStorage.getItem('nightMode')
if(themeNumber != null){ if(themeNumber != null){

View File

@ -4,10 +4,6 @@ const helpers = {}
helpers.timeAgo = (time) => { helpers.timeAgo = (time) => {
if(time == null){
time = Math.round(time/1000)
}
if(time.toString().length >= 13){ if(time.toString().length >= 13){
time = Math.round(time/1000) time = Math.round(time/1000)
} }

View File

@ -18,8 +18,7 @@
:root { :root {
/*main accent for all buttons, icons and logos*/ --main-accent: #16ab39;
--main-accent: #21BA45;
/*theme colors */ /*theme colors */
--body_bg_color: #f5f6f7; --body_bg_color: #f5f6f7;
@ -86,8 +85,6 @@ body {
text-align: center; text-align: center;
} }
.ui.form input:not([type]), .ui.form input:not([type]),
.ui.form input:not([type]):focus, .ui.form input:not([type]):focus,
.ui.form textarea:not([type]), .ui.form textarea:not([type]),
@ -96,21 +93,6 @@ body {
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
border-color: var(--dark_border_color); border-color: var(--dark_border_color);
} }
.ui.form input[type="password"],
.ui.form input[type="text"],
.ui.input > input {
color: var(--text_color);
background-color: var(--small_element_bg_color);
border-color: var(--dark_border_color);
}
.ui.form input[type="password"]:focus, .ui.form input[type="password"]:active,
.ui.form input[type="text"]:focus, .ui.form input[type="text"]:active,
.ui.input > input:focus, .ui.input > input:active {
color: var(--text_color);
background-color: var(--small_element_bg_color);
border-color: var(--main-accent);
border-right-color: var(--main-accent) !important;
}
.ui.basic.label, .ui.header, .ui.header div.sub.header { .ui.basic.label, .ui.header, .ui.header div.sub.header {
color: var(--text_color); color: var(--text_color);
background-color: transparent; background-color: transparent;
@ -123,7 +105,7 @@ div.ui.basic.green.label {
background-color: var(--small_element_bg_color) !important; background-color: var(--small_element_bg_color) !important;
} }
.ui.basic.button, .ui.basic.buttons .button { .ui.basic.button, .ui.basic.buttons .button {
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color) !important;
color: var(--text_color) !important; color: var(--text_color) !important;
border: 1px solid; border: 1px solid;
border-color: var(--dark_border_color) !important; border-color: var(--dark_border_color) !important;
@ -143,24 +125,6 @@ div.ui.basic.green.label {
color: var(--text_color) !important; color: var(--text_color) !important;
border-color: var(--dark_border_color) !important; border-color: var(--dark_border_color) !important;
} }
/*Overwrites for modifiable theme color */
i.green.icon.icon.icon.icon {
color: var(--main-accent);
}
.ui.green.buttons, .ui.green.button, .ui.green.button:hover {
background-color: var(--main-accent);
}
.ui.basic.green.button, .ui.basic.green.buttons .button:hover, .ui.basic.green.button:hover, .ui.basic.green.button:focus {
box-shadow: var(--main-accent) 0px 0px 0px 1px inset;
}
.ui.green.labels .label, .ui.ui.ui.green.label {
background-color: var(--main-accent);
border-color: var(--main-accent);
}
.ui.grid > .green.row, .ui.grid > .green.column, .ui.grid > .row > .green.column {
background-color: var(--main-accent);
}
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/ /* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
/*// /*//
@ -271,7 +235,7 @@ i.green.icon.icon.icon.icon {
/*border-bottom: 1px solid #ccc;*/ /*border-bottom: 1px solid #ccc;*/
scrollbar-width: none; scrollbar-width: none;
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
caret-color: var(--main-accent); caret-color: #21BA45;
} }
.squire-box::selection, .squire-box::selection,
.squire-box::-moz-selection { .squire-box::-moz-selection {
@ -289,7 +253,7 @@ i.green.icon.icon.icon.icon {
cursor: pointer; cursor: pointer;
} }
.night-mode .note-card-text i:not(.icon), .night-mode .note-card-text i:not(.icon),
.night-mode .squire-box i:not(.icon) { .night-mode .squire-box i {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
} }
@ -358,76 +322,9 @@ i.green.icon.icon.icon.icon {
font-family: 'Icons'; font-family: 'Icons';
content: "\f058"; content: "\f058";
color: var(--main-accent); color: #21BA45;
opacity: 1; opacity: 1;
} }
.note-title-display-card .divide,
.squire-box .divide {
width: 100%;
display: inline-block;
height: 2px;
background-color: var(--main-accent);
}
table {
width: 100%;
border-collapse: collapse;
}
tr {
display: flex;
}
th, td {
border: 1px solid #ddd;
border-bottom: 1px solid #ddd;
font-weight: normal;
flex: 1;
}
/* table:hover th, table:hover td {
border: 1px solid black;
}*/
th, td {
padding: 3px;
text-align: left;
}
.table-tic-table {
}
.table-tic-table > div {
height: 21px;
margin: 0;
padding: 0;
}
.tabletic {
display: inline-block;
border: 1px solid black;
border-radius: 2px;
width: 20px;
height: 20px;
margin: 0 1px 1px 0;
cursor: pointer;
}
.t-table {
width: 100%;
display: inline-block;
border: 1px solid black;
}
.t-table > span,
.t-table > div {
display: flex; /* aligns all child elements (flex items) in a row */
}
.t-table > span > span,
.t-table > div > div {
flex: 1; /* distributes space on the line equally among items */
border: 1px solid #DDD;
}
/* adjust checkboxes for mobile. Make them a little bigger, easier to click */ /* adjust checkboxes for mobile. Make them a little bigger, easier to click */
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
@ -468,7 +365,7 @@ i.green.icon.icon.icon.icon {
font-family: 'Icons'; font-family: 'Icons';
content: "\f058"; content: "\f058";
color: var(--main-accent); color: #21BA45;
opacity: 1; opacity: 1;
} }
} }

View File

@ -1096,9 +1096,6 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
} }
while ( true ) { while ( true ) {
if ( endContainer === endMax || endContainer === root ) {
break;
}
if ( maySkipBR && if ( maySkipBR &&
endContainer.nodeType !== TEXT_NODE && endContainer.nodeType !== TEXT_NODE &&
endContainer.childNodes[ endOffset ] && endContainer.childNodes[ endOffset ] &&
@ -1106,7 +1103,9 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
endOffset += 1; endOffset += 1;
maySkipBR = false; maySkipBR = false;
} }
if ( endOffset !== getLength( endContainer ) ) { if ( endContainer === endMax ||
endContainer === root ||
endOffset !== getLength( endContainer ) ) {
break; break;
} }
parent = endContainer.parentNode; parent = endContainer.parentNode;
@ -1118,20 +1117,6 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
range.setEnd( endContainer, endOffset ); range.setEnd( endContainer, endOffset );
}; };
var moveRangeBoundaryOutOf = function ( range, nodeName, root ) {
var parent = getNearest( range.endContainer, root, 'A' );
if ( parent ) {
var clone = range.cloneRange();
parent = parent.parentNode;
moveRangeBoundariesUpTree( clone, parent, parent, root );
if ( clone.endContainer === parent ) {
range.setStart( clone.endContainer, clone.endOffset );
range.setEnd( clone.endContainer, clone.endOffset );
}
}
return range;
};
// Returns the first block at least partially contained by the range, // Returns the first block at least partially contained by the range,
// or null if no block is contained by the range. // or null if no block is contained by the range.
var getStartBlockOfRange = function ( range, root ) { var getStartBlockOfRange = function ( range, root ) {
@ -1300,13 +1285,10 @@ var onKey = function ( event ) {
if ( event.altKey ) { modifiers += 'alt-'; } if ( event.altKey ) { modifiers += 'alt-'; }
if ( event.ctrlKey ) { modifiers += 'ctrl-'; } if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
if ( event.metaKey ) { modifiers += 'meta-'; } if ( event.metaKey ) { modifiers += 'meta-'; }
if ( event.shiftKey ) { modifiers += 'shift-'; }
} }
// However, on Windows, shift-delete is apparently "cut" (WTF right?), so // However, on Windows, shift-delete is apparently "cut" (WTF right?), so
// we want to let the browser handle shift-delete in this situation. // we want to let the browser handle shift-delete.
if ( isWin && event.shiftKey && key === 'delete' ) { if ( event.shiftKey ) { modifiers += 'shift-'; }
modifiers += 'shift-';
}
key = modifiers + key; key = modifiers + key;
@ -1483,7 +1465,12 @@ var handleEnter = function ( self, shiftKey, range ) {
// just play it safe and insert a <br>. // just play it safe and insert a <br>.
if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) { if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
// If inside an <a>, move focus out // If inside an <a>, move focus out
moveRangeBoundaryOutOf( range, 'A', root ); parent = getNearest( range.endContainer, root, 'A' );
if ( parent ) {
parent = parent.parentNode;
moveRangeBoundariesUpTree( range, parent, parent, root );
range.collapse( false );
}
insertNodeInRange( range, self.createElement( 'BR' ) ); insertNodeInRange( range, self.createElement( 'BR' ) );
range.collapse( false ); range.collapse( false );
self.setSelection( range ); self.setSelection( range );
@ -1834,45 +1821,16 @@ if ( !isMac ) {
}; };
} }
const changeIndentationLevel = function ( methodIfInQuote, methodIfInList ) {
return function ( self, event ) {
event.preventDefault();
var path = self.getPath();
if ( /(?:^|>)BLOCKQUOTE/.test( path ) ||
!/(?:^|>)[OU]L/.test( path ) ) {
self[ methodIfInQuote ]();
} else {
self[ methodIfInList ]();
}
};
};
const toggleList = function ( listRegex, methodIfNotInList ) {
return function ( self, event ) {
event.preventDefault();
var path = self.getPath();
if ( !listRegex.test( path ) ) {
self[ methodIfNotInList ]();
} else {
self.removeList();
}
};
};
keyHandlers[ ctrlKey + 'b' ] = mapKeyToFormat( 'B' ); keyHandlers[ ctrlKey + 'b' ] = mapKeyToFormat( 'B' );
keyHandlers[ ctrlKey + 'i' ] = mapKeyToFormat( 'I' ); keyHandlers[ ctrlKey + 'i' ] = mapKeyToFormat( 'I' );
keyHandlers[ ctrlKey + 'u' ] = mapKeyToFormat( 'U' ); keyHandlers[ ctrlKey + 'u' ] = mapKeyToFormat( 'U' );
keyHandlers[ ctrlKey + 'shift-7' ] = mapKeyToFormat( 'S' ); keyHandlers[ ctrlKey + 'shift-7' ] = mapKeyToFormat( 'S' );
keyHandlers[ ctrlKey + 'shift-5' ] = mapKeyToFormat( 'SUB', { tag: 'SUP' } ); keyHandlers[ ctrlKey + 'shift-5' ] = mapKeyToFormat( 'SUB', { tag: 'SUP' } );
keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } ); keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } );
keyHandlers[ ctrlKey + 'shift-8' ] = keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
toggleList( /(?:^|>)UL/, 'makeUnorderedList' ); keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
keyHandlers[ ctrlKey + 'shift-9' ] = keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
toggleList( /(?:^|>)OL/, 'makeOrderedList' ); keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
keyHandlers[ ctrlKey + '[' ] =
changeIndentationLevel( 'decreaseQuoteLevel', 'decreaseListLevel' );
keyHandlers[ ctrlKey + ']' ] =
changeIndentationLevel( 'increaseQuoteLevel', 'increaseListLevel' );
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' ); keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' ); keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' ); keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
@ -4459,12 +4417,6 @@ proto.insertHTML = function ( html, isPaste ) {
this._docWasChanged(); this._docWasChanged();
} }
range.collapse( false ); range.collapse( false );
// After inserting the fragment, check whether the cursor is inside
// an <a> element and if so if there is an equivalent cursor
// position after the <a> element. If there is, move it there.
moveRangeBoundaryOutOf( range, 'A', root );
this._ensureBottomLine(); this._ensureBottomLine();
} }
@ -4995,4 +4947,4 @@ if ( typeof exports === 'object' ) {
} }
} }
}( document ) ); }( document ) );

View File

@ -182,6 +182,7 @@
openNote(){ openNote(){
const noteId = this.item.note_id const noteId = this.item.note_id
this.$router.push('/notes/open/'+noteId) this.$router.push('/notes/open/'+noteId)
this.$bus.$emit('open_note', noteId)
}, },
openEditAttachments(){ openEditAttachments(){
const noteId = this.item.note_id const noteId = this.item.note_id

View File

@ -24,9 +24,9 @@
} }
}, },
beforeMount(){ beforeMount(){
// this.$bus.$on('reset_fast_filters', () => { this.$bus.$on('reset_fast_filters', () => {
// this.orderString = 'Order by Last Edited' this.orderString = 'Order by Last Edited'
// }) })
}, },
methods:{ methods:{
displayString(){ displayString(){

View File

@ -2,31 +2,30 @@
.popup-body { .popup-body {
position: fixed; position: fixed;
top: 15px; bottom: 15px;
left: 15px; left: 15px;
min-height: 50px; min-height: 50px;
min-width: 200px; min-width: 200px;
max-width: calc(100% - 30px); max-width: calc(100% - 20px);
z-index: 1002; z-index: 1002;
border-top: 2px solid #21ba45;
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-top-right-radius: 4px;
border-top-left-radius: 4px;
color: white;
background-color: var(--main-accent);
} }
.popup-row { .popup-row {
padding: 1em 5px; padding: 1em 5px;
cursor: pointer; cursor: pointer;
white-space: nowrap;
} }
.popup-row > span { .popup-row > span {
/*width: calc(100% - 50px);*/ width: calc(100% - 50px);
display: inline-block; display: inline-block;
text-align: left; text-align: center;
box-sizing: border-box; box-sizing: border-box;
padding: 0 10px 0; padding: 0 10px 0;
font-size: 1.25em; font-size: 1.25em;
border-radius: 4px;
} }
.popup-row + .popup-row { .popup-row + .popup-row {
border-top: 1px solid #FFF; border-top: 1px solid #FFF;
@ -37,10 +36,12 @@
} }
@keyframes slide-in-bottom { @keyframes slide-in-bottom {
0% { 0% {
transform: translateY(-1000px); transform: translateY(1000px);
opacity: 0;
} }
100% { 100% {
transform: translateY(0); transform: translateY(0);
opacity: 1;
} }
} }
@ -62,46 +63,14 @@
} }
} }
.meter {
height: 2px;
display: inline-block;
width: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
overflow: hidden;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
.meter span {
display: block;
height: 100%;
}
.progress {
background-color: white;
animation: progressBar 3s linear;
animation-fill-mode: both;
}
@keyframes progressBar {
0% { width: 0; }
100% { width: 100%; }
}
</style> </style>
<template> <template>
<div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0"> <div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0">
<div class="popup-row" v-for="item in notifications"> <div class="popup-row color-fade" v-for="item in notifications">
<div class="meter"> <span>{{ item }}</span>
<span><span class="progress"></span></span>
</div>
<span><i class="small info circle icon"></i>{{ item }}</span>
</div> </div>
</div> </div>
</template> </template>
@ -125,9 +94,9 @@
}, },
mounted(){ mounted(){
// this.$bus.$emit('notification', 'Password Protection Removed Login did not succeed') // this.$bus.$emit('notification', 'Password Protection Removed')
// this.$bus.$emit('notification', 'Password Protection Removed your life is exposed to the internet') // this.$bus.$emit('notification', 'Password Protection Removed')
// this.$bus.$emit('notification', 'Password Protection Removed everyone can see everything') // this.$bus.$emit('notification', 'Password Protection Removed')
}, },
methods: { methods: {
@ -136,7 +105,7 @@
clearTimeout(this.totalTimeout) clearTimeout(this.totalTimeout)
this.totalTimeout = setTimeout(() => { this.totalTimeout = setTimeout(() => {
this.dismiss() this.dismiss()
}, 3000) }, 4000)
}, },
dismiss(){ dismiss(){
this.notifications = [] this.notifications = []

View File

@ -19,10 +19,9 @@
bottom: 0; bottom: 0;
} }
.menu-logo-display { .menu-logo-display {
width: 27px; width: 25px;
margin: 5px 0 0 41px; margin: 5px 0 0 42px;
display: inline-block; display: inline-block;
height: auto;
} }
.menu-item { .menu-item {
@ -80,17 +79,15 @@
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: var(--border_color); border-color: var(--border_color);
/*padding: 5px 1rem 5px;*/ padding: 5px 1rem 5px;
display: flex;
justify-content: space-around;
} }
.place-holder { .place-holder {
width: 100%; width: 100%;
height: 50px; height: 50px;
} }
.logo-display { .top-menu-bar img {
width: 27px; width: 30px;
height: auto; height: 30px;
} }
.version-display { .version-display {
position: absolute; position: absolute;
@ -104,19 +101,6 @@
cursor: pointer; cursor: pointer;
} }
.mobile-button {
display: inline-block;
font-size: 2em;
padding: 6px 3px 5px;
cursor: pointer;
}
.mobile-button.active {
background-color: transparent;
}
.mobile-button i {
margin: 0;
}
</style> </style>
<template> <template>
@ -126,51 +110,45 @@
<!-- collapsed menu, appears as a bar --> <!-- collapsed menu, appears as a bar -->
<div class="top-menu-bar" v-if="(collapsed || mobile) && !menuOpen"> <div class="top-menu-bar" v-if="(collapsed || mobile) && !menuOpen">
<div class="ui grid">
<div class="seven wide column">
<div class="ui large basic compact icon button" v-on:click="collapseMenu">
<i class="green bars icon"></i>
</div>
<!-- <router-link v-if="loggedIn" class="ui large basic compact icon button" to="/notes" v-on:click.native="emitReloadEvent()">
<i class="green home icon"></i>
</router-link> -->
<router-link v-if="loggedIn" class="ui basic icon button" exact-active-class="active" to="/attachments">
<i class="open folder outline icon"></i>
</router-link>
</div>
<div class="two wide center aligned bottom aligned column">
<img loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
</div>
<div class="seven wide right aligned column">
<div v-on:click="toggleNightMode" class="ui large basic compact icon button">
<i class="green moon outline icon"></i>
</div>
<!-- mobile create note button -->
<span v-if="loggedIn">
<span v-if="!disableNewNote" @click="createNote" class="ui large green compact icon button">
<i class="plus icon"></i>
</span>
<span v-if="disableNewNote" class="ui large basic compact icon button">
<i class="grey plus icon"></i>
</span>
</span>
</div>
<div class="mobile-button">
<i class="green link bars icon" v-on:click="collapseMenu"></i>
</div> </div>
<div class="mobile-button"></div>
<!-- open straight to note -->
<router-link
v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"
exact-active-class="active"
class="mobile-button"
:to="`/notes/open/${$store.getters.totals['quickNote']}`">
<i class="green sticky note outline icon"></i>
</router-link>
<!-- create new and redirect to new note id -->
<a
v-if="loggedIn && $store.getters.totals && !$store.getters.totals['quickNote']"
v-on:click="newQuickNote()"
exact-active-class="active"
class="mobile-button">
<i class="green sticky note outline icon"></i>
</a>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/notes" v-on:click.native="emitReloadEvent()">
<logo class="logo-display" color="var(--main-accent)" />
</router-link>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/attachments">
<i class="green open folder outline icon"></i>
</router-link>
<div class="mobile-button"></div>
<!-- mobile create note button -->
<span v-if="loggedIn">
<span v-if="!disableNewNote" @click="createNote" class="mobile-button">
<i class="green plus icon"></i>
</span>
<span v-if="disableNewNote" class="mobile-button">
<i class="grey plus icon"></i>
</span>
</span>
</div> </div>
<div class="shade" v-if="mobile && !collapsed" v-on:click="collapseMenu"></div> <div class="shade" v-if="mobile && !collapsed" v-on:click="collapseMenu"></div>
@ -181,8 +159,10 @@
<div class="global-menu" v-if="!collapsed" v-on:click="menuClicked"> <div class="global-menu" v-if="!collapsed" v-on:click="menuClicked">
<div class="menu-section" v-on:click="collapseMenu"> <div class="menu-section" v-on:click="collapseMenu">
<i class="white angle left icon"></i> <!-- <div class="menu-item menu-button" > -->
<logo class="menu-logo-display" color="var(--main-accent)" /> <i class="white angle left icon"></i>
<img class="menu-logo-display" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
<!-- </div> -->
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
@ -228,26 +208,9 @@
</div> </div>
<div class="menu-section" v-if="loggedIn"> <div class="menu-section" v-if="loggedIn">
<router-link v-if="loggedIn" exact-active-class="active" class="menu-item menu-button" to="/quick">
<!-- open straight to note -->
<router-link
v-if="loggedIn && $store.getters.totals && $store.getters.totals['quickNote']"
exact-active-class="active"
class="menu-item menu-button"
:to="`/notes/open/${$store.getters.totals['quickNote']}`">
<i class="sticky note outline icon"></i>Scratch Pad <i class="sticky note outline icon"></i>Scratch Pad
</router-link> </router-link>
<!-- create new and redirect to new note id -->
<a
v-if="loggedIn && $store.getters.totals && !$store.getters.totals['quickNote']"
v-on:click="newQuickNote()"
exact-active-class="active"
class="menu-item menu-button">
<i class="sticky note outline icon"></i>Scratch Pad
</a>
</div> </div>
<div class="menu-section" v-if="!loggedIn"> <div class="menu-section" v-if="!loggedIn">
@ -273,14 +236,14 @@
<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" v-if="loggedIn" :data-tooltip="`Logout ${this.$store.getters.getUsername}`" data-inverted="" data-position="right center">
<router-link class="menu-item menu-button" exact-active-class="active" to="/settings"> <div v-on:click="destroyLoginToken" class="menu-item menu-button">
<i v-if="userIcon" class="cog icon"></i>{{ usernameDisplay }} <i v-if="userIcon" class="user outline icon"></i>{{ usernameDisplay }}
</router-link> </div>
</div> </div>
@ -301,7 +264,6 @@
components: { components: {
'search-input': require('@/components/SearchInput.vue').default, 'search-input': require('@/components/SearchInput.vue').default,
'counter':require('@/components/AnimatedCounterComponent.vue').default, 'counter':require('@/components/AnimatedCounterComponent.vue').default,
'logo':require('@/components/LogoComponent.vue').default,
}, },
data: function(){ data: function(){
return { return {
@ -312,14 +274,10 @@
disableNewNote: false, disableNewNote: false,
menuOpen: true, menuOpen: true,
userIcon: true, userIcon: true,
resizeDebounce: null,
} }
}, },
beforeMount(){ beforeCreate: function(){
window.addEventListener('resize', this.resizeEventHandler)
},
beforeDestroy(){
window.removeEventListener('resize', this.resizeEventHandler)
}, },
mounted: function(){ mounted: function(){
this.mobile = this.$store.getters.getIsUserOnMobile this.mobile = this.$store.getters.getIsUserOnMobile
@ -333,8 +291,6 @@
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
this.version = localStorage.getItem('currentVersion') this.version = localStorage.getItem('currentVersion')
} }
this.resizeEventHandler() //Trigger resize event
}, },
computed: { computed: {
@ -360,32 +316,6 @@
}, },
}, },
methods: { methods: {
newQuickNote(){
axios.post('/api/quick-note/get')
.then( ({data}) => {
console.log(data)
this.$router.push({'path':'/notes/open/'+data.noteId})
})
},
resizeEventHandler(e) {
clearTimeout(this.resizeDebounce)
this.resizeDebounce = setTimeout(() => {
this.mobile = false
this.menuOpen = false
this.collapsed = false
if(window.innerWidth < 700){
this.collapsed = true
this.mobile = true
}
}, 100)
},
menuClicked(){ menuClicked(){
//Collapse menu when item is clicked in mobile //Collapse menu when item is clicked in mobile
if(this.mobile && !this.collapsed){ if(this.mobile && !this.collapsed){
@ -404,22 +334,28 @@
}, },
createNote(event){ createNote(event){
const title = ''
this.disableNewNote = true this.disableNewNote = true
axios.post('/api/note/create', {title:''}) axios.post('/api/note/create', {title})
.then(response => { .then(response => {
if(response.data && response.data.id){ if(response.data && response.data.id){
//Redirect to note page if user is not on it
//Push new note to url and it will open this.$bus.$emit('open_note', response.data.id)
this.$router.push('/notes/open/'+response.data.id)
this.disableNewNote = false this.disableNewNote = false
} }
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
}, },
destroyLoginToken() {
axios.post('/api/user/logout')
setTimeout(() => {
this.$bus.$emit('notification', 'Logged Out')
this.$store.commit('destroyLoginToken')
this.$router.push('/')
}, 200)
},
toggleNightMode(){ toggleNightMode(){
this.$store.commit('toggleNightMode') this.$store.commit('toggleNightMode')
}, },

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="loading-container"> <div class="loading-container">
<svg version="1.1" id="L6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"> <svg version="1.1" id="L6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<rect fill="none" :stroke="$store.getters.getIsNightMode > 0 ? '#FFF':'var(--main-accent)'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5"> <rect fill="none" :stroke="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5">
<animateTransform <animateTransform
attributeName="transform" attributeName="transform"
dur="0.5s" dur="0.5s"
@ -12,7 +12,7 @@
attributeType="XML" attributeType="XML"
begin="rectBox.end"/> begin="rectBox.end"/>
</rect> </rect>
<rect x="25" y="25" :fill="$store.getters.getIsNightMode > 0 ? '#FFF':'var(--main-accent)'" width="50" height="50"> <rect x="25" y="25" :fill="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" width="50" height="50">
<animate <animate
attributeName="height" attributeName="height"
dur="1.3s" dur="1.3s"

View File

@ -1,4 +1,3 @@
<template> <template>
<div v-on:keyup.enter="login()"> <div v-on:keyup.enter="login()">
@ -15,11 +14,6 @@
<input v-model="password" type="password" name="password" placeholder="Password"> <input v-model="password" type="password" name="password" placeholder="Password">
</div> </div>
</div> </div>
<div class="field" v-if="require2FA">
<div class="ui input">
<input v-model="authToken" ref="authForm" type="text" name="authToken" placeholder="Authorization Token">
</div>
</div>
<div class="sixteen wide field"> <div class="sixteen wide field">
<div class="ui fluid buttons"> <div class="ui fluid buttons">
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui green button"> <div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui green button">
@ -33,54 +27,34 @@
</div> </div>
</div> </div>
</div> </div>
<div class="sixteen wide column">
<span class="small-terms">
By signing up you agree to Solid Scribe's
<router-link to="/help">
Terms of Use
</router-link>
</span>
</div>
</div> </div>
<!-- Thin form display --> <!-- Thin form display -->
<div v-if="thin" class="ui small form"> <div v-if="thin" class="ui small form">
<div class="equal width fields"> <div class="fields">
<div class="field"> <div class="four wide field">
<div class="ui input"> <div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail"> <input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
</div> </div>
</div> </div>
<div class="field"> <div class="four wide field">
<div class="ui input"> <div class="ui input">
<input v-model="password" type="password" name="password" placeholder="Password"> <input v-model="password" type="password" name="password" placeholder="Password">
</div> </div>
</div> </div>
<div class="field" v-if="require2FA"> <div class="four wide field">
<div class="ui input">
<input v-model="authToken" ref="authForm" type="text" name="authToken" placeholder="Authorization Token">
</div>
</div>
<!-- hide this field if someone is logging in with 2FA -->
<div class="field" v-if="!require2FA">
<div v-on:click="register()" class="ui fluid green button"> <div v-on:click="register()" class="ui fluid green button">
<i class="plug icon"></i> <i class="plug icon"></i>
Sign Up Sign Up
</div> </div>
</div> </div>
<div class="field"> <div class="four wide field">
<div v-on:click="login()" class="ui fluid button"> <div v-on:click="login()" class="ui fluid button">
<i class="power icon"></i> <i class="power icon"></i>
Login Login
</div> </div>
</div> </div>
</div> </div>
<span class="small-terms">
By signing up you agree to Solid Scribe's
<router-link to="/help">
Terms of Use
</router-link>
</span>
</div> </div>
@ -109,9 +83,7 @@
return { return {
enabled: false, enabled: false,
username: '', username: '',
password: '', password: ''
authToken: '',
require2FA: false,
} }
}, },
methods: { methods: {
@ -167,30 +139,14 @@
return return
} }
axios.post('/api/user/login', {'username': this.username, 'password': this.password, 'authToken':this.authToken }) axios.post('/api/user/login', {'username': this.username, 'password': this.password})
.then(({data}) => { .then(({data}) => {
//Enable 2FA on form if(data == false){
if(data.success == false && data.verificationRequired == true && this.require2FA == false){ this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
this.$bus.$emit('notification', data.message)
this.require2FA = true
this.$nextTick(() => {
this.$refs.authForm.focus()
})
return
} }
if(data.success == false){ this.finalizeLogin(data)
this.$bus.$emit('notification', data.message)
return
}
if(data.success){
this.finalizeLogin(data)
return
}
}) })
.catch(error => { .catch(error => {
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password') this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
@ -198,13 +154,4 @@
} }
} }
} }
</script> </script>
<style type="text/css" scoped="true">
.small-terms {
display: inline-block;
text-align: right;
width: 100%;
font-size: 0.9em;
}
</style>

View File

@ -1,80 +0,0 @@
<template>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 132.29166 132.29167"
height="500"
width="500">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
style="display:inline"
transform="translate(0,-164.70832)"
id="layer1">
<path
class="darken-accent"
id="path3813-4"
d="m 56.22733,165.36641 -55.56249926,15.875 8e-7,63.5 47.62499846,11.90625 v 27.78125 l -47.76066333,-13.9757 0.13566407,10.00695 55.56249926,15.875 v -47.625 l -47.6249985,-11.90625 -8e-7,-47.625 47.7606633,-13.94121 c 0.135664,-2.30629 -0.135664,-9.87129 -0.135664,-9.87129 z"
:style="`fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1`" />
<path
class="brighten-accent"
id="path4563"
d="m 20.508581,220.92891 c 15.265814,-14.23899 27.809717,-7.68002 39.687499,3.96875 v -7.9375 C 51.75093,200.8366 37.512584,206.01499 20.508581,205.05391 Z"
:style="`fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`" />
<path
class="brighten-accent"
id="path4563-6"
d="m 111.78985,220.92891 c -15.265834,-14.23899 -27.809737,-7.68002 -39.68752,3.96875 v -7.9375 c 8.445151,-16.12356 22.683497,-10.94517 39.68752,-11.90625 z"
:style="`display:inline;fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1`" />
<path
class="brighten-accent"
id="path3813-4-2"
d="m 76.07108,165.36641 55.5625,15.875 v 63.5 l -47.625,11.90625 v 27.78125 l 47.76067,-13.9757 -0.13567,10.00695 -55.5625,15.875 v -47.625 l 47.625,-11.90626 V 189.17891 L 75.93542,175.2377 c -0.13567,-2.30629 0.13566,-9.87129 0.13566,-9.87129 z"
:style="`display:inline;fill:${displayColor};fill-opacity:1;stroke:none;stroke-width:0.52916676;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1`" />
</g>
</svg>
</template>
<script>
export default {
name: 'LoadingIcon',
props:[ 'color' ],
data(){
return {
displayColor: '#21BA45', //Default green color
}
},
created(){
//Set color if passed
if(this.color){
this.displayColor = this.color
}
},
}
</script>
<style type="text/css" scoped>
.darken-accent {
filter: brightness(62%);
}
.brighten-accent {
filter: saturate(145%);
}
</style>

View File

@ -2,8 +2,9 @@
<!-- 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 full-focus"
@keyup.esc="closeButtonAction()" @keyup.esc="close()"
:class="[ 'position-'+position ]"
> >
<!-- Giant Edit Note Menu --> <!-- Giant Edit Note Menu -->
@ -13,7 +14,7 @@
<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="close()" data-tooltip="Close" data-position="bottom center" data-inverted>
<i class="close icon"></i> <i class="close icon"></i>
</div> </div>
@ -60,15 +61,7 @@
</div> </div>
<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>
<i class="border all icon"></i>
</div>
<div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center" data-inverted>
<i class="grip lines icon"></i>
</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" data-inverted>
<i class="remove format icon"></i> <i class="remove format icon"></i>
</div> </div>
@ -102,8 +95,7 @@
<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>
<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="green archive icon"></i></span>
<span v-if="archived != 1"><i class="archive icon"></i></span> <span v-if="archived != 1"><i class="archive icon"></i></span>
</div> </div>
@ -113,13 +105,17 @@
<span v-if="pinned != 1"><i class="pin icon"></i></span> <span v-if="pinned != 1"><i class="pin icon"></i></span>
</div> --> </div> -->
<!-- data-tooltip="Files on note" -->
<!-- <div v-if="attachmentCount > 0" class="edit-button" v-on:click="openEditAttachment" data-tooltip="Files" data-position="bottom center" data-inverted>
<i class="folder icon"></i>
{{ attachmentCount }}
</div> -->
<div class="edit-button" v-if="usersOnNote > 1"> <div class="edit-button" v-if="usersOnNote > 1">
<i class="green eye icon"></i> {{ usersOnNote }} <i class="green eye icon"></i> {{ usersOnNote }}
</div> </div>
<!-- <!-- <div class="edit-button" v-on:click="simulateTyping()">
<div class="edit-button" v-on:click="simulateTyping()">
<i class="purple bolt icon"></i> <i class="purple bolt icon"></i>
</div> --> </div> -->
@ -136,6 +132,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="bottom-edit-menu"></div> <div class="bottom-edit-menu"></div>
@ -166,7 +163,7 @@
</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" placeholder="Note Text"></div>
</div> </div>
@ -227,19 +224,19 @@
<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">
<i class="sort amount up icon"></i> <i class="sort amount up icon"></i>
Sort List Sort List items (Move checked to bottom)
</div> </div>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems"> <div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems">
<i class="trash icon"></i> <i class="trash icon"></i>
Delete Checked Delete Checked Items
</div> </div>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems"> <div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems">
<i class="list ul icon"></i> <i class="list ul icon"></i>
Uncheck All Uncheck all Checked items
</div> </div>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
@ -248,15 +245,6 @@
Simple Math Simple Math
</div> </div>
</div> </div>
<div class="eight wide column">
<!-- data-tooltip="Files on note" -->
<div v-on:click="openEditAttachment" class="ui labeled icon fluid basic button">
<i class="folder icon"></i>
Note Files
{{ attachmentCount }}
</div>
</div>
<div class="sixteen wide column" v-if="rawTextId > 0"> <div class="sixteen wide column" v-if="rawTextId > 0">
<h2>Share Note</h2> <h2>Share Note</h2>
<share-note-component <share-note-component
@ -269,27 +257,13 @@
</div> </div>
</side-slide-menu> </side-slide-menu>
<!-- create table option -->
<side-slide-menu v-if="table" v-on:close="table = false; fetchNoteTags()" name="table" :style-object="styleObject">
<div class="ui basic segment">
<h2>Insert Table</h2>
<div class="table-tic-table">
<div v-for="i in 10">
<div v-for="j in 10" class="tabletic" v-on:click="insertTable(i,j)">
</div>
</div>
</div>
</div>
</side-slide-menu>
<!-- Show side shades if user is on desktop only --> <!-- Show side shades if user is on desktop only -->
<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="close()"></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="close()"></div>
</div> </div>
</template> </template>
@ -361,7 +335,6 @@
images: false, images: false,
options: false, options: false,
colorpicker: false, colorpicker: false,
table: false,
//Diff text/sync text variables //Diff text/sync text variables
diffTextTimeout: null, diffTextTimeout: null,
@ -382,7 +355,7 @@
//Handle changes in URL to //Handle changes in URL to
if(newVal.id == undefined || newVal.id != this.noteid){ if(newVal.id == undefined || newVal.id != this.noteid){
// this.closeButtonAction() this.close()
} }
//Reset all note menus on URL change //Reset all note menus on URL change
@ -391,7 +364,6 @@
this.tags = false this.tags = false
this.options = false this.options = false
this.images = false this.images = false
this.table = false
//If a menu value is set, open it //If a menu value is set, open it
if(newVal.openMenu){ if(newVal.openMenu){
@ -422,20 +394,18 @@
document.removeEventListener('visibilitychange', this.checkForUpdatedNote) document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
//Obliterate squire instance // if(this.editor){
this.editor.destroy() this.editor.destroy()
// }
this.close()
}, },
mounted: function() { mounted: function() {
//Show loading for a minimum time
setTimeout(()=>{ setTimeout(()=>{
this.forceShowLoading = false this.forceShowLoading = false
}, 500) }, 500)
// document.addEventListener('visibilitychange', this.checkForUpdatedNote) document.addEventListener('visibilitychange', this.checkForUpdatedNote)
//Init squire as early as possible //Init squire as early as possible
this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' }) this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
@ -629,30 +599,6 @@
this.editor.addEventListener('keydown', event => { this.editor.addEventListener('keydown', event => {
//Tab to increase quote level, tab + shigt to decrease quote level
const keyCode = event.key
if(keyCode == 'Tab'){
if(event.shiftKey){
this.editor.decreaseQuoteLevel()
} else {
this.editor.increaseQuoteLevel()
}
event.preventDefault()
return false
}
//Save on pressing CTRL/CMD + S
if(keyCode == 's' && (event.ctrlKey || event.metaKey) ){
this.$bus.$emit('notification', 'Note Saved')
this.save()
event.preventDefault()
return false
}
//Prevent new list items from having //Prevent new list items from having
this.$nextTick( () => { this.$nextTick( () => {
//Wait a moment to get item under cursor //Wait a moment to get item under cursor
@ -666,6 +612,10 @@
}) })
}) })
this.editor.addEventListener('keydown', event => {
})
//Bind event handlers //Bind event handlers
this.editor.addEventListener('keyup', event => { this.editor.addEventListener('keyup', event => {
@ -732,7 +682,7 @@
//Block notes you don't have access to from opening //Block notes you don't have access to from opening
if(response.data === false){ if(response.data === false){
this.$bus.$emit('notification', 'Error opening Note') this.$bus.$emit('notification', 'Error opening Note')
this.close() this.close(true)
return return
} }
@ -965,21 +915,18 @@
return hash; return hash;
}, },
closeButtonAction(){ close(force = false){
this.sizeDown = true
//This timeout allows animation to play before closing
setTimeout(() => {
this.$router.push('/notes')
}, 300)
},
close(){
// force = true // force = true
// console.log(`Close Note ${this.noteid} -> force: ${force}, modified: ${this.modified}`) // console.log(`Close Note ${this.noteid} -> force: ${force}, modified: ${this.modified}`)
//Skip everything if foce close is true. Note will just die. if(force){
if(this.currentNoteId == 0){ return } this.$bus.$emit('close_active_note', {
noteId: this.noteid, modified: this.modified
})
return
}
this.loadingMessage = 'Saving...' this.loadingMessage = 'Saving...'
this.loading = true this.loading = true
@ -991,10 +938,14 @@
axios.post('/api/note/reindex') axios.post('/api/note/reindex')
} }
this.$bus.$emit('close_active_note', { this.sizeDown = true
noteId: this.noteid, modified: this.modified //This timeout allows animation to play before closing
}) setTimeout(() => {
return this.$bus.$emit('close_active_note', {
noteId: this.noteid, modified: this.modified
})
return
}, 300)
}) })
}, },
destroyWebSockets(){ destroyWebSockets(){
@ -1054,8 +1005,8 @@
let element = this.$refs.titleTextarea let element = this.$refs.titleTextarea
let padding = 0 let padding = 0
element.style.height = 'auto' element.style.height = 'auto';
element.style.height = (element.scrollHeight + padding) +'px' element.style.height = (element.scrollHeight + padding) +'px';
}, },
} }
} }
@ -1201,7 +1152,7 @@
background-color: var(--menu-accent); background-color: var(--menu-accent);
} }
.edit-active { .edit-active {
background-color: var(--main-accent); background-color: #21BA45;
color: white; color: white;
} }
.edit-divide { .edit-divide {

View File

@ -67,7 +67,7 @@
</span> </span>
<span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> <span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
{{$helpers.timeAgo( note.updated )}} {{$helpers.timeAgo(note.updated)}}
</span> </span>
<span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> <span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
@ -234,12 +234,12 @@
}, },
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(() => {
@ -356,14 +356,13 @@
/*Strict font sizes for card display*/ /*Strict font sizes for card display*/
.small-text { .small-text {
max-height: 267px; max-height: 261px;
width: 100%;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
} }
.small-text, .small-text > p, .small-text > h1, .small-text > h2 { .small-text, .small-text > p, .small-text > h1, .small-text > h2 {
/*font-size: 1.0em !important;*/ /*font-size: 1.0em !important;*/
font-size: 14px !important; font-size: 16px !important;
} }
.small-text > p, , .small-text > h1, .small-text > h2 { .small-text > p, , .small-text > h1, .small-text > h2 {
margin-bottom: 0.5em; margin-bottom: 0.5em;
@ -371,7 +370,7 @@
.big-text > p:first-child, .big-text > p:first-child,
.big-text > h1, .big-text > h2 { .big-text > h1, .big-text > h2 {
/*font-size: 1.3em !important;*/ /*font-size: 1.3em !important;*/
font-size: 20px !important; font-size: 17px !important;
font-weight: bold; font-weight: bold;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@ -416,14 +415,14 @@
border-color: var(--border_color); border-color: var(--border_color);
/*width: calc(33.333% - 10px);*/ /*width: calc(33.333% - 10px);*/
width: calc(25% - 10px); width: calc(25% - 10px);
/*min-width: 190px;*/ min-width: 190px;
min-height: 130px; min-height: 130px;
/*transition: box-shadow 0.3s;*/ /*transition: box-shadow 0.3s;*/
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
line-height: 1.8rem; line-height: 1.8rem;
letter-spacing: 0.05rem; letter-spacing: 0.02rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-align: left; text-align: left;
@ -556,50 +555,27 @@
float: right; float: right;
} }
/* Break points determine when display cards shrink */ /* Tweak mobile display to show only one column */
@media only screen and (max-width: 700px) { @media only screen and (min-width: 1500px) {
.note-title-display-card {
width: calc(20% - 10px);
}
}
@media only screen and (max-width: 740px) {
.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) {
.note-title-display-card {
width: calc(50% - 10px);
}
}
@media only screen and (min-width: 900px) and (max-width: 1100px) {
.note-title-display-card {
width: calc(33.33333% - 10px);
}
}
@media only screen and (min-width: 1100px) and (max-width: 1300px) {
.note-title-display-card {
width: calc(25% - 10px);
}
}
@media only screen and (min-width: 1300px) and (max-width: 1800px) {
.note-title-display-card {
width: calc(20% - 10px);
}
}
@media only screen and (min-width: 1800px) {
.note-title-display-card {
width: calc(16.66666% - 10px);
}
}
/*Animations for cool border effects*/ /*Animations for cool border effects*/
@keyframes bgin { @keyframes bgin {
0% { 0% {
background-image: background-image:
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* TopLeft to Right */ linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* TopLeft to Right */
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%), /* TopRight to Bottom */ linear-gradient(to bottom, #21BA45 50%, #21BA45 100%), /* TopRight to Bottom */
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* BottomLeft to Right*/ linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* BottomLeft to Right*/
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%); /* TopLeft to Bottom */ linear-gradient(to bottom, #21BA45 50%, #21BA45 100%); /* TopLeft to Bottom */
/*Initial state, no BG*/ /*Initial state, no BG*/
background-size: 0 4px, 4px 0, 0 4px, 4px 0; background-size: 0 4px, 4px 0, 0 4px, 4px 0;
} }
@ -614,10 +590,10 @@
30% { 30% {
background-size: 100% 4px, 4px 100%, 100% 4px, 4px 100%; background-size: 100% 4px, 4px 100%, 100% 4px, 4px 100%;
background-image: background-image:
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* TopLeft to Right */ linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* TopLeft to Right */
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%), /* TopRight to Bottom */ linear-gradient(to bottom, #21BA45 50%, #21BA45 100%), /* TopRight to Bottom */
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* BottomLeft to Right*/ linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* BottomLeft to Right*/
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%); /* TopLeft to Bottom */ linear-gradient(to bottom, #21BA45 50%, #21BA45 100%); /* TopLeft to Bottom */
} }
100% { 100% {
background-image: background-image:

View File

@ -98,17 +98,13 @@
}, },
beforeCreate: function(){ beforeCreate: function(){
}, },
beforeMount(){ mounted: function(){
//search clear //search clear
this.$bus.$on('reset_fast_filters', () => { this.$bus.$on('reset_fast_filters', () => {
this.searchTerm = '' this.searchTerm = ''
this.tagSuggestions = [] this.tagSuggestions = []
}) })
},
beforeDestroy(){
this.$bus.$off('reset_fast_filters')
},
mounted: function(){
}, },
methods: { methods: {
@ -152,6 +148,7 @@
if(response.data && response.data.id){ if(response.data && response.data.id){
this.$router.push('/notes/open/'+response.data.id) this.$router.push('/notes/open/'+response.data.id)
this.$bus.$emit('open_note', response.data.id)
} }
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to create note') })

View File

@ -12,7 +12,6 @@
<ul> <ul>
<li>Shared notes can be read and edited by you and all shared users.</li> <li>Shared notes can be read and edited by you and all shared users.</li>
<li>Shared notes can only be shared by the creator of the note.</li> <li>Shared notes can only be shared by the creator of the note.</li>
<li>If you turn off sharing, no one else can read the note.</li>
</ul> </ul>
</div> </div>

View File

@ -17,7 +17,6 @@ const SquireButtonFunctions = {
// //
pathChangeEvent(e){ pathChangeEvent(e){
//Reset all button states //Reset all button states
this.activeBold = false this.activeBold = false
this.activeTitle = false this.activeTitle = false
@ -144,23 +143,28 @@ const SquireButtonFunctions = {
// Uncheck All List Items // Uncheck All List Items
// //
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.options = false
}
//Fetch the container //Fetch the container
let container = document.getElementById('squire-id') let container = document.getElementById('squire-id')
Array.from( container.getElementsByClassName('active') ).forEach(item => { Array.from( container.getElementsByClassName('active') ).forEach(item => {
item.classList.remove('active'); item.classList.remove('active');
}) })
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.$router.go(-1)
}
}, },
deleteCompletedListItems(){ deleteCompletedListItems(){
// //
// Delete Completed List Items // Delete Completed List Items
// //
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.options = false
}
//Fetch the container //Fetch the container
let container = document.getElementById('squire-id') let container = document.getElementById('squire-id')
@ -206,17 +210,17 @@ const SquireButtonFunctions = {
} }
}) })
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.$router.go(-1)
}
}, },
sortList(){ sortList(){
// //
// Sort list, checked at the bottom, unchecked at the top // Sort list, checked at the bottom, unchecked at the top
// //
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.options = false
}
//Fetch the container //Fetch the container
let container = document.getElementById('squire-id') let container = document.getElementById('squire-id')
@ -270,17 +274,17 @@ const SquireButtonFunctions = {
} }
}) })
//Close menu if user is on mobile
if(this.$store.getters.getIsUserOnMobile){
this.$router.go(-1)
}
}, },
calculateMath(){ calculateMath(){
// //
// Find math in note and calculate the outcome // Find math in note and calculate the outcome
// //
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.options = false
}
//Fetch the container //Fetch the container
let container = document.getElementById('squire-id') let container = document.getElementById('squire-id')
@ -323,11 +327,6 @@ const SquireButtonFunctions = {
} }
}) })
//Close menu if user is on mobile, then sort list
if(this.$store.getters.getIsUserOnMobile){
this.$router.go(-1)
}
}, },
setText(inText){ setText(inText){
@ -339,33 +338,6 @@ const SquireButtonFunctions = {
return this.editor.getHTML() return this.editor.getHTML()
}, },
insertDivide(){
this.editor.insertHTML(`<p><div class='divide'></div><br></p>`)
this.editor.focus()
this.editor.moveCursorToEnd()
},
insertTable(tall, wide){
console.log(`Table: ${wide} x ${tall}`)
//Insert a table
let tableSyntax = '<div>'
tableSyntax += '<table>'
for (let i = 0; i < tall; i++) {
tableSyntax += '<tr>'
for (let j = 0; j < wide; j++) {
tableSyntax += '<td><p><br></p></td>'
}
tableSyntax += '</tr>'
}
tableSyntax += '</table></div><p><br></p>'
this.editor.insertHTML(tableSyntax)
this.editor.focus()
this.editor.moveCursorToEnd()
this.$router.go(-1)
},
}, },
} }

File diff suppressed because one or more lines are too long

View File

@ -110,7 +110,7 @@
</h2> </h2>
<h3 class="subtext"> <h3 class="subtext">
A free, secure, online note taking application<i class="i cursor icon blinking"></i> A free, secure Note App<i class="i cursor icon blinking"></i>
</h3> </h3>
</div> </div>
@ -210,18 +210,7 @@
<i class="bottom left corner blue pen icon"></i> <i class="bottom left corner blue pen icon"></i>
</i> </i>
Document Editing Tools Document Editing Tools
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div> <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>
@ -236,6 +225,17 @@
</div> </div>
</h2> </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"> <h2 class="ui dividing header">
<div class="content"> <div class="content">
<i class="icons"> <i class="icons">
@ -254,7 +254,7 @@
<i class="bottom left corner share icon"></i> <i class="bottom left corner share icon"></i>
</i> </i>
Share Encrypted Notes Share Encrypted Notes
<div class="sub header">Share encrypted notes with friends without compromising security.</div> <div class="sub header">Share notes with friends without compromising security. And its easy to disable sharing.</div>
</div> </div>
</h2> </h2>
@ -290,17 +290,6 @@
<div class="sub header">Add text to Images and links than can be searched.</div> <div class="sub header">Add text to Images and links than can be searched.</div>
</div> </div>
</h2> </h2>
<h2 class="ui dividing header">
<div class="content">
<i class="icons">
<i class="grey tv icon"></i>
<i class="bottom left corner blue mobile icon"></i>
</i>
Two Factor Authentication
<div class="sub header">Enable two factor authentication for added peace of mind.</div>
</div>
</h2>
</div> </div>
<div class="four wide column"> <div class="four wide column">

View File

@ -25,7 +25,7 @@
</div> </div>
<p>You will remain logged in on this browser, for 20 days or until you log out.</p> <p>You will remain logged in on this browser, for 30 days or until you log out.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -31,7 +31,7 @@
<i v-if="titleView" class="th icon"></i> <i v-if="titleView" class="th icon"></i>
<i v-if="!titleView" class="bars icon"></i> <i v-if="!titleView" class="bars icon"></i>
</div> </div>
</div> </div>
<div class="eight wide column" v-if="showClear"> <div class="eight wide column" v-if="showClear">
@ -117,7 +117,7 @@
:onClick="openNote" :onClick="openNote"
:data="note" :data="note"
:title-view="titleView" :title-view="titleView"
:currently-open="activeNoteId1 == note.id" :currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated" :key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
/> />
</div> </div>
@ -132,12 +132,13 @@
</div> </div>
<note-input-panel <input-notes
v-if="activeNoteId1 != null" v-if="activeNoteId1 != null"
:key="activeNoteId1" :key="'active_note_'+activeNoteId1"
:noteid="activeNoteId1" :noteid="activeNoteId1"
:position="activeNote1Position"
:url-data="$route.params" :url-data="$route.params"
/> ref="note1" />
</div> </div>
</template> </template>
@ -150,7 +151,7 @@
name: 'SearchBar', name: 'SearchBar',
components: { components: {
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'), 'input-notes': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default, 'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
// 'fast-filters': require('@/components/FastFilters.vue').default, // 'fast-filters': require('@/components/FastFilters.vue').default,
@ -246,7 +247,7 @@
//Do not update note if its open //Do not update note if its open
if(this.activeNoteId1 != noteId){ if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId, true) this.updateSingleNote(noteId, false)
} }
}) })
@ -263,18 +264,10 @@
//Close note event //Close note event
this.$bus.$on('close_active_note', ({noteId, modified}) => { this.$bus.$on('close_active_note', ({noteId, modified}) => {
if(modified){ this.closeNote()
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
}
//A note has been closed
if(this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified //Focus and animate if modified
this.updateSingleNote(noteId, modified) this.updateSingleNote(parseInt(noteId), modified)
}) })
this.$bus.$on('note_deleted', (noteId) => { this.$bus.$on('note_deleted', (noteId) => {
@ -319,25 +312,35 @@
}) })
}) })
//Reload page content - don't trigger if load is in progress //New note button pushes open note event
this.$bus.$on('note_reload', () => { this.$bus.$on('open_note', noteId => {
if(!this.loadingInProgress){ this.openNote(noteId)
this.reset()
}
}) })
//Reload page content
this.$bus.$on('note_reload', () => {
this.reset()
})
//Mount notes on load if note ID is set
if(this.$route.params && this.$route.params.id){
const id = this.$route.params.id
this.openNote(id)
}
window.addEventListener('scroll', this.onScroll) window.addEventListener('scroll', this.onScroll)
//Close notes when back button is pressed //Close notes when back button is pressed
// window.addEventListener('hashchange', this.hashChangeAction) window.addEventListener('hashchange', this.hashChangeAction)
//update note on visibility change //update note on visibility change
// document.addEventListener('visibilitychange', this.visibiltyChangeAction); document.addEventListener('visibilitychange', this.visibiltyChangeAction);
}, },
beforeDestroy(){ beforeDestroy(){
window.removeEventListener('scroll', this.onScroll) window.removeEventListener('scroll', this.onScroll)
// document.removeEventListener('visibilitychange', this.visibiltyChangeAction) window.removeEventListener('hashchange', this.hashChangeAction)
document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
this.$bus.$off('note_reload') this.$bus.$off('note_reload')
this.$bus.$off('close_active_note') this.$bus.$off('close_active_note')
@ -345,6 +348,7 @@
this.$bus.$off('note_deleted') this.$bus.$off('note_deleted')
this.$bus.$off('update_fast_filters') this.$bus.$off('update_fast_filters')
this.$bus.$off('update_search_term') this.$bus.$off('update_search_term')
this.$bus.$off('open_note')
//We want to remove event listeners, but something here is messing them up and preventing ALL event listeners from working //We want to remove event listeners, but something here is messing them up and preventing ALL event listeners from working
// this.$off() // Remove all event listeners // this.$off() // Remove all event listeners
@ -352,20 +356,11 @@
}, },
mounted() { mounted() {
//Open note on load if ID is set
if(this.$route.params.id > 1){
this.activeNoteId1 = this.$route.params.id
}
//Loads initial batch and tags //Loads initial batch and tags
this.reset() this.reset()
}, // this.search(true, this.firstLoadBatchSize, false)
watch: { // .then( r => this.search(false, this.batchSize, true))
'$route.params.id': function(id){
//Open note on ID, null id will close note
this.activeNoteId1 = id
}
}, },
methods: { methods: {
toggleTitleView(){ toggleTitleView(){
@ -387,9 +382,17 @@
if(nodeClick == 'A'){ return } if(nodeClick == 'A'){ return }
} }
//Open note if a link was not clicked //1 note open
this.$router.push('/notes/open/'+id) if(this.activeNoteId1 == null){
return this.activeNoteId1 = id
this.activeNote1Position = 0 //Middel of page
this.$router.push('/notes/open/'+this.activeNoteId1).catch(e => { console.log(e) })
return
}
},
closeNote(position){
this.activeNoteId1 = null
this.$router.push('/notes')
}, },
toggleTagFilter(tagId){ toggleTagFilter(tagId){
@ -426,6 +429,34 @@
return return
}, },
//Try to close notes on URL hash change /notes/open/123 to /notes - parse 123, close note id 123
hashChangeAction(event){
//Clean up path of hash change
let path = window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.hash
let newPath = event.newURL.replace(path,'')
let oldPath = event.oldURL.replace(path,'')
// console.log(this.$route.params)
// console.log(this.$router)
//Open note if user goes forward to a note id
if(this.$route.params && this.$route.params.id){
this.openNote(this.$route.params.id)
}
//If we go from open note ID to no note ID, close the note
if(newPath == '' && oldPath.indexOf('/open/') != -1){
//Pull note ID out of URL
const noteIdToClose = oldPath.split('/').pop()
// console.log(noteIdToClose)
if(this.$refs.note1 && this.$refs.note1.currentNoteId == noteIdToClose){
// this.$refs.note1.close()
}
}
},
visibiltyChangeAction(event){ visibiltyChangeAction(event){
//Fuck this shit, just use web sockets //Fuck this shit, just use web sockets
@ -453,11 +484,8 @@
let note = null let note = null
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){ if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){
note = this.$refs['note-'+noteId][0].note note = this.$refs['note-'+noteId][0].note
//Show that note is working on updating
this.$refs['note-'+noteId][0].showWorking = true
} }
//Lookup one note using passed in ID //Lookup one note using passed in ID
const postData = { const postData = {
searchQuery: this.searchTerm, searchQuery: this.searchTerm,
@ -480,13 +508,18 @@
if(note && newNote){ if(note && newNote){
//Don't move notes that were not changed
if(note.updated == newNote.updated){
// return
}
//go through each prop and update it with new values //go through each prop and update it with new values
Object.keys(newNote).forEach(prop => { Object.keys(newNote).forEach(prop => {
note[prop] = newNote[prop] note[prop] = newNote[prop]
}) })
//Push new note to front if its modified or we want it to //Push new note to front if its modified
if( focuseAndAnimate || note.updated != newNote.updated ){ if(focuseAndAnimate){
// Find note, in section, move to front // Find note, in section, move to front
Object.keys(this.noteSections).forEach( key => { Object.keys(this.noteSections).forEach( key => {
@ -503,7 +536,6 @@
this.$nextTick( () => { this.$nextTick( () => {
//Trigger close animation on note //Trigger close animation on note
this.$refs['note-'+noteId][0].justClosed() this.$refs['note-'+noteId][0].justClosed()
this.$refs['note-'+noteId][0].showWorking = false
}) })
} }
@ -515,14 +547,9 @@
//Trigger close animation on note //Trigger close animation on note
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){ if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].justClosed() this.$refs['note-'+noteId][0].justClosed()
this.$refs['note-'+noteId][0].showWorking = false
} }
} }
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].showWorking = false
}
//Trigger section rebuild //Trigger section rebuild
this.rebuildNoteCategorise() this.rebuildNoteCategorise()
}) })
@ -583,6 +610,7 @@
//Perform search - or die //Perform search - or die
this.loadingInProgress = true this.loadingInProgress = true
// console.time('Fetch TitleCard Batch '+notesInNextLoad)
axios.post('/api/note/search', postData) axios.post('/api/note/search', postData)
.then(response => { .then(response => {
@ -707,6 +735,7 @@
this.fastFilters = {} this.fastFilters = {}
this.foundAttachments = [] //Remove all attachments this.foundAttachments = [] //Remove all attachments
this.$bus.$emit('reset_fast_filters')
this.updateFastFilters(5) //This loads notes this.updateFastFilters(5) //This loads notes
}, },

View File

@ -14,11 +14,6 @@
<div class="sixteen wide middle aligned column" v-if="quickNoteId > 0"> <div class="sixteen wide middle aligned column" v-if="quickNoteId > 0">
<div v-if="quickNoteId" v-on:click="openNoteEdit" class="ui compact basic button">
<i class="file outline icon"></i>
Open Note
</div>
<div class="ui compact basic right floated button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true"> <div class="ui compact basic right floated button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true">
<i class="sync alternate reload icon"></i> <i class="sync alternate reload icon"></i>
New Scratch Pad New Scratch Pad
@ -51,6 +46,10 @@
<i class="folder open outline icon"></i> <i class="folder open outline icon"></i>
Files Files
</div> </div>
<div v-if="quickNoteId" v-on:click="openNoteEdit" class="ui right floated basic button">
<i class="file outline icon"></i>
Open Note
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,257 +0,0 @@
<template>
<div class="ui grid">
<div class="row"></div>
<!-- spacer column -->
<div class="sixteen wide column">
<h2 class="ui dividing header">
<i class="cog icon"></i>
Settings
</h2>
<div class="ui segment">
<h3>Change Password</h3>
<p>Create a new scratch pad. Old scratch pad will turn into a normal note.</p>
<div class="ui compact basic button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true">
<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 -->
<div class="ui segment">
<div class="ui grid">
<div class="sixteen wide column">
<h3 class="ui header">
Accent Color
</h3>
<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 -->
<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>
</template>
<script>
import axios from 'axios'
export default {
name: 'SettingsPage',
components: {
'logo':require('@/components/LogoComponent.vue').default,
},
data () {
return {
password: '',
qrCode: '',
verificationToken: '',
showNewNoteConfirm: false,
themeColors: [
'#21BA45', //Green
'#b5cc18', //Lime
'#00b5ad', //Teal
'#2185d0', //Blue
'#7128b9', //Violet
'#a333c8', // "Purple"
'#e03997', //Pink
'#db2828', //Red
'#f2711c', //Orange
'#fbbd08', //Yellow
'#767676', //Grey
'#303030', //Black-almost
],
change1: '',
change2: '',
change3: '',
}
},
methods: {
newQuickNote(){
this.showNewNoteConfirm = true
axios.post('/api/quick-note/new')
.then( ({data}) => {
this.showNewNoteConfirm = false
this.$store.dispatch('fetchAndUpdateUserTotals')
this.$bus.$emit('notification', 'New Scratch Pad Created')
})
},
logout() {
this.$store.commit('destroyLoginToken')
this.$router.push('/')
axios.post('/api/user/logout')
setTimeout(() => {
this.$bus.$emit('notification', 'Logged Out')
}, 200)
},
setAccentColor(color){
let root = document.documentElement
root.style.setProperty('--main-accent', color)
localStorage.setItem('main-accent', color)
if(!color || color == '#21BA45'){
localStorage.removeItem('main-accent')
}
},
getQrCode(){
axios.post('/api/user/twofactorsetup', { password:this.password })
.then(({data}) => {
this.qrCode = data
})
},
verifyQrCode(){
axios.post('/api/user/verifytwofactorsetuptoken', { password:this.password, token: this.verificationToken })
.then(({data}) => {
if(data == true){
//Two FA is set up
} else {
//It failed
}
})
},
passwordChange(){
if(this.change1 == '' || this.change2 == '' || this.change3 == ''){
this.$bus.$emit('notification', 'All Password Fields Required')
return
}
if(this.change1 == this.change2){
this.$bus.$emit('notification', 'Old password matches new password')
return
}
if(this.change2 != this.change3){
this.$bus.$emit('notification', 'New Passwords do not match')
return
}
const postData = {
'currentPass':this.change1,
'newPass':this.change3
}
axios.post('/api/user/changepassword', postData)
.then(({data}) => {
if(data){
this.$bus.$emit('notification', 'Success: Password Changed')
this.change1 = ''
this.change2 = ''
this.change3 = ''
} else {
this.$bus.$emit('notification', 'Failed to change password')
this.change1 = ''
}
})
},
revokeAllSessions(){
axios.post('/api/user/revokesessions')
.then(({data}) => {
this.$bus.$emit('notification', 'All other active sessions revoked.')
})
}
}
}
</script>

View File

@ -4,55 +4,38 @@
<div class="sixteen wide column"></div> <div class="sixteen wide column"></div>
<div class="sixteen wide column" v-if="text.length > 0 || title.length > 0"> <div class="sixteen wide column" v-if="text.length > 0 || title.length > 0">
<div class="ui text container"> <div class="ui text container squire-box" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
<div class="ui segment" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}"> <h1 v-if="title">{{title}}</h1>
<h1 v-if="title">{{title}}</h1> <div v-if="text" v-html="text"></div>
<div v-if="text" v-html="text" class="squire-box"></div>
</div>
</div> </div>
</div> </div>
<div class="sixteen wide column" v-if="!$store.getters.getLoggedIn"> <div class="sixteen wide column" v-if="!$store.getters.getLoggedIn">
<div class="ui text container"> <div class="ui text container">
<h2 class="ui header">
<div class="ui segment"> <img class="small-logo" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
<div class="content">
<div class="ui grid"> Solid Scribe is an easy, free, secure Note App
<div class="three wide middle aligned center aligned column"> <div class="sub header">
<img class="small-logo" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo"> Encrypted notes, only readable by you. Unless you share them.
</div>
<div class="thirteen wide column">
<!-- header -->
<h2 class="ui header">
<div class="content">
Solid Scribe is an easy, free, secure Note App
<div class="sub header">
Encrypted notes, only readable by you. Unless you share them.
</div>
</div>
</h2>
<!-- buttons -->
<div class="ui grid">
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/login">
<i class="plug icon"></i>Sign Up
</router-link>
</div>
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/">
<i class="comment outline icon"></i>
Learn More
</router-link>
</div>
</div>
</div> </div>
</div> </div>
</h2>
<div class="ui grid">
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/login">
<i class="plug icon"></i>Sign Up
</router-link>
</div>
<div class="eight wide center aligned column">
<router-link class="ui compact green button" to="/">
<i class="comment outline icon"></i>
Learn More
</router-link>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -116,7 +99,7 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.small-logo { .small-logo {
width: 100%; width: 30px;
height: auto; height: auto;
} }
</style> </style>

View File

@ -6,7 +6,6 @@ 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 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')
const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage') const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage')
@ -52,12 +51,6 @@ export default new Router({
name: 'Help', name: 'Help',
meta: {title:'Help'}, meta: {title:'Help'},
component: HelpPage component: HelpPage
},
{
path: '/settings',
name: 'Settings',
meta: {title:'Settings'},
component: SettingsPage
}, },
{ {
path: '/public/note/:id/:token', path: '/public/note/:id/:token',

View File

@ -85,7 +85,6 @@ export default new Vuex.Store({
Object.keys( themes[currentTheme] ).forEach( attribute => { Object.keys( themes[currentTheme] ).forEach( attribute => {
root.style.setProperty('--'+attribute, themes[currentTheme][attribute]) root.style.setProperty('--'+attribute, themes[currentTheme][attribute])
}) })
}, },
detectIsUserOnMobile(state){ detectIsUserOnMobile(state){

265
package-lock.json generated
View File

@ -49,19 +49,6 @@
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
} }
}, },
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"append-field": { "append-field": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
@ -125,21 +112,11 @@
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
}, },
"base32.js": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.0.1.tgz",
"integrity": "sha1-0EVzalex9sE58MffQlGKhOkbsro="
},
"base64-arraybuffer": { "base64-arraybuffer": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
}, },
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"base64id": { "base64id": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@ -205,39 +182,11 @@
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz",
"integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA=="
}, },
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"requires": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"buffer-equal-constant-time": { "buffer-equal-constant-time": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
}, },
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
},
"buffer-from": { "buffer-from": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@ -280,11 +229,6 @@
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
}, },
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"camelize": { "camelize": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
@ -308,29 +252,6 @@
"parse5": "^3.0.1" "parse5": "^3.0.1"
} }
}, },
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"combined-stream": { "combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -473,11 +394,6 @@
"ms": "2.0.0" "ms": "2.0.0"
} }
}, },
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"delayed-stream": { "delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -525,11 +441,6 @@
} }
} }
}, },
"dijkstrajs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs="
},
"dns-prefetch-control": { "dns-prefetch-control": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz", "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
@ -598,11 +509,6 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
}, },
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
"encodeurl": { "encodeurl": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -802,14 +708,6 @@
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
} }
}, },
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
}
},
"forever-agent": { "forever-agent": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@ -848,11 +746,6 @@
"is-property": "^1.0.2" "is-property": "^1.0.2"
} }
}, },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"getpass": { "getpass": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
@ -1033,11 +926,6 @@
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
}, },
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"indexof": { "indexof": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@ -1053,11 +941,6 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
"integrity": "sha1-N9905DCg5HVQ/lSi3v4w2KzZX2U=" "integrity": "sha1-N9905DCg5HVQ/lSi3v4w2KzZX2U="
}, },
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-property": { "is-property": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
@ -1157,15 +1040,6 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
@ -1383,27 +1257,6 @@
"ee-first": "1.1.1" "ee-first": "1.1.1"
} }
}, },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"parse5": { "parse5": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
@ -1433,11 +1286,6 @@
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=" "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ="
}, },
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
},
"path-to-regexp": { "path-to-regexp": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
@ -1448,11 +1296,6 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
}, },
"pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -1482,27 +1325,6 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew="
}, },
"qrcode": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
"integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==",
"requires": {
"buffer": "^5.4.3",
"buffer-alloc": "^1.2.0",
"buffer-from": "^1.1.1",
"dijkstrajs": "^1.0.1",
"isarray": "^2.0.1",
"pngjs": "^3.3.0",
"yargs": "^13.2.4"
},
"dependencies": {
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
}
}
},
"qs": { "qs": {
"version": "6.5.2", "version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@ -1585,16 +1407,6 @@
"lodash": "^4.17.15" "lodash": "^4.17.15"
} }
}, },
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@ -1653,11 +1465,6 @@
"send": "0.17.1" "send": "0.17.1"
} }
}, },
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"setprototypeof": { "setprototypeof": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
@ -1792,14 +1599,6 @@
} }
} }
}, },
"speakeasy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz",
"integrity": "sha1-hckaBxsJpcuGQlkNmDVmFl9XYTo=",
"requires": {
"base32.js": "0.0.1"
}
},
"sqlstring": { "sqlstring": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz",
@ -1836,16 +1635,6 @@
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
}, },
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"string_decoder": { "string_decoder": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
@ -1854,14 +1643,6 @@
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
}, },
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
},
"to-array": { "to-array": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
@ -1974,21 +1755,6 @@
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
}, },
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}
},
"ws": { "ws": {
"version": "7.2.1", "version": "7.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz",
@ -2009,42 +1775,11 @@
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q=" "integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q="
}, },
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
},
"yallist": { "yallist": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}, },
"yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"requires": {
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
},
"yeast": { "yeast": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",

View File

@ -21,11 +21,9 @@
"multer": "^1.4.2", "multer": "^1.4.2",
"mysql2": "^1.7.0", "mysql2": "^1.7.0",
"node-tesseract-ocr": "^1.0.0", "node-tesseract-ocr": "^1.0.0",
"qrcode": "^1.4.4",
"request": "^2.88.2", "request": "^2.88.2",
"request-promise": "^4.2.5", "request-promise": "^4.2.5",
"socket.io": "^2.3.0", "socket.io": "^2.3.0"
"speakeasy": "^2.0.0"
}, },
"_moduleAliases": { "_moduleAliases": {
"@root": ".", "@root": ".",

View File

@ -1,13 +1,11 @@
const db = require('@config/database') const db = require('@config/database')
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken')
const cs = require('@helpers/CryptoString') const cs = require('@helpers/CryptoString')
const speakeasy = require('speakeasy')
let Auth = {} let Auth = {}
const tokenSecretKey = process.env.JSON_KEY const tokenSecretKey = process.env.JSON_KEY
//Creates session token
Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -43,7 +41,6 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) =>
}) })
} }
//Decodes session token
Auth.decodeToken = (token, request = null) => { Auth.decodeToken = (token, request = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -123,7 +120,6 @@ Auth.decodeToken = (token, request = null) => {
} }
Auth.terminateSession = (sessionId) => { Auth.terminateSession = (sessionId) => {
return db.promise().query('DELETE from user_active_session WHERE session_id = ?', [sessionId]) return db.promise().query('DELETE from user_active_session WHERE session_id = ?', [sessionId])
} }
@ -134,143 +130,6 @@ Auth.deletAllLoginKeys = (userId) => {
return db.promise().query('DELETE FROM user_active_session WHERE user_hash = ?', [userHash]) return db.promise().query('DELETE FROM user_active_session WHERE user_hash = ?', [userHash])
} }
//Generate two factor secret key, if key is not verified, return a new one
//Only return QR code if user is not verified, only show unique QR code, once
Auth.generateTwoFactorSecretKey = (userId, password) => {
return new Promise((resolve, reject) => {
const QRCode = require('qrcode')
const User = require('@models/User')
User.getMasterKey(userId, password)
.then(masterKey => {
return db.promise().query('SELECT username, two_fa_enabled FROM user WHERE id = ?', [userId])
})
.then((r,f) => {
const tfaEnabled = r[0][0]['two_fa_enabled'] == 1
const username = r[0][0]['username']
if(!tfaEnabled){
var secret = speakeasy.generateSecret({length: 20, name: username+' - solidscribe.com'})
const twoFaSecretToken = secret.base32
const otpauthUrl = secret.otpauth_url
//Generate test Token
var token = speakeasy.totp({
secret: twoFaSecretToken,
encoding: 'base32'
})
db.promise().query('UPDATE user SET two_fa_secret = ? WHERE id = ? LIMIT 1', [twoFaSecretToken, userId])
.then((r,f) => {
QRCode.toDataURL(otpauthUrl, function(err, qrCode) {
//Return A QR code for the user, one time use
return resolve({qrCode, token})
})
})
} else {
return reject('Two factor already enabled for user')
}
})
.catch(error => {
console.log('Key auth error')
console.log(error)
return reject(false)
})
})
}
Auth.setTwoFactorEnabled = (userId, password, token, enable) => {
return new Promise((resolve, reject) => {
Auth.validateTwoFactorToken(userId, password, token)
.then(isValid => {
if(isValid){
db.promise().query('UPDATE user SET two_fa_enabled = ? WHERE id = ? LIMIT 1', [enable, userId])
.then((r, f) => {
return resolve(true)
})
} else {
return resolve(false)
}
})
})
}
Auth.validateTwoFactorToken = (userId, password, token) => {
return new Promise((resolve, reject) => {
const User = require('@models/User')
User.getMasterKey(userId, password)
.then(masterKey => {
return db.promise().query('SELECT two_fa_secret FROM user WHERE id = ?', [userId])
})
.then((r,f) => {
//Verify Token
const tokenValidates = speakeasy.totp.verify({
'secret': r[0][0]['two_fa_secret'],
'encoding': 'base32',
'token': token,
'window': 6
})
return resolve(tokenValidates)
})
.catch(error => {
console.log('Token Validation Error')
return resolve(false)
})
})
}
Auth.testTwoFactor = () => {
const userId = 93
const pass = '1'
let tfaToken = null
console.log('Test Two Factor')
Auth.generateTwoFactorSecretKey(userId, pass)
.then( ({qrCode, token}) => {
tfaToken = token
Auth.validateTwoFactorToken(userId, pass, tfaToken)
.then(validToken => {
console.log('Is Token Valid:', validToken)
})
return Auth.setTwoFactorEnabled(userId, pass, tfaToken, true)
})
.then(twoFactorEnbled => {
console.log('Was it enabled?', twoFactorEnbled)
return Auth.setTwoFactorEnabled(userId, pass, tfaToken, false)
})
.then(twoFactorEnbled => {
console.log('Was it disabled?', twoFactorEnbled)
})
.catch(error => {
console.log(error)
})
}
Auth.test = () => { Auth.test = () => {
const testUserId = 22 const testUserId = 22

View File

@ -259,15 +259,13 @@ const printResults = true
let UserTest = require('@models/User') 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('genMan15', '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)
}) })
// Test Area
//Test //Test

View File

@ -182,7 +182,7 @@ Note.create = (userId, noteTitle = '', noteText = '', masterKey) => {
const encryptedText = cs.encrypt(masterKey, salt, textObject) const encryptedText = cs.encrypt(masterKey, salt, textObject)
db.promise() db.promise()
.query(`INSERT INTO note_raw_text (text, salt, updated) VALUE (?, ?, ?)`, [encryptedText, salt, (+new Date)]) .query(`INSERT INTO note_raw_text (text, salt, updated) VALUE (?, ?, ?)`, [encryptedText, salt, created])
.then( (rows, fields) => { .then( (rows, fields) => {
const rawTextId = rows[0].insertId const rawTextId = rows[0].insertId
@ -515,7 +515,7 @@ Note.setPinned = (userId, noteId, pinnedBoolean) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const pinned = pinnedBoolean ? 1:0 const pinned = pinnedBoolean ? 1:0
const now = (+new Date) const now = Math.round((+new Date)/1000)
//Update other note attributes //Update other note attributes
return db.promise() return db.promise()
@ -532,7 +532,7 @@ Note.setArchived = (userId, noteId, archivedBoolead) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const archived = archivedBoolead ? 1:0 const archived = archivedBoolead ? 1:0
const now = (+new Date) const now = Math.round((+new Date)/1000)
//Update other note attributes //Update other note attributes
return db.promise() return db.promise()
@ -549,7 +549,7 @@ Note.setTrashed = (userId, noteId, trashedBoolean, masterKey) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const trashed = trashedBoolean ? 1:0 const trashed = trashedBoolean ? 1:0
const now = (+new Date) const now = Math.round((+new Date)/1000)
//Update other note attributes //Update other note attributes
return db.promise() return db.promise()
@ -1048,7 +1048,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
// Always prioritize pinned notes in searches. // Always prioritize pinned notes in searches.
//Default Sort, order by last updated //Default Sort, order by last updated
let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, note.created DESC, note.opened DESC' let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, note.created DESC, note.opened DESC, id DESC'
//Order by Last Created Date //Order by Last Created Date
if(fastFilters.lastCreated == 1){ if(fastFilters.lastCreated == 1){

View File

@ -10,28 +10,19 @@ QuickNote.get = (userId, masterKey) => {
db.promise() db.promise()
.query(` .query(`
SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1`, [userId]) SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1
`, [userId])
.then((rows, fields) => { .then((rows, fields) => {
//Quick Note is set, return note text //Quick Note is set, return note text
if(rows[0][0] != undefined){ if(rows[0][0] != undefined){
let noteId = rows[0][0].id let noteId = rows[0][0].id
return resolve({'noteId':noteId}) Note.get(userId, noteId, masterKey)
.then( noteObject => {
} else { return resolve(noteObject)
//Or create a new note and get the id
let finalId = null
return Note.create(userId, 'Scratch Pad', '', masterKey)
.then(insertedId => {
finalId = insertedId
db.promise().query('UPDATE note SET quick_note = 1 WHERE id = ? AND user_id = ?',[insertedId, userId])
.then((rows, fields) => {
return resolve({'noteId':finalId})
})
}) })
} else {
return resolve(null)
} }

View File

@ -5,32 +5,22 @@ const Note = require('@models/Note')
const db = require('@config/database') const db = require('@config/database')
const Auth = require('@helpers/Auth') const Auth = require('@helpers/Auth')
const cs = require('@helpers/CryptoString') const cs = require('@helpers/CryptoString')
const speakeasy = require('speakeasy')
let User = module.exports = {} let User = module.exports = {}
const version = '3.1.5' const version = '3.0.1'
//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
User.login = (username, password, authToken = null) => { User.login = (username, password) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const lowerName = username.toLowerCase() const lowerName = username.toLowerCase();
let statusObject = {
success: false,
token: null,
userId: null,
verificationRequired: false,
message: 'Incorrect Username or Password'
}
db.promise() db.promise()
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName]) .query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
.then((rows, fields) => { .then((rows, fields) => {
//
// Login User // Login User
// //
if(rows[0].length == 1){ if(rows[0].length == 1){
@ -38,76 +28,34 @@ User.login = (username, password, authToken = null) => {
//Pull out user data from database results //Pull out user data from database results
const lookedUpUser = rows[0][0] const lookedUpUser = rows[0][0]
//Verify Token if set //hash the password and check for a match
const tokenValidates = speakeasy.totp.verify({ // const salt = new Buffer(lookedUpUser.salt, 'binary')
'secret': lookedUpUser['two_fa_secret'], const salt = Buffer.from(lookedUpUser.salt, 'binary')
'encoding': 'base32', crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
'token': authToken, if(delivered_key.toString('hex') === lookedUpUser.password){
'window': 2
})
if(lookedUpUser.two_fa_enabled == 1 && !authToken){ User.generateMasterKey(lookedUpUser.id, password)
.then( result => User.getMasterKey(lookedUpUser.id, password))
.then(masterKey => {
statusObject['verificationRequired'] = true User.generateKeypair(lookedUpUser.id, masterKey)
statusObject['message'] = '2FA authentication required.' .then(({publicKey, privateKey}) => {
return resolve(statusObject) //Passback a json web token
} Auth.createToken(lookedUpUser.id, masterKey)
.then(token => {
if(lookedUpUser.two_fa_enabled == 1 && !tokenValidates){ return resolve({ token: token, userId:lookedUpUser.id })
statusObject['verificationRequired'] = true
statusObject['message'] = 'Invalid Authorization Token.'
return resolve(statusObject)
}
if(lookedUpUser.two_fa_enabled == 0 || (lookedUpUser.two_fa_enabled == 1 && tokenValidates) ){
//hash the password and check for a match
const salt = Buffer.from(lookedUpUser.salt, 'binary')
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
if(delivered_key.toString('hex') === lookedUpUser.password){
User.generateMasterKey(lookedUpUser.id, password)
.then( result => User.getMasterKey(lookedUpUser.id, password))
.then(masterKey => {
User.generateKeypair(lookedUpUser.id, masterKey)
.then(({publicKey, privateKey}) => {
//Passback a json web token
Auth.createToken(lookedUpUser.id, masterKey)
.then(token => {
statusObject['token'] = token
statusObject['userId'] = lookedUpUser.id
statusObject['success'] = true
return resolve(statusObject)
})
}) })
}) })
})
} else { } else {
return resolve(statusObject)
}
})
}
reject('Password does not match database')
}
})
} else { } else {
return reject('Incorrect Username or Password')
//If user is not found, say two factor authentication is required
statusObject['verificationRequired'] = true
statusObject['message'] = '2FA authentication required.'
//Show fake auth token message
if(authToken){
statusObject['message'] = 'Invalid Authorization Token.'
}
return resolve(statusObject)
} }
}) })
.catch(console.log) .catch(console.log)
@ -238,12 +186,6 @@ User.getCounts = (userId) => {
Object.assign(countTotals, rows[0][0]) //combine results Object.assign(countTotals, rows[0][0]) //combine results
return db.promise().query('SELECT id AS quickNote FROM note WHERE quick_note = 1 AND user_id = ?', [userId])
}).then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results
//Convert everything to an int or 0 //Convert everything to an int or 0
Object.keys(countTotals).forEach( key => { Object.keys(countTotals).forEach( key => {
const count = parseInt(countTotals[key]) const count = parseInt(countTotals[key])
@ -311,11 +253,12 @@ User.generateMasterKey = (userId, password) => {
} }
User.getMasterKey = (userId, password) => { User.getMasterKey = (userId, password) => {
return new Promise((resolve, reject) => {
if(!userId || !password){ if(!userId || !password){
reject('Need userId and password to fetch key') reject('Need userId and password to fetch key')
} }
return new Promise((resolve, reject) => {
db.promise().query('SELECT * FROM user_key WHERE user_id = ? LIMIT 1', [userId]) db.promise().query('SELECT * FROM user_key WHERE user_id = ? LIMIT 1', [userId])
.then((rows, fields) => { .then((rows, fields) => {
@ -432,66 +375,6 @@ User.getByUserName = (username) => {
}) })
} }
User.changePassword = (userId, oldPass, newPass) => {
return new Promise((resolve, reject) => {
User.getMasterKey(userId, oldPass)
.then(masterKey => {
User.getPrivateKey(userId, masterKey)
.then(privateKey => {
//If success, user has correct password
// Generate new master pass, encrypt with new password
// const masterPassword = cs.createSmallSalt()
const salt = cs.createSmallSalt()
const encryptedMasterPassword = cs.encrypt(newPass, salt, masterKey)
const encryptedPrivateKey = cs.encrypt(masterKey, salt, privateKey)
db.promise()
.query(
'UPDATE user_key SET salt = ?, `key` = ?, private_key_encrypted = ? WHERE user_id = ? LIMIT 1',
[salt, encryptedMasterPassword, encryptedPrivateKey, userId]
).then((r,f) => {
//Create login using password
let shasum = crypto.createHash('sha512') //Prepare Hash
const saltString = shasum.digest('hex')
const passwordSalt = Buffer.from(saltString, 'binary') //Generate Salt hash
const iterations = 25000
crypto.pbkdf2(newPass, passwordSalt, iterations, 512, 'sha512', function(err, delivered_key) {
const deliveredPass = delivered_key.toString('hex')
db.promise().query('UPDATE user SET password = ?, salt = ? WHERE id = ? LIMIT 1', [deliveredPass, passwordSalt, userId])
.then((r,f) => {
return resolve(true)
})
})
})
})
})
.catch(error => {
resolve(false)
})
})
}
User.revokeActiveSessions = (userId, sessionId) => {
return new Promise((resolve, reject) => {
const userHash = cs.hash(String(userId)).toString('base64')
db.promise().query('DELETE FROM user_active_session WHERE user_hash = ? AND session_id != ?', [userHash, sessionId])
.then((r,f) => {
resolve(true)
})
})
}
User.deleteUser = (userId, password) => { User.deleteUser = (userId, password) => {
//Verify user is correct by decryptig master key with password //Verify user is correct by decryptig master key with password
@ -531,7 +414,6 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
const randomUsername = Math.random().toString(36).substring(2, 15); const randomUsername = Math.random().toString(36).substring(2, 15);
const randomPassword = '1' const randomPassword = '1'
const secondPassword = '2'
User.register(testUserName, password) User.register(testUserName, password)
.then( ({ token, userId }) => { .then( ({ token, userId }) => {
@ -539,7 +421,7 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
if(printResults) console.log('Test: Register User '+testUserName+' - Pass') if(printResults) console.log('Test: Register User '+testUserName+' - Pass')
return User.getMasterKey(testUserId, password) return User.getMasterKey(testUserId, password)
}) })
.then(newMasterKey => { .then(newMasterKey => {
masterKey = newMasterKey masterKey = newMasterKey
@ -571,26 +453,6 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
if(printResults) console.log('Test: Login New User - Pass') if(printResults) console.log('Test: Login New User - Pass')
return User.changePassword(testUserId, randomPassword, secondPassword)
})
.then(passwordChangeResults => {
if(printResults) console.log('Test: Password Change - ', passwordChangeResults?'Pass':'Fail')
return User.login(testUserName, secondPassword)
})
.then(reLogin => {
if(printResults) console.log('Test: Login With new Password - Pass')
return User.getMasterKey(testUserId, secondPassword)
})
.then(newMasterKey => {
masterKey = newMasterKey
resolve({testUserId, masterKey}) resolve({testUserId, masterKey})
}) })
}) })

View File

@ -6,8 +6,9 @@ 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) {
if(req.headers.userId){ if(userId = req.headers.userId){
userId = req.headers.userId userId = req.headers.userId
masterKey = req.headers.masterKey masterKey = req.headers.masterKey
} }

View File

@ -1,8 +1,7 @@
var express = require('express') var express = require('express')
var router = express.Router() var router = express.Router()
const User = require('@models/User') let User = require('@models/User');
const Auth = require('@helpers/Auth')
const cs = require('@helpers/CryptoString') const cs = require('@helpers/CryptoString')
// middleware that is specific to this router // middleware that is specific to this router
@ -10,14 +9,26 @@ router.use(function timeLog (req, res, next) {
// console.log('Time: ', Date.now()) // console.log('Time: ', Date.now())
next() next()
}) })
// define the home page route
router.get('/', function (req, res) {
res.send('User Home Page ' + User.getUsername())
})
// define the about route
router.get('/about', function (req, res) {
User.getUsername(req.headers.userId)
.then( data => res.send(data) )
})
// Login User // Login User
router.post('/login', function (req, res) { router.post('/login', function (req, res) {
User.login(req.body.username, req.body.password, req.body.authToken) User.login(req.body.username, req.body.password)
.then( returnData => { .then( returnData => {
res.send(returnData) res.send(returnData)
}) })
.catch(e => {
res.send(false)
})
}) })
// Logout User // Logout User
router.post('/logout', function (req, res) { router.post('/logout', function (req, res) {
@ -28,7 +39,10 @@ router.post('/logout', function (req, res) {
}) })
}) })
// Register User
// Login User
router.post('/register', function (req, res) { router.post('/register', function (req, res) {
User.register(req.body.username, req.body.password) User.register(req.body.username, req.body.password)
@ -41,24 +55,6 @@ router.post('/register', function (req, res) {
}) })
}) })
// change password
router.post('/changepassword', function (req, res) {
User.changePassword(req.headers.userId, req.body.currentPass, req.body.newPass)
.then( returnData => {
res.send(returnData)
})
})
//Revoke all active session keys for user
router.post('/revokesessions', function(req, res) {
User.revokeActiveSessions(req.headers.userId, req.headers.sessionId)
.then( returnData => {
res.send(returnData)
})
})
// fetch counts of users notes // fetch counts of users notes
router.post('/totals', function (req, res) { router.post('/totals', function (req, res) {
@ -66,30 +62,5 @@ router.post('/totals', function (req, res) {
.then( countsObject => res.send( countsObject )) .then( countsObject => res.send( countsObject ))
}) })
//
// Two Factor Auth Setup
//
router.post('/twofactorsetup', function (req, res) {
//Send QR code to user for 2FA setup
Auth.generateTwoFactorSecretKey(req.headers.userId, req.body.password)
.then( ({ qrCode }) => { res.send( qrCode ) })
})
router.post('/verifytwofactorsetuptoken', function (req, res) {
//Verify Users QR code with token
Auth.setTwoFactorEnabled(req.headers.userId, req.body.password, req.body.token, true)
.then( ( results ) => { res.send( results ) })
})
router.post('/validatetwofactortoken', function (req, res) {
//Verify Users QR code with token
Auth.validateTwoFactorToken(req.headers.userId, req.body.password, req.body.token)
.then( ( results ) => { res.send( results ) })
})
module.exports = router module.exports = router