Added a night mode and no way to toggle it!

Tweaked a lot of styles and added some cool animations
Added a little to the help text
Quickly adding a note, saving and closing no longer causes half formed or empty notes to appear
Close Editor animation
Display cards text show at the bottom of card
Added a delete function, and it works
Added browser title attributes
More debugging and error checking on scraped links
Updated not search to display title and text below the title
This commit is contained in:
Max G 2019-07-29 07:22:47 +00:00
parent b0a8071b41
commit fcee24a61d
13 changed files with 387 additions and 105 deletions

View File

@ -1,18 +1,110 @@
/*body, h3, h2, h1, p {
color: #3d3d3d;
}*/
:root {
--primary_color: #1C84DA;
--secondary_color: #1EAEDB;
--element_background_color: #FFF;
--background_color: #fff;
--text_color: #3d3d3d;
--outline_color: rgba(34,36,38,.15);
}
/* Night mode colors */
:root {
--background_color: #000;
--text_color: #a98457;
--outline_color: #a98457;
}
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
body{
color: var(--text_color);
background-color: var(--background_color);
}
.ui.form input:not([type]),
.ui.form input:not([type]):focus {
color: var(--text_color);
background-color: var(--background_color);
border-color: var(--border_color);
}
.ui.basic.label {
color: var(--text_color);
background-color: var(--background_color);
border-color: var(--border_color);
}
div.ui.basic.green.label {
background-color: var(--background_color) !important;
}
.ui.basic.button, .ui.basic.buttons .button {
background-color: var(--background_color) !important;
color: var(--text_color) !important;
border: 1px solid;
border-color: var(--border_color) !important;
box-shadow: none;
}
.ui.basic.button:focus, .ui.basic.button:hover {
background-color: var(--background_color) !important;
color: var(--text_color) !important;
box-shadow: none;
}
.ui.tabular.menu .item {
background-color: var(--background_color) !important;
color: var(--text_color) !important;
}
.ui.tabular.menu .item.active {
background-color: var(--background_color) !important;
color: var(--text_color) !important;
border-color: var(--border_color) !important;
}
/* OVERWRITE DEFAULT SEMANTIC STYLES FOR CUSTOM/NIGHT MODES*/
.clickable {
cursor: pointer;
}
.textarea-height {
height: calc(100% - 105px);
}
.ck-content {
font-family: 'Open Sans' !important;
font-size: 1.3rem !important;
background-color: rgba(255, 255, 255, 0);
height: 100%;
height: calc(100% - 40px);
overflow: hidden;
}
.ck .ck-editor__nested-editable:focus {
background-color: var(--background_color) !important;
}
.ui.white.button {
background: #FFF;
}
.fade-in-fwd {
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
* ----------------------------------------
*/
@keyframes fade-in-fwd {
0% {
transform: translateZ(-80px);
opacity: 0;
}
100% {
transform: translateZ(0);
opacity: 1;
}
}

View File

@ -0,0 +1,40 @@
<template>
<span>
<span class="clickable" @click="confirmDelete()" v-if="click == 0">
<i class="grey trash alternate icon"></i>
</span>
<span class="clickable" @click="actuallyDelete()" @mouseleave="reset" v-if="click == 1" data-tooltip="Click again to delete." data-position="left center">
<i class="red trash alternate icon"></i>
</span>
</span>
</template>
<script>
import axios from 'axios'
export default {
name: 'NoteTitleDisplayCard',
props: [ 'noteId', 'displayText' ],
data () {
return {
click: 0,
}
},
methods:{
confirmDelete(){
this.click++
},
actuallyDelete(){
axios.post('/api/notes/delete', {'noteId':this.noteId}).then(response => {
if(response.data == true){
this.$bus.$emit('note_deleted')
}
})
},
reset(){
this.click = 0
}
}
}
</script>

View File

@ -2,6 +2,9 @@
<div class="ui basic segment">
<div class="ui container">
CTRL + SHIFT + V - paste without formatting
CTRL + Z - Undo in note, <b>Undo youtube video player embed.</b>
<h2>Block formatting</h2>
<p>The following block formatting options are available:</p>

View File

@ -1,6 +1,10 @@
<template>
<div id="InputNotes" class="master-note-edit" :class="[ 'position-'+position ]" @keyup.esc="close" :style="{'background-color':color, 'color':fontColor}">
<div
id="InputNotes" class="master-note-edit"
@keyup.esc="close"
:class="[{'size-down':(sizeDown == true)}, 'position-'+position ]"
:style="{'background-color':color, 'color':fontColor}"
>
<div v-if="fancyInput == 1" class="textarea-height no-flow">
<ckeditor ref="main-edit"
@ -16,9 +20,9 @@
/>
<div class="ui buttons">
<div class="ui right floated green button">{{statusText}}</div>
<div @click="close" class="ui button">Close + Save (ESC)</div>
<div class="ui button">Delete</div>
<div @click="close" class="ui button">Close (ESC)</div>
<div @click="onToggleFancyInput" class="ui button">
Fancy ({{fancyInput?'On':'Off'}})
</div>
@ -39,6 +43,7 @@
<button @click="onChangeColor" class="ui icon grey button"></button>
<button @click="onChangeColor" class="ui icon black button"></button>
</div>
<div class="ui right floated green button">{{statusText}}</div>
<!-- <p>
Last Updated: {{$helpers.timeAgo(updated)}}
</p> -->
@ -64,7 +69,7 @@
return {
currentNoteId: 0,
noteText: '',
statusText: 'Save',
statusText: 'Saved',
lastNoteHash: null,
saveDebounce: null, //Prevent save from being called numerous times quickly
lastSaved: 0,
@ -74,6 +79,7 @@
fancyInput: 0, //Default to basic text edit. Upgrade if set to 1
color: '#FFF',
fontColor: '#000',
sizeDown: false,
editor: DecoupledEditor,
editorConfig: {
@ -127,7 +133,7 @@
if(this.color == "rgb(255, 255, 255)" || this.color == '#FFF'){
this.color = null
this.fontColor = '#000'
this.fontColor = null
}
this.lastNoteHash = 0 //Update hash to force note update on next save
@ -150,7 +156,7 @@
this.fontColor = '#FFF'
if(this.color == "rgb(255, 255, 255)" || this.color == '#FFF' || this.color == null){
this.color = null
this.fontColor = '#000'
this.fontColor = null
}
if(response.data.raw_input == 1){
@ -201,14 +207,16 @@
} );
},
//Used by simple editor
onKeyup(){
let vm = this
vm.statusText = 'Modified'
//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
//Save after 30 keystrokes
vm.keyPressesCounter = (vm.keyPressesCounter + 1)
if(vm.keyPressesCounter > 30){
vm.keyPressesCounter = 0
@ -216,12 +224,15 @@
}
},
save(){
return new Promise((resolve, reject) => {
clearTimeout(this.editDebounce)
//Don't save note if its hash doesn't change
if( this.lastNoteHash == this.hashString(this.noteText) ){
setTimeout(() => {
resolve(true)
return
}, 500)
}
const postData = {
@ -238,14 +249,15 @@
//Only notify user if saving - may help with debugging in the future
vm.statusText = 'Saving'
axios.post('/api/notes/update', postData).then( response => {
vm.statusText = 'Save'
vm.statusText = 'Saved'
vm.updated = Math.round((+new Date)/1000)
//Update last saved note hash
vm.lastNoteHash = vm.hashString(vm.noteText)
resolve(true)
})
}, 500)
})
},
hashString(text){
@ -261,8 +273,11 @@
return hash;
},
close(){
this.save()
this.sizeDown = true
this.save().then( () => {
this.$bus.$emit('close_active_note', this.position)
})
}
}
}
@ -270,9 +285,7 @@
<style type="text/css" scoped>
.textarea-height {
height: calc(100% - 177px);
}
.no-flow {
overflow: hidden;
}
@ -280,7 +293,7 @@
.raw-edit {
font-family: 'Open Sans' !important;
font-size: 1.3rem !important;
background: white;
background: rgba(0,0,0,0);
width: 100%;
resize: none;
padding: 15px;
@ -292,7 +305,8 @@
.master-note-edit {
position: fixed;
bottom: 0;
background: white;
background: var(--background_color);
/*color: var(--text_color);*/
height: 100vh;
box-shadow: 0px 0px 5px 2px rgba(140,140,140,1);
}
@ -320,4 +334,19 @@
right: 50%;
}
.size-down {
animation: size-down 0.5s linear both;
}
@keyframes size-down {
0% {
opacity: 1;
top: 0;
}
100% {
opacity: 0;
top: 405vh;
}
}
</style>

