* Updated color picker interface
* Updated note status bar * Added fast filters * Added pinned and archived notes options * Added loading indicators to notes and loading of notes * updated tag edit area * Updated how search results are displayed * Fixed bug with opening and closing two notes one after another * Added mobile detection to global store * Added a lot of style tweaks and UX tweaks
This commit is contained in:
parent
dd0205a3c1
commit
7b77bd37f3
@ -4,7 +4,9 @@
|
|||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Hide this menu on the notes page -->
|
<!-- Hide this menu on the notes page -->
|
||||||
<div class="ui basic segment" v-if="this.$router.currentRoute.path != '/notes'">
|
<div class="ui basic segment" v-if="
|
||||||
|
this.$router.currentRoute.name != 'NotesPage'
|
||||||
|
">
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui tabular menu">
|
<div class="ui tabular menu">
|
||||||
|
|
||||||
@ -52,6 +54,9 @@ export default {
|
|||||||
this.$store.commit('destroyLoginToken')
|
this.$store.commit('destroyLoginToken')
|
||||||
this.$router.push({'path':'/'})
|
this.$router.push({'path':'/'})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Detect if user is on a mobile browser and set a flag in store
|
||||||
|
this.$store.commit('detectIsUserOnMobile')
|
||||||
},
|
},
|
||||||
mounted: function(){
|
mounted: function(){
|
||||||
},
|
},
|
||||||
|
@ -48,13 +48,49 @@ div.ui.basic.green.label {
|
|||||||
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
|
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
|
||||||
|
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
color: var(--text_color);
|
||||||
|
background-color: var(--background_color);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 175px;
|
||||||
|
height: 100px;
|
||||||
|
top: 26px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
left: -63px;
|
||||||
|
z-index: 100;
|
||||||
|
border: 1px solid;
|
||||||
|
border-color: var(--border_color) !important;
|
||||||
|
}
|
||||||
|
.color-picker .button{
|
||||||
|
border: 1px solid !important;
|
||||||
|
border-color: var(--border_color) !important;
|
||||||
|
color: var(--border_color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-status-indicator {
|
||||||
|
float: right;
|
||||||
|
width: 100px;
|
||||||
|
padding: 9px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.textarea-height {
|
.textarea-height {
|
||||||
height: calc(100% - 105px);
|
height: calc(100% - 90px);
|
||||||
}
|
}
|
||||||
.ck-content {
|
.ck-content {
|
||||||
font-family: 'Open Sans' !important;
|
font-family: 'Open Sans' !important;
|
||||||
@ -74,11 +110,6 @@ div.ui.basic.green.label {
|
|||||||
animation: fade-in-fwd 0.8s both;
|
animation: fade-in-fwd 0.8s both;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------
|
|
||||||
* Generated by Animista on 2019-7-25 17:12:5
|
|
||||||
* w: http://animista.net, t: @cssanimista
|
|
||||||
* ---------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
* animation fade-in-fwd
|
* animation fade-in-fwd
|
||||||
|
96
client/src/components/FastFilters.vue
Normal file
96
client/src/components/FastFilters.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<span v-on:mouseover="open = true" v-on:mouseleave="open = false" class="relative clickable filter-header" :class="{'filter-active':open}">
|
||||||
|
{{displayString()}} <i class="grey caret down icon"></i>
|
||||||
|
<span class="filter-menu" v-if="open">
|
||||||
|
<span v-for="(filter,label) in filterOptions" class="filter-option" v-on:click="updateFilter(filter, label)">{{label}}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FastFilters',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
click: 0,
|
||||||
|
open: false,
|
||||||
|
filter: {},
|
||||||
|
orderString: 'Order by Last Edited',
|
||||||
|
filterOptions:{
|
||||||
|
'Order by Last Edited' :'lastEdited',
|
||||||
|
'Order by Last Opened' :'lastOpened',
|
||||||
|
'Order by Last Created' :'lastCreated',
|
||||||
|
'Only Show Notes with Links' :'withLinks',
|
||||||
|
'Only Show Notes with Tags' :'withTags',
|
||||||
|
'Only Show Archived Notes' :'onlyArchived',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount(){
|
||||||
|
this.$bus.$on('reset_fast_filters', () => {
|
||||||
|
this.orderString = 'Order by Last Edited'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
confirmDelete(){
|
||||||
|
this.click++
|
||||||
|
},
|
||||||
|
displayString(){
|
||||||
|
return this.orderString.replace('Order by','').replace('Only Show','')
|
||||||
|
},
|
||||||
|
updateFilter(option, label){
|
||||||
|
|
||||||
|
this.orderString = label
|
||||||
|
this.open = false
|
||||||
|
|
||||||
|
let filter = {}
|
||||||
|
filter[option] = 1
|
||||||
|
|
||||||
|
this.$bus.$emit('update_fast_filters', filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style type="text/css" scoped>
|
||||||
|
|
||||||
|
.filter-header {
|
||||||
|
width: 270px;
|
||||||
|
padding: 0 0 0 10px;
|
||||||
|
border: 1px solid rgba(0,0,0,0);
|
||||||
|
border-bottom: none;
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
}
|
||||||
|
.filter-menu {
|
||||||
|
|
||||||
|
color: var(--text_color);
|
||||||
|
background-color: var(--background_color);
|
||||||
|
border-color: var(--border_color);
|
||||||
|
|
||||||
|
border: 1px solid;
|
||||||
|
border-top: none;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
width: 270px;
|
||||||
|
left: -1px;
|
||||||
|
z-index: 10;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
.filter-active {
|
||||||
|
border: 1px solid;
|
||||||
|
border-bottom: none;
|
||||||
|
border-color: var(--border_color);
|
||||||
|
}
|
||||||
|
.filter-option {
|
||||||
|
font-size: 17px;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 0 5px 10px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<span class="clickable" @click="confirmDelete()" v-if="click == 0">
|
<span class="clickable" @click="confirmDelete()" v-if="click == 0" data-tooltip="Delete">
|
||||||
<i class="grey trash alternate icon"></i>
|
<i class="grey trash alternate icon"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="clickable" @click="actuallyDelete()" @mouseleave="reset" v-if="click == 1" data-tooltip="Click again to delete." data-position="left center">
|
<span class="clickable" @click="actuallyDelete()" @mouseleave="reset" v-if="click == 1" data-tooltip="Click again to delete." data-position="left center">
|
||||||
|
@ -6,28 +6,32 @@
|
|||||||
:style="{'background-color':color, 'color':fontColor}"
|
:style="{'background-color':color, 'color':fontColor}"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div v-if="fancyInput == 1" class="textarea-height no-flow">
|
<div v-if="loading" class="loading-note">
|
||||||
<ckeditor ref="main-edit"
|
<div class="ui active dimmer">
|
||||||
:editor="editor" @ready="onReady" v-model="noteText" :config="editorConfig" v-on:blur="save" />
|
<div class="ui text loader">{{loadingMessage}}...</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<div class="note-top-menu">
|
||||||
class="textarea-height raw-edit"
|
<div @click="close" class="ui button"><i class="green close icon"></i>Close (ESC)</div>
|
||||||
v-if="fancyInput == 0"
|
|
||||||
v-model="noteText"
|
|
||||||
v-on:blur="save"
|
|
||||||
v-on:keyup="onKeyup"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="ui buttons">
|
|
||||||
<div @click="close" class="ui button">Close + Save (ESC)</div>
|
|
||||||
<div class="ui button">Delete</div>
|
|
||||||
|
|
||||||
<div @click="onToggleFancyInput" class="ui button">
|
<div @click="onToggleFancyInput" class="ui button">
|
||||||
Fancy ({{fancyInput?'On':'Off'}})
|
Fancy ({{fancyInput?'On':'Off'}})
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div @click="onTogglePinned" class="ui button">
|
||||||
|
<i class="pin icon" :class="{green:(pinned == 1)}"></i> {{(pinned == 1)?'pinned':'not pinned'}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui buttons">
|
<div @click="onToggleArchived" class="ui button">
|
||||||
|
<i class="archive icon" :class="{green:(archived == 1)}"></i> {{(archived == 1)?'archived':'not archived'}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="relative" v-on:mouseover="showColorPicker = true" v-on:mouseleave="showColorPicker = false">
|
||||||
|
<span class="ui icon button">
|
||||||
|
<i class="paint brush icon"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="showColorPicker" class="color-picker">
|
||||||
<button @click="onChangeColor" class="ui icon white button"></button>
|
<button @click="onChangeColor" class="ui icon white button"></button>
|
||||||
<button @click="onChangeColor" class="ui icon red button"></button>
|
<button @click="onChangeColor" class="ui icon red button"></button>
|
||||||
<button @click="onChangeColor" class="ui icon orange button"></button>
|
<button @click="onChangeColor" class="ui icon orange button"></button>
|
||||||
@ -42,11 +46,24 @@
|
|||||||
<button @click="onChangeColor" class="ui icon brown button"></button>
|
<button @click="onChangeColor" class="ui icon brown button"></button>
|
||||||
<button @click="onChangeColor" class="ui icon grey button"></button>
|
<button @click="onChangeColor" class="ui icon grey button"></button>
|
||||||
<button @click="onChangeColor" class="ui icon black button"></button>
|
<button @click="onChangeColor" class="ui icon black button"></button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="note-status-indicator">{{statusText}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui right floated green button">{{statusText}}</div>
|
|
||||||
<!-- <p>
|
<div v-if="fancyInput == 1" class="textarea-height no-flow">
|
||||||
Last Updated: {{$helpers.timeAgo(updated)}}
|
<ckeditor ref="main-edit"
|
||||||
</p> -->
|
:editor="editor" @ready="onReady" v-model="noteText" :config="editorConfig" v-on:blur="save" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
class="textarea-height raw-edit"
|
||||||
|
v-if="fancyInput == 0"
|
||||||
|
v-model="noteText"
|
||||||
|
v-on:blur="save"
|
||||||
|
v-on:keyup="onKeyup"
|
||||||
|
/>
|
||||||
|
|
||||||
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
|
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
|
||||||
|
|
||||||
@ -58,6 +75,13 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document';
|
import DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document';
|
||||||
|
|
||||||
|
//Start working on some plugin, tag plugin, link to other note, interactive checkbox
|
||||||
|
class InsertImage extends Plugin {
|
||||||
|
init() {
|
||||||
|
console.log( 'InsertImage was initialized' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InputNotes',
|
name: 'InputNotes',
|
||||||
@ -67,6 +91,8 @@
|
|||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
|
loading: true,
|
||||||
|
loadingMessage: 'Loading Note',
|
||||||
currentNoteId: 0,
|
currentNoteId: 0,
|
||||||
noteText: '',
|
noteText: '',
|
||||||
statusText: 'Saved',
|
statusText: 'Saved',
|
||||||
@ -77,9 +103,12 @@
|
|||||||
editDebounce: null,
|
editDebounce: null,
|
||||||
keyPressesCounter: 0,
|
keyPressesCounter: 0,
|
||||||
fancyInput: 0, //Default to basic text edit. Upgrade if set to 1
|
fancyInput: 0, //Default to basic text edit. Upgrade if set to 1
|
||||||
color: '#FFF',
|
pinned: 0,
|
||||||
|
archived: 0,
|
||||||
|
color: '#fff',
|
||||||
fontColor: '#000',
|
fontColor: '#000',
|
||||||
sizeDown: false,
|
sizeDown: false,
|
||||||
|
showColorPicker: false,
|
||||||
|
|
||||||
editor: DecoupledEditor,
|
editor: DecoupledEditor,
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
@ -116,7 +145,6 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onToggleFancyInput(){
|
onToggleFancyInput(){
|
||||||
|
|
||||||
if(this.fancyInput == 0){
|
if(this.fancyInput == 0){
|
||||||
this.fancyInput = 1
|
this.fancyInput = 1
|
||||||
} else {
|
} else {
|
||||||
@ -125,12 +153,31 @@
|
|||||||
//Update last note hash, this will tell note to save next update
|
//Update last note hash, this will tell note to save next update
|
||||||
this.lastNoteHash = 0
|
this.lastNoteHash = 0
|
||||||
},
|
},
|
||||||
|
onTogglePinned(){
|
||||||
|
if(this.pinned == 0){
|
||||||
|
this.pinned = 1
|
||||||
|
} else {
|
||||||
|
this.pinned = 0;
|
||||||
|
}
|
||||||
|
//Update last note hash, this will tell note to save next update
|
||||||
|
this.lastNoteHash = 0
|
||||||
|
},
|
||||||
|
onToggleArchived(){
|
||||||
|
if(this.archived == 0){
|
||||||
|
this.archived = 1
|
||||||
|
} else {
|
||||||
|
this.archived = 0;
|
||||||
|
}
|
||||||
|
//Update last note hash, this will tell note to save next update
|
||||||
|
this.lastNoteHash = 0
|
||||||
|
},
|
||||||
onChangeColor(event){
|
onChangeColor(event){
|
||||||
//Grab the color of the button clicked
|
//Grab the color of the button clicked
|
||||||
const style = getComputedStyle(event.target)
|
const style = getComputedStyle(event.target)
|
||||||
this.color = style['background-color']
|
this.color = style['background-color']
|
||||||
this.fontColor = '#FFF'
|
this.fontColor = '#FFF'
|
||||||
|
|
||||||
|
//If background is white, default to colors in CSS
|
||||||
if(this.color == "rgb(255, 255, 255)" || this.color == '#FFF'){
|
if(this.color == "rgb(255, 255, 255)" || this.color == '#FFF'){
|
||||||
this.color = null
|
this.color = null
|
||||||
this.fontColor = null
|
this.fontColor = null
|
||||||
@ -140,19 +187,33 @@
|
|||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
loadNote(noteId){
|
loadNote(noteId){
|
||||||
|
|
||||||
let vm = this
|
let vm = this
|
||||||
|
|
||||||
|
let doing = ['Loading','Loading','Getting','Fetching','Grabbing','Sequencing','Organizing','Untangling','Processing','Refining','Extracting','Fusing','Pruning','Expanding','Enlarging','Transfiguring','Quantizing','Ingratiating']
|
||||||
|
let thing = ['Note','Note','Note','Note','Data','Text','Document','Algorithm','Buffer','Client','Download','File','Frame','Graphics','Hardware','HTML','Interface','Logic','Mainframe','Memory','Media','Nodes','Network','Chaos']
|
||||||
|
let p1 = doing[Math.floor(Math.random() * doing.length)]
|
||||||
|
let p2 = thing[Math.floor(Math.random() * thing.length)]
|
||||||
|
vm.loadingMessage = p1 + ' ' + p2
|
||||||
|
|
||||||
|
|
||||||
//Component is activated with NoteId in place, lookup text with associated ID
|
//Component is activated with NoteId in place, lookup text with associated ID
|
||||||
if(this.$store.getters.getLoggedIn){
|
if(this.$store.getters.getLoggedIn){
|
||||||
axios.post('/api/note/get', {'noteId': noteId})
|
axios.post('/api/note/get', {'noteId': noteId})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
||||||
|
vm.loading = false
|
||||||
|
|
||||||
//Set up local data
|
//Set up local data
|
||||||
vm.currentNoteId = noteId
|
vm.currentNoteId = noteId
|
||||||
vm.noteText = response.data.text
|
vm.noteText = response.data.text
|
||||||
vm.updated = response.data.updated
|
vm.updated = response.data.updated
|
||||||
vm.lastNoteHash = vm.hashString(response.data.text)
|
vm.lastNoteHash = vm.hashString(response.data.text)
|
||||||
console.log(vm.lastNoteHash)
|
|
||||||
vm.color = response.data.color
|
vm.color = response.data.color
|
||||||
|
if(response.data.pinned != null){
|
||||||
|
vm.pinned = response.data.pinned
|
||||||
|
}
|
||||||
|
vm.archived = response.data.archived
|
||||||
|
|
||||||
this.fontColor = '#FFF'
|
this.fontColor = '#FFF'
|
||||||
if(this.color == "rgb(255, 255, 255)" || this.color == '#FFF' || this.color == null){
|
if(this.color == "rgb(255, 255, 255)" || this.color == '#FFF' || this.color == null){
|
||||||
@ -166,6 +227,7 @@
|
|||||||
|
|
||||||
//Put focus on note, at the end of the note text
|
//Put focus on note, at the end of the note text
|
||||||
vm.$nextTick(() => {
|
vm.$nextTick(() => {
|
||||||
|
|
||||||
// vm.$refs['custom-input'].focus()
|
// vm.$refs['custom-input'].focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -230,7 +292,6 @@
|
|||||||
|
|
||||||
//Don't save note if its hash doesn't change
|
//Don't save note if its hash doesn't change
|
||||||
if( this.lastNoteHash == this.hashString(this.noteText) ){
|
if( this.lastNoteHash == this.hashString(this.noteText) ){
|
||||||
console.log('Note was not modified')
|
|
||||||
resolve(false)
|
resolve(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -239,7 +300,9 @@
|
|||||||
'noteId':this.currentNoteId,
|
'noteId':this.currentNoteId,
|
||||||
'text': this.noteText,
|
'text': this.noteText,
|
||||||
'fancyInput': this.fancyInput,
|
'fancyInput': this.fancyInput,
|
||||||
'color': this.color
|
'color': this.color,
|
||||||
|
'pinned': this.pinned,
|
||||||
|
'archived':this.archived,
|
||||||
}
|
}
|
||||||
|
|
||||||
let vm = this
|
let vm = this
|
||||||
@ -295,7 +358,6 @@
|
|||||||
|
|
||||||
<style type="text/css" scoped>
|
<style type="text/css" scoped>
|
||||||
|
|
||||||
|
|
||||||
.no-flow {
|
.no-flow {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -310,6 +372,10 @@
|
|||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-top-menu {
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
/* container styles change based on mobile and number of open screens */
|
/* container styles change based on mobile and number of open screens */
|
||||||
.master-note-edit {
|
.master-note-edit {
|
||||||
@ -319,11 +385,19 @@
|
|||||||
/*color: var(--text_color);*/
|
/*color: var(--text_color);*/
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
|
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
.loading-note {
|
||||||
|
position: absolute;
|
||||||
|
top: 38px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 50px;
|
||||||
}
|
}
|
||||||
/* One note open, in the middle of the screen */
|
/* One note open, in the middle of the screen */
|
||||||
.master-note-edit.position-0 {
|
.master-note-edit.position-0 {
|
||||||
left: 30%;
|
left: 30%;
|
||||||
right: 1%;
|
right: 0;
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 740px) {
|
@media only screen and (max-width: 740px) {
|
||||||
.master-note-edit.position-0 {
|
.master-note-edit.position-0 {
|
||||||
|
@ -1,11 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-on:mouseover="fullTagEdit = true">
|
||||||
<div class="ui icon large label" v-for="tag in tags" :class="{ 'green':(newTagInput == tag.text) }">
|
|
||||||
{{ucWords(tag.text)}} <i class="delete icon" v-on:click="removeTag(tag.id)"></i>
|
<!-- simple string view -->
|
||||||
|
<div v-if="!fullTagEdit" class="ui basic segment">
|
||||||
|
<div class="simple-tag-display">
|
||||||
|
|
||||||
|
<!-- Show Loading -->
|
||||||
|
<span v-if="!loaded">Loading Tags...</span>
|
||||||
|
|
||||||
|
<!-- Default count -->
|
||||||
|
<span v-if="loaded">
|
||||||
|
<i class="tags icon"></i> <b>{{tags.length}} Tags</b>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- No tags default text -->
|
||||||
|
<span v-if="tags.length == 0 && loaded" class="ui small compact green button">
|
||||||
|
<i class="plus icon"></i> Add a tag
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- display tags in comma delimited list -->
|
||||||
|
<span v-if="tags.length > 0">
|
||||||
|
<span v-for="(tag, i) in tags"><span v-if="i > 0">, </span>{{ucWords(tag.text)}}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- hover over view -->
|
||||||
|
<div v-if="fullTagEdit" class="full-tag-area fade-in-fwd" v-on:mouseleave="fullTagEdit = false; clearSuggestions()">
|
||||||
|
|
||||||
|
<!-- tag input and suggestion popup -->
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<input
|
<input
|
||||||
placeholder="Add Tag"
|
placeholder="Tag name. Press (Up) to select suggestion. Press (Enter) to submit."
|
||||||
v-model="newTagInput"
|
v-model="newTagInput"
|
||||||
v-on:keydown="tagInput"
|
v-on:keydown="tagInput"
|
||||||
v-on:keyup="onKeyup"
|
v-on:keyup="onKeyup"
|
||||||
@ -18,6 +44,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- existing tags -->
|
||||||
|
<div class="delete-tag-display" v-if="tags.length > 0">
|
||||||
|
<div class="ui icon large label" v-for="tag in tags" :class="{ 'green':(newTagInput == tag.text) }">
|
||||||
|
{{ucWords(tag.text)}} <i class="delete icon" v-on:click="removeTag(tag.id)"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -30,12 +65,14 @@
|
|||||||
props: [ 'noteId' ],
|
props: [ 'noteId' ],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
tags: null,
|
tags: [],
|
||||||
newTagInput: '',
|
newTagInput: '',
|
||||||
typeDebounce: null,
|
typeDebounce: null,
|
||||||
|
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
selection: 0
|
selection: 0,
|
||||||
|
fullTagEdit: false,
|
||||||
|
loaded: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount(){
|
beforeMount(){
|
||||||
@ -50,6 +87,7 @@
|
|||||||
let vm = this
|
let vm = this
|
||||||
axios.post('/api/tag/get', {'noteId': this.noteId})
|
axios.post('/api/tag/get', {'noteId': this.noteId})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
vm.loaded = true
|
||||||
//Set up local data
|
//Set up local data
|
||||||
vm.tags = response.data
|
vm.tags = response.data
|
||||||
})
|
})
|
||||||
@ -57,10 +95,6 @@
|
|||||||
tagInput(event){
|
tagInput(event){
|
||||||
let vm = this
|
let vm = this
|
||||||
|
|
||||||
if(this.newTagInput.length == 0){
|
|
||||||
this.clearSuggestions()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Cancel any of the following key events
|
//Cancel any of the following key events
|
||||||
const code = event.keyCode
|
const code = event.keyCode
|
||||||
if([38, 40, 13, 9].includes(code)){
|
if([38, 40, 13, 9].includes(code)){
|
||||||
@ -94,6 +128,7 @@
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Tab - 9 - Fill space with suggestion
|
//Tab - 9 - Fill space with suggestion
|
||||||
|
|
||||||
//Anything else, perform search
|
//Anything else, perform search
|
||||||
@ -136,10 +171,11 @@
|
|||||||
vm.newTagInput = ''
|
vm.newTagInput = ''
|
||||||
vm.clearSuggestions()
|
vm.clearSuggestions()
|
||||||
vm.getTags()
|
vm.getTags()
|
||||||
|
//Trigger focus event to reload tag suggestions
|
||||||
|
vm.onFocus()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onFocus(){
|
onFocus(){
|
||||||
console.log('Focused on tag edit')
|
|
||||||
//Show suggested tags
|
//Show suggested tags
|
||||||
let vm = this
|
let vm = this
|
||||||
let postData = {
|
let postData = {
|
||||||
@ -153,9 +189,13 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onKeyup(){
|
onKeyup(){
|
||||||
//Clear tags if backspaced
|
|
||||||
if(this.newTagInput == ''){
|
const code = event.keyCode
|
||||||
this.clearSuggestions()
|
|
||||||
|
//Backspace - clear old suggestions, load latest suggestions
|
||||||
|
if(code == 8 && this.newTagInput.length == 0){
|
||||||
|
//Fetch suggestions
|
||||||
|
this.onFocus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onBlur(){
|
onBlur(){
|
||||||
@ -190,6 +230,34 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style type="text/css" scoped>
|
<style type="text/css" scoped>
|
||||||
|
|
||||||
|
/* note tag edit area */
|
||||||
|
.full-tag-area {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
color: var(--text_color);
|
||||||
|
background-color: var(--background_color);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-color: var(--border_color);
|
||||||
|
}
|
||||||
|
.full-tag-area .delete-tag-display {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.full-tag-area .ui.label {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.simple-tag-display {
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tag suggestion box styles */
|
||||||
.suggestion-box {
|
.suggestion-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 38px;
|
bottom: 38px;
|
||||||
@ -202,6 +270,7 @@
|
|||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: white;
|
background: white;
|
||||||
|
color: black;
|
||||||
}
|
}
|
||||||
.suggestion-item.active {
|
.suggestion-item.active {
|
||||||
background: green;
|
background: green;
|
||||||
|
@ -1,50 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="note-title-display-card fade-in-fwd" :style="{'background-color':color, 'color':fontColor}">
|
<div class="note-title-display-card fade-in-fwd"
|
||||||
|
:style="{'background-color':color, 'color':fontColor}"
|
||||||
|
:class="{'currently-open':currentlyOpen}"
|
||||||
|
>
|
||||||
|
|
||||||
<div class="ui grid max-height">
|
<div class="ui grid max-height">
|
||||||
|
|
||||||
<div class="top aligned row">
|
<!-- Show title and snippet below it -->
|
||||||
<div class="sixteen wide column overflow-hidden">
|
<div class="top aligned row" @click.stop="onClick(note.id)">
|
||||||
<h3 @click="onClick(note.id)" class="clickable">{{note.title}}</h3>
|
|
||||||
|
<div class="sixteen wide column overflow-hidden" v-if="isShowingSearchResults()">
|
||||||
|
<!-- Display highlights from solr results -->
|
||||||
|
<div v-if="note.note_highlights.length > 0" class="term-usage">
|
||||||
|
<h4><i class="paragraph icon"></i> Found in Text</h4>
|
||||||
|
<div class="usage-row" v-for="highlight in note.note_highlights" v-html="cleanHighlight(highlight)"></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="note.attachment_highlights.length > 0" class="term-usage">
|
||||||
|
<h4><i class="linkify icon"></i> Found in URL</h4>
|
||||||
|
<div class="usage-row" v-for="highlight in note.attachment_highlights" v-html="cleanHighlight(highlight)"></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="note.tag_highlights.length > 0" class="term-usage">
|
||||||
|
<h4><i class="tags icon"></i> Found in Tags</h4>
|
||||||
|
<div class="usage-row" v-for="highlight in note.tag_highlights">
|
||||||
|
<span
|
||||||
|
v-for="tag in splitTags(highlight)"
|
||||||
|
class="ui label"
|
||||||
|
>
|
||||||
|
<span v-html="tag"></span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sixteen wide column overflow-hidden">
|
|
||||||
<p @click="onClick(note.id)" class="clickable">{{note.subtext}}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom aligned row">
|
<div class="sixteen wide column overflow-hidden">
|
||||||
<div class="ten wide column clickable" @click="onClick(note.id)">Edited: {{$helpers.timeAgo(note.updated)}}</div>
|
<h3 class="clickable">{{note.title}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="sixteen wide column overflow-hidden">
|
||||||
|
<p class="clickable">{{note.subtext}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="six wide right aligned column">
|
<div class="bottom aligned row" @click.self.stop="onClick(note.id)">
|
||||||
<span v-if="note.attachment_count > 0" class>
|
<div class="six wide column clickable" @click.stop="onClick(note.id)">
|
||||||
<i class="grey linkify icon"></i> {{note.attachment_count}}
|
{{$helpers.timeAgo(note.updated)}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ten wide right aligned column split-spans">
|
||||||
|
<span v-if="note.pinned == 1" data-tooltip="Pinned">
|
||||||
|
<i class="green pin icon"></i>
|
||||||
|
</span>
|
||||||
|
<span v-if="note.archived == 1" data-tooltip="Archived">
|
||||||
|
<i class="green archive icon"></i>
|
||||||
|
</span>
|
||||||
|
<span v-if="note.attachment_count > 0">
|
||||||
|
<i class="linkify icon"></i> {{note.attachment_count}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="note.tag_count == 1" data-tooltip="Note has 1 tag">
|
<span v-if="note.tag_count == 1" data-tooltip="Note has 1 tag">
|
||||||
<i class="grey tags icon"></i> {{note.tag_count}}
|
<i class="tags icon"></i> {{note.tag_count}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="note.tag_count > 1" :data-tooltip="`Note has ${note.tag_count} tags`">
|
<span v-if="note.tag_count > 1" :data-tooltip="`Note has ${note.tag_count} tags`">
|
||||||
<i class="grey tags icon"></i> {{note.tag_count}}
|
<i class="tags icon"></i> {{note.tag_count}}
|
||||||
</span>
|
</span>
|
||||||
<delete-button :note-id="note.id" />
|
<delete-button :note-id="note.id" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Display highlights from solr results -->
|
|
||||||
<div v-if="note.note_highlights.length > 0" class="term-usage">
|
|
||||||
<p><i class="paragraph icon"></i> Note Text</p>
|
|
||||||
<div class="usage-row" v-for="highlight in note.note_highlights" v-html="highlight"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.attachment_highlights.length > 0" class="term-usage">
|
|
||||||
<p><i class="linkify icon"></i> Note URL Text</p>
|
|
||||||
<div class="usage-row" v-for="highlight in note.attachment_highlights" v-html="highlight"></div>
|
|
||||||
</div>
|
|
||||||
<div v-if="note.tag_highlights.length > 0" class="term-usage">
|
|
||||||
<i class="tags icon"></i> Tag
|
|
||||||
<div class="ui icon large label" v-for="highlight in note.tag_highlights" v-html="highlight"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -52,10 +73,28 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NoteTitleDisplayCard',
|
name: 'NoteTitleDisplayCard',
|
||||||
props: [ 'onClick', 'data' ],
|
props: [ 'onClick', 'data', 'currentlyOpen' ],
|
||||||
components: {
|
components: {
|
||||||
'delete-button': require('@/components/NoteDeleteButtonComponent.vue').default,
|
'delete-button': require('@/components/NoteDeleteButtonComponent.vue').default,
|
||||||
},
|
},
|
||||||
|
methods:{
|
||||||
|
cleanHighlight(text){
|
||||||
|
//Basically just remove whitespace
|
||||||
|
let updated = text.replace(/ /g, '').replace(/<br>/g,'')
|
||||||
|
.replace(/<p><\/p>/g,'').replace(/<p> <\/p>/g,'')
|
||||||
|
|
||||||
|
return updated
|
||||||
|
},
|
||||||
|
isShowingSearchResults(){
|
||||||
|
if(this.note.note_highlights.length > 0 || this.note.attachment_highlights.length > 0 || this.note.tag_highlights.length > 0){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
splitTags(text){
|
||||||
|
return text.split(',')
|
||||||
|
}
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
note: null,
|
note: null,
|
||||||
@ -77,13 +116,13 @@
|
|||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
|
||||||
.term-usage {
|
.term-usage {
|
||||||
border-top: 1px solid #DDD;
|
border-bottom: 1px solid #DDD;
|
||||||
padding: 10px;
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.term-usage em {
|
.term-usage em {
|
||||||
color: green;
|
color: green;
|
||||||
background-color: white;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.usage-row + .usage-row {
|
.usage-row + .usage-row {
|
||||||
@ -100,12 +139,16 @@
|
|||||||
border-radius: .28571429rem;
|
border-radius: .28571429rem;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--border_color);
|
border-color: var(--border_color);
|
||||||
width: 31.5%;
|
width: calc(33.333% - 15px);
|
||||||
/*transition: width 0.2s;*/
|
transition: box-shadow 0.3s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.note-title-display-card:hover {
|
||||||
|
box-shadow: 0 1px 2px -0 rgba(34,36,38,.50);
|
||||||
}
|
}
|
||||||
.one-column .note-title-display-card {
|
.one-column .note-title-display-card {
|
||||||
margin-right: 65%;
|
margin-right: 65%;
|
||||||
width: 18%;
|
width: 33%;
|
||||||
}
|
}
|
||||||
.overflow-hidden {
|
.overflow-hidden {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -116,6 +159,22 @@
|
|||||||
.max-height {
|
.max-height {
|
||||||
height: calc(100% + 30px);
|
height: calc(100% + 30px);
|
||||||
}
|
}
|
||||||
|
.currently-open:after {
|
||||||
|
content: 'Open';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #000000b0;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #cecece;
|
||||||
|
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 740px) {
|
@media only screen and (max-width: 740px) {
|
||||||
.note-title-display-card {
|
.note-title-display-card {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
|
|
||||||
|
|
||||||
<div class="ui equal width grid">
|
<div class="ui equal width grid">
|
||||||
|
|
||||||
|
<!-- <div class="ui row">{{ $store.getters.getIsUserOnMobile ? 'Mobile Device':'Normal Browser' }}</div> -->
|
||||||
|
|
||||||
<!-- mobile search menu -->
|
<!-- mobile search menu -->
|
||||||
<div class="ui mobile only row">
|
<div class="ui mobile only row">
|
||||||
<!-- Small screen new note button -->
|
<!-- Small screen new note button -->
|
||||||
@ -40,12 +41,18 @@
|
|||||||
|
|
||||||
<router-link class="ui basic button" to="/help">Help</router-link>
|
<router-link class="ui basic button" to="/help">Help</router-link>
|
||||||
|
|
||||||
<div v-on:click="toggleNightMode" class="ui basic button">
|
<div v-on:click="toggleNightMode" class="ui basic icon button">
|
||||||
Dark Theme:
|
<i class="eye icon"></i> Dark Theme:
|
||||||
<span v-if="$store.getters.getIsNightMode">On</span>
|
<span v-if="$store.getters.getIsNightMode">On</span>
|
||||||
<span v-else>Off</span>
|
<span v-else>Off</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-on:click="toggleArchivedVisible" class="ui basic icon button">
|
||||||
|
<i class="archive icon"></i> Archived:
|
||||||
|
<span v-if="showArchived == 1">Visible</span>
|
||||||
|
<span v-else>Hidden</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ui right floated basic button"
|
<div class="ui right floated basic button"
|
||||||
data-tooltip="Log Out" data-position="left center"
|
data-tooltip="Log Out" data-position="left center"
|
||||||
v-on:click="destroyLoginToken"><i class="user icon"></i> {{username}}</div>
|
v-on:click="destroyLoginToken"><i class="user icon"></i> {{username}}</div>
|
||||||
@ -55,8 +62,10 @@
|
|||||||
<div class="ui row">
|
<div class="ui row">
|
||||||
|
|
||||||
<!-- tags display -->
|
<!-- tags display -->
|
||||||
<div class="ui two wide large screen only column">
|
<div class="ui two wide large screen only column" v-if="activeNoteId1 == null && activeNoteId2 == null">
|
||||||
<div class="ui basic fluid button" @click="reset"><i class="undo icon"></i>All Notes</div>
|
<div class="ui small basic fluid button" @click="reset">
|
||||||
|
<i class="undo icon"></i>Reset Filters
|
||||||
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="ui section list">
|
<div class="ui section list">
|
||||||
<div class="item" v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
|
<div class="item" v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
|
||||||
@ -70,13 +79,24 @@
|
|||||||
<!-- Note title cards -->
|
<!-- Note title cards -->
|
||||||
<div class="ui fourteen wide computer sixteen wide mobile column">
|
<div class="ui fourteen wide computer sixteen wide mobile column">
|
||||||
<h2>
|
<h2>
|
||||||
Notes ({{notes.length}})
|
({{notes.length}}) <fast-filters />
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="notes !== null" class="note-card-display-area" :class="{'one-column':(activeNoteId1 != null || activeNoteId2 != null )}">
|
|
||||||
|
<h3 v-if="searchTerm.length > 0 && notes.length == 0">No notes found. Check your spelling, try completing the word or using a different phrase.</h3>
|
||||||
|
|
||||||
|
<h3 v-if="searchTerm.length == 0 && notes.length == 0">Create your first note. Click the "New Note" button.</h3>
|
||||||
|
|
||||||
|
<div v-if="working"><div class="ui active inline loader"></div> Working...</div>
|
||||||
|
|
||||||
|
<div v-if="notes !== null && !working"
|
||||||
|
class="note-card-display-area"
|
||||||
|
:class="{'one-column':(activeNoteId1 != null || activeNoteId2 != null )}
|
||||||
|
">
|
||||||
<note-title-display-card
|
<note-title-display-card
|
||||||
v-for="note in notes"
|
v-for="note in notes"
|
||||||
:onClick="openNote"
|
:onClick="openNote"
|
||||||
:data="note"
|
:data="note"
|
||||||
|
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||||
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -101,6 +121,7 @@
|
|||||||
components: {
|
components: {
|
||||||
'input-notes': require('@/components/NoteInputPanel.vue').default,
|
'input-notes': require('@/components/NoteInputPanel.vue').default,
|
||||||
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
||||||
|
'fast-filters': require('@/components/FastFilters.vue').default,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@ -111,6 +132,9 @@
|
|||||||
searchTags: [],
|
searchTags: [],
|
||||||
notes: [],
|
notes: [],
|
||||||
searchDebounce: null,
|
searchDebounce: null,
|
||||||
|
fastFilters: {},
|
||||||
|
showArchived: 0,
|
||||||
|
working: false,
|
||||||
|
|
||||||
//Currently open notes in app
|
//Currently open notes in app
|
||||||
activeNoteId1: null,
|
activeNoteId1: null,
|
||||||
@ -131,6 +155,18 @@
|
|||||||
this.$bus.$on('note_deleted', () => {
|
this.$bus.$on('note_deleted', () => {
|
||||||
this.search()
|
this.search()
|
||||||
})
|
})
|
||||||
|
this.$bus.$on('update_fast_filters', newFilter => {
|
||||||
|
this.fastFilters = newFilter
|
||||||
|
this.search()
|
||||||
|
})
|
||||||
|
|
||||||
|
//Mount notes on load if note ID is set
|
||||||
|
if(this.$route.params && this.$route.params.id){
|
||||||
|
const id = this.$route.params.id
|
||||||
|
console.log('About to load note ', id)
|
||||||
|
this.openNote(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -150,6 +186,7 @@
|
|||||||
if(this.activeNoteId1 == null && this.activeNoteId2 == null){
|
if(this.activeNoteId1 == null && this.activeNoteId2 == null){
|
||||||
this.activeNoteId1 = id
|
this.activeNoteId1 = id
|
||||||
this.activeNote1Position = 0 //Middel of page
|
this.activeNote1Position = 0 //Middel of page
|
||||||
|
this.$router.push('/notes/open/'+this.activeNoteId1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//2 notes open
|
//2 notes open
|
||||||
@ -159,6 +196,14 @@
|
|||||||
this.activeNote2Position = 2 //Left side of page
|
this.activeNote2Position = 2 //Left side of page
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//2 notes open
|
||||||
|
if(this.activeNoteId2 != null && this.activeNoteId1 == null){
|
||||||
|
this.activeNoteId1 = id
|
||||||
|
this.activeNote1Position = 2 //Right side of page
|
||||||
|
this.activeNote2Position = 1 //Left side of page
|
||||||
|
return
|
||||||
|
}
|
||||||
},
|
},
|
||||||
closeNote(position){
|
closeNote(position){
|
||||||
//One note open, close that note
|
//One note open, close that note
|
||||||
@ -174,10 +219,12 @@
|
|||||||
this.activeNoteId2 = null
|
this.activeNoteId2 = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$router.push('/notes')
|
||||||
|
|
||||||
this.activeNote1Position = 0
|
this.activeNote1Position = 0
|
||||||
this.activeNote2Position = 0
|
this.activeNote2Position = 0
|
||||||
|
|
||||||
this.search()
|
this.search(false)
|
||||||
},
|
},
|
||||||
toggleTagFilter(tagId){
|
toggleTagFilter(tagId){
|
||||||
|
|
||||||
@ -189,21 +236,33 @@
|
|||||||
|
|
||||||
this.search()
|
this.search()
|
||||||
},
|
},
|
||||||
search(){
|
search(showLoading = true){
|
||||||
|
|
||||||
|
//Add archived to fast filters
|
||||||
|
this.fastFilters['archived'] = 0
|
||||||
|
if(this.showArchived == 1){
|
||||||
|
this.fastFilters['archived'] = 1
|
||||||
|
}
|
||||||
|
|
||||||
let postData = {
|
let postData = {
|
||||||
searchQuery: this.searchTerm,
|
searchQuery: this.searchTerm,
|
||||||
searchTags: this.searchTags
|
searchTags: this.searchTags,
|
||||||
|
fastFilters: this.fastFilters,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(showLoading){
|
||||||
|
this.working = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//Perform search
|
//Perform search
|
||||||
let vm = this
|
let vm = this
|
||||||
axios.post('/api/note/search', postData).
|
axios.post('/api/note/search', postData).
|
||||||
then(response => {
|
then(response => {
|
||||||
console.log('Notes and Tags')
|
|
||||||
console.log(response.data)
|
|
||||||
|
|
||||||
vm.commonTags = response.data.tags
|
vm.commonTags = response.data.tags
|
||||||
vm.notes = response.data.notes
|
vm.notes = response.data.notes
|
||||||
vm.highlights = response.data.highlights
|
vm.highlights = response.data.highlights
|
||||||
|
this.working = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
searchKeyUp(){
|
searchKeyUp(){
|
||||||
@ -234,6 +293,8 @@
|
|||||||
reset(){
|
reset(){
|
||||||
this.searchTerm = ''
|
this.searchTerm = ''
|
||||||
this.searchTags = []
|
this.searchTags = []
|
||||||
|
this.fastFilters = {}
|
||||||
|
this.$bus.$emit('reset_fast_filters')
|
||||||
this.search()
|
this.search()
|
||||||
},
|
},
|
||||||
destroyLoginToken() {
|
destroyLoginToken() {
|
||||||
@ -242,6 +303,14 @@
|
|||||||
},
|
},
|
||||||
toggleNightMode(){
|
toggleNightMode(){
|
||||||
this.$store.commit('toggleNightMode')
|
this.$store.commit('toggleNightMode')
|
||||||
|
},
|
||||||
|
toggleArchivedVisible(){
|
||||||
|
if(this.showArchived == 0){
|
||||||
|
this.showArchived = 1
|
||||||
|
} else {
|
||||||
|
this.showArchived = 0
|
||||||
|
}
|
||||||
|
this.search()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,12 @@ export default new Router({
|
|||||||
meta: {title:'Notes'},
|
meta: {title:'Notes'},
|
||||||
component: NotesPage
|
component: NotesPage
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/notes/open/:id',
|
||||||
|
name: 'NotesPage',
|
||||||
|
meta: {title:'Notes'},
|
||||||
|
component: NotesPage
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/help',
|
path: '/help',
|
||||||
name: 'Help',
|
name: 'Help',
|
||||||
|
@ -11,6 +11,7 @@ export default new Vuex.Store({
|
|||||||
token: null,
|
token: null,
|
||||||
username: null,
|
username: null,
|
||||||
nightMode: false,
|
nightMode: false,
|
||||||
|
isUserOnMobile: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
increment (state) {
|
increment (state) {
|
||||||
@ -68,6 +69,18 @@ export default new Vuex.Store({
|
|||||||
Object.keys(themeColors).forEach( attribute => {
|
Object.keys(themeColors).forEach( attribute => {
|
||||||
root.style.setProperty('--'+attribute, themeColors[attribute])
|
root.style.setProperty('--'+attribute, themeColors[attribute])
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
detectIsUserOnMobile(state){
|
||||||
|
|
||||||
|
//Script from http://detectmobilebrowsers.com
|
||||||
|
//Create closure and pass in browser data and state
|
||||||
|
(function(a, state){
|
||||||
|
if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))
|
||||||
|
{
|
||||||
|
state.isUserOnMobile = true
|
||||||
|
}
|
||||||
|
})(navigator.userAgent||navigator.vendor||window.opera, state);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@ -87,5 +100,8 @@ export default new Vuex.Store({
|
|||||||
getIsNightMode: state => {
|
getIsNightMode: state => {
|
||||||
return state.nightMode
|
return state.nightMode
|
||||||
},
|
},
|
||||||
|
getIsUserOnMobile: state => {
|
||||||
|
return state.isUserOnMobile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
12
dontSync.txt
Normal file
12
dontSync.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
node_modules*
|
||||||
|
files*
|
||||||
|
public/temp*
|
||||||
|
compressed*
|
||||||
|
forever.log
|
||||||
|
babel_cache*
|
||||||
|
bundle.js
|
||||||
|
*.min.js
|
||||||
|
common.js
|
||||||
|
*/unminified/bundle.*
|
||||||
|
bundle.*
|
||||||
|
client/dist*
|
@ -32,13 +32,14 @@ Note.create = (userId, noteText) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Note.update = (userId, noteId, noteText, fancyInput, color) => {
|
Note.update = (userId, noteId, noteText, fancyInput, color, pinned, archived) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const now = Math.round((+new Date)/1000)
|
const now = Math.round((+new Date)/1000)
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('UPDATE note SET text = ?, raw_input = ?, updated = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1', [noteText, fancyInput, now, color, noteId, userId])
|
.query('UPDATE note SET text = ?, raw_input = ?, pinned = ?, archived = ?, updated = ?, color = ? WHERE id = ? AND user_id = ? LIMIT 1',
|
||||||
|
[noteText, fancyInput, pinned, archived, now, color, noteId, userId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
//Process note text and attachment data
|
//Process note text and attachment data
|
||||||
@ -91,9 +92,12 @@ Note.delete = (userId, noteId) => {
|
|||||||
Note.get = (userId, noteId) => {
|
Note.get = (userId, noteId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise()
|
db.promise()
|
||||||
.query('SELECT text, updated, raw_input, color FROM note WHERE user_id = ? AND id = ? LIMIT 1', [userId,noteId])
|
.query('SELECT text, updated, raw_input, pinned, archived, color FROM note WHERE user_id = ? AND id = ? LIMIT 1', [userId,noteId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
|
//Return note data
|
||||||
resolve(rows[0][0])
|
resolve(rows[0][0])
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
})
|
})
|
||||||
@ -125,18 +129,15 @@ Note.solrQuery = (userId, searchQuery, searchTags) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Note.search = (userId, searchQuery, searchTags) => {
|
Note.search = (userId, searchQuery, searchTags, fastFilters) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Define return data objects
|
//Define return data objects
|
||||||
let returnData = {
|
let returnData = {
|
||||||
'notes':[],
|
'notes':[],
|
||||||
'tags':[]
|
'tags':[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Note.solrQuery(userId, searchQuery, searchTags).then( solrResult => {
|
Note.solrQuery(userId, searchQuery, searchTags).then( solrResult => {
|
||||||
|
|
||||||
let highlights = solrResult.highlighting
|
let highlights = solrResult.highlighting
|
||||||
@ -149,13 +150,20 @@ Note.search = (userId, searchQuery, searchTags) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//No results, return empty data
|
||||||
|
if(solrNoteIds.length == 0 && searchQuery.length > 0){
|
||||||
|
resolve(returnData)
|
||||||
|
}
|
||||||
|
|
||||||
//Default note lookup gets all notes
|
//Default note lookup gets all notes
|
||||||
let noteSearchQuery = `
|
let noteSearchQuery = `
|
||||||
SELECT note.id,
|
SELECT note.id,
|
||||||
SUBSTRING(note.text, 1, 400) as text,
|
SUBSTRING(note.text, 1, 400) as text,
|
||||||
updated, color,
|
updated, color,
|
||||||
count(distinct note_tag.id) as tag_count,
|
count(distinct note_tag.id) as tag_count,
|
||||||
count(distinct attachment.id) as attachment_count
|
count(distinct attachment.id) as attachment_count,
|
||||||
|
note.pinned,
|
||||||
|
note.archived
|
||||||
FROM note
|
FROM note
|
||||||
LEFT JOIN note_tag ON (note.id = note_tag.note_id)
|
LEFT JOIN note_tag ON (note.id = note_tag.note_id)
|
||||||
LEFT JOIN attachment ON (note.id = attachment.note_id AND attachment.attachment_type = 1)
|
LEFT JOIN attachment ON (note.id = attachment.note_id AND attachment.attachment_type = 1)
|
||||||
@ -178,8 +186,45 @@ Note.search = (userId, searchQuery, searchTags) => {
|
|||||||
noteSearchQuery += ' AND note_tag.tag_id IN (?)'
|
noteSearchQuery += ' AND note_tag.tag_id IN (?)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Toggle archived, show archived if tags are searched
|
||||||
|
// - archived will show archived in search results
|
||||||
|
// - onlyArchive will exclude notes that are not archived
|
||||||
|
if(fastFilters.archived == 1 || searchTags.length > 0 || fastFilters.onlyArchived == 1){
|
||||||
|
//Do nothing
|
||||||
|
} else {
|
||||||
|
noteSearchQuery += ' AND note.archived = 0' //Exclude archived
|
||||||
|
}
|
||||||
|
|
||||||
//Finish up note query
|
//Finish up note query
|
||||||
noteSearchQuery += ' GROUP BY note.id ORDER BY updated DESC, created DESC, id DESC'
|
noteSearchQuery += ' GROUP BY note.id'
|
||||||
|
|
||||||
|
//Only show notes with Tags
|
||||||
|
if(fastFilters.withTags == 1){
|
||||||
|
noteSearchQuery += ' HAVING tag_count > 0'
|
||||||
|
}
|
||||||
|
//Only show notes with links
|
||||||
|
if(fastFilters.withLinks == 1){
|
||||||
|
noteSearchQuery += ' HAVING attachment_count > 0'
|
||||||
|
}
|
||||||
|
//Only show archived notes
|
||||||
|
if(fastFilters.onlyArchived == 1){
|
||||||
|
noteSearchQuery += ' HAVING note.archived = 1'
|
||||||
|
}
|
||||||
|
|
||||||
|
//Default Sort, order by last updated
|
||||||
|
let defaultOrderBy = ' ORDER BY note.pinned DESC, updated DESC, created DESC, opened DESC, id DESC'
|
||||||
|
|
||||||
|
//Order by Last Created Date
|
||||||
|
if(fastFilters.lastCreated == 1){
|
||||||
|
defaultOrderBy = ' ORDER BY note.pinned DESC, created DESC, updated DESC, opened DESC, id DESC'
|
||||||
|
}
|
||||||
|
//Order by last Opened Date
|
||||||
|
if(fastFilters.lastOpened == 1){
|
||||||
|
defaultOrderBy = ' ORDER BY note.pinned DESC, opened DESC, updated DESC, created DESC, id DESC'
|
||||||
|
}
|
||||||
|
|
||||||
|
//Append Order by to query
|
||||||
|
noteSearchQuery += defaultOrderBy
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(noteSearchQuery, searchParams)
|
.query(noteSearchQuery, searchParams)
|
||||||
|
@ -85,6 +85,13 @@ Tag.add = (tagText) => {
|
|||||||
|
|
||||||
Tag.get = (userId, noteId) => {
|
Tag.get = (userId, noteId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
//Update last opened date of note
|
||||||
|
const now = Math.round((+new Date)/1000)
|
||||||
|
db.promise()
|
||||||
|
.query('UPDATE note SET opened = ? WHERE id = ? AND user_id = ? LIMIT 1', [now, noteId, userId])
|
||||||
|
.then((rows, fields) => {})
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(`SELECT note_tag.id, tag.text FROM note_tag
|
.query(`SELECT note_tag.id, tag.text FROM note_tag
|
||||||
JOIN tag ON (tag.id = note_tag.tag_id)
|
JOIN tag ON (tag.id = note_tag.tag_id)
|
||||||
@ -138,7 +145,7 @@ Tag.suggest = (userId, noteId, tagText) => {
|
|||||||
SELECT note_tag.tag_id FROM note_tag WHERE note_tag.note_id = ?
|
SELECT note_tag.tag_id FROM note_tag WHERE note_tag.note_id = ?
|
||||||
)
|
)
|
||||||
GROUP BY text
|
GROUP BY text
|
||||||
LIMIT 6;`, [userId, tagText, noteId])
|
LIMIT 6`, [userId, tagText, noteId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
resolve(rows[0]) //Return new ID
|
resolve(rows[0]) //Return new ID
|
||||||
})
|
})
|
||||||
@ -146,7 +153,7 @@ Tag.suggest = (userId, noteId, tagText) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Suggest note tags - don't suggest tags already on note
|
//Latest Tags - don't suggest tags already on note
|
||||||
Tag.latest = (userId, noteId) => {
|
Tag.latest = (userId, noteId) => {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -160,9 +167,9 @@ Tag.latest = (userId, noteId) => {
|
|||||||
)
|
)
|
||||||
GROUP BY tag.text, note.updated
|
GROUP BY tag.text, note.updated
|
||||||
ORDER BY note.updated DESC
|
ORDER BY note.updated DESC
|
||||||
LIMIT 6;`, [userId, noteId])
|
LIMIT 8;`, [userId, noteId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
resolve(rows[0]) //Return new ID
|
resolve(rows[0]) //Return found tags
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
})
|
})
|
||||||
|
@ -29,12 +29,12 @@ router.post('/create', function (req, res) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.post('/update', function (req, res) {
|
router.post('/update', function (req, res) {
|
||||||
Notes.update(userId, req.body.noteId, req.body.text, req.body.fancyInput, req.body.color)
|
Notes.update(userId, req.body.noteId, req.body.text, req.body.fancyInput, req.body.color, req.body.pinned, req.body.archived)
|
||||||
.then( id => res.send({id}) )
|
.then( id => res.send({id}) )
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/search', function (req, res) {
|
router.post('/search', function (req, res) {
|
||||||
Notes.search(userId, req.body.searchQuery, req.body.searchTags)
|
Notes.search(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters)
|
||||||
.then( notesAndTags => res.send(notesAndTags))
|
.then( notesAndTags => res.send(notesAndTags))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user