Large refactor on the front end
Created pages directory Added night mode
This commit is contained in:
36
client/src/pages/HelpPage.vue
Normal file
36
client/src/pages/HelpPage.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui container">
|
||||
|
||||
<h2>Note Editing - Keyboard Shortcuts</h2>
|
||||
|
||||
<p>CTRL + SHIFT + V - paste without formatting</p>
|
||||
<p>CTRL + Z - Undo in note, <b>Undo youtube video player embed.</b></p>
|
||||
<p>ESC - Close note editor</p>
|
||||
|
||||
<h2>Block formatting</h2>
|
||||
|
||||
<p>The following block formatting options are available:</p>
|
||||
|
||||
<p> Bulleted list – Start a line with * or - followed by a space.</p>
|
||||
<p> Numbered list – Start a line with 1. or 1) followed by a space.</p>
|
||||
<p> Headings – Start a line with # or ## or ### followed by a space to create a heading 1, heading 2 or heading 3 (up to heading 6 if options defines more headings).</p>
|
||||
<p> Block quote – Start a line with > followed by a space.</p>
|
||||
|
||||
<h2>Inline formatting</h2>
|
||||
|
||||
<p>The following inline formatting options are available:<p>
|
||||
|
||||
<p>Bold – Type **text** or __text__,</p>
|
||||
<p>Italic – Type *text* or _text_,</p>
|
||||
<p>Code – Type `text`.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelpPage'
|
||||
}
|
||||
</script>
|
13
client/src/pages/HomePage.vue
Normal file
13
client/src/pages/HomePage.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui container">
|
||||
<h1>Welcome</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WelcomePage'
|
||||
}
|
||||
</script>
|
86
client/src/pages/LoginPage.vue
Normal file
86
client/src/pages/LoginPage.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="ui container">
|
||||
<h3>Login</h3>
|
||||
<p>Begin the login process by typing your username or email.</p>
|
||||
<p>To create an account, type in the username you want to use followed by the password.</p>
|
||||
<p>You will remain logged in on this browser, until you log out.</p>
|
||||
|
||||
<div class="ui segment" v-on:keyup.enter="submit">
|
||||
<div class="ui large form">
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" v-if="username.length > 0">
|
||||
<div class="ui input">
|
||||
<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">Login</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
//ajax calls
|
||||
import axios from 'axios';
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data () {
|
||||
return {
|
||||
message: 'Login stuff',
|
||||
enabled: false,
|
||||
username: '',
|
||||
password: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit(){
|
||||
|
||||
//Both fields are required
|
||||
if(this.username <= 0){
|
||||
return false
|
||||
}
|
||||
if(this.password <= 0){
|
||||
return false
|
||||
}
|
||||
|
||||
let vm = this
|
||||
|
||||
let data = {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}
|
||||
|
||||
axios.post('/api/user/login', data)
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
if(response.data.success){
|
||||
|
||||
const token = response.data.token
|
||||
const username = response.data.username
|
||||
vm.$store.commit('setLoginToken', {token, username})
|
||||
|
||||
//Redirect user to notes section after login
|
||||
this.$router.push('/notes')
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('There was an error with log in request')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getRudeMessage'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
257
client/src/pages/NotesPage.vue
Normal file
257
client/src/pages/NotesPage.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="ui basic segment">
|
||||
|
||||
|
||||
<div class="ui equal width grid">
|
||||
|
||||
<!-- mobile search menu -->
|
||||
<div class="ui mobile only row">
|
||||
<!-- Small screen new note button -->
|
||||
<div class="ui four wide column">
|
||||
<div @click="createNote" class="ui fluid green icon button">
|
||||
<i class="plus icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui twelve wide column">
|
||||
<div class="ui form">
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- search menu -->
|
||||
<div class="ui large screen only row">
|
||||
|
||||
<div class="ui two wide column">
|
||||
<div @click="createNote" class="ui fluid green button">
|
||||
<i class="plus icon"></i>
|
||||
New Note
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui five wide column">
|
||||
<div class="ui form">
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui nine wide column">
|
||||
|
||||
<router-link class="ui basic button" to="/help">Help</router-link>
|
||||
|
||||
<div v-on:click="toggleNightMode" class="ui basic button">
|
||||
Dark Theme:
|
||||
<span v-if="$store.getters.getIsNightMode">On</span>
|
||||
<span v-else>Off</span>
|
||||
</div>
|
||||
|
||||
<div class="ui right floated basic button"
|
||||
data-tooltip="Log Out" data-position="left center"
|
||||
v-on:click="destroyLoginToken">{{username}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui row">
|
||||
|
||||
<!-- tags display -->
|
||||
<div class="ui two wide large screen only column">
|
||||
<div class="ui basic fluid button" @click="reset"><i class="undo icon"></i>All Notes</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui section list">
|
||||
<div class="item" v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
|
||||
<div class="ui clickable basic fluid large label" :class="{ 'green':(searchTags.includes(tag.id)) }">
|
||||
{{ucWords(tag.text)}} <div class="detail">{{tag.usages}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Note title cards -->
|
||||
<div class="ui fourteen wide computer sixteen wide mobile column">
|
||||
<h2>
|
||||
Notes ({{notes.length}})
|
||||
</h2>
|
||||
<div v-if="notes !== null" class="note-card-display-area" :class="{'one-column':(activeNoteId1 != null || activeNoteId2 != null )}">
|
||||
<note-title-display-card
|
||||
v-for="note in notes"
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:key="note.id + note.color + searchTerm + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<input-notes v-if="activeNoteId1 != null" :noteid="activeNoteId1" :position="activeNote1Position" />
|
||||
<input-notes v-if="activeNoteId2 != null" :noteid="activeNoteId2" :position="activeNote2Position" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'SearchBar',
|
||||
components: {
|
||||
'input-notes': require('@/components/NoteInputPanel.vue').default,
|
||||
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
username:'',
|
||||
initComponent: true,
|
||||
commonTags: [],
|
||||
searchTerm: '',
|
||||
searchTags: [],
|
||||
notes: [],
|
||||
searchDebounce: null,
|
||||
|
||||
//Currently open notes in app
|
||||
activeNoteId1: null,
|
||||
activeNoteId2: null,
|
||||
//Position determines how note is Positioned
|
||||
activeNote1Position: 0,
|
||||
activeNote2Position: 0,
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
let username = this.$store.getters.getUsername
|
||||
this.username = this.ucWords(username)
|
||||
|
||||
this.$bus.$on('close_active_note', position => {
|
||||
this.closeNote(position)
|
||||
})
|
||||
this.$bus.$on('note_deleted', () => {
|
||||
this.search()
|
||||
})
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
this.search()
|
||||
|
||||
},
|
||||
methods: {
|
||||
openNote(id){
|
||||
|
||||
//Do not open same note twice
|
||||
if(this.activeNoteId1 == id || this.activeNoteId2 == id){
|
||||
return;
|
||||
}
|
||||
|
||||
//1 note open
|
||||
if(this.activeNoteId1 == null && this.activeNoteId2 == null){
|
||||
this.activeNoteId1 = id
|
||||
this.activeNote1Position = 0 //Middel of page
|
||||
return
|
||||
}
|
||||
//2 notes open
|
||||
if(this.activeNoteId1 != null && this.activeNoteId2 == null){
|
||||
this.activeNoteId2 = id
|
||||
this.activeNote1Position = 1 //Right side of page
|
||||
this.activeNote2Position = 2 //Left side of page
|
||||
return
|
||||
}
|
||||
},
|
||||
closeNote(position){
|
||||
//One note open, close that note
|
||||
if(position == 0){
|
||||
this.activeNoteId1 = null
|
||||
this.activeNoteId2 = null
|
||||
}
|
||||
//Right note closed, thats 1
|
||||
if(position == 1){
|
||||
this.activeNoteId1 = null
|
||||
}
|
||||
if(position == 2){
|
||||
this.activeNoteId2 = null
|
||||
}
|
||||
|
||||
this.activeNote1Position = 0
|
||||
this.activeNote2Position = 0
|
||||
|
||||
this.search()
|
||||
},
|
||||
toggleTagFilter(tagId){
|
||||
|
||||
if(this.searchTags.includes(tagId)){
|
||||
this.searchTags.splice( this.searchTags.indexOf(tagId) , 1);
|
||||
} else {
|
||||
this.searchTags.push(tagId)
|
||||
}
|
||||
|
||||
this.search()
|
||||
},
|
||||
search(){
|
||||
let postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
searchTags: this.searchTags
|
||||
}
|
||||
//Perform search
|
||||
let vm = this
|
||||
axios.post('/api/notes/search', postData).
|
||||
then(response => {
|
||||
console.log('Notes and Tags')
|
||||
console.log(response.data)
|
||||
|
||||
vm.commonTags = response.data.tags
|
||||
vm.notes = response.data.notes
|
||||
vm.highlights = response.data.highlights
|
||||
})
|
||||
},
|
||||
searchKeyUp(){
|
||||
let vm = this
|
||||
clearTimeout(vm.searchDebounce)
|
||||
vm.searchDebounce = setTimeout(() => {
|
||||
vm.search()
|
||||
}, 300)
|
||||
},
|
||||
createNote(event){
|
||||
const title = ''
|
||||
let vm = this
|
||||
|
||||
axios.post('/api/notes/create', {title})
|
||||
.then(response => {
|
||||
|
||||
if(response.data && response.data.id){
|
||||
vm.openNote(response.data.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
ucWords(str){
|
||||
return (str + '')
|
||||
.replace(/^(.)|\s+(.)/g, function ($1) {
|
||||
return $1.toUpperCase()
|
||||
})
|
||||
},
|
||||
reset(){
|
||||
this.searchTerm = ''
|
||||
this.searchTags = []
|
||||
this.search()
|
||||
},
|
||||
destroyLoginToken() {
|
||||
this.$store.commit('destroyLoginToken')
|
||||
this.$router.push('/')
|
||||
},
|
||||
toggleNightMode(){
|
||||
this.$store.commit('toggleNightMode')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css" scoped>
|
||||
.detail {
|
||||
float: right;
|
||||
}
|
||||
.note-card-display-area {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user