* Adjusted theme colors to add more contrast on white theme while making black more OLED friendly

* Links now get an underline on hover
* Cleaned up CSS variable names, added another theme color for more control
* Cleaned up unused CSS, removed scrollbars popping up, tons of other little UI tweaks
* Renamed shared notes to inbox
* Tweaked form display, seperated login and create accouts
* Put login/sign up form on home page
* Created more legitimate marketing for home page
* Tons up updates to note page and note input panel
* Better support for two users editing a note
* MUCH better diff handling, web sockets restore notes with unsaved diffs
* Moved all squire text modifier functions into a mixin class
* It now says saving when closing a note
* Lots of cleanup and better handiling of events on mount and destroy
* Scroll behavior modified to load notes when closer to bottom of page
* Pretty decent shared notes and sharable link support
* Updated help text
* Search now includes tag suggestions and attachment suggestions
* Cleaned up scratch pad a ton, allow for users to create new scratch pads
* Created a 404 Page and a Shared note page
* So many other small improvements. Oh my god, what is wrong with me, not doing commits!?
This commit is contained in:
Max G
2020-06-07 20:57:35 +00:00
parent 8e5e06be9b
commit 6bb856689d
31 changed files with 1605 additions and 1095 deletions

View File

@@ -6,6 +6,7 @@
display: inline-block;
border: 1px solid;
border-color: var(--border_color);
background-color: var(--small_element_bg_color);
border-radius: 4px;
margin: 0 0 15px;
max-height: 10000px;

View File

