Compare commits
2 Commits
cca89a60d8
...
e7d1cc7bc9
Author | SHA1 | Date | |
---|---|---|---|
|
e7d1cc7bc9 | ||
|
47fff0e1ee |
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
@ -97,6 +97,12 @@ export default {
|
||||
//Detect if user is on a mobile browser and set a flag in store
|
||||
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
|
||||
const themeNumber = localStorage.getItem('nightMode')
|
||||
if(themeNumber != null){
|
||||
|
@ -4,6 +4,10 @@ const helpers = {}
|
||||
|
||||
helpers.timeAgo = (time) => {
|
||||
|
||||
if(time == null){
|
||||
time = Math.round(time/1000)
|
||||
}
|
||||
|
||||
if(time.toString().length >= 13){
|
||||
time = Math.round(time/1000)
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
:root {
|
||||
|
||||
--main-accent: #16ab39;
|
||||
/*main accent for all buttons, icons and logos*/
|
||||
--main-accent: #21BA45;
|
||||
|
||||
/*theme colors */
|
||||
--body_bg_color: #f5f6f7;
|
||||
@ -85,6 +86,8 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ui.form input:not([type]),
|
||||
.ui.form input:not([type]):focus,
|
||||
.ui.form textarea:not([type]),
|
||||
@ -93,6 +96,21 @@ body {
|
||||
background-color: var(--small_element_bg_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 {
|
||||
color: var(--text_color);
|
||||
background-color: transparent;
|
||||
@ -105,7 +123,7 @@ div.ui.basic.green.label {
|
||||
background-color: var(--small_element_bg_color) !important;
|
||||
}
|
||||
.ui.basic.button, .ui.basic.buttons .button {
|
||||
background-color: var(--small_element_bg_color) !important;
|
||||
background-color: var(--small_element_bg_color);
|
||||
color: var(--text_color) !important;
|
||||
border: 1px solid;
|
||||
border-color: var(--dark_border_color) !important;
|
||||
@ -125,6 +143,24 @@ div.ui.basic.green.label {
|
||||
color: var(--text_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*/
|
||||
|
||||
/*//
|
||||
@ -235,7 +271,7 @@ div.ui.basic.green.label {
|
||||
/*border-bottom: 1px solid #ccc;*/
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: transparent transparent;
|
||||
caret-color: #21BA45;
|
||||
caret-color: var(--main-accent);
|
||||
}
|
||||
.squire-box::selection,
|
||||
.squire-box::-moz-selection {
|
||||
@ -253,7 +289,7 @@ div.ui.basic.green.label {
|
||||
cursor: pointer;
|
||||
}
|
||||
.night-mode .note-card-text i:not(.icon),
|
||||
.night-mode .squire-box i {
|
||||
.night-mode .squire-box i:not(.icon) {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
@ -322,9 +358,76 @@ div.ui.basic.green.label {
|
||||
|
||||
font-family: 'Icons';
|
||||
content: "\f058";
|
||||
color: #21BA45;
|
||||
color: var(--main-accent);
|
||||
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 */
|
||||
@media only screen and (max-width: 740px) {
|
||||
|
||||
@ -365,7 +468,7 @@ div.ui.basic.green.label {
|
||||
|
||||
font-family: 'Icons';
|
||||
content: "\f058";
|
||||
color: #21BA45;
|
||||
color: var(--main-accent);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -1096,6 +1096,9 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
|
||||
}
|
||||
|
||||
while ( true ) {
|
||||
if ( endContainer === endMax || endContainer === root ) {
|
||||
break;
|
||||
}
|
||||
if ( maySkipBR &&
|
||||
endContainer.nodeType !== TEXT_NODE &&
|
||||
endContainer.childNodes[ endOffset ] &&
|
||||
@ -1103,9 +1106,7 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
|
||||
endOffset += 1;
|
||||
maySkipBR = false;
|
||||
}
|
||||
if ( endContainer === endMax ||
|
||||
endContainer === root ||
|
||||
endOffset !== getLength( endContainer ) ) {
|
||||
if ( endOffset !== getLength( endContainer ) ) {
|
||||
break;
|
||||
}
|
||||
parent = endContainer.parentNode;
|
||||
@ -1117,6 +1118,20 @@ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
|
||||
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,
|
||||
// or null if no block is contained by the range.
|
||||
var getStartBlockOfRange = function ( range, root ) {
|
||||
@ -1285,10 +1300,13 @@ var onKey = function ( event ) {
|
||||
if ( event.altKey ) { modifiers += 'alt-'; }
|
||||
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
|
||||
if ( event.metaKey ) { modifiers += 'meta-'; }
|
||||
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
||||
}
|
||||
// However, on Windows, shift-delete is apparently "cut" (WTF right?), so
|
||||
// we want to let the browser handle shift-delete.
|
||||
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
||||
// we want to let the browser handle shift-delete in this situation.
|
||||
if ( isWin && event.shiftKey && key === 'delete' ) {
|
||||
modifiers += 'shift-';
|
||||
}
|
||||
|
||||
key = modifiers + key;
|
||||
|
||||
@ -1465,12 +1483,7 @@ var handleEnter = function ( self, shiftKey, range ) {
|
||||
// just play it safe and insert a <br>.
|
||||
if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
|
||||
// If inside an <a>, move focus out
|
||||
parent = getNearest( range.endContainer, root, 'A' );
|
||||
if ( parent ) {
|
||||
parent = parent.parentNode;
|
||||
moveRangeBoundariesUpTree( range, parent, parent, root );
|
||||
range.collapse( false );
|
||||
}
|
||||
moveRangeBoundaryOutOf( range, 'A', root );
|
||||
insertNodeInRange( range, self.createElement( 'BR' ) );
|
||||
range.collapse( false );
|
||||
self.setSelection( range );
|
||||
@ -1821,16 +1834,45 @@ 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 + 'i' ] = mapKeyToFormat( 'I' );
|
||||
keyHandlers[ ctrlKey + 'u' ] = mapKeyToFormat( 'U' );
|
||||
keyHandlers[ ctrlKey + 'shift-7' ] = mapKeyToFormat( 'S' );
|
||||
keyHandlers[ ctrlKey + 'shift-5' ] = mapKeyToFormat( 'SUB', { tag: 'SUP' } );
|
||||
keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } );
|
||||
keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
|
||||
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
|
||||
keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
||||
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
||||
keyHandlers[ ctrlKey + 'shift-8' ] =
|
||||
toggleList( /(?:^|>)UL/, 'makeUnorderedList' );
|
||||
keyHandlers[ ctrlKey + 'shift-9' ] =
|
||||
toggleList( /(?:^|>)OL/, 'makeOrderedList' );
|
||||
keyHandlers[ ctrlKey + '[' ] =
|
||||
changeIndentationLevel( 'decreaseQuoteLevel', 'decreaseListLevel' );
|
||||
keyHandlers[ ctrlKey + ']' ] =
|
||||
changeIndentationLevel( 'increaseQuoteLevel', 'increaseListLevel' );
|
||||
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
|
||||
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
||||
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
||||
@ -4417,6 +4459,12 @@ proto.insertHTML = function ( html, isPaste ) {
|
||||
this._docWasChanged();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,6 @@
|
||||
openNote(){
|
||||
const noteId = this.item.note_id
|
||||
this.$router.push('/notes/open/'+noteId)
|
||||
this.$bus.$emit('open_note', noteId)
|
||||
},
|
||||
openEditAttachments(){
|
||||
const noteId = this.item.note_id
|
||||
|
@ -24,9 +24,9 @@
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
this.$bus.$on('reset_fast_filters', () => {
|
||||
this.orderString = 'Order by Last Edited'
|
||||
})
|
||||
// this.$bus.$on('reset_fast_filters', () => {
|
||||
// this.orderString = 'Order by Last Edited'
|
||||
// })
|
||||
},
|
||||
methods:{
|
||||
displayString(){
|
||||
|
@ -2,30 +2,31 @@
|
||||
|
||||
.popup-body {
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
min-height: 50px;
|
||||
min-width: 200px;
|
||||
max-width: calc(100% - 20px);
|
||||
max-width: calc(100% - 30px);
|
||||
z-index: 1002;
|
||||
|
||||
border-top: 2px solid #21ba45;
|
||||
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
|
||||
border-top-right-radius: 4px;
|
||||
border-top-left-radius: 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
color: white;
|
||||
background-color: var(--main-accent);
|
||||
}
|
||||
.popup-row {
|
||||
padding: 1em 5px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.popup-row > span {
|
||||
width: calc(100% - 50px);
|
||||
/*width: calc(100% - 50px);*/
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
padding: 0 10px 0;
|
||||
font-size: 1.25em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.popup-row + .popup-row {
|
||||
border-top: 1px solid #FFF;
|
||||
@ -36,12 +37,10 @@
|
||||
}
|
||||
@keyframes slide-in-bottom {
|
||||
0% {
|
||||
transform: translateY(1000px);
|
||||
opacity: 0;
|
||||
transform: translateY(-1000px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,14 +62,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
<template>
|
||||
<div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0">
|
||||
<div class="popup-row color-fade" v-for="item in notifications">
|
||||
<span>{{ item }}</span>
|
||||
<div class="popup-row" v-for="item in notifications">
|
||||
<div class="meter">
|
||||
<span><span class="progress"></span></span>
|
||||
</div>
|
||||
<span><i class="small info circle icon"></i>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -94,9 +125,9 @@
|
||||
},
|
||||
mounted(){
|
||||
|
||||
// this.$bus.$emit('notification', 'Password Protection Removed')
|
||||
// this.$bus.$emit('notification', 'Password Protection Removed')
|
||||
// this.$bus.$emit('notification', 'Password Protection Removed')
|
||||
// this.$bus.$emit('notification', 'Password Protection Removed Login did not succeed')
|
||||
// this.$bus.$emit('notification', 'Password Protection Removed your life is exposed to the internet')
|
||||
// this.$bus.$emit('notification', 'Password Protection Removed everyone can see everything')
|
||||
|
||||
},
|
||||
methods: {
|
||||
@ -105,7 +136,7 @@
|
||||
clearTimeout(this.totalTimeout)
|
||||
this.totalTimeout = setTimeout(() => {
|
||||
this.dismiss()
|
||||
}, 4000)
|
||||
}, 3000)
|
||||
},
|
||||
dismiss(){
|
||||
this.notifications = []
|
||||
|
@ -19,9 +19,10 @@
|
||||
bottom: 0;
|
||||
}
|
||||
.menu-logo-display {
|
||||
width: 25px;
|
||||
margin: 5px 0 0 42px;
|
||||
width: 27px;
|
||||
margin: 5px 0 0 41px;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
@ -79,15 +80,17 @@
|
||||
background-color: var(--small_element_bg_color);
|
||||
border-bottom: 1px solid;
|
||||
border-color: var(--border_color);
|
||||
padding: 5px 1rem 5px;
|
||||
/*padding: 5px 1rem 5px;*/
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.place-holder {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
.top-menu-bar img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
.logo-display {
|
||||
width: 27px;
|
||||
height: auto;
|
||||
}
|
||||
.version-display {
|
||||
position: absolute;
|
||||
@ -101,6 +104,19 @@
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -110,46 +126,52 @@
|
||||
|
||||
<!-- collapsed menu, appears as a bar -->
|
||||
<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 class="mobile-button">
|
||||
<i class="green link bars icon" v-on:click="collapseMenu"></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> -->
|
||||
<div class="mobile-button"></div>
|
||||
|
||||
<router-link v-if="loggedIn" class="ui basic icon button" exact-active-class="active" to="/attachments">
|
||||
<i class="open folder outline icon"></i>
|
||||
<!-- 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>
|
||||
|
||||
</div>
|
||||
<!-- 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>
|
||||
|
||||
<div class="two wide center aligned bottom aligned column">
|
||||
<img loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="seven wide right aligned column">
|
||||
<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 v-on:click="toggleNightMode" class="ui large basic compact icon button">
|
||||
<i class="green moon outline icon"></i>
|
||||
</div>
|
||||
<div class="mobile-button"></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 v-if="!disableNewNote" @click="createNote" class="mobile-button">
|
||||
<i class="green plus icon"></i>
|
||||
</span>
|
||||
<span v-if="disableNewNote" class="ui large basic compact icon button">
|
||||
<span v-if="disableNewNote" class="mobile-button">
|
||||
<i class="grey plus icon"></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shade" v-if="mobile && !collapsed" v-on:click="collapseMenu"></div>
|
||||
|
||||
@ -159,10 +181,8 @@
|
||||
<div class="global-menu" v-if="!collapsed" v-on:click="menuClicked">
|
||||
|
||||
<div class="menu-section" v-on:click="collapseMenu">
|
||||
<!-- <div class="menu-item menu-button" > -->
|
||||
<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> -->
|
||||
<logo class="menu-logo-display" color="var(--main-accent)" />
|
||||
</div>
|
||||
|
||||
<div class="menu-section" v-if="loggedIn">
|
||||
@ -208,9 +228,26 @@
|
||||
</div>
|
||||
|
||||
<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
|
||||
</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 class="menu-section" v-if="!loggedIn">
|
||||
@ -236,14 +273,14 @@
|
||||
|
||||
<div class="menu-section">
|
||||
<router-link class="menu-item menu-button" exact-active-class="active" to="/help">
|
||||
<i class="question circle outline icon"></i>Help
|
||||
<i class="question circle outline icon"></i>Help | Terms
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="menu-section" v-if="loggedIn" :data-tooltip="`Logout ${this.$store.getters.getUsername}`" data-inverted="" data-position="right center">
|
||||
<div v-on:click="destroyLoginToken" class="menu-item menu-button">
|
||||
<i v-if="userIcon" class="user outline icon"></i>{{ usernameDisplay }}
|
||||
</div>
|
||||
<div class="menu-section" v-if="loggedIn" :data-tooltip="`Settings for ${this.$store.getters.getUsername}`" data-inverted="" data-position="right center">
|
||||
<router-link class="menu-item menu-button" exact-active-class="active" to="/settings">
|
||||
<i v-if="userIcon" class="cog icon"></i>{{ usernameDisplay }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
@ -264,6 +301,7 @@
|
||||
components: {
|
||||
'search-input': require('@/components/SearchInput.vue').default,
|
||||
'counter':require('@/components/AnimatedCounterComponent.vue').default,
|
||||
'logo':require('@/components/LogoComponent.vue').default,
|
||||
},
|
||||
data: function(){
|
||||
return {
|
||||
@ -274,10 +312,14 @@
|
||||
disableNewNote: false,
|
||||
menuOpen: true,
|
||||
userIcon: true,
|
||||
resizeDebounce: null,
|
||||
}
|
||||
},
|
||||
beforeCreate: function(){
|
||||
|
||||
beforeMount(){
|
||||
window.addEventListener('resize', this.resizeEventHandler)
|
||||
},
|
||||
beforeDestroy(){
|
||||
window.removeEventListener('resize', this.resizeEventHandler)
|
||||
},
|
||||
mounted: function(){
|
||||
this.mobile = this.$store.getters.getIsUserOnMobile
|
||||
@ -292,6 +334,8 @@
|
||||
this.version = localStorage.getItem('currentVersion')
|
||||
}
|
||||
|
||||
this.resizeEventHandler() //Trigger resize event
|
||||
|
||||
},
|
||||
computed: {
|
||||
loggedIn () {
|
||||
@ -316,6 +360,32 @@
|
||||
},
|
||||
},
|
||||
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(){
|
||||
//Collapse menu when item is clicked in mobile
|
||||
if(this.mobile && !this.collapsed){
|
||||
@ -334,28 +404,22 @@
|
||||
|
||||
},
|
||||
createNote(event){
|
||||
const title = ''
|
||||
|
||||
this.disableNewNote = true
|
||||
|
||||
axios.post('/api/note/create', {title})
|
||||
axios.post('/api/note/create', {title:''})
|
||||
.then(response => {
|
||||
|
||||
if(response.data && response.data.id){
|
||||
//Redirect to note page if user is not on it
|
||||
this.$bus.$emit('open_note', response.data.id)
|
||||
|
||||
//Push new note to url and it will open
|
||||
this.$router.push('/notes/open/'+response.data.id)
|
||||
|
||||
this.disableNewNote = false
|
||||
}
|
||||
})
|
||||
.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(){
|
||||
this.$store.commit('toggleNightMode')
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<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">
|
||||
<rect fill="none" :stroke="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5">
|
||||
<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">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
dur="0.5s"
|
||||
@ -12,7 +12,7 @@
|
||||
attributeType="XML"
|
||||
begin="rectBox.end"/>
|
||||
</rect>
|
||||
<rect x="25" y="25" :fill="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" width="50" height="50">
|
||||
<rect x="25" y="25" :fill="$store.getters.getIsNightMode > 0 ? '#FFF':'var(--main-accent)'" width="50" height="50">
|
||||
<animate
|
||||
attributeName="height"
|
||||
dur="1.3s"
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
<template>
|
||||
|
||||
<div v-on:keyup.enter="login()">
|
||||
@ -14,6 +15,11 @@
|
||||
<input v-model="password" type="password" name="password" placeholder="Password">
|
||||
</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="ui fluid buttons">
|
||||
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui green button">
|
||||
@ -27,34 +33,54 @@
|
||||
</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>
|
||||
|
||||
<!-- Thin form display -->
|
||||
<div v-if="thin" class="ui small form">
|
||||
<div class="fields">
|
||||
<div class="four wide field">
|
||||
<div class="equal width fields">
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
|
||||
</div>
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="password" type="password" name="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<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>
|
||||
<!-- 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">
|
||||
<i class="plug icon"></i>
|
||||
Sign Up
|
||||
</div>
|
||||
</div>
|
||||
<div class="four wide field">
|
||||
<div class="field">
|
||||
<div v-on:click="login()" class="ui fluid button">
|
||||
<i class="power icon"></i>
|
||||
Login
|
||||
</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>
|
||||
|
||||
|
||||
@ -83,7 +109,9 @@
|
||||
return {
|
||||
enabled: false,
|
||||
username: '',
|
||||
password: ''
|
||||
password: '',
|
||||
authToken: '',
|
||||
require2FA: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -139,14 +167,30 @@
|
||||
return
|
||||
}
|
||||
|
||||
axios.post('/api/user/login', {'username': this.username, 'password': this.password})
|
||||
axios.post('/api/user/login', {'username': this.username, 'password': this.password, 'authToken':this.authToken })
|
||||
.then(({data}) => {
|
||||
|
||||
if(data == false){
|
||||
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
|
||||
//Enable 2FA on form
|
||||
if(data.success == false && data.verificationRequired == true && this.require2FA == false){
|
||||
this.$bus.$emit('notification', data.message)
|
||||
this.require2FA = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.authForm.focus()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if(data.success == false){
|
||||
this.$bus.$emit('notification', data.message)
|
||||
return
|
||||
}
|
||||
|
||||
if(data.success){
|
||||
this.finalizeLogin(data)
|
||||
return
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.$bus.$emit('notification', 'Unable to Login - Incorrect Username or Password')
|
||||
@ -155,3 +199,12 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css" scoped="true">
|
||||
.small-terms {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
80
client/src/components/LogoComponent.vue
Normal file
80
client/src/components/LogoComponent.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<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>
|
@ -2,9 +2,8 @@
|
||||
<!-- change class to .master-note-edit to have it popup on the screen -->
|
||||
<div
|
||||
id="InputNotes"
|
||||
class="master-note-edit full-focus"
|
||||
@keyup.esc="close()"
|
||||
:class="[ 'position-'+position ]"
|
||||
class="master-note-edit full-focus position-0"
|
||||
@keyup.esc="closeButtonAction()"
|
||||
>
|
||||
|
||||
<!-- Giant Edit Note Menu -->
|
||||
@ -14,7 +13,7 @@
|
||||
<div class="edit-spacer"></div>
|
||||
|
||||
<div class="menu-top-half">
|
||||
<div class="edit-button" v-on:click="close()" data-tooltip="Close" data-position="bottom center" data-inverted>
|
||||
<div class="edit-button" v-on:click="closeButtonAction()" data-tooltip="Close" data-position="bottom center" data-inverted>
|
||||
<i class="close icon"></i>
|
||||
</div>
|
||||
|
||||
@ -62,6 +61,14 @@
|
||||
|
||||
<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>
|
||||
<i class="remove format icon"></i>
|
||||
</div>
|
||||
@ -95,7 +102,8 @@
|
||||
|
||||
<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="archive icon"></i></span>
|
||||
</div>
|
||||
@ -105,17 +113,13 @@
|
||||
<span v-if="pinned != 1"><i class="pin icon"></i></span>
|
||||
</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">
|
||||
<i class="green eye icon"></i> {{ usersOnNote }}
|
||||
</div>
|
||||
|
||||
<!-- <div class="edit-button" v-on:click="simulateTyping()">
|
||||
<!--
|
||||
<div class="edit-button" v-on:click="simulateTyping()">
|
||||
<i class="purple bolt icon"></i>
|
||||
</div> -->
|
||||
|
||||
@ -132,7 +136,6 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="bottom-edit-menu"></div>
|
||||
@ -163,7 +166,7 @@
|
||||
</textarea>
|
||||
|
||||
<!-- Squire Box -->
|
||||
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Type Note Here"></div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -224,19 +227,19 @@
|
||||
<div class="sixteen wide column">
|
||||
<div class="ui labeled icon fluid basic button" v-on:click="sortList">
|
||||
<i class="sort amount up icon"></i>
|
||||
Sort List items (Move checked to bottom)
|
||||
Sort List
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems">
|
||||
<i class="trash icon"></i>
|
||||
Delete Checked Items
|
||||
Delete Checked
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems">
|
||||
<i class="list ul icon"></i>
|
||||
Uncheck all Checked items
|
||||
Uncheck All
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
@ -245,6 +248,15 @@
|
||||
Simple Math
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide column">
|
||||
<!-- data-tooltip="Files on note" -->
|
||||
<div v-on:click="openEditAttachment" class="ui labeled icon fluid basic button">
|
||||
<i class="folder icon"></i>
|
||||
Note Files
|
||||
{{ attachmentCount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sixteen wide column" v-if="rawTextId > 0">
|
||||
<h2>Share Note</h2>
|
||||
<share-note-component
|
||||
@ -257,13 +269,27 @@
|
||||
</div>
|
||||
</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 -->
|
||||
<div class="full-focus-shade shade1"
|
||||
:class="{ 'slide-out-left':sizeDown }"
|
||||
v-on:click="close()"></div>
|
||||
v-on:click="closeButtonAction()"></div>
|
||||
<div class="full-focus-shade shade2"
|
||||
:class="{ 'slide-out-right':sizeDown }"
|
||||
v-on:click="close()"></div>
|
||||
v-on:click="closeButtonAction()"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -335,6 +361,7 @@
|
||||
images: false,
|
||||
options: false,
|
||||
colorpicker: false,
|
||||
table: false,
|
||||
|
||||
//Diff text/sync text variables
|
||||
diffTextTimeout: null,
|
||||
@ -355,7 +382,7 @@
|
||||
//Handle changes in URL to
|
||||
|
||||
if(newVal.id == undefined || newVal.id != this.noteid){
|
||||
this.close()
|
||||
// this.closeButtonAction()
|
||||
}
|
||||
|
||||
//Reset all note menus on URL change
|
||||
@ -364,6 +391,7 @@
|
||||
this.tags = false
|
||||
this.options = false
|
||||
this.images = false
|
||||
this.table = false
|
||||
|
||||
//If a menu value is set, open it
|
||||
if(newVal.openMenu){
|
||||
@ -394,18 +422,20 @@
|
||||
|
||||
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
// if(this.editor){
|
||||
//Obliterate squire instance
|
||||
this.editor.destroy()
|
||||
// }
|
||||
|
||||
this.close()
|
||||
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
//Show loading for a minimum time
|
||||
setTimeout(()=>{
|
||||
this.forceShowLoading = false
|
||||
}, 500)
|
||||
|
||||
document.addEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
// document.addEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
//Init squire as early as possible
|
||||
this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
|
||||
@ -599,6 +629,30 @@
|
||||
|
||||
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
|
||||
this.$nextTick( () => {
|
||||
//Wait a moment to get item under cursor
|
||||
@ -612,10 +666,6 @@
|
||||
})
|
||||
})
|
||||
|
||||
this.editor.addEventListener('keydown', event => {
|
||||
|
||||
})
|
||||
|
||||
//Bind event handlers
|
||||
this.editor.addEventListener('keyup', event => {
|
||||
|
||||
@ -682,7 +732,7 @@
|
||||
//Block notes you don't have access to from opening
|
||||
if(response.data === false){
|
||||
this.$bus.$emit('notification', 'Error opening Note')
|
||||
this.close(true)
|
||||
this.close()
|
||||
return
|
||||
}
|
||||
|
||||
@ -915,18 +965,21 @@
|
||||
|
||||
return hash;
|
||||
},
|
||||
close(force = false){
|
||||
closeButtonAction(){
|
||||
this.sizeDown = true
|
||||
//This timeout allows animation to play before closing
|
||||
setTimeout(() => {
|
||||
this.$router.push('/notes')
|
||||
}, 300)
|
||||
},
|
||||
close(){
|
||||
|
||||
// force = true
|
||||
|
||||
// console.log(`Close Note ${this.noteid} -> force: ${force}, modified: ${this.modified}`)
|
||||
|
||||
if(force){
|
||||
this.$bus.$emit('close_active_note', {
|
||||
noteId: this.noteid, modified: this.modified
|
||||
})
|
||||
return
|
||||
}
|
||||
//Skip everything if foce close is true. Note will just die.
|
||||
if(this.currentNoteId == 0){ return }
|
||||
|
||||
this.loadingMessage = 'Saving...'
|
||||
this.loading = true
|
||||
@ -938,14 +991,10 @@
|
||||
axios.post('/api/note/reindex')
|
||||
}
|
||||
|
||||
this.sizeDown = true
|
||||
//This timeout allows animation to play before closing
|
||||
setTimeout(() => {
|
||||
this.$bus.$emit('close_active_note', {
|
||||
noteId: this.noteid, modified: this.modified
|
||||
})
|
||||
return
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
destroyWebSockets(){
|
||||
@ -1005,8 +1054,8 @@
|
||||
let element = this.$refs.titleTextarea
|
||||
let padding = 0
|
||||
|
||||
element.style.height = 'auto';
|
||||
element.style.height = (element.scrollHeight + padding) +'px';
|
||||
element.style.height = 'auto'
|
||||
element.style.height = (element.scrollHeight + padding) +'px'
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1152,7 +1201,7 @@
|
||||
background-color: var(--menu-accent);
|
||||
}
|
||||
.edit-active {
|
||||
background-color: #21BA45;
|
||||
background-color: var(--main-accent);
|
||||
color: white;
|
||||
}
|
||||
.edit-divide {
|
||||
|
@ -67,7 +67,7 @@
|
||||
</span>
|
||||
|
||||
<span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
|
||||
{{$helpers.timeAgo(note.updated)}}
|
||||
{{$helpers.timeAgo( note.updated )}}
|
||||
</span>
|
||||
|
||||
<span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
|
||||
@ -234,12 +234,12 @@
|
||||
},
|
||||
justClosed(){
|
||||
|
||||
//Scroll note into view
|
||||
// this.$el.scrollIntoView({
|
||||
// behavior: 'smooth',
|
||||
// block: 'center',
|
||||
// inline: 'center'
|
||||
// })
|
||||
// Scroll note into view
|
||||
this.$el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'center'
|
||||
})
|
||||
|
||||
//After scroll, trigger green outline animation
|
||||
setTimeout(() => {
|
||||
@ -356,13 +356,14 @@
|
||||
|
||||
/*Strict font sizes for card display*/
|
||||
.small-text {
|
||||
max-height: 261px;
|
||||
max-height: 267px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
.small-text, .small-text > p, .small-text > h1, .small-text > h2 {
|
||||
/*font-size: 1.0em !important;*/
|
||||
font-size: 16px !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.small-text > p, , .small-text > h1, .small-text > h2 {
|
||||
margin-bottom: 0.5em;
|
||||
@ -370,7 +371,7 @@
|
||||
.big-text > p:first-child,
|
||||
.big-text > h1, .big-text > h2 {
|
||||
/*font-size: 1.3em !important;*/
|
||||
font-size: 17px !important;
|
||||
font-size: 20px !important;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
@ -415,14 +416,14 @@
|
||||
border-color: var(--border_color);
|
||||
/*width: calc(33.333% - 10px);*/
|
||||
width: calc(25% - 10px);
|
||||
min-width: 190px;
|
||||
/*min-width: 190px;*/
|
||||
min-height: 130px;
|
||||
/*transition: box-shadow 0.3s;*/
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
line-height: 1.8rem;
|
||||
letter-spacing: 0.02rem;
|
||||
letter-spacing: 0.05rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
@ -555,27 +556,50 @@
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Tweak mobile display to show only one column */
|
||||
@media only screen and (min-width: 1500px) {
|
||||
.note-title-display-card {
|
||||
width: calc(20% - 10px);
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 740px) {
|
||||
/* Break points determine when display cards shrink */
|
||||
@media only screen and (max-width: 700px) {
|
||||
.note-title-display-card {
|
||||
width: calc(100% + 10px);
|
||||
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*/
|
||||
@keyframes bgin {
|
||||
0% {
|
||||
background-image:
|
||||
linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* TopLeft to Right */
|
||||
linear-gradient(to bottom, #21BA45 50%, #21BA45 100%), /* TopRight to Bottom */
|
||||
linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* BottomLeft to Right*/
|
||||
linear-gradient(to bottom, #21BA45 50%, #21BA45 100%); /* TopLeft to Bottom */
|
||||
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* TopLeft to Right */
|
||||
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%), /* TopRight to Bottom */
|
||||
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* BottomLeft to Right*/
|
||||
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%); /* TopLeft to Bottom */
|
||||
/*Initial state, no BG*/
|
||||
background-size: 0 4px, 4px 0, 0 4px, 4px 0;
|
||||
}
|
||||
@ -590,10 +614,10 @@
|
||||
30% {
|
||||
background-size: 100% 4px, 4px 100%, 100% 4px, 4px 100%;
|
||||
background-image:
|
||||
linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* TopLeft to Right */
|
||||
linear-gradient(to bottom, #21BA45 50%, #21BA45 100%), /* TopRight to Bottom */
|
||||
linear-gradient(to right, #21BA45 50%, #21BA45 100%), /* BottomLeft to Right*/
|
||||
linear-gradient(to bottom, #21BA45 50%, #21BA45 100%); /* TopLeft to Bottom */
|
||||
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* TopLeft to Right */
|
||||
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%), /* TopRight to Bottom */
|
||||
linear-gradient(to right, var(--main-accent) 50%, var(--main-accent) 100%), /* BottomLeft to Right*/
|
||||
linear-gradient(to bottom, var(--main-accent) 50%, var(--main-accent) 100%); /* TopLeft to Bottom */
|
||||
}
|
||||
100% {
|
||||
background-image:
|
||||
|
@ -98,13 +98,17 @@
|
||||
},
|
||||
beforeCreate: function(){
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
beforeMount(){
|
||||
//search clear
|
||||
this.$bus.$on('reset_fast_filters', () => {
|
||||
this.searchTerm = ''
|
||||
this.tagSuggestions = []
|
||||
})
|
||||
},
|
||||
beforeDestroy(){
|
||||
this.$bus.$off('reset_fast_filters')
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
},
|
||||
methods: {
|
||||
@ -148,7 +152,6 @@
|
||||
|
||||
if(response.data && 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') })
|
||||
|
@ -12,6 +12,7 @@
|
||||
<ul>
|
||||
<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>If you turn off sharing, no one else can read the note.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
@ -17,6 +17,7 @@ const SquireButtonFunctions = {
|
||||
//
|
||||
|
||||
pathChangeEvent(e){
|
||||
|
||||
//Reset all button states
|
||||
this.activeBold = false
|
||||
this.activeTitle = false
|
||||
@ -143,28 +144,23 @@ const SquireButtonFunctions = {
|
||||
// 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
|
||||
let container = document.getElementById('squire-id')
|
||||
|
||||
Array.from( container.getElementsByClassName('active') ).forEach(item => {
|
||||
item.classList.remove('active');
|
||||
})
|
||||
|
||||
//Close menu if user is on mobile, then sort list
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
deleteCompletedListItems(){
|
||||
//
|
||||
// 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
|
||||
let container = document.getElementById('squire-id')
|
||||
|
||||
@ -210,17 +206,17 @@ const SquireButtonFunctions = {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
//Close menu if user is on mobile, then sort list
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
sortList(){
|
||||
//
|
||||
// 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
|
||||
let container = document.getElementById('squire-id')
|
||||
|
||||
@ -274,17 +270,17 @@ const SquireButtonFunctions = {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
//Close menu if user is on mobile
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
calculateMath(){
|
||||
//
|
||||
// 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
|
||||
let container = document.getElementById('squire-id')
|
||||
|
||||
@ -327,6 +323,11 @@ const SquireButtonFunctions = {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
//Close menu if user is on mobile, then sort list
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
setText(inText){
|
||||
|
||||
@ -338,6 +339,33 @@ const SquireButtonFunctions = {
|
||||
|
||||
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
@ -110,7 +110,7 @@
|
||||
</h2>
|
||||
|
||||
<h3 class="subtext">
|
||||
A free, secure Note App<i class="i cursor icon blinking"></i>
|
||||
A free, secure, online note taking application<i class="i cursor icon blinking"></i>
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
@ -210,18 +210,7 @@
|
||||
<i class="bottom left corner blue pen icon"></i>
|
||||
</i>
|
||||
Document Editing Tools
|
||||
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables Color Text, Color Background and more.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
<i class="grey tags icon"></i>
|
||||
<i class="bottom left corner purple plus icon"></i>
|
||||
</i>
|
||||
Tag Notes
|
||||
<div class="sub header">Easily add and edit tags on notes then sort notes by tag.</div>
|
||||
<div class="sub header">Bold, Underline, Title, Add Links, Add Tables, Color Text, Color Background and more.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
@ -236,6 +225,17 @@
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
<i class="grey tags icon"></i>
|
||||
<i class="bottom left corner purple plus icon"></i>
|
||||
</i>
|
||||
Tag Notes
|
||||
<div class="sub header">Easily add and edit tags on notes then sort notes by tag.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
<h2 class="ui dividing header">
|
||||
<div class="content">
|
||||
<i class="icons">
|
||||
@ -254,7 +254,7 @@
|
||||
<i class="bottom left corner share icon"></i>
|
||||
</i>
|
||||
Share Encrypted Notes
|
||||
<div class="sub header">Share notes with friends without compromising security. And its easy to disable sharing.</div>
|
||||
<div class="sub header">Share encrypted notes with friends without compromising security.</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
@ -291,6 +291,17 @@
|
||||
</div>
|
||||
</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 class="four wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/onboarding.svg" alt="">
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<p>You will remain logged in on this browser, for 30 days or until you log out.</p>
|
||||
<p>You will remain logged in on this browser, for 20 days or until you log out.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -117,7 +117,7 @@
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:title-view="titleView"
|
||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||
:currently-open="activeNoteId1 == note.id"
|
||||
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
|
||||
/>
|
||||
</div>
|
||||
@ -132,13 +132,12 @@
|
||||
</div>
|
||||
|
||||
|
||||
<input-notes
|
||||
<note-input-panel
|
||||
v-if="activeNoteId1 != null"
|
||||
:key="'active_note_'+activeNoteId1"
|
||||
:key="activeNoteId1"
|
||||
:noteid="activeNoteId1"
|
||||
:position="activeNote1Position"
|
||||
:url-data="$route.params"
|
||||
ref="note1" />
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -151,7 +150,7 @@
|
||||
name: 'SearchBar',
|
||||
components: {
|
||||
|
||||
'input-notes': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
|
||||
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
|
||||
|
||||
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
||||
// 'fast-filters': require('@/components/FastFilters.vue').default,
|
||||
@ -247,7 +246,7 @@
|
||||
|
||||
//Do not update note if its open
|
||||
if(this.activeNoteId1 != noteId){
|
||||
this.updateSingleNote(noteId, false)
|
||||
this.updateSingleNote(noteId, true)
|
||||
}
|
||||
})
|
||||
|
||||
@ -264,10 +263,18 @@
|
||||
//Close note event
|
||||
this.$bus.$on('close_active_note', ({noteId, modified}) => {
|
||||
|
||||
this.closeNote()
|
||||
if(modified){
|
||||
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')
|
||||
//Focus and animate if modified
|
||||
this.updateSingleNote(parseInt(noteId), modified)
|
||||
this.updateSingleNote(noteId, modified)
|
||||
})
|
||||
|
||||
this.$bus.$on('note_deleted', (noteId) => {
|
||||
@ -312,35 +319,25 @@
|
||||
})
|
||||
})
|
||||
|
||||
//New note button pushes open note event
|
||||
this.$bus.$on('open_note', noteId => {
|
||||
this.openNote(noteId)
|
||||
})
|
||||
|
||||
//Reload page content
|
||||
//Reload page content - don't trigger if load is in progress
|
||||
this.$bus.$on('note_reload', () => {
|
||||
if(!this.loadingInProgress){
|
||||
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)
|
||||
|
||||
//Close notes when back button is pressed
|
||||
window.addEventListener('hashchange', this.hashChangeAction)
|
||||
// window.addEventListener('hashchange', this.hashChangeAction)
|
||||
|
||||
//update note on visibility change
|
||||
document.addEventListener('visibilitychange', this.visibiltyChangeAction);
|
||||
// document.addEventListener('visibilitychange', this.visibiltyChangeAction);
|
||||
|
||||
},
|
||||
beforeDestroy(){
|
||||
window.removeEventListener('scroll', this.onScroll)
|
||||
window.removeEventListener('hashchange', this.hashChangeAction)
|
||||
document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
|
||||
// document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
|
||||
|
||||
this.$bus.$off('note_reload')
|
||||
this.$bus.$off('close_active_note')
|
||||
@ -348,7 +345,6 @@
|
||||
this.$bus.$off('note_deleted')
|
||||
this.$bus.$off('update_fast_filters')
|
||||
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
|
||||
// this.$off() // Remove all event listeners
|
||||
@ -356,11 +352,20 @@
|
||||
},
|
||||
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
|
||||
this.reset()
|
||||
|
||||
// this.search(true, this.firstLoadBatchSize, false)
|
||||
// .then( r => this.search(false, this.batchSize, true))
|
||||
},
|
||||
watch: {
|
||||
'$route.params.id': function(id){
|
||||
//Open note on ID, null id will close note
|
||||
this.activeNoteId1 = id
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleTitleView(){
|
||||
@ -382,17 +387,9 @@
|
||||
if(nodeClick == 'A'){ return }
|
||||
}
|
||||
|
||||
//1 note open
|
||||
if(this.activeNoteId1 == null){
|
||||
this.activeNoteId1 = id
|
||||
this.activeNote1Position = 0 //Middel of page
|
||||
this.$router.push('/notes/open/'+this.activeNoteId1).catch(e => { console.log(e) })
|
||||
//Open note if a link was not clicked
|
||||
this.$router.push('/notes/open/'+id)
|
||||
return
|
||||
}
|
||||
},
|
||||
closeNote(position){
|
||||
this.activeNoteId1 = null
|
||||
this.$router.push('/notes')
|
||||
},
|
||||
toggleTagFilter(tagId){
|
||||
|
||||
@ -429,34 +426,6 @@
|
||||
|
||||
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){
|
||||
|
||||
//Fuck this shit, just use web sockets
|
||||
@ -484,8 +453,11 @@
|
||||
let note = null
|
||||
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && 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
|
||||
const postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
@ -508,18 +480,13 @@
|
||||
|
||||
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
|
||||
Object.keys(newNote).forEach(prop => {
|
||||
note[prop] = newNote[prop]
|
||||
})
|
||||
|
||||
//Push new note to front if its modified
|
||||
if(focuseAndAnimate){
|
||||
//Push new note to front if its modified or we want it to
|
||||
if( focuseAndAnimate || note.updated != newNote.updated ){
|
||||
|
||||
// Find note, in section, move to front
|
||||
Object.keys(this.noteSections).forEach( key => {
|
||||
@ -536,6 +503,7 @@
|
||||
this.$nextTick( () => {
|
||||
//Trigger close animation on note
|
||||
this.$refs['note-'+noteId][0].justClosed()
|
||||
this.$refs['note-'+noteId][0].showWorking = false
|
||||
})
|
||||
}
|
||||
|
||||
@ -547,9 +515,14 @@
|
||||
//Trigger close animation on note
|
||||
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
|
||||
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
|
||||
this.rebuildNoteCategorise()
|
||||
})
|
||||
@ -610,7 +583,6 @@
|
||||
|
||||
//Perform search - or die
|
||||
this.loadingInProgress = true
|
||||
// console.time('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
axios.post('/api/note/search', postData)
|
||||
.then(response => {
|
||||
|
||||
@ -735,7 +707,6 @@
|
||||
this.fastFilters = {}
|
||||
this.foundAttachments = [] //Remove all attachments
|
||||
|
||||
this.$bus.$emit('reset_fast_filters')
|
||||
this.updateFastFilters(5) //This loads notes
|
||||
|
||||
},
|
||||
|
@ -14,6 +14,11 @@
|
||||
|
||||
<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">
|
||||
<i class="sync alternate reload icon"></i>
|
||||
New Scratch Pad
|
||||
@ -46,10 +51,6 @@
|
||||
<i class="folder open outline icon"></i>
|
||||
Files
|
||||
</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>
|
||||
|
257
client/src/pages/SettingsPage.vue
Normal file
257
client/src/pages/SettingsPage.vue
Normal file
@ -0,0 +1,257 @@
|
||||
<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>
|
@ -4,19 +4,31 @@
|
||||
<div class="sixteen wide column"></div>
|
||||
|
||||
<div class="sixteen wide column" v-if="text.length > 0 || title.length > 0">
|
||||
<div class="ui text container squire-box" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
<div class="ui text container">
|
||||
|
||||
<div class="ui segment" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
|
||||
<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 class="sixteen wide column" v-if="!$store.getters.getLoggedIn">
|
||||
<div class="ui text container">
|
||||
<h2 class="ui header">
|
||||
|
||||
<div class="ui segment">
|
||||
|
||||
<div class="ui grid">
|
||||
<div class="three wide middle aligned center aligned column">
|
||||
<img class="small-logo" loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
|
||||
</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">
|
||||
@ -24,6 +36,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
<!-- buttons -->
|
||||
<div class="ui grid">
|
||||
<div class="eight wide center aligned column">
|
||||
<router-link class="ui compact green button" to="/login">
|
||||
@ -40,6 +53,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui sixteen wide center aligned column">
|
||||
<h4>{{ failText }}</h4>
|
||||
</div>
|
||||
@ -99,7 +116,7 @@
|
||||
|
||||
<style type="text/css" scoped>
|
||||
.small-logo {
|
||||
width: 30px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
@ -6,6 +6,7 @@ import Router from 'vue-router'
|
||||
const HomePage = () => import(/* webpackChunkName: "HomePage" */ '@/pages/HomePage')
|
||||
const LoginPage = () => import(/* webpackChunkName: "LoginPage" */ '@/pages/LoginPage')
|
||||
const HelpPage = () => import(/* webpackChunkName: "HelpPage" */ '@/pages/HelpPage')
|
||||
const SettingsPage = () => import(/* webpackChunkName: "SettingsPage" */ '@/pages/SettingsPage')
|
||||
const SharePage = () => import(/* webpackChunkName: "SharePage" */ '@/pages/SharePage')
|
||||
const NotesPage = () => import(/* webpackChunkName: "NotesPage" */ '@/pages/NotesPage')
|
||||
const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage')
|
||||
@ -51,6 +52,12 @@ export default new Router({
|
||||
name: 'Help',
|
||||
meta: {title:'Help'},
|
||||
component: HelpPage
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
meta: {title:'Settings'},
|
||||
component: SettingsPage
|
||||
},
|
||||
{
|
||||
path: '/public/note/:id/:token',
|
||||
|
@ -85,6 +85,7 @@ export default new Vuex.Store({
|
||||
Object.keys( themes[currentTheme] ).forEach( attribute => {
|
||||
root.style.setProperty('--'+attribute, themes[currentTheme][attribute])
|
||||
})
|
||||
|
||||
},
|
||||
detectIsUserOnMobile(state){
|
||||
|
||||
|
265
package-lock.json
generated
265
package-lock.json
generated
@ -49,6 +49,19 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
@ -112,11 +125,21 @@
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"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": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
@ -182,11 +205,39 @@
|
||||
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz",
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
@ -229,6 +280,11 @@
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
|
||||
@ -252,6 +308,29 @@
|
||||
"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": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@ -394,6 +473,11 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -441,6 +525,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dijkstrajs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
||||
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs="
|
||||
},
|
||||
"dns-prefetch-control": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
|
||||
@ -509,6 +598,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
@ -708,6 +802,14 @@
|
||||
"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": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
@ -746,6 +848,11 @@
|
||||
"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": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
@ -926,6 +1033,11 @@
|
||||
"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": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||
@ -941,6 +1053,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
@ -1040,6 +1157,15 @@
|
||||
"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": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
@ -1257,6 +1383,27 @@
|
||||
"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": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
|
||||
@ -1286,6 +1433,11 @@
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"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": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
@ -1296,6 +1448,11 @@
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
@ -1325,6 +1482,27 @@
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"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": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
@ -1407,6 +1585,16 @@
|
||||
"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": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
@ -1465,6 +1653,11 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
@ -1599,6 +1792,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz",
|
||||
@ -1635,6 +1836,16 @@
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
|
||||
"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": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
|
||||
@ -1643,6 +1854,14 @@
|
||||
"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": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
|
||||
@ -1755,6 +1974,21 @@
|
||||
"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": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz",
|
||||
@ -1775,11 +2009,42 @@
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"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": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
|
@ -21,9 +21,11 @@
|
||||
"multer": "^1.4.2",
|
||||
"mysql2": "^1.7.0",
|
||||
"node-tesseract-ocr": "^1.0.0",
|
||||
"qrcode": "^1.4.4",
|
||||
"request": "^2.88.2",
|
||||
"request-promise": "^4.2.5",
|
||||
"socket.io": "^2.3.0"
|
||||
"socket.io": "^2.3.0",
|
||||
"speakeasy": "^2.0.0"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@root": ".",
|
||||
|
@ -1,11 +1,13 @@
|
||||
const db = require('@config/database')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const cs = require('@helpers/CryptoString')
|
||||
const speakeasy = require('speakeasy')
|
||||
|
||||
let Auth = {}
|
||||
|
||||
const tokenSecretKey = process.env.JSON_KEY
|
||||
|
||||
//Creates session token
|
||||
Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@ -41,6 +43,7 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) =>
|
||||
})
|
||||
}
|
||||
|
||||
//Decodes session token
|
||||
Auth.decodeToken = (token, request = null) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@ -120,6 +123,7 @@ Auth.decodeToken = (token, request = null) => {
|
||||
}
|
||||
|
||||
Auth.terminateSession = (sessionId) => {
|
||||
|
||||
return db.promise().query('DELETE from user_active_session WHERE session_id = ?', [sessionId])
|
||||
}
|
||||
|
||||
@ -130,6 +134,143 @@ Auth.deletAllLoginKeys = (userId) => {
|
||||
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 = () => {
|
||||
|
||||
const testUserId = 22
|
||||
|
@ -259,13 +259,15 @@ const printResults = true
|
||||
let UserTest = require('@models/User')
|
||||
let NoteTest = require('@models/Note')
|
||||
let AuthTest = require('@helpers/Auth')
|
||||
|
||||
Auth.testTwoFactor()
|
||||
|
||||
Auth.test()
|
||||
UserTest.keyPairTest('genMan15', '1', printResults)
|
||||
UserTest.keyPairTest('genMan23', '1', printResults)
|
||||
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
||||
.then( message => {
|
||||
if(printResults) console.log(message)
|
||||
})
|
||||
// Test Area
|
||||
|
||||
|
||||
//Test
|
||||
|
@ -182,7 +182,7 @@ Note.create = (userId, noteTitle = '', noteText = '', masterKey) => {
|
||||
const encryptedText = cs.encrypt(masterKey, salt, textObject)
|
||||
|
||||
db.promise()
|
||||
.query(`INSERT INTO note_raw_text (text, salt, updated) VALUE (?, ?, ?)`, [encryptedText, salt, created])
|
||||
.query(`INSERT INTO note_raw_text (text, salt, updated) VALUE (?, ?, ?)`, [encryptedText, salt, (+new Date)])
|
||||
.then( (rows, fields) => {
|
||||
|
||||
const rawTextId = rows[0].insertId
|
||||
@ -515,7 +515,7 @@ Note.setPinned = (userId, noteId, pinnedBoolean) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const pinned = pinnedBoolean ? 1:0
|
||||
const now = Math.round((+new Date)/1000)
|
||||
const now = (+new Date)
|
||||
|
||||
//Update other note attributes
|
||||
return db.promise()
|
||||
@ -532,7 +532,7 @@ Note.setArchived = (userId, noteId, archivedBoolead) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const archived = archivedBoolead ? 1:0
|
||||
const now = Math.round((+new Date)/1000)
|
||||
const now = (+new Date)
|
||||
|
||||
//Update other note attributes
|
||||
return db.promise()
|
||||
@ -549,7 +549,7 @@ Note.setTrashed = (userId, noteId, trashedBoolean, masterKey) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const trashed = trashedBoolean ? 1:0
|
||||
const now = Math.round((+new Date)/1000)
|
||||
const now = (+new Date)
|
||||
|
||||
//Update other note attributes
|
||||
return db.promise()
|
||||
@ -1048,7 +1048,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
|
||||
// Always prioritize pinned notes in searches.
|
||||
|
||||
//Default Sort, order by last updated
|
||||
let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, note.created DESC, note.opened DESC, id DESC'
|
||||
let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, note.created DESC, note.opened DESC'
|
||||
|
||||
//Order by Last Created Date
|
||||
if(fastFilters.lastCreated == 1){
|
||||
|
@ -10,19 +10,28 @@ QuickNote.get = (userId, masterKey) => {
|
||||
|
||||
db.promise()
|
||||
.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) => {
|
||||
|
||||
//Quick Note is set, return note text
|
||||
if(rows[0][0] != undefined){
|
||||
|
||||
let noteId = rows[0][0].id
|
||||
Note.get(userId, noteId, masterKey)
|
||||
.then( noteObject => {
|
||||
return resolve(noteObject)
|
||||
})
|
||||
return resolve({'noteId':noteId})
|
||||
|
||||
} else {
|
||||
return resolve(null)
|
||||
//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})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,22 +5,32 @@ const Note = require('@models/Note')
|
||||
const db = require('@config/database')
|
||||
const Auth = require('@helpers/Auth')
|
||||
const cs = require('@helpers/CryptoString')
|
||||
const speakeasy = require('speakeasy')
|
||||
|
||||
let User = module.exports = {}
|
||||
|
||||
const version = '3.0.1'
|
||||
const version = '3.1.5'
|
||||
|
||||
//Login a user, if that user does not exist create them
|
||||
//Issues login token
|
||||
User.login = (username, password) => {
|
||||
User.login = (username, password, authToken = null) => {
|
||||
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()
|
||||
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
|
||||
.then((rows, fields) => {
|
||||
|
||||
//
|
||||
// Login User
|
||||
//
|
||||
if(rows[0].length == 1){
|
||||
@ -28,8 +38,34 @@ User.login = (username, password) => {
|
||||
//Pull out user data from database results
|
||||
const lookedUpUser = rows[0][0]
|
||||
|
||||
//Verify Token if set
|
||||
const tokenValidates = speakeasy.totp.verify({
|
||||
'secret': lookedUpUser['two_fa_secret'],
|
||||
'encoding': 'base32',
|
||||
'token': authToken,
|
||||
'window': 2
|
||||
})
|
||||
|
||||
if(lookedUpUser.two_fa_enabled == 1 && !authToken){
|
||||
|
||||
statusObject['verificationRequired'] = true
|
||||
statusObject['message'] = '2FA authentication required.'
|
||||
|
||||
return resolve(statusObject)
|
||||
}
|
||||
|
||||
if(lookedUpUser.two_fa_enabled == 1 && !tokenValidates){
|
||||
|
||||
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 = new Buffer(lookedUpUser.salt, 'binary')
|
||||
|
||||
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){
|
||||
@ -44,18 +80,34 @@ User.login = (username, password) => {
|
||||
//Passback a json web token
|
||||
Auth.createToken(lookedUpUser.id, masterKey)
|
||||
.then(token => {
|
||||
return resolve({ token: token, userId:lookedUpUser.id })
|
||||
|
||||
statusObject['token'] = token
|
||||
statusObject['userId'] = lookedUpUser.id
|
||||
statusObject['success'] = true
|
||||
|
||||
return resolve(statusObject)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
reject('Password does not match database')
|
||||
return resolve(statusObject)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
} 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)
|
||||
@ -186,6 +238,12 @@ User.getCounts = (userId) => {
|
||||
|
||||
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
|
||||
Object.keys(countTotals).forEach( key => {
|
||||
const count = parseInt(countTotals[key])
|
||||
@ -253,13 +311,12 @@ User.generateMasterKey = (userId, password) => {
|
||||
}
|
||||
|
||||
User.getMasterKey = (userId, password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(!userId || !password){
|
||||
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])
|
||||
.then((rows, fields) => {
|
||||
|
||||
@ -375,6 +432,66 @@ 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) => {
|
||||
|
||||
//Verify user is correct by decryptig master key with password
|
||||
@ -414,6 +531,7 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
|
||||
|
||||
const randomUsername = Math.random().toString(36).substring(2, 15);
|
||||
const randomPassword = '1'
|
||||
const secondPassword = '2'
|
||||
|
||||
User.register(testUserName, password)
|
||||
.then( ({ token, userId }) => {
|
||||
@ -453,6 +571,26 @@ User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
|
||||
|
||||
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})
|
||||
})
|
||||
})
|
||||
|
@ -6,9 +6,8 @@ let QuickNote = require('@models/QuickNote');
|
||||
let userId = null
|
||||
let masterKey = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function setUserId (req, res, next) {
|
||||
if(userId = req.headers.userId){
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
var express = require('express')
|
||||
var router = express.Router()
|
||||
|
||||
let User = require('@models/User');
|
||||
const User = require('@models/User')
|
||||
const Auth = require('@helpers/Auth')
|
||||
const cs = require('@helpers/CryptoString')
|
||||
|
||||
// middleware that is specific to this router
|
||||
@ -9,26 +10,14 @@ router.use(function timeLog (req, res, next) {
|
||||
// console.log('Time: ', Date.now())
|
||||
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
|
||||
router.post('/login', function (req, res) {
|
||||
|
||||
User.login(req.body.username, req.body.password)
|
||||
User.login(req.body.username, req.body.password, req.body.authToken)
|
||||
.then( returnData => {
|
||||
|
||||
res.send(returnData)
|
||||
})
|
||||
.catch(e => {
|
||||
res.send(false)
|
||||
})
|
||||
})
|
||||
// Logout User
|
||||
router.post('/logout', function (req, res) {
|
||||
@ -39,10 +28,7 @@ router.post('/logout', function (req, res) {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
// Login User
|
||||
// Register User
|
||||
router.post('/register', function (req, res) {
|
||||
|
||||
User.register(req.body.username, req.body.password)
|
||||
@ -55,6 +41,24 @@ 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
|
||||
router.post('/totals', function (req, res) {
|
||||
@ -62,5 +66,30 @@ router.post('/totals', function (req, res) {
|
||||
.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
|
Loading…
Reference in New Issue
Block a user