* 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:
parent
67b218329b
commit
e87e8513bc
@ -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">
|
||||
|
@ -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})
|
||||
}
|
||||
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
@ -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]
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,10 +62,14 @@
|
||||
|
||||
</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>
|
||||
@ -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(/ /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;
|
||||
|
@ -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 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>
|
||||
|
||||
<!-- 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-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="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="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,
|
||||
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){
|
||||
|
||||
if(this.showFixedSearch){
|
||||
this.$nextTick( () => {
|
||||
this.$emit('tagClick', tagId)
|
||||
this.tagSuggestions = []
|
||||
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
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$refs.fixedSearch.blur()
|
||||
}
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
|
||||
this.$refs.desktopSearch.focus()
|
||||
//Blur after search on mobile
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$refs.desktopSearch.blur()
|
||||
}
|
||||
|
||||
this.$bus.$emit('update_search_term', this.searchTerm)
|
||||
},
|
||||
}
|
||||
|
@ -22,6 +22,14 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelpPage'
|
||||
name: 'HelpPage',
|
||||
props:[ 'message' ],
|
||||
data () {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
@ -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 class="six wide column" v-if="!$store.getters.getIsUserOnMobile">
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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()
|
||||
}
|
||||
|
@ -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,14 +138,7 @@
|
||||
element.style.height = (element.scrollHeight + padding) +'px';
|
||||
|
||||
//Enter Key submits by default
|
||||
if(event.keyCode == 13 && this.enterToSubmit == true){
|
||||
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){
|
||||
if(event.keyCode == 13){
|
||||
this.appendQuickNote()
|
||||
return
|
||||
}
|
||||
@ -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(){
|
||||
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>
|
@ -60,7 +60,7 @@ export default new Router({
|
||||
{
|
||||
path: '/quick',
|
||||
name: 'Quick',
|
||||
meta: {title:'Quick'},
|
||||
meta: {title:'Scratch Pad'},
|
||||
component: QuickPage
|
||||
},
|
||||
{
|
||||
|
@ -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 = {
|
||||
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',
|
||||
}
|
||||
//Night mode colors
|
||||
if(state.nightMode){
|
||||
themeColors = {
|
||||
},
|
||||
'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){
|
||||
|
@ -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) })
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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 = ' ' }
|
||||
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)
|
||||
|
||||
}
|
@ -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
|
||||
})
|
||||
|
@ -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}) )
|
||||
})
|
||||
|
||||
|
@ -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) )
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user