Started to build out the app. Its got a basic set of features and it should really be in VC
This commit is contained in:
121
client/src/components/HelloWorld.vue
Normal file
121
client/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<h2>Essential Links</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Core Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://forum.vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Forum
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://chat.vuejs.org"
|
||||
target="_blank"
|
||||
>
|
||||
Community Chat
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://twitter.com/vuejs"
|
||||
target="_blank"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<a
|
||||
href="http://vuejs-templates.github.io/webpack/"
|
||||
target="_blank"
|
||||
>
|
||||
Docs for This Template
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Ecosystem</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="http://router.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vue-router
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://vuex.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vuex
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://vue-loader.vuejs.org/"
|
||||
target="_blank"
|
||||
>
|
||||
vue-loader
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/awesome-vue"
|
||||
target="_blank"
|
||||
>
|
||||
awesome-vue
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="http://marvin.local/solr"
|
||||
target="_self"
|
||||
>
|
||||
solr
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
data () {
|
||||
return {
|
||||
msg: 'Welcome to Your mother fucking Vue App'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h1, h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
225
client/src/components/InputNotes.vue
Normal file
225
client/src/components/InputNotes.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div id="InputNotes" class="master-note-edit">
|
||||
|
||||
<ckeditor ref="main-edit"
|
||||
:editor="editor" @ready="onReady" v-model="noteText" :config="editorConfig" v-on:blur="save"></ckeditor>
|
||||
|
||||
<div class="ui buttons">
|
||||
<div class="ui green button">{{statusText}}</div>
|
||||
<div class="ui button">Delete</div>
|
||||
<div v-on:click="close" class="ui button">Close</div>
|
||||
<div class="ui disabled button">{{lastNoteHash}}</div>
|
||||
<div class="ui disabled button">Last Update: {{updated}}</div>
|
||||
</div>
|
||||
|
||||
<div class="ui segment">
|
||||
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
|
||||
</div>
|
||||
|
||||
<div class="ui segment" v-if="false">
|
||||
Block formatting
|
||||
|
||||
The following block formatting options are available:
|
||||
|
||||
<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>
|
||||
|
||||
Inline formatting
|
||||
|
||||
The following inline formatting options are available:
|
||||
|
||||
Bold – Type **text** or __text__,
|
||||
Italic – Type *text* or _text_,
|
||||
Code – Type `text`.
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
import DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'InputNotes',
|
||||
props: [ 'noteid' ],
|
||||
components:{
|
||||
'note-tag-edit': require('./NoteTagEdit.vue').default
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
currentNoteId: 0,
|
||||
noteText: '',
|
||||
statusText: 'Save',
|
||||
lastNoteHash: null,
|
||||
updated: 'Never',
|
||||
editDebounce: null,
|
||||
keyPressesCounter: 0,
|
||||
|
||||
editor: DecoupledEditor,
|
||||
editorConfig: {
|
||||
startupFocus: 'end',
|
||||
toolbar: ["alignment", "fontSize", "removeHighlight", "highlight", "bold", "italic", "strikethrough", "underline", "blockQuote", "heading", "link", "numberedList", "bulletedList", "insertTable", "|", "undo", "redo"]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
noteid:function(newVal, oldVal){
|
||||
|
||||
if(newVal == this.currentNoteId){
|
||||
return
|
||||
}
|
||||
|
||||
if(newVal == oldVal){
|
||||
return
|
||||
}
|
||||
|
||||
this.currentNoteId = newVal
|
||||
this.loadNote(this.currentNoteId)
|
||||
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
},
|
||||
beforeDestroy(){
|
||||
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
this.loadNote(this.noteid)
|
||||
},
|
||||
methods: {
|
||||
loadNote(noteId){
|
||||
let vm = this
|
||||
//Component is activated with NoteId in place, lookup text with associated ID
|
||||
if(this.$store.getters.getLoggedIn){
|
||||
axios.post('/api/notes/get', {'noteId': noteId})
|
||||
.then(response => {
|
||||
//Set up local data
|
||||
vm.currentNoteId = noteId
|
||||
vm.noteText = response.data.text
|
||||
vm.updated = response.data.updated
|
||||
vm.lastNoteHash = vm.hashString(response.data.text)
|
||||
|
||||
//Put focus on note, at the end of the note text
|
||||
vm.$nextTick(() => {
|
||||
// vm.$refs['custom-input'].focus()
|
||||
})
|
||||
|
||||
})
|
||||
} else {
|
||||
console.log('Could not fetch note')
|
||||
}
|
||||
},
|
||||
onReady(editor){
|
||||
|
||||
let vm = this
|
||||
console.log(vm)
|
||||
|
||||
// Insert the toolbar before the editable area.
|
||||
editor.ui.getEditableElement().parentElement.insertBefore(
|
||||
editor.ui.view.toolbar.element,
|
||||
editor.ui.getEditableElement()
|
||||
);
|
||||
|
||||
editor.editing.view.focus()
|
||||
|
||||
// const editor = this.editor;
|
||||
const view = editor.editing.view;
|
||||
const viewDocument = view.document;
|
||||
|
||||
//Insert 5 spaces when tab is pressed
|
||||
viewDocument.on( 'keyup', ( evt, data ) => {
|
||||
|
||||
//Each note, save after 5 seconds, focus lost or 30 characters typed.
|
||||
clearTimeout(vm.editDebounce)
|
||||
vm.editDebounce = setTimeout(() => {
|
||||
vm.save()
|
||||
}, 5000)
|
||||
//Save after 20 keystrokes
|
||||
vm.keyPressesCounter = (vm.keyPressesCounter + 1)
|
||||
if(vm.keyPressesCounter > 30){
|
||||
vm.save()
|
||||
}
|
||||
|
||||
if( (data.keyCode == 9) && viewDocument.isFocused ){
|
||||
|
||||
//Insert 5 spaces to simulate tab
|
||||
editor.execute( 'input', { text: " " } );
|
||||
|
||||
evt.stop(); // Prevent executing the default handler.
|
||||
data.preventDefault();
|
||||
view.scrollToTheSelection();
|
||||
}
|
||||
|
||||
} );
|
||||
},
|
||||
save(){
|
||||
|
||||
console.log('Save, ', this.keyPressesCounter)
|
||||
|
||||
this.keyPressesCounter = 0
|
||||
clearTimeout(this.editDebounce)
|
||||
|
||||
//Don't save note if its hash doesn't change
|
||||
if(this.lastNoteHash == this.hashString(this.noteText)){
|
||||
return
|
||||
}
|
||||
|
||||
const postData = {
|
||||
'noteId':this.currentNoteId,
|
||||
'text': this.noteText
|
||||
}
|
||||
|
||||
let vm = this
|
||||
axios.post('/api/notes/update', postData).then( response => {
|
||||
vm.statusText = 'Saved'
|
||||
|
||||
//Update last saved note hash
|
||||
vm.lastNoteHash = vm.hashString(vm.noteText)
|
||||
|
||||
setTimeout(() => {
|
||||
vm.statusText = 'Save'
|
||||
}, 5000)
|
||||
})
|
||||
},
|
||||
hashString(text){
|
||||
var hash = 0;
|
||||
if (text.length == 0) {
|
||||
return hash;
|
||||
}
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
let char = text.charCodeAt(i);
|
||||
hash = ((hash<<5)-hash)+char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
},
|
||||
close(){
|
||||
this.$bus.$emit('close_active_note')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css" scoped>
|
||||
|
||||
.master-note-edit {
|
||||
position: fixed;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
bottom: 0;
|
||||
background: green;
|
||||
height: 98vh;
|
||||
border-top-right-radius: 4px;
|
||||
border-top-left-radius: 4px;
|
||||
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
|
||||
}
|
||||
|
||||
</style>
|
77
client/src/components/Login.vue
Normal file
77
client/src/components/Login.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{getRudeMessage}}</h3>
|
||||
<p>Login</p>
|
||||
|
||||
|
||||
<div class="ui segment" v-on:keyup.enter="submit">
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="username" type="text" name="email" placeholder="E-mail address" autofocus>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="password" type="password" name="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div v-on:click="submit" class="ui teal submit button">Login</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: {
|
||||
validate(){
|
||||
console.log('Validate this bitch')
|
||||
},
|
||||
submit(){
|
||||
|
||||
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
|
||||
vm.$store.commit('setLoginToken', token)
|
||||
|
||||
//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>
|
74
client/src/components/NoteTagEdit.vue
Normal file
74
client/src/components/NoteTagEdit.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui icon large label" v-for="tag in tags">
|
||||
{{ucWords(tag.text)}} <i class="delete icon" v-on:click="removeTag(tag.id)"></i>
|
||||
</div>
|
||||
<input v-model="newTagInput" v-on:keyup.enter="addTag" placeholder="Add Tag" />
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'NoteTagEdit',
|
||||
props: [ 'noteId' ],
|
||||
data () {
|
||||
return {
|
||||
tags: null,
|
||||
newTagInput: ''
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.getTags()
|
||||
},
|
||||
methods: {
|
||||
getTags(){
|
||||
//Get Note Tags -> /api/tags/get
|
||||
let vm = this
|
||||
axios.post('/api/tags/get', {'noteId': this.noteId})
|
||||
.then(response => {
|
||||
//Set up local data
|
||||
vm.tags = response.data
|
||||
})
|
||||
},
|
||||
addTag(event){
|
||||
|
||||
//post to -> /api/addtagtonote
|
||||
let postData = {
|
||||
'tagText':this.newTagInput,
|
||||
'noteId':this.noteId
|
||||
}
|
||||
let vm = this
|
||||
axios.post('/api/tags/addtonote', postData)
|
||||
.then(response => {
|
||||
vm.newTagInput = ''
|
||||
vm.getTags()
|
||||
})
|
||||
},
|
||||
removeTag(tagId){
|
||||
|
||||
let postData = {
|
||||
'tagId':tagId,
|
||||
'noteId':this.noteId
|
||||
}
|
||||
let vm = this
|
||||
axios.post('/api/tags/removefromnote', postData)
|
||||
.then(response => {
|
||||
vm.getTags()
|
||||
})
|
||||
},
|
||||
ucWords(str){
|
||||
return (str + '')
|
||||
.replace(/^(.)|\s+(.)/g, function ($1) {
|
||||
return $1.toUpperCase()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
47
client/src/components/Notes.vue
Normal file
47
client/src/components/Notes.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div id="NotesPage">
|
||||
<div class="ui segment">
|
||||
<search-bar />
|
||||
</div>
|
||||
|
||||
<input-notes v-if="activeNoteId != null" :noteid="activeNoteId"></input-notes>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
//ajax calls
|
||||
import axios from 'axios';
|
||||
// import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Notes',
|
||||
components:{
|
||||
'input-notes': require('./InputNotes.vue').default,
|
||||
'search-bar': require('./SearchBar.vue').default
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
notes: null,
|
||||
activeNoteId: null
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
this.$bus.$on('close_active_note', () => {
|
||||
this.activeNoteId = null
|
||||
})
|
||||
|
||||
this.$bus.$on('open_note', openNoteId => {
|
||||
this.activeNoteId = openNoteId
|
||||
})
|
||||
},
|
||||
mounted: function() {
|
||||
//this.getLatest()
|
||||
},
|
||||
methods: {
|
||||
openNote(id){
|
||||
this.activeNoteId = id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
124
client/src/components/SearchBar.vue
Normal file
124
client/src/components/SearchBar.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
|
||||
<div class="ui grid">
|
||||
<!-- search menu -->
|
||||
<div class="ui row">
|
||||
<div @click="createNote" class="ui green button">
|
||||
<i class="plus icon"></i>
|
||||
New Note
|
||||
</div>
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
|
||||
<div class="ui button" @click="reset">Reset</div>
|
||||
</div>
|
||||
<div class="ui row">
|
||||
<div class="ui two wide column">
|
||||
<div class="ui clickable basic fluid large label" v-for="tag in commonTags" @click="toggleTagFilter(tag.id)"
|
||||
:class="{ 'green':(searchTags.includes(tag.id)) }">
|
||||
{{ucWords(tag.text)}} <div class="detail">{{tag.usages}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui fourteen wide column">
|
||||
<div v-if="notes !== null">
|
||||
<div class="ui vertical segment clickable" v-for="note in notes" v-on:click="openNote(note.id)" v-html="note.text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'SearchBar',
|
||||
data () {
|
||||
return {
|
||||
initComponent: true,
|
||||
commonTags: [],
|
||||
searchTerm: '',
|
||||
searchTags: [],
|
||||
notes: null,
|
||||
searchDebounce: null
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.search()
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
})
|
||||
},
|
||||
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)
|
||||
}
|
||||
})
|
||||
},
|
||||
openNote(noteId){
|
||||
//Emit open note event
|
||||
this.$bus.$emit('open_note', noteId)
|
||||
},
|
||||
ucWords(str){
|
||||
return (str + '')
|
||||
.replace(/^(.)|\s+(.)/g, function ($1) {
|
||||
return $1.toUpperCase()
|
||||
})
|
||||
},
|
||||
reset(){
|
||||
this.searchTerm = ''
|
||||
this.searchTags = []
|
||||
this.search()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css" scoped>
|
||||
.detail {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user