Added privacy policy

Updated marketing
Added some keyboard shortcuts
Added settings page
Added accent theming
Added beta 2FA
This commit is contained in:
Max G
2020-07-07 04:04:55 +00:00
parent 2ae84ab73e
commit 06b8f0ad6a
29 changed files with 1428 additions and 362 deletions

View File

@@ -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){

View File

@@ -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)
}

View File

@@ -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;
@@ -105,7 +106,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 +126,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 +254,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 +272,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 +341,55 @@ 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;
}
th, td {
border: 1px solid #ddd;
border-bottom: 1px solid #ddd;
font-weight: normal;
}
/* table:hover th, table:hover td {
border: 1px solid black;
}*/
th, td {
padding: 3px;
text-align: left;
}
.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) {

View File

@@ -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();
}
@@ -4947,4 +4995,4 @@ if ( typeof exports === 'object' ) {
}
}
}( document ) );
}( document ) );

View File

@@ -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

View File

@@ -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(){

View File

@@ -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,45 +126,37 @@
<!-- 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>
<!-- <router-link v-if="loggedIn" class="ui large basic compact icon button" to="/notes" v-on:click.native="emitReloadEvent()">
<i class="green home icon"></i>
</router-link> -->
<router-link v-if="loggedIn" class="ui basic icon button" exact-active-class="active" to="/attachments">
<i class="open folder outline icon"></i>
</router-link>
</div>
<div class="two wide center aligned bottom aligned column">
<img loading="lazy" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo">
</div>
<div class="seven wide right aligned column">
<div v-on:click="toggleNightMode" class="ui large basic compact icon button">
<i class="green moon outline icon"></i>
</div>
<!-- mobile create note button -->
<span v-if="loggedIn">
<span v-if="!disableNewNote" @click="createNote" class="ui large green compact icon button">
<i class="plus icon"></i>
</span>
<span v-if="disableNewNote" class="ui large basic compact icon button">
<i class="grey plus icon"></i>
</span>
</span>
</div>
<div class="mobile-button">
<i class="green link bars icon" v-on:click="collapseMenu"></i>
</div>
<div class="mobile-button"></div>
<router-link v-if="loggedIn" to="/quick" class="mobile-button" exact-active-class="active">
<i class="green sticky note outline icon"></i>
</router-link>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/notes" v-on:click.native="emitReloadEvent()">
<logo class="logo-display" color="var(--main-accent)" />
</router-link>
<router-link v-if="loggedIn" class="mobile-button" exact-active-class="active" to="/attachments">
<i class="green open folder outline icon"></i>
</router-link>
<div class="mobile-button"></div>
<!-- mobile create note button -->
<span v-if="loggedIn">
<span v-if="!disableNewNote" @click="createNote" class="mobile-button">
<i class="green plus icon"></i>
</span>
<span v-if="disableNewNote" class="mobile-button">
<i class="grey plus icon"></i>
</span>
</span>
</div>
<div class="shade" v-if="mobile && !collapsed" v-on:click="collapseMenu"></div>
@@ -161,7 +169,7 @@
<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">
<logo class="menu-logo-display" color="var(--main-accent)" />
<!-- </div> -->
</div>
@@ -236,14 +244,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 +272,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 +283,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
@@ -316,6 +329,19 @@
},
},
methods: {
resizeEventHandler(e) {
clearTimeout(this.resizeDebounce)
this.resizeDebounce = setTimeout(() => {
this.menuOpen = false
this.collapsed = false
if(window.innerWidth < 700){
this.collapsed = true
}
}, 100)
},
menuClicked(){
//Collapse menu when item is clicked in mobile
if(this.mobile && !this.collapsed){
@@ -334,28 +360,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')
},

View File

@@ -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"

View File

@@ -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
}
this.finalizeLogin(data)
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')
@@ -154,4 +198,13 @@
}
}
}
</script>
</script>
<style type="text/css" scoped="true">
.small-terms {
display: inline-block;
text-align: right;
width: 100%;
font-size: 0.9em;
}
</style>