@@ -40,7 +40,7 @@
let filter = {}
filter[option] = 1
this.$bus.$emit('update_fast_filters', filter)
// this.$bus.$emit('update_fast_filters', filter)
}
}
}
@@ -62,7 +62,7 @@
.filter-menu {
color: var(--text_color);
background-color: var(--background_color);
background-color: var(--small_element_bg_color);
border: 1px solid;

View File

@@ -20,16 +20,16 @@
}
.menu-logo-display {
width: 25px;
margin: 5px 0 0 34px;
margin: 5px 0 0 42px;
display: inline-block;
}
.menu-item {
color: #fff;
padding: 0.8em 10px 0.8em 10px;
padding: 9px 10px;
display: inline-block;
width: 100%;
font-size: 1.15em;
font-size: 1.1em;
box-sizing: border-box;
}
.menu-item i.icon {
@@ -76,7 +76,7 @@
left: 0;
right: 0;
z-index: 999;
background-color: var(--background_color);
background-color: var(--small_element_bg_color);
border-bottom: 1px solid;
border-color: var(--border_color);
padding: 5px 1rem 5px;
@@ -117,9 +117,9 @@
<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()">
<!-- <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> -->
<router-link v-if="loggedIn" class="ui basic icon button" exact-active-class="active" to="/attachments">
<i class="open folder outline icon"></i>
@@ -184,6 +184,16 @@
<counter v-if="$store.getters.totals && $store.getters.totals['totalNotes']" class="float-right" number-id="totalNotes" />
</router-link>
<div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(3)" v-if="$store.getters.totals && ($store.getters.totals['sharedToNotes'] > 0 || $store.getters.totals['sharedFromNotes'] > 0)">
<i class="grey mail outline icon"></i>Inbox
</div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(2)" v-if="$store.getters.totals && $store.getters.totals['archivedNotes'] > 0">
<i class="grey archive icon"></i>Archived
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
</div>
<div class="menu-item menu-button sub" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0">
<i class="grey trash alternate outline icon"></i>Trashed
</div>
<!-- <div class="menu-item sub">Show Only <i class="caret down icon"></i></div> -->
<!-- <div v-on:click="updateFastFilters(0)" class="menu-item menu-button sub"><i class="grey linkify icon"></i>Links</div> -->
<!-- <div v-on:click="updateFastFilters(1)" class="menu-item menu-button sub"><i class="grey tags icon"></i>Tags</div> -->
@@ -218,7 +228,7 @@
<span v-if="$store.getters.getIsNightMode == 0">
<i class="moon outline icon"></i>Black Theme</span>
<span v-if="$store.getters.getIsNightMode == 1">
<i class="moon outline icon"></i>Night Theme</span>
<i class="moon outline icon"></i>Flux Theme</span>
<span v-if="$store.getters.getIsNightMode == 2">
<i class="moon outline icon"></i>Light Theme</span>
</div>
@@ -257,7 +267,7 @@
},
data: function(){
return {
version: '2.2.3',
version: '2.3.4',
username: '',
collapsed: false,
mobile: false,
@@ -354,27 +364,17 @@
//Reloads note page to initial state
this.$bus.$emit('note_reload')
},
updateFastFilters(index){
updateFastFilters(filterIndex){
//A little hacky, brings user to notes page then filters on click
if(this.$route.name != 'NotesPage'){
if(this.$route.name != 'Note Page'){
this.$router.push('/notes')
setTimeout( () => {
this.updateFastFilters(index)
this.$bus.$emit('update_fast_filters', filterIndex)
}, 500 )
} else {
this.$bus.$emit('update_fast_filters', filterIndex)
}
const options = [
'withLinks', // 'Only Show Notes with Links'
'withTags', // 'Only Show Notes with Tags'
'onlyArchived', //'Only Show Archived Notes'
'onlyShowSharedNotes', //Only show shared notes
]
let filter = {}
filter[options[index]] = 1
this.$bus.$emit('update_fast_filters', filter)
},
reloadPage(){
location.reload(true)

View File

@@ -33,29 +33,17 @@
export default {
name: 'LoadingIcon',
props:[ 'message' ],
data () {
return {
items: []
}
},
beforeMount(){
},
mounted(){
},
methods: {
onClickTag(index){
console.log('yup')
},
}
}
</script>
<style type="text/css" scoped>
.loading-container {
text-align: center;
width: 100%;
height: 100px;
min-height: 100px;
margin: 20px 0;
padding: 40px;
border-radius: 7px;
background-color: var(--small_element_bg_color);
}
.loading-container svg {
width: 60px;

View File

@@ -1,11 +1,12 @@
<template>
<div v-on:keyup.enter="submit()">
<div v-on:keyup.enter="login()">
<!-- thicc form display -->
<div v-if="!thin" class="ui large form">
<div class="field">
<div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
</div>
</div>
<div class="field">
@@ -13,27 +14,48 @@
<input v-model="password" type="password" name="password" placeholder="Password">
</div>
</div>
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui massive compact fluid green submit button">Sign Up / Login</div>
</div>
<div v-if="thin" class="ui small form">
<div class="fields">
<div class="six wide field">
<div class="ui input">
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
</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">
<i class="power icon"></i>
Login
</div>
<div class="six wide field">
<div class="ui input">
<input v-model="password" type="password" name="password" placeholder="Password">
</div>
</div>
<div class="four wide field">
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui fluid green submit button">Sign Up / Login</div>
<div class="or"></div>
<div v-on:click="register()" class="ui button">
<i class="plug icon"></i>
Sign Up
</div>
</div>
</div>
</div>
<!-- Thin form display -->
<div v-if="thin" class="ui small form">
<div class="fields">
<div class="four wide 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="ui input">
<input v-model="password" type="password" name="password" placeholder="Password">
</div>
</div>
<div class="four wide field">
<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 v-on:click="login()" class="ui fluid button">
<i class="power icon"></i>
Login
</div>
</div>
</div>
</div>
@@ -65,43 +87,64 @@
}
},
methods: {
submit(){
finalizeLogin(data){
//Both fields are required
if(this.username <= 0){
return false
}
if(this.password <= 0){
return false
//Destroy local data if there is an error
if(data == false){
this.$store.commit('destroyLoginToken')
return
}
let vm = this
//Login user if we have a valid token
if(data && data.token && data.token.length > 0){
const token = data.token
const username = this.username
let data = {
username: this.username,
password: this.password
this.$store.commit('setLoginToken', {token, username})
//Setup socket io after user logs in
this.$io.emit('user_connect', token)
//Redirect user to notes section after login
this.$router.push('/notes')
}
},
register(){
if( this.username.length == 0 || this.password.length == 0 ){
this.$bus.$emit('notification', 'Username and Password Required')
return
}
axios.post('/api/user/login', data)
.then(response => {
if(response.data.success){
const token = response.data.token
const username = response.data.username
const masterKey = response.data.masterKey
axios.post('/api/user/register', {'username': this.username, 'password': this.password})
.then(({data}) => {
this.$store.commit('setLoginToken', {token, username, masterKey})
//Setup socket io after user logs in
this.$io.emit('user_connect', token)
//Redirect user to notes section after login
this.$router.push('/notes')
} else {
// this.password = ''
this.$bus.$emit('notification', 'Incorrect Username or Password')
vm.$store.commit('destroyLoginToken')
if(data == false){
this.$bus.$emit('notification', 'Username already in use')
}
this.finalizeLogin(data)
})
.catch(error => {
this.$bus.$emit('notification', 'Username already in use')
})
},
login(){
if( this.username.length == 0 || this.password.length == 0 ){
this.$bus.$emit('notification', 'Username and Password Required')
return
}
axios.post('/api/user/login', {'username': this.username, 'password': this.password})
.then(({data}) => {
if(data == false){
this.$bus.$emit('notification', 'Incorrect Username or Password')
}
this.finalizeLogin(data)
})
.catch(error => {
this.$bus.$emit('notification', 'Incorrect Username or Password')

File diff suppressed because it is too large Load Diff

View File

@@ -323,7 +323,7 @@
height: 40px;
padding: 10px 15px;
cursor: pointer;
background-color: var(--background_color);
background-color: var(--small_element_bg_color);
color: var(--text_color);
}
.suggestion-item.active {

View File

@@ -242,11 +242,11 @@
justClosed(){
//Scroll note into view
this.$el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
})
// this.$el.scrollIntoView({
// behavior: 'smooth',
// block: 'center',
// inline: 'center'
// })
//After scroll, trigger green outline animation
setTimeout(() => {
@@ -353,7 +353,7 @@
display: inline-block;
min-width: 30px;
color: var(--text_color);
background-color: var(--background_color);
background-color: var(--small_element_bg_color);
}
.subtext {
display: inline-block;
@@ -368,7 +368,7 @@
}
.small-text, .small-text > p, .small-text > h1, .small-text > h2 {
/*font-size: 1.0em !important;*/
font-size: 15px !important;
font-size: 16px !important;
}
.small-text > p, , .small-text > h1, .small-text > h2 {
margin-bottom: 0.5em;
@@ -410,19 +410,17 @@
.note-title-display-card {
position: relative;
/*box-shadow: 0 1px 3px 0 rgba(34,36,38,.15);*/
/*box-shadow: 0 0px 5px 1px rgba(34,36,38,0);*/
/*box-shadow: 0 1px 3px 0 rgba(34,36,38,.15);*/
box-shadow: 0px 1px 2px 1px rgba(210, 211, 211, 0.46);
transition: box-shadow ease 0.3s;
background-color: var(--small_element_bg_color);
/*The subtle shadow*/
/*box-shadow: 0px 1px 2px 1px rgba(210, 211, 211, 0.46);*/
transition: box-shadow ease 0.5s, transform linear 0.1s;
margin: 5px;
/*padding: 0.7em 1em;*/
border-radius: .28571429rem;
border: 1px solid transparent;
/*border-color: var(--border_color);*/
border-color: var(--border_color);
/*width: calc(33.333% - 10px);*/
width: calc(25% - 10px);
max-width: 300px;
min-width: 190px;
min-height: 130px;
/*transition: box-shadow 0.3s;*/
@@ -436,13 +434,15 @@
text-align: left;
}
.note-title-display-card:hover {
box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);
/*box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);*/
/*transform: translateY(-2px);*/
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
}
.note-title-display-card.title-view {
width: 100%;
min-height: 10px;
min-height: 20px;
max-width: none;
box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);
/*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/
}
.single-line-text {
@@ -527,6 +527,7 @@
.one-column .note-title-display-card {
width: 100%;
max-width: none;
/*margin: 0px -5px 10px -5px;*/
}
.overflow-hidden {
overflow: hidden;
@@ -561,11 +562,15 @@
}
/* 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) {
.note-title-display-card {
width: calc(100% + 10px);
margin: 0px -5px 10px -5px;
max-width: none;
}
}

View File

@@ -71,14 +71,6 @@
</div>
</div>
<div class="ui very compact grid" v-if="tagSuggestions.length > 0">
<div class="sixteen wide column">
<div class="ui clickable green label" v-for="tag in tagSuggestions" v-on:click="tagClick(tag.id)">
<i class="tag icon"></i>
{{ tag.text }}
</div>
</div>
</div>
</div>
</div>
</div>
@@ -141,11 +133,7 @@
axios.post('/api/quick-note/update', { 'pushText':text.trim() } )
.then( response => {
//Open Quick Note
if(response.data && response.data.id){
this.$router.push('/notes/open/'+response.data.id)
this.$bus.$emit('open_note', response.data.id)
}
this.$bus.$emit('notification', 'Saved To Scratch Pad')
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Update The Scratch Pad') })
},
@@ -173,20 +161,20 @@
clearTimeout(this.tagSearchDebounce)
if(this.searchTerm.length == 0){
this.tagSuggestions = []
return
}
// if(this.searchTerm.length == 0){
// this.tagSuggestions = []
// return
// }
this.tagSearchDebounce = setTimeout(() => {
this.tagSuggestions = []
axios.post('/api/tag/suggest', postData)
.then( response => {
// this.tagSearchDebounce = setTimeout(() => {
// this.tagSuggestions = []
// axios.post('/api/tag/suggest', postData)
// .then( response => {
this.tagSuggestions = response.data
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Get Suggested Tags') })
}, 800)
// this.tagSuggestions = response.data
// })
// .catch(error => { this.$bus.$emit('notification', 'Failed to Get Suggested Tags') })
// }, 800)
},
onKeyDown(event){

View File

@@ -5,7 +5,30 @@
<template>
<div>
<div class="ui grid" v-if="this.shareUsername == null">
<div class="ui grid" v-if="shareUsername == null">
<div v-if="!isNoteShared" class="sixteen wide column">
<div class="ui button" v-on:click="makeShared()">Enable Shared</div>
<p>Shared notes are different and junk.</p>
</div>
<div v-if="isNoteShared" class="sixteen wide column">
<p>Generating a shared URL will expose the password of this note.</p>
<div class="ui button" v-on:click="removeShared()">Remove Shared</div>
<div class="ui button" v-on:click="getSharedUrl()">Get Shareable URL</div>
<div v-if="sharedUrl.length > 0">
<a target="_blank" :href="sharedUrl">{{ sharedUrl }}</a>
<div class="ui input">
<input type="text" v-model="sharedUrl">
</div>
</div>
</div>
</div>
<div class="ui grid" v-if="shareUsername == null">
<div class="row">
<div class="eight wide column">
@@ -38,7 +61,7 @@
</div>
<div class="ui grid" v-if="this.shareUsername != null">
<div class="ui grid" v-if="shareUsername != null">
<div class="sixteen wide column">
Shared with you by <h3><i class="green user circle icon"></i>{{shareUsername}}</h3>
</div>
@@ -56,10 +79,12 @@
props: [ 'noteId', 'rawTextId', 'shareUsername' ],
data () {
return {
isNoteShared: false,
sharedWithUsers: [],
shareUserInput: '',
debounce: null,
enableSubmitShare: false,
sharedUrl: '',
}
},
beforeMount(){
@@ -67,6 +92,8 @@
},
mounted(){
// this.isNoteShared = this.noteShared
if(this.shareUsername == null){
this.loadShareList()
}
@@ -74,12 +101,39 @@
},
methods: {
loadShareList(){
axios.post('/api/note/getshareusers', {'rawTextId':this.rawTextId })
axios.post('/api/note/getshareinfo', {'noteId':this.noteId, 'rawTextId':this.rawTextId })
.then( ({data}) => {
this.sharedWithUsers = data
this.isNoteShared = (data.shareStatus == 2)
this.sharedWithUsers = data.shareUsers
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Load Shared') })
},
makeShared(){
axios.post('/api/note/enableshare', {'noteId':this.noteId })
.then( ({data}) => {
this.isNoteShared = true
})
.catch(error => { this.$bus.$emit('notification', 'Failed to fetch Shared URL') })
},
removeShared(){
axios.post('/api/note/disableshare', {'noteId':this.noteId })
.then( ({data}) => {
this.isNoteShared = false
})
.catch(error => { this.$bus.$emit('notification', 'Failed to remove share status') })
},
getSharedUrl(){
axios.post('/api/note/getsharekey', {'noteId':this.noteId })
.then( ({data}) => {
const encodedKey = encodeURIComponent(data)
this.sharedUrl = `${window.location.protocol}//${window.location.hostname}/#/public/note/${this.noteId}/${encodedKey}`
})
.catch(error => { this.$bus.$emit('notification', 'Failed to fetch Shared URL') })
},
onRevokeAccess(sharedNoteId){
const postData = {

View File

@@ -10,14 +10,14 @@
height: 100%;
color: var(--text_color);
background-color: var(--background_color);
background-color: var(--small_element_bg_color);
}
.slide-content {
box-sizing: border-box;
/*padding: 1em 1.5em;*/
height: calc(100% - 43px);
border-right: 1px solid var(--menu-border);
/*background-color: var(--background_color);*/
/*background-color: var(--small_element_bg_color);*/
overflow-x: scroll;
}
.slide-shadow {