View File

@ -1,7 +1,35 @@
<template>
<div class="ui clickable segment" @click="onClick(note.id)" :style="{'background-color':color, 'color':fontColor}">
<h3>{{note.text}}</h3>
<p>Edited: {{$helpers.timeAgo(note.updated)}}</p>
<div class="note-title-display-card fade-in-fwd" :style="{'background-color':color, 'color':fontColor}">
<div class="ui grid max-height">
<div class="top aligned row">
<div class="sixteen wide column overflow-hidden">
<h3 @click="onClick(note.id)" class="clickable">{{note.title}}</h3>
</div>
<div class="sixteen wide column overflow-hidden">
<p @click="onClick(note.id)" class="clickable">{{note.subtext}}</p>
</div>
</div>
<div class="bottom aligned row">
<div class="ten wide column clickable" @click="onClick(note.id)">Edited: {{$helpers.timeAgo(note.updated)}}</div>
<div class="six wide right aligned column">
<span v-if="note.attachment_count > 0" class>
<i class="grey linkify icon"></i> {{note.attachment_count}}
</span>
<span v-if="note.tag_count == 1" data-tooltip="Note has 1 tag">
<i class="grey tags icon"></i> {{note.tag_count}}
</span>
<span v-if="note.tag_count > 1" :data-tooltip="`Note has ${note.tag_count} tags`">
<i class="grey tags icon"></i> {{note.tag_count}}
</span>
<delete-button :note-id="note.id" />
</div>
</div>
</div>
<!-- Display highlights from solr results -->
<div v-if="note.note_highlights.length > 0" class="term-usage">
@ -9,13 +37,14 @@
<div class="usage-row" v-for="highlight in note.note_highlights" v-html="highlight"></div>
</div>
<div v-if="note.attachment_highlights.length > 0" class="term-usage">
<p>Note URL Text</p>
<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">
Tag
<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>
</template>
@ -24,11 +53,14 @@
export default {
name: 'NoteTitleDisplayCard',
props: [ 'onClick', 'data' ],
components: {
'delete-button': require('./DeleteButtonComponent.vue').default,
},
data () {
return {
note: null,
color: '#FFF',
fontColor: '#000'
color: null, //'#FFF',
fontColor: null, //'#000'
}
},
beforeMount(){
@ -43,6 +75,7 @@
}
</script>
<style type="text/css">
.term-usage {
border: 1px solid #DDD;
padding: 10px;
@ -57,4 +90,34 @@
border-top: 1px solid #DDD;
margin: 8px 0 0;
}
.note-title-display-card {
position: relative;
box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);
margin: 0 15px 15px 0;
padding: 1em;
border-radius: .28571429rem;
border: 1px solid;
border-color: var(--border_color);
width: 31.5%;
/*transition: width 0.2s;*/
}
.one-column .note-title-display-card {
margin-right: 65%;
width: 18%;
}
.overflow-hidden {
overflow: hidden;
word-break: break-all;
}
.max-height {
height: calc(100% + 30px);
}
@media only screen and (max-width: 740px) {
.note-title-display-card {
width: 100%;
margin: 15px 0 0 0;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div id="NotesPage">
<div class="ui segment">
<div class="ui basic segment">
<search-bar />
</div>

View File

@ -50,7 +50,7 @@
<!-- tags display -->
<div class="ui two wide large screen only column">
<div class="ui basic fluid button" @click="reset">Reset</div>
<div class="ui basic fluid button" @click="reset"><i class="undo icon"></i>All Notes</div>
<div class="ui divider"></div>
<div class="ui clickable basic fluid large label" v-for="tag in commonTags" @click="toggleTagFilter(tag.id)"
:class="{ 'green':(searchTags.includes(tag.id)) }">
@ -61,7 +61,7 @@
<!-- Note title cards -->
<div class="ui fourteen wide computer sixteen wide mobile column">
<h2>Notes ({{notes.length}})</h2>
<div v-if="notes !== null">
<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"
@ -113,7 +113,9 @@
this.$bus.$on('close_active_note', position => {
this.closeNote(position)
})
this.$bus.$on('note_deleted', () => {
this.search()
})
},
mounted() {
@ -223,6 +225,7 @@
},
destroyLoginToken() {
this.$store.commit('destroyLoginToken')
this.$router.push('/')
}
}
}
@ -231,4 +234,8 @@
.detail {
float: right;
}
.note-card-display-area {
display: flex;
flex-wrap: wrap;
}
</style>