View File

@@ -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>

View File

@@ -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>
@@ -61,7 +60,19 @@
</div>
<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="insertTable(4,4)" data-tooltip="Insert Table" data-position="bottom center" data-inverted>
<i class="book dead 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 +106,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 +117,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 +140,6 @@
</div>
</div>
</div>
<div class="bottom-edit-menu"></div>
@@ -163,7 +170,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 +231,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 +252,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 +273,20 @@
</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">
Create a table
</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 +358,7 @@
images: false,
options: false,
colorpicker: false,
table: false,
//Diff text/sync text variables
diffTextTimeout: null,
@@ -355,7 +379,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 +388,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 +419,20 @@
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
// if(this.editor){
this.editor.destroy()
// }
//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 +626,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 +663,6 @@
})
})
this.editor.addEventListener('keydown', event => {
})
//Bind event handlers
this.editor.addEventListener('keyup', event => {
@@ -682,7 +729,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 +962,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 +988,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)
this.$bus.$emit('close_active_note', {
noteId: this.noteid, modified: this.modified
})
return
})
},
destroyWebSockets(){
@@ -1005,8 +1051,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 +1198,7 @@
background-color: var(--menu-accent);
}
.edit-active {
background-color: #21BA45;
background-color: var(--main-accent);
color: white;
}
.edit-divide {

View File

@@ -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:

View File

@@ -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') })

View File

@@ -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,69 @@ const SquireButtonFunctions = {
return this.editor.getHTML()
},
insertDivide(){
this.editor.insertHTML(`<p><div class='divide'></div><br></p>`)
this.editor.focus()
this.editor.moveCursorToEnd()
},
insertTable(wide, tall){
console.log('Insert a table')
let tableSyntax = `
<div>
<table>
<tr>
<th><p><br></p></th>
<th><p><br></p></th>
</tr>
<tr>
<td><p><br></p></td>
<td><p><br></p></td>
</tr>
<tr>
<td><p><br></p></td>
<td><p><br></p></td>
</tr>
</table>
</div>
`
tableSyntax = `
<span class="t-table">
<span>
<span>
<p><br></p>
</span>
<span>
<p><br></p>
</span>
</span>
<span>
<span>
<p><br></p>
</span>
<span>
<p><br></p>
</span>
</span>
</span>
<p><br></p>
`
tableSyntax = ''
tableSyntax += '<span class="t-table">'
for (let i = 0; i < tall; i++) {
for (let j = 0; j < wide; j++) {
}
}
tableSyntax += '</span><p><br></p>'
this.editor.insertHTML(tableSyntax)
this.editor.focus()
this.editor.moveCursorToEnd()
},
},
}

File diff suppressed because one or more lines are too long

View File

@@ -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>
@@ -290,6 +290,17 @@
<div class="sub header">Add text to Images and links than can be searched.</div>
</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">

View File

@@ -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>

View File

@@ -31,7 +31,7 @@
<i v-if="titleView" class="th icon"></i>
<i v-if="!titleView" class="bars icon"></i>
</div>
</div>
<div class="eight wide column" v-if="showClear">
@@ -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', () => {
this.reset()
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) })
return
}
},
closeNote(position){
this.activeNoteId1 = null
this.$router.push('/notes')
//Open note if a link was not clicked
this.$router.push('/notes/open/'+id)
return
},
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
},

View File

@@ -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>

View File

@@ -0,0 +1,182 @@
<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>
<!-- 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 type="text" placeholder="Current Password">
</div>
</div>
<div class="five wide column">
<label>New Password</label>
<div class="ui fluid input">
<input type="text" placeholder="New Password">
</div>
</div>
<div class="six wide column">
<label>Rereat New Password</label>
<div class="ui fluid action input">
<input type="text" placeholder="Repeat Password">
<div class="ui green button">
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">
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: '',
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
]
}
},
methods: {
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
}
})
}
}
}
</script>

View File

@@ -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',

View File

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