* Made splash page dark and updated description

* Cleaned up unused things
* Updated squire which had a comment typo update...thats it
* Background color picker has matching colors and styles to text color picker
* Added new black theme
* Moved search to main page, show it on mobile and added options to push things to notes from search with experimental tag searching
* Added active note menu buttons based on cursor location in text
* Added more instant updating if app is open in two locations for the same user Scratch Pad and home page update with new notes and new text in real time
This commit is contained in:
Max G 2020-05-15 23:12:09 +00:00
parent 67b218329b
commit e87e8513bc
20 changed files with 526 additions and 271 deletions

View File

@ -10,13 +10,16 @@
<meta name="theme-color" content="#000" />
<link rel="manifest" href="/api/static/assets/manifest.json">
<title>Solid Scribe - A Note Taking Website</title>
<title>Solid Scribe - An easy, encrypted Note App</title>
</head>
<body>
<div id="app">
<!-- placeholder data for scrapers with no JS -->
<style>
body {
background-color: #212221;
color: #aeaeae;
}
.centered {
position: fixed;
top: 50%;
@ -37,7 +40,8 @@
<div class="centered">
<img class="logo" src="/api/static/assets/logo.svg" alt="Solid Scribe Logo - if you can read this your connection is really slow">
<h1>Solid Scribe</h1>
<h3>Loading...</h3>
<h3>An easy, encrypted Note App</h3>
<h4>Loading...</h4>
</div>
<div class="scrape-info">

View File

@ -1,5 +1,5 @@
<template>
<div id="app" :class="{ 'night-mode':($store.getters.getIsNightMode) }">
<div id="app" :class="{ 'night-mode':($store.getters.getIsNightMode == 2) }">
<global-site-menu />
@ -30,7 +30,6 @@ export default {
//Puts token into state on page load
let token = localStorage.getItem('loginToken')
let username = localStorage.getItem('username')
let masterKey = localStorage.getItem('masterKey')
// const socket = io({ path:'/socket' });
const socket = this.$io
@ -45,13 +44,14 @@ export default {
this.$store.commit('detectIsUserOnMobile')
//Set color theme based on local storage
if(localStorage.getItem('nightMode') == 'true'){
this.$store.commit('toggleNightMode')
const themeNumber = localStorage.getItem('nightMode')
if(themeNumber != null){
this.$store.commit('toggleNightMode', themeNumber)
}
//Put user data into global store on load
if(token){
this.$store.commit('setLoginToken', {token, username, masterKey})
this.$store.commit('setLoginToken', {token, username})
}
},

View File

@ -392,7 +392,7 @@ function createElement ( doc, tag, props, children ) {
function fixCursor ( node, root ) {
// In Webkit and Gecko, block level elements are collapsed and
// unfocussable if they have no content. To remedy this, a <BR> must be
// unfocusable if they have no content. To remedy this, a <BR> must be
// inserted. In Opera and IE, we just need a textnode in order for the
// cursor to appear.
var self = root.__squire__;
@ -2615,6 +2615,8 @@ function Squire ( root, config ) {
this.setConfig( config );
root.setAttribute( 'contenteditable', 'true' );
// Grammarly breaks the editor, *sigh*
root.setAttribute( 'data-gramm', 'false' );
// Remove Firefox's built-in controls
try {
@ -4945,4 +4947,4 @@ if ( typeof exports === 'object' ) {
}
}
}( document ) );
}( document ) );

View File

@ -64,8 +64,8 @@
return {
allStyles:{ 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null },
blankStyle:{ 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null },
colors: [
"#ffebee","#ffcdd2","#ef9a9a","#e57373","#ef5350","#f44336","#e53935","#d32f2f","#c62828","#b71c1c","#fce4ec","#f8bbd0","#f48fb1","#f06292","#ec407a","#e91e63","#d81b60","#c2185b","#ad1457","#880e4f","#f3e5f5","#e1bee7","#ce93d8","#ba68c8","#ab47bc","#9c27b0","#8e24aa","#7b1fa2","#6a1b9a","#4a148c","#ede7f6","#d1c4e9","#b39ddb","#9575cd","#7e57c2","#673ab7","#5e35b1","#512da8","#4527a0","#311b92","#e8eaf6","#c5cae9","#9fa8da","#7986cb","#5c6bc0","#3f51b5","#3949ab","#303f9f","#283593","#1a237e","#e3f2fd","#bbdefb","#90caf9","#64b5f6","#42a5f5","#2196f3","#1e88e5","#1976d2","#1565c0","#0d47a1","#e1f5fe","#b3e5fc","#81d4fa","#4fc3f7","#29b6f6","#03a9f4","#039be5","#0288d1","#0277bd","#01579b","#e0f7fa","#b2ebf2","#80deea","#4dd0e1","#26c6da","#00bcd4","#00acc1","#0097a7","#00838f","#006064","#e0f2f1","#b2dfdb","#80cbc4","#4db6ac","#26a69a","#009688","#00897b","#00796b","#00695c","#004d40","#e8f5e9","#c8e6c9","#a5d6a7","#81c784","#66bb6a","#4caf50","#43a047","#388e3c","#2e7d32","#1b5e20","#f1f8e9","#dcedc8","#c5e1a5","#aed581","#9ccc65","#8bc34a","#7cb342","#689f38","#558b2f","#33691e","#f9fbe7","#f0f4c3","#e6ee9c","#dce775","#d4e157","#cddc39","#c0ca33","#afb42b","#9e9d24","#827717","#fffde7","#fff9c4","#fff59d","#fff176","#ffee58","#ffeb3b","#fdd835","#fbc02d","#f9a825","#f57f17","#fff8e1","#ffecb3","#ffe082","#ffd54f","#ffca28","#ffc107","#ffb300","#ffa000","#ff8f00","#ff6f00","#fff3e0","#ffe0b2","#ffcc80","#ffb74d","#ffa726","#ff9800","#fb8c00","#f57c00","#ef6c00","#e65100","#fbe9e7","#ffccbc","#ffab91","#ff8a65","#ff7043","#ff5722","#f4511e","#e64a19","#d84315","#bf360c","#efebe9","#d7ccc8","#bcaaa4","#a1887f","#8d6e63","#795548","#6d4c41","#5d4037","#4e342e","#3e2723","#fafafa","#f5f5f5","#eeeeee","#e0e0e0","#bdbdbd","#9e9e9e","#757575","#616161","#424242","#212121","#eceff1","#cfd8dc","#b0bec5","#90a4ae","#78909c","#607d8b","#546e7a","#455a64","#37474f","#263238","#ffffff","#000000"],
colors: [null,
'rgb(67,67,67)','rgb(102,102,102)','rgb(153,153,153)','rgb(183,183,183)','rgb(204,204,204)','rgb(217,217,217)','rgb(239,239,239)','rgb(243,243,243)','rgb(255,255,255)','rgb(152,0,0)','rgb(255,0,0)','rgb(255,153,0)','rgb(255,255,0)','rgb(0,255,0)','rgb(0,255,255)','rgb(74,134,232)','rgb(0,0,255)','rgb(153,0,255)','rgb(255,0,255)','rgb(230,184,175)','rgb(244,204,204)','rgb(252,229,205)','rgb(255,242,204)','rgb(217,234,211)','rgb(208,224,227)','rgb(201,218,248)','rgb(207,226,243)','rgb(217,210,233)','rgb(234,209,220)','rgb(221,126,107)','rgb(234,153,153)','rgb(249,203,156)','rgb(255,229,153)','rgb(182,215,168)','rgb(162,196,201)','rgb(164,194,244)','rgb(159,197,232)','rgb(180,167,214)','rgb(213,166,189)','rgb(204,65,37)','rgb(224,102,102)','rgb(246,178,107)','rgb(255,217,102)','rgb(147,196,125)','rgb(118,165,175)','rgb(109,158,235)','rgb(111,168,220)','rgb(142,124,195)','rgb(194,123,160)','rgb(166,28,0)','rgb(204,0,0)','rgb(230,145,56)','rgb(241,194,50)','rgb(106,168,79)','rgb(69,129,142)','rgb(60,120,216)','rgb(61,133,198)','rgb(103,78,167)','rgb(166,77,121)','rgb(133,32,12)','rgb(153,0,0)','rgb(180,95,6)','rgb(191,144,0)','rgb(56,118,29)','rgb(19,79,92)','rgb(17,85,204)','rgb(11,83,148)','rgb(53,28,117)','rgb(116,27,71)','rgb(91,15,0)','rgb(102,0,0)','rgb(120,63,4)','rgb(127,96,0)','rgb(39,78,19)','rgb(12,52,61)','rgb(28,69,135)','rgb(7,55,99)','rgb(32,18,77)','rgb(76,17,48)'],
icons: ['ambulance','anchor','balance scale','bath','bed','beer','bell','bell slash','bell slash outline','bicycle','binoculars','birthday cake','blind','bomb','book','bookmark','briefcase','building','car','coffee','crosshairs','dollar sign','eye','eye slash','fighter jet','fire','fire extinguisher','flag','flag checkered','flask','gamepad','gavel','gift','glass martini','globe','graduation cap','h square','heart','heart outline','heartbeat','home','hospital','hospital outline','image','image outline','images','images outline','industry','info','info circle','key','leaf','lemon','lemon outline','life ring','life ring outline','lightbulb','lightbulb outline','location arrow','low vision','magnet','male','map','map outline','map marker','map marker alternate','map pin','map signs','medkit','money bill alternate','money bill alternate outline','motorcycle','music','newspaper','newspaper outline','paw','phone','phone square','phone volume','plane','plug','plus','plus square','plus square outline','print','recycle','road','rocket','search','search minus','search plus','ship','shopping bag','shopping basket','shopping cart','shower','street view','subway','suitcase','tag','tags','taxi','thumbtack','ticket alternate','tint','train','tree','trophy','truck','tty','umbrella','university','utensil spoon','utensils','wheelchair','wifi','wrench']
}
},
@ -84,15 +84,12 @@
this.colors.forEach((color,i) => {
if(i%20 <= 10){
return
}
let mod = (i % 10)+1 //1 - 10
let lines = [3, 5, 8, 9, 10]
if(lines.includes(mod)){
// if(lines.includes(mod)){
reduced.push(color)
}
// }
})
reduced.push("#000")
@ -110,6 +107,11 @@
//Set not background to color that was chosen
this.allStyles.noteBackground = inColor
if(inColor == null){
this.$emit('changeColor', this.allStyles)
return
}
//Automatically select note text color
// Convert hex color to RGB - http://gist.github.com/983661
@ -148,16 +150,18 @@
<style type="text/css" scoped>
.icon-button {
height: 40px;
width: 14.2%;
width: calc(10% - 7px);
display: inline-block;
cursor: pointer;
font-size: 1.3em;
}
.color-button {
height: 50px;
width: 20%;
display: block;
display: inline-block;
width: calc(10% - 7px);
height: 30px;
border-radius: 30px;
box-shadow: 0px 1px 3px 0px #3e3e3e;
margin: 7px 7px 0 0;
cursor: pointer;
float: left;
}
</style>

View File

@ -125,7 +125,6 @@
<i class="open folder outline icon"></i>
</router-link>
</div>
<div class="two wide center aligned bottom aligned column">
@ -138,7 +137,6 @@
<i class="green moon outline icon"></i>
</div>
<search-input v-if="loggedIn && mobile"></search-input>
<!-- mobile create note button -->
<span v-if="loggedIn">
<span v-if="!disableNewNote" @click="createNote" class="ui large green compact icon button">
@ -199,11 +197,11 @@
</router-link>
</div>
<!-- <div class="menu-section" v-if="loggedIn">
<div class="menu-section" v-if="loggedIn">
<router-link v-if="loggedIn" exact-active-class="active" class="menu-item menu-button" to="/quick">
<i class="paper plane outline icon"></i>Quick Note
<i class="sticky note outline icon"></i>Scratch Pad
</router-link>
</div> -->
</div>
<div class="menu-section" v-if="!loggedIn">
<router-link v-if="!loggedIn" class="menu-item menu-button" exact-active-class="active" to="/">
@ -217,10 +215,12 @@
<div class="menu-section">
<div v-on:click="toggleNightMode" class="menu-item menu-button">
<span v-if="$store.getters.getIsNightMode">
<span v-if="$store.getters.getIsNightMode == 0">
<i class="moon outline icon"></i>Black Theme</span>
<span v-if="$store.getters.getIsNightMode == 1">
<i class="moon outline icon"></i>Night Theme</span>
<span v-if="$store.getters.getIsNightMode == 2">
<i class="moon outline icon"></i>Light Theme</span>
<span v-else>
<i class="moon outline icon"></i>Dark Theme</span>
</div>
</div>
@ -257,7 +257,7 @@
},
data: function(){
return {
version: '2.1.0',
version: '2.1.2',
username: '',
collapsed: false,
mobile: false,
@ -329,7 +329,7 @@
.then(response => {
if(response.data && response.data.id){
this.$router.push('/notes/open/'+response.data.id)
// this.$router.push('/notes/open/'+response.data.id)
this.$bus.$emit('open_note', response.data.id)
this.disableNewNote = false
}
@ -380,7 +380,7 @@
location.reload(true)
},
getVersionIcon(){
const icons = ['cat','crow','dog','dove','dragon','fish','frog','hippo','horse','kiwi bird','otter','spider']
const icons = ['cat','crow','dog','dove','dragon','fish','frog','hippo','horse','kiwi bird','otter','spider', 'smile', 'robot', 'hat wizard', 'microchip', 'atom', 'grin tongue squint', 'radiation']
const index = ( parseInt(this.version.replace(/\./g,'')) % (icons.length))
return icons[index]

View File

@ -1,7 +1,7 @@
<template>
<div class="loading-container">
<svg version="1.1" id="L6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<rect fill="none" :stroke="$store.getters.getIsNightMode?'#FFF':'#16ab39'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5">
<rect fill="none" :stroke="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5">
<animateTransform
attributeName="transform"
dur="0.5s"
@ -12,7 +12,7 @@
attributeType="XML"
begin="rectBox.end"/>
</rect>
<rect x="25" y="25" :fill="$store.getters.getIsNightMode?'#FFF':'#16ab39'" width="50" height="50">
<rect x="25" y="25" :fill="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" width="50" height="50">
<animate
attributeName="height"
dur="1.3s"

View File

@ -21,25 +21,25 @@
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="toggleList('ul')" data-tooltip="Task List" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="toggleList('ul')" data-tooltip="Task List" data-position="bottom center" data-inverted :class="{'edit-active':activeToDo}">
<i class="tasks icon"></i>
</div>
<div class="edit-button" v-on:click="toggleList('ol')" data-tooltip="Numbered List" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="toggleList('ol')" data-tooltip="Numbered List" data-position="bottom center" data-inverted :class="{'edit-active':activeList}">
<i class="list ol icon"></i>
</div>
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center" data-inverted :style="{'color':activeColor}">
<i class="palette icon"></i>
</div>
<div class="edit-button" v-on:click="toggleBold()" data-tooltip="Bold" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="toggleBold()" data-tooltip="Bold" data-position="bottom center" data-inverted :class="{'edit-active':activeBold}">
<i class="bold icon"></i>
</div>
<div class="edit-button" v-on:click="toggleItalic()" data-tooltip="Quote" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="toggleItalic()" data-tooltip="Quote" data-position="bottom center" data-inverted :class="{'edit-active':activeQuote}">
<i class="quote left icon"></i>
</div>
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" data-inverted :class="{'edit-active':activeTitle}">
<i class="text height icon"></i>
</div>
<div class="edit-button" v-on:click="editor.increaseQuoteLevel()" data-tooltip="Indent" data-position="bottom center" data-inverted>
@ -63,7 +63,7 @@
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="bottom center" data-inverted>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="bottom center" data-inverted :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
<i class="paint brush icon"></i>
</div>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" data-tooltip="Tags" data-position="bottom center" data-inverted>
@ -112,7 +112,7 @@
<div class="bottom-edit-menu"></div>
<div class="input-container-wrapper" :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true)}" >
<div class="input-container-wrapper" :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}" :style="{ 'background-color':styleObject['noteBackground'] }">
<!-- Squire box grows -->
<div class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
@ -142,11 +142,6 @@
</div>
<!-- && this.$store.getters.getIsUserOnMobile -->
<span class="note-status-indicator" v-on:click="save()" v-if="statusText != 'Saved' && $store.getters.getIsUserOnMobile">
<div class="ui green button">{{statusText}}</div>
</span>
</div>
<!-- color picker -->
@ -298,6 +293,14 @@
images: false,
options: false,
colorpicker: false,
//active button states
activeBold: false,
activeQuote: false,
activeTitle: false,
activeList: false,
activeToDo: false,
activeColor: null,
}
},
watch: {
@ -383,7 +386,7 @@
this.setText(this.noteText)
this.lastNoteHash = this.hashString(this.getText())
console.log('hash on load', this.lastNoteHash)
// console.log('hash on load', this.lastNoteHash)
//focus on open, not on mobile, thats annoying
if(!this.$store.getters.getIsUserOnMobile){
@ -398,6 +401,41 @@
}
//Change button states on editor when element is active
//eg; Bold button turns green when on bold text
this.editor.addEventListener('pathChange', e => {
//Reset all button states
this.activeBold = false
this.activeTitle = false
this.activeQuote = false
this.activeList = false
this.activeToDo = false
this.activeColor = null
let colors = e.path.match(/\d+/g)
if(colors && colors.length == 3){
this.activeColor=`rgb(${colors.join(',')})`
}
//@ TODO - Update this to match all elements, like color and bold
// index of and then the specific thing might more indexOf('B'), indexOf('I'), etc
let element = e.path.split('>').pop()
switch (element) {
case 'B': this.activeBold = true; break;
case 'I': this.activeQuote = true; break;
case 'SPAN.size[fontSize=1.4em]': this.activeTitle = true; break;
}
let parent = e.path.split('>').shift()
switch (parent) {
case 'OL': this.activeList = true; break;
case 'UL': this.activeToDo = true; break;
}
})
//Click Event - Open links when clicked in editor or toggle checks
this.editor.addEventListener('click', e => {
@ -1003,17 +1041,17 @@
this.statusText = ''
this.diffText()
// this.diffText()
//Each note, save after 5 seconds, focus lost or 30 characters typed.
//Each note, save after 15 seconds, focus lost or 30 characters typed.
clearTimeout(this.editDebounce)
this.editDebounce = setTimeout(() => {
this.save()
}, 5000)
}, 15 * 1000)
//Save after 30 keystrokes
//Save after 50 keystrokes
this.keyPressesCounter = (this.keyPressesCounter + 1)
if(this.keyPressesCounter > 30){
if(this.keyPressesCounter > 50){
this.keyPressesCounter = 0
this.save()
}
@ -1048,7 +1086,7 @@
'hash': currentHash,
}
console.log('Save Hash', currentHash)
// console.log('Save Hash', currentHash)
this.statusText = 'Saving'
axios.post('/api/note/update', postData).then( response => {
@ -1148,8 +1186,8 @@
setupWebSockets(){
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
console.log('Current hash', this.lastNoteHash)
console.log('Incoming Hash', hash)
// console.log('Current hash', this.lastNoteHash)
// console.log('Incoming Hash', hash)
})
return
@ -1255,6 +1293,10 @@
.edit-button:hover {
background-color: var(--menu-accent);
}
.edit-active {
background-color: #21BA45;
color: white;
}
.edit-divide {
display: inline-block;
background-color: var(--menu-accent);
@ -1313,7 +1355,7 @@
width: 100%;
height: 100%;
min-height: 300px;
background: var(--background_color);
/*background: var(--background_color);*/
/*opacity: 0.;*/
z-index: 1;
}

View File

@ -1,12 +1,12 @@
<template>
<div class="note-title-display-card"
:style="{'background-color':color, 'color':fontColor, 'border-color':color }"
:class="{'currently-open':currentlyOpen, 'bgboy':triggerClosedAnimation}"
:class="{'currently-open':currentlyOpen, 'bgboy':triggerClosedAnimation, 'title-view':titleView }"
>
<!-- Show title and snippet below it -->
<div class="overflow-hidden note-card-text" @click="cardClicked">
<div class="overflow-hidden note-card-text" @click="cardClicked" v-if="!titleView">
<span class="subtext" v-if="note.shareUsername">
Shared by {{ note.shareUsername }}
@ -62,13 +62,17 @@
</div>
<div v-if="titleView" class="single-line-text" @click="cardClicked">
<span class="title-line" v-if="note.title.length > 0">{{ note.title }}<br></span>
<span class="sub-line" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span>
<span v-if="note.title.length == 0 && note.title.length == 0">Empty Note</span>
</div>
<!-- Toolbar on the bottom -->
<div class="tool-bar" @click.self="cardClicked">
<div class="tool-bar" @click.self="cardClicked" v-if="!titleView">
<div class="icon-bar">
<!-- <span v-if="note.pinned == 1" data-position="top left" data-tooltip="Pinned" data-inverted>
<!-- <span v-if="note.pinned == 1" data-position="top left" data-tooltip="Pinned" data-inverted>
<i class="green pin icon"></i>
</span>
<span v-if="note.archived == 1" data-position="top left" data-tooltip="Archived" data-inverted>
@ -113,9 +117,8 @@
<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
</div>
</div>
</div>
<side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true">
<div class="ui basic segment">
<note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/>
@ -136,7 +139,7 @@
export default {
name: 'NoteTitleDisplayCard',
props: [ 'onClick', 'data', 'currentlyOpen', 'textResults', 'attachmentResults', 'tagResults' ],
props: [ 'onClick', 'data', 'currentlyOpen', 'textResults', 'attachmentResults', 'tagResults', 'titleView' ],
components: {
'delete-button': require('@/components/NoteDeleteButtonComponent.vue').default,
'note-tag-edit': require('@/components/NoteTagEdit.vue').default,
@ -148,6 +151,17 @@
this.beenClicked = true
this.onClick(this.note.id)
},
removeHtml(string){
if(string == undefined || string == null || string.length == 0){
return ''
}
return string
.replace(/&[[#A-Za-z0-9]+A-Za-z0-9]+;/g,' ') //Rip out all HTML entities
.replace(/<[^>]+>/g, ' ') //Rip out all HTML tags
.replace(/\s+/g, ' ') //Remove all whitespace
.trim()
},
cleanHighlight(text){
//Basically just remove whitespace
let updated = text.replace(/&nbsp;/g, '').replace(/<br>/g,'')
@ -395,6 +409,27 @@
.note-title-display-card:hover {
box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);
}
.note-title-display-card.title-view {
width: 100%;
min-height: 10px;
max-width: none;
box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);
}
.single-line-text {
width: calc(100% - 25px);
margin: 5px 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
}
.title-line {
font-weight: bold;
font-size: 1.2em;
padding: 0 20px 0 0;
}
.icon-bar {
display: inline-block;
padding: 5px 10px 0;

View File

@ -8,45 +8,69 @@
}
.floating-button {
position: absolute;
right: 0;
right: 7px;
top: 4px;
z-index: 2;
}
.floating-note-options {
position: absolute;
right: 0;
left: 0;
top: 35px;
z-index: 2;
}
.floating-note-options > .segment {
border-top: none;
border-top-right-radius: 0;
border-top-left-radius: 0;
}
</style>
<template>
<span>
<span v-on:mouseenter="extraMenuHover = true" v-on:mouseleave="extraMenuHover = false">
<div class="ui form" v-if="!$store.getters.getIsUserOnMobile">
<div class="ui form">
<!-- normal search menu -->
<div class="ui left icon fluid input">
<input ref="desktopSearch" v-model="searchTerm" @keyup.enter="search" placeholder="Search Notes and Files" />
<input ref="desktopSearch" v-on:blur="focused = false" v-on:focus="focused = true" v-model="searchTerm" @keydown="onKeyDown" @keyup="onKeyUp" placeholder="Search or Start Typing New Note" />
<i class="search icon"></i>
</div>
<div class="floating-button" v-if="searchTerm.length > 0 && !searched">
<div class="floating-button" v-if="searchTerm.length > 0 && !searched && searchTerm.indexOf(' ') == -1">
<div class="ui green compact button" v-on:click="search()">Search</div>
</div>
<div class="floating-button" v-if="searchTerm.length > 0 && searched">
<div class="ui grey compact button" v-on:click="clear()">Clear</div>
</div>
</div>
<!-- Only show button on mobile -->
<span class="ui basic icon button" v-on:click="openFloatingSearch" v-if="$store.getters.getIsUserOnMobile">
<i class="green search icon"></i>
</span>
<div class="floating-button" v-if="!searched && searchTerm.length > 0 && searchTerm.indexOf(' ') != -1">
<div class="ui grey compact button" v-on:click="searchTerm = ''">Clear</div>
</div>
<div class="fixed-search" v-if="showFixedSearch">
<div class="ui raised segment">
<h2 class="ui center aligned header">Search!</h2>
<div class="ui form">
<div class="ui left icon fluid input">
<input
ref="fixedSearch"
v-model="searchTerm"
@keyup.enter="search"
v-on:blur="showFixedSearch = false"
placeholder="Press Enter to Search" />
<i class="search icon"></i>
<div class="floating-note-options"
v-if="(searchTerm.indexOf(' ') != -1 || tagSuggestions.length > 0) && (extraMenuHover)">
<div class="ui segment">
<div class="ui very compact grid" v-if="searchTerm.indexOf(' ') != -1">
<div class="eight wide column">
<div class="ui green compact shrinking button" v-on:click="pushToNewNote()">
<i class="plus icon"></i>A New Note
</div>
</div>
<div class="eight wide right aligned column">
<div class="ui green compact shrinking button" v-on:click="pushToQuickNote()">
<i class="sticky note outline icon"></i>The Scratch Pad
</div>
</div>
</div>
<div class="ui very compact grid" v-if="tagSuggestions.length > 0">
<div class="sixteen wide column">
<div class="ui clickable green label" v-for="tag in tagSuggestions" v-on:click="tagClick(tag.id)">
<i class="tag icon"></i>
{{ tag.text }}
</div>
</div>
</div>
</div>
</div>
</div>
@ -57,13 +81,19 @@
<script>
import axios from 'axios'
export default {
data: function(){
return {
searchTerm: '',
showFixedSearch: false,
searchTerm:'',
searched: false,
tagSuggestions: [],
tagSearchDebounce: null,
extraMenuHover: false,
}
},
beforeCreate: function(){
@ -73,36 +103,105 @@
//search clear
this.$bus.$on('reset_fast_filters', () => {
this.searchTerm = ''
this.tagSuggestions = []
})
},
methods: {
openFloatingSearch(){
this.showFixedSearch = !this.showFixedSearch
tagClick(tagId){
this.$emit('tagClick', tagId)
this.tagSuggestions = []
this.searchTerm = ''
if(this.showFixedSearch){
this.$nextTick( () => {
this.searchTerm = ''
this.$refs.fixedSearch.focus()
})
}
},
clear(){
this.searched = false
this.searchTerm = ''
this.tagSuggestions = []
if(!this.$store.getters.getIsUserOnMobile){
this.$refs.desktopSearch.focus()
}
this.$bus.$emit('note_reload')
},
pushToQuickNote(){
const text = this.searchTerm
this.searchTerm = ''
this.tagSuggestions = []
axios.post('/api/quick-note/update', { 'pushText':text.trim() } )
.then( response => {
//Open Quick Note
if(response.data && response.data.id){
this.$router.push('/notes/open/'+response.data.id)
this.$bus.$emit('open_note', response.data.id)
}
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Update The Scratch Pad') })
},
pushToNewNote(){
const text = this.searchTerm
this.searchTerm = ''
this.tagSuggestions = []
axios.post('/api/note/create', { text })
.then(response => {
if(response.data && response.data.id){
this.$router.push('/notes/open/'+response.data.id)
this.$bus.$emit('open_note', response.data.id)
}
})
.catch(error => { this.$bus.$emit('notification', 'Failed to create note') })
},
onKeyUp(event){
//Search Tags
const postData = {
'tagText':this.searchTerm.trim()
}
clearTimeout(this.tagSearchDebounce)
if(this.searchTerm.length == 0){
this.tagSuggestions = []
return
}
this.tagSearchDebounce = setTimeout(() => {
this.tagSuggestions = []
axios.post('/api/tag/suggest', postData)
.then( response => {
this.tagSuggestions = response.data
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Get Suggested Tags') })
}, 800)
},
onKeyDown(event){
//Commant + Enter
if((event.metaKey || event.ctrlKey) && event.keyCode == 13 ){
this.pushToQuickNote()
return
}
if(event.keyCode == 13){
this.search()
return
}
},
search(){
this.searched = true
this.$refs.desktopSearch.focus()
//Blur after search on mobile
if(this.$store.getters.getIsUserOnMobile){
this.$refs.fixedSearch.blur()
}
if(!this.$store.getters.getIsUserOnMobile){
this.$refs.desktopSearch.focus()
this.$refs.desktopSearch.blur()
}
this.$bus.$emit('update_search_term', this.searchTerm)
},
}

View File

@ -22,6 +22,14 @@
<script>
export default {
name: 'HelpPage'
name: 'HelpPage',
props:[ 'message' ],
data () {
return {
items: []
}
},
methods: {
}
}
</script>

View File

@ -31,10 +31,16 @@
v-on:tagClick="tagId => toggleTagFilter(tagId)"
/>
<div class="ui basic shrinking icon button" v-on:click="toggleTitleView()">
<i v-if="titleView" class="th icon"></i>
<i v-if="!titleView" class="bars icon"></i>
</div>
</div>
<div class="six wide column" v-if="!$store.getters.getIsUserOnMobile">
<div class="six wide column">
<search-input
v-on:tagClick="tagId => toggleTagFilter(tagId)"
v-if="$store.getters.totals && $store.getters.totals['totalNotes']" />
</div>
@ -49,9 +55,16 @@
</div>
<h2 v-if="searchTerm.length > 0 && !loadingInProgress">
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
</h2>
<div class="sixteen wide column" v-if="searchTerm.length > 0 && !loadingInProgress">
<h2 class="ui header">
<div class="content">
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
<div v-if="searchResultsCount == 0" class="sub header">
Search can only find key words. Try a single word search.
</div>
</div>
</h2>
</div>
<h2 v-if="fastFilters['withLinks'] == 1">Notes with Links</h2>
<h2 v-if="fastFilters['withTags'] == 1">Notes with Tags</h2>
@ -81,6 +94,7 @@
:ref="'note-'+note.id"
:onClick="openNote"
:data="note"
:title-view="titleView"
: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 + '-' + note.tag_count + note.updated"
/>
@ -152,6 +166,7 @@
highlights: [],
searchDebounce: null,
fastFilters: {},
titleView: false,
//Load up notes in batches
firstLoadBatchSize: 10, //First set of rapidly loaded notes
@ -207,10 +222,18 @@
this.$parent.loginGateway()
this.$io.on('new_note_created', noteId => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.updateSingleNote(parseInt(noteId))
}
})
//Update title cards when new note text is saved
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
console.log('notePage: update display of note ', noteId)
this.updateSingleNote(parseInt(noteId))
}
})
@ -237,6 +260,7 @@
this.noteSections[key].forEach( (note, index) => {
if(note.id == noteId){
this.noteSections[key].splice(index,1)
this.$store.dispatch('fetchAndUpdateUserTotals')
return
}
})
@ -312,6 +336,9 @@
this.reset()
},
methods: {
toggleTitleView(){
this.titleView = !this.titleView
},
showOneColumn(){
return this.$store.getters.getIsUserOnMobile
@ -328,32 +355,13 @@
if(nodeClick == 'A'){ return }
}
//Do not open same note twice
if(this.activeNoteId1 == id || this.activeNoteId2 == id){
return;
}
//1 note open
if(this.activeNoteId1 == null && this.activeNoteId2 == null){
if(this.activeNoteId1 == null){
this.activeNoteId1 = id
this.activeNote1Position = 0 //Middel of page
this.$router.push('/notes/open/'+this.activeNoteId1)
return
}
//2 notes open
if(this.activeNoteId1 != null && this.activeNoteId2 == null){
this.activeNoteId2 = id
this.activeNote1Position = 1 //Right side of page
this.activeNote2Position = 2 //Left side of page
return
}
//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){
//One note open, close that note
@ -448,10 +456,6 @@
if(this.$refs.note1 && this.$refs.note1.currentNoteId == noteIdToClose){
// this.$refs.note1.close()
}
if(this.$refs.note2 && this.$refs.note2.currentNoteId == noteIdToClose){
//this.$refs.note2.close()
}
}
},
visibiltyChangeAction(event){
@ -475,6 +479,9 @@
// @TODO Don't even trigger this if the note wasn't changed
updateSingleNote(noteId){
//Find local note, if it exists; continue
//Lookup one note using passed in ID
const postData = {
searchQuery: this.searchTerm,
@ -484,8 +491,7 @@
}
}
//Note data must be fetched, then sorted into existing note data
axios.post('/api/note/search', postData)
.then(results => {
@ -526,6 +532,7 @@
this.$nextTick( () => {
//Trigger close animation on note
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].justClosed()
}

View File

@ -4,31 +4,27 @@
<div class="ui sixteen wide column">
<h2 class="ui header">
<i class="paper plane outline icon"></i>
<i class="sticky note outline icon"></i>
<div class="content">
Quick
<div class="sub header">Rapidly save text</div>
The Scratch Pad
<div class="sub header">One place to put random junk</div>
</div>
</h2>
</div>
<div class="sixteen wide middle aligned column">
<div class="ui compact basic button"
v-on:click="enterToSubmit = !enterToSubmit">
<i v-if="enterToSubmit" class="green toggle on icon"></i>
<i v-else class="toggle off icon"></i>
<span v-if="enterToSubmit">Save after Enter press</span>
<span v-else>CTRL + Enter to Save</span>
<div class="ui compact basic right floated button shrinking" v-if="!showNewNoteConfirm" v-on:click="showNewNoteConfirm = true">
<i class="sync alternate reload icon"></i>
New Quick Note
</div>
<div class="ui compact basic button"
v-on:click="pasteToSubmit = !pasteToSubmit">
<i v-if="pasteToSubmit" class="green check circle outline icon"></i>
<i v-else class="circle outline icon"></i>
Save after Pasting
<div v-if="showNewNoteConfirm" class="ui compact basic right floated button shrinking" v-on:click="showNewNoteConfirm = false">
<i class="close icon"></i>
Cancel
</div>
<div v-if="showNewNoteConfirm" class="ui compact basic right floated button shrinking" v-on:click="newQuickNote()">
<i class="green thumbs up icon"></i>
Confirm
</div>
</div>
@ -41,25 +37,28 @@
ref="fastInput"
v-model="newText"
v-on:keydown="checkKeyup"
v-on:paste="onPaste"
placeholder="Push to the top of the quick note."
></textarea>
</div>
<div class="field">
<div v-on:click="appendQuickNote" class="ui green button">Save</div>
<div v-on:click="appendQuickNote" class="ui green button">Save (Enter)</div>
<div v-if="quickNoteId" class="ui right floated basic button" v-on:click="$router.push('/attachments/note/'+quickNoteId)">
<i class="folder open outline icon"></i>
Files
</div>
<div v-if="quickNoteId" v-on:click="openNoteEdit" class="ui right floated basic button">
<i class="file outline icon"></i>
Edit
Open Note
</div>
</div>
</div>
</div>
<div class="fun" v-html="savedQuickNoteText"></div>
<div class="one wide column"></div>
<div class="fourteen wide column">
<div class="note-card-text" v-html="savedQuickNoteText"></div>
</div>
<div class="one wide column"></div>
</div>
</div>
</template>
@ -79,8 +78,7 @@
newText: '',
savedQuickNoteText: '',
quickNoteId: null,
pasteToSubmit: true,
enterToSubmit: true,
showNewNoteConfirm: false,
}
},
beforeCreate: function(){
@ -89,6 +87,16 @@
//
this.$parent.loginGateway()
},
beforeMount(){
this.$io.on('new_note_created', noteId => {
this.getQuickNote()
})
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
this.getQuickNote()
})
},
mounted: function(){
if(this.$refs.fastInput){
@ -107,6 +115,17 @@
}
},
methods: {
newQuickNote(){
this.showNewNoteConfirm = false
axios.post('/api/quick-note/new')
.then( ({data}) => {
this.savedQuickNoteText = ''
this.quickNoteId = ''
})
},
openNoteEdit(){
this.$router.push({'path':'/notes/open/'+this.quickNoteId})
},
@ -119,17 +138,10 @@
element.style.height = (element.scrollHeight + padding) +'px';
//Enter Key submits by default
if(event.keyCode == 13 && this.enterToSubmit == true){
if(event.keyCode == 13){
this.appendQuickNote()
return
}
//Alternate submit
//If command+enter or control+enter is pressed, submit
if((event.metaKey || event.ctrlKey) && [13].includes(event.keyCode) && this.enterToSubmit == false){
this.appendQuickNote()
return
}
},
appendQuickNote(){
@ -142,28 +154,17 @@
this.newText = '' //Clear text area
this.$refs.fastInput.style.height = 'auto' //Back to normal size
this.savedQuickNoteText = results.data.text
this.quickNoteId = results.data.id
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Update Quick Note') })
},
getQuickNote (){
getQuickNote(){
axios.post('/api/quick-note/get')
.then( results => {
this.savedQuickNoteText = results.data.text
this.quickNoteId = results.data.id
.then( ({data}) => {
this.savedQuickNoteText = data.text
this.quickNoteId = data.id
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Fetch Quick Note') })
},
onPaste(event){
if(this.pasteToSubmit == true){
setTimeout( () => {
this.appendQuickNote()
}, 10)
}
return true
},
}
}
</script>

View File

@ -60,7 +60,7 @@ export default new Router({
{
path: '/quick',
name: 'Quick',
meta: {title:'Quick'},
meta: {title:'Scratch Pad'},
component: QuickPage
},
{

View File

@ -41,38 +41,61 @@ export default new Vuex.Store({
state.token = null
state.username = null
},
toggleNightMode(state){
toggleNightMode(state, pastTheme){
//Toggle state and save to local storage
state.nightMode = !(state.nightMode)
localStorage.setItem('nightMode', state.nightMode)
//Default theme colors
let themeColors = {
'background_color': '#fff',
'text_color': '#3d3d3d',
'outline_color': 'rgba(34,36,38,0.15)',
'border_color': 'rgba(34,36,38,0.20)',
'menu-accent': '#cecece',
'menu-text': '#5e6268',
}
//Night mode colors
if(state.nightMode){
themeColors = {
const themes = {
'white':{
'background_color': '#fff',
'text_color': '#3d3d3d',
'outline_color': 'rgba(34,36,38,0.15)',
'border_color': 'rgba(34,36,38,0.20)',
'menu-accent': '#cecece',
'menu-text': '#5e6268',
},
'black':{
'background_color': '#000',
'text_color': '#FFF',
'outline_color': '#FFF',
'border_color': 'rgba(255, 255, 255, 0.70)',
'menu-accent': '#626262',
'menu-text': '#d9d9d9',
},
'night':{
'background_color': '#000',
'text_color': '#a98457',
'outline_color': '#a98457',
'border_color': 'rgba(255, 255, 255, 0.31)',
'menu-accent': '#626262',
'menu-text': '#d9d9d9',
}
},
}
//Catch values not in set
const totalThemes = Object.keys(themes).length
state.nightMode++
if(state.nightMode > totalThemes-1){
state.nightMode = 0
}
if(pastTheme != null){
state.nightMode = pastTheme
}
//Final catch for numbers
if(Number.isInteger(parseInt(state.nightMode)) == false){
state.nightMode = 0
}
const currentTheme = Object.keys(themes)[state.nightMode]
//Toggle state and save to local storage
localStorage.setItem('nightMode', state.nightMode)
//Go through each color and set CSS variable
let root = document.documentElement
Object.keys(themeColors).forEach( attribute => {
root.style.setProperty('--'+attribute, themeColors[attribute])
Object.keys( themes[currentTheme] ).forEach( attribute => {
root.style.setProperty('--'+attribute, themes[currentTheme][attribute])
})
},
detectIsUserOnMobile(state){

View File

@ -139,8 +139,8 @@ app.use(function(req, res, next){
// Test Area
// -> right here
let UserTest = require('@models/User')
let NoteTest = require('@models/Note')
// let UserTest = require('@models/User')
// let NoteTest = require('@models/Note')
// UserTest.keyPairTest()
// .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey))
// .then( message => { console.log(message) })

View File

@ -24,7 +24,7 @@ Note.test = (userId, masterKey) => {
let testNoteId = 0
Note.create(userId, '','', masterKey)
Note.create(null, userId, '','', masterKey)
.then(newNoteId => {
console.log('Test: Create Note - Pass')
@ -164,7 +164,7 @@ Note.encryptEveryNote = (userId, masterKey) => {
}
//Returns insertedId of new note
Note.create = (userId, noteTitle, noteText, masterKey) => {
Note.create = (io, userId, noteTitle = '', noteText = '', masterKey) => {
return new Promise((resolve, reject) => {
if(userId == null || userId < 10){ reject('User Id required to create note') }
@ -173,6 +173,9 @@ Note.create = (userId, noteTitle, noteText, masterKey) => {
const salt = cs.createSmallSalt()
const snippetSalt = cs.createSmallSalt()
const snippetObj = JSON.stringify([noteTitle, noteText.substring(0, 500)])
const snippet = cs.encrypt(masterKey, snippetSalt, snippetObj)
const textObject = JSON.stringify([noteTitle, noteText])
const encryptedText = cs.encrypt(masterKey, salt, textObject)
@ -183,10 +186,15 @@ Note.create = (userId, noteTitle, noteText, masterKey) => {
const rawTextId = rows[0].insertId
return db.promise()
.query('INSERT INTO note (user_id, note_raw_text_id, created, quick_note, snippet_salt) VALUES (?,?,?,?,?)',
[userId, rawTextId, created, 0, snippetSalt])
.query('INSERT INTO note (user_id, note_raw_text_id, created, quick_note, snippet, snippet_salt) VALUES (?,?,?,?,?,?)',
[userId, rawTextId, created, 0, snippet, snippetSalt])
})
.then((rows, fields) => {
if(io){
io.to(userId).emit('new_note_created', rows[0].insertId)
}
// Indexing is done on save
resolve(rows[0].insertId) //Only return the new note ID when creating a new note
})

View File

@ -5,45 +5,76 @@ let Note = require('@models/Note')
let QuickNote = module.exports = {}
QuickNote.get = (userId) => {
QuickNote.get = (userId, masterKey) => {
return new Promise((resolve, reject) => {
db.promise()
.query(`
SELECT note.id, text FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE quick_note = 1 AND user_id = ? LIMIT 1
SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1
`, [userId])
.then((rows, fields) => {
//Quick Note is set, return note text
if(rows[0].length == 1){
resolve({
id: rows[0][0].id,
text: rows[0][0].text
if(rows[0][0] != undefined){
let noteId = rows[0][0].id
Note.get(userId, noteId, masterKey)
.then( noteObject => {
return resolve(noteObject)
})
} else {
return resolve(null)
}
resolve({
id: null,
text: 'Enter something to create a quick note.'
})
})
.catch(console.log)
})
}
QuickNote.update = (userId, pushText) => {
QuickNote.newNote = (userId) => {
return new Promise((resolve, reject) => {
db.promise().query('UPDATE note SET quick_note = 0 WHERE quick_note = 1 AND user_id = ?',[userId])
.then((rows, fields) => {
resolve(true)
})
})
}
QuickNote.makeUrlLink = (inputText) => {
var replacedText, replacePattern1, replacePattern2, replacePattern3;
//URLs starting with http://, https://, or ftp://
replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
//Change email addresses to mailto:: links.
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
return replacedText;
}
QuickNote.update = (io, userId, pushText, masterKey) => {
return new Promise((resolve, reject) => {
let finalId = null
let finalText = ''
//Process pushText, split at \n (new lines), put <p> tags around each new line
let broken = '<blockquote>' +
let broken = '<p>' +
pushText.split(/\r?\n/).map( (line, index) => {
let clean = line
.replace(/&[#A-Za-z0-9]+;/g,'') //Rip out all HTML entities
.replace(/<[^>]+>/g, '') //Rip out all HTML tags
//Turn links into actual linx
clean = QuickNote.makeUrlLink(clean)
if(clean == ''){ clean = '&nbsp;' }
let newLine = ''
if(index > 0){ newLine = '<br>' }
@ -51,51 +82,31 @@ QuickNote.update = (userId, pushText) => {
//Return line wrapped in p tags
return `${newLine}<span>${clean}</span>`
}).join('') + '</blockquote>'
}).join('') + '</p><p><br></p>'
db.promise()
.query(`
SELECT note.id, text, color, pinned, archived
FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE quick_note = 1 AND user_id = ? LIMIT 1
`, [userId])
.then((rows, fields) => {
QuickNote.get(userId, masterKey)
.then(noteObject => {
//Quick Note is set, push it the front of existing note
if(rows[0].length == 1){
if(noteObject == null){
let d = rows[0][0] //Get row data
finalText += broken
//Push old text behind fresh new text
let newText = broken +''+ d.text
//Save that, then return the new text
Note.update(null, userId, d.id, newText, '', d.color, d.pinned, d.archived)
.then( saveResults => {
resolve({
id:d.id,
text:newText
})
return Note.create(io, userId, 'Quick Note', finalText, masterKey)
.then(insertedId => {
finalId = insertedId
return db.promise().query('UPDATE note SET quick_note = 1 WHERE id = ? AND user_id = ?',[insertedId, userId])
})
} else {
//Create a new note with the quick text submitted.
Note.create(userId, broken, 1)
.then( insertId => {
resolve({
id:insertId,
text:broken
})
})
finalText += (broken + noteObject.text)
finalId = noteObject.id
return Note.update(io, userId, noteObject.id, finalText, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey)
}
})
.catch(console.log)
.then( saveResults => {
return resolve(true)
})
})
//Lookup quick note,
//Note.create(userId, 'Quick Note', 1)
}

View File

@ -176,16 +176,24 @@ Tag.suggest = (userId, noteId, tagText) => {
tagText += '%'
return new Promise((resolve, reject) => {
db.promise()
.query(`SELECT text FROM note_tag
let params = [userId, tagText]
let query = `SELECT tag.id, text FROM note_tag
JOIN tag ON note_tag.tag_id = tag.id
WHERE note_tag.user_id = ?
AND tag.text LIKE ?
AND note_tag.tag_id NOT IN (
SELECT note_tag.tag_id FROM note_tag WHERE note_tag.note_id = ?
)
GROUP BY text
LIMIT 6`, [userId, tagText, noteId])
AND tag.text LIKE ?`
if(noteId && noteId > 0){
params.push(noteId)
query += `AND note_tag.tag_id NOT IN
(SELECT note_tag.tag_id FROM note_tag WHERE note_tag.note_id = ?)`
}
query += ` GROUP BY text, id LIMIT 6`
db.promise()
.query(query, params)
.then((rows, fields) => {
resolve(rows[0]) //Return new ID
})

View File

@ -34,7 +34,7 @@ router.post('/delete', function (req, res) {
})
router.post('/create', function (req, res) {
Notes.create(userId, req.body.title, req.body.text, masterKey)
Notes.create(req.io, userId, req.body.title, req.body.text, masterKey)
.then( id => res.send({id}) )
})

View File

@ -2,12 +2,15 @@ var express = require('express')
var router = express.Router()
let QuickNote = require('@models/QuickNote');
let userId = null
let masterKey = null
// middleware that is specific to this router
router.use(function setUserId (req, res, next) {
if(userId = req.headers.userId){
userId = req.headers.userId
masterKey = req.headers.masterKey
}
next()
@ -15,19 +18,19 @@ router.use(function setUserId (req, res, next) {
//Get quick note text
router.post('/get', function (req, res) {
QuickNote.get(userId)
QuickNote.get(userId, masterKey)
.then( data => res.send(data) )
})
//Push text to quick note
router.post('/update', function (req, res) {
QuickNote.update(userId, req.body.pushText)
QuickNote.update(req.io, userId, req.body.pushText, masterKey)
.then( data => res.send(data) )
})
//Change quick note to a new note
router.post('/change', function (req, res) {
QuickNote.change(userId, req.body.noteId)
//Push text to quick note
router.post('/new', function (req, res) {
QuickNote.newNote(userId)
.then( data => res.send(data) )
})