View File

@ -9,6 +9,14 @@ import store from './stores/mainStore';
import App from './App'
import router from './router'
// This callback runs before every route change, including on page load.
// Sets the title of the page using vue router
router.beforeEach((to, from, next) => {
document.title = to.meta.title;
next();
});
//Attach event bus to main vue object, all components will inherit event bus
import EventBus from './EventBus'
import Helpers from './Helpers'

View File

@ -13,21 +13,25 @@ export default new Router({
{
path: '/',
name: 'HelloWorld',
meta: {title:'Home'},
component: HelloWorld
},
{
path: '/login',
name: 'Login',
meta: {title:'Login'},
component: Login
},
{
path: '/notes',
name: 'Notes',
meta: {title:'Notes'},
component: Notes
},
{
path: '/help',
name: 'Help',
meta: {title:'Help'},
component: HelpPage
},
]

View File

@ -17,8 +17,6 @@ export default new Vuex.Store({
},
setLoginToken(state, userData){
console.log(userData)
const username = userData.username
const token = userData.token

View File

@ -38,7 +38,10 @@ Attachment.scanTextForWebsites = (userId, noteId, noteText) => {
//Find all URLs in text
const urlPattern = /(?:(?:https?|ftp|file):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])/igm
let foundUrls = noteText.match(urlPattern)
let allUrls = noteText.match(urlPattern)
//Remove all duplicates
let foundUrls = [...new Set(allUrls)]
//Go through each attachment, check for existing URLs
attachments.forEach(attachment => {
@ -54,7 +57,7 @@ Attachment.scanTextForWebsites = (userId, noteId, noteText) => {
})
//No newly scraped URLs, resolve with looked up attachment text
if(foundUrls.length == 0){
if(foundUrls == null || foundUrls.length == 0){
resolve(solrAttachmentText)
}
@ -166,12 +169,14 @@ Attachment.processUrl = (userId, noteId, url) => {
let finalWords = []
for(let i=0; i<15; i++){
if(sortable[i][0]){
if(sortable[i] && sortable[i][0]){
finalWords.push(sortable[i][0])
}
}
desiredSearchText += finalWords.join(', ')
console.log('TexT Scraped')
console.log(desiredSearchText)
const created = Math.round((+new Date)/1000)

View File

@ -75,7 +75,20 @@ Notes.update = (userId, noteId, noteText, fancyInput, color) => {
Notes.delete = (userId, noteId) => {
return new Promise((resolve, reject) => {
//Create new note, return created or finger your butt
// DELETE FROM notes WHERE notes.id = 290 AND notes.user = 61;
// DELETE FROM attachment WHERE attachment.note_id = 290 AND attachment.user_id = 61;
// DELETE FROM notes_tags WHERE notes_tags.note_id = 290 AND notes_tags.user_id = 61;
db.promise().query('DELETE FROM notes WHERE notes.id = ? AND notes.user = ?', [noteId,userId])
.then((rows, fields) => {
db.promise().query('DELETE FROM attachment WHERE attachment.note_id = ? AND attachment.user_id = ?', [noteId,userId])
.then((rows, fields)=> {
db.promise().query('DELETE FROM notes_tags WHERE notes_tags.note_id = ? AND notes_tags.user_id = ?', [noteId,userId])
.then((rows, fields)=> {
console.log('All Deleted')
resolve(true)
})
})
})
})
}
@ -153,9 +166,10 @@ Notes.search = (userId, searchQuery, searchTags) => {
//Default note lookup gets all notes
let noteSearchQuery = `
SELECT notes.id, SUBSTRING(text, 1, 200) as text, updated, color
SELECT notes.id, SUBSTRING(notes.text, 1, 400) as text, updated, color, count(distinct notes_tags.id) as tag_count, count(distinct attachment.id) as attachment_count
FROM notes
LEFT JOIN notes_tags ON (notes.id = notes_tags.note_id)
LEFT JOIN attachment ON (notes.id = attachment.note_id AND attachment.attachment_type = 1)
WHERE user = ?`
let searchParams = [userId]
@ -193,16 +207,30 @@ Notes.search = (userId, searchQuery, searchTags) => {
noteIds.push(note.id)
//Attempt to pull string out of first tag in note
let reg = note.text.match(/<([\w]+)[^>]*>(.*?)<\/\1>/)
if(reg != null){
note.text = reg[2]
let reg = note.text.match(/<([\w]+)[^>]*>(.*?)<\/\1>/g)
//Pull out first html tag contents, that is the title
if(reg != null && reg[0]){
note.title = reg[0] //First line from HTML
} else {
note.title = note.text //Entire note
}
//Return all notes with HTML tags pulled out
note.text = note.text
//Clean up html title
note.title = note.title
.replace(/&[#A-Za-z0-9]+;/g,'') //Rip out all HTML entities
.replace(/<[^>]+>/g, '') //Rip out all HTML tags
//Generate Subtext
if(note.text != '' && note.title != ''){
note.subtext = note.text
.replace(/&[#A-Za-z0-9]+;/g,' ') //Rip out all HTML entities
.replace(/<[^>]+>/g, ' ') //Rip out all HTML tags
.replace(/\s+/g, ' ') //Remove all whitespace
.substring(note.title.length + 2)
}
note.note_highlights = []
note.attachment_highlights = []
note.tag_highlights = []

View File

@ -24,6 +24,11 @@ router.post('/get', function (req, res) {
.then( data => res.send(data) )
})
router.post('/delete', function (req, res) {
Notes.delete(userId, req.body.noteId)
.then( data => res.send(data) )
})
router.post('/create', function (req, res) {
Notes.create(userId, req.body.title)
.then( id => res.send({id}) )