Compare commits
No commits in common. "9c4fff791307e47f8b2aa053daac91b6f1c6c9f9" and "c8033588dde91454fa924269722e7b0c3a0a7315" have entirely different histories.
9c4fff7913
...
c8033588dd
@ -10,16 +10,13 @@
|
||||
<meta name="theme-color" content="#000" />
|
||||
<link rel="manifest" href="/api/static/assets/manifest.json">
|
||||
|
||||
<title>Solid Scribe - An easy, encrypted Note App</title>
|
||||
<title>Solid Scribe - A Note Taking Website</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%;
|
||||
@ -40,8 +37,7 @@
|
||||
<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>An easy, encrypted Note App</h3>
|
||||
<h4>Loading...</h4>
|
||||
<h3>Loading...</h3>
|
||||
</div>
|
||||
|
||||
<div class="scrape-info">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="app" :class="{ 'night-mode':($store.getters.getIsNightMode == 2) }">
|
||||
<div id="app" :class="{ 'night-mode':($store.getters.getIsNightMode) }">
|
||||
|
||||
<global-site-menu />
|
||||
|
||||
@ -44,9 +44,8 @@ export default {
|
||||
this.$store.commit('detectIsUserOnMobile')
|
||||
|
||||
//Set color theme based on local storage
|
||||
const themeNumber = localStorage.getItem('nightMode')
|
||||
if(themeNumber != null){
|
||||
this.$store.commit('toggleNightMode', themeNumber)
|
||||
if(localStorage.getItem('nightMode') == 'true'){
|
||||
this.$store.commit('toggleNightMode')
|
||||
}
|
||||
|
||||
//Put user data into global store on load
|
||||
|
@ -392,7 +392,7 @@ function createElement ( doc, tag, props, children ) {
|
||||
|
||||
function fixCursor ( node, root ) {
|
||||
// In Webkit and Gecko, block level elements are collapsed and
|
||||
// unfocusable if they have no content. To remedy this, a <BR> must be
|
||||
// unfocussable 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,8 +2615,6 @@ 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: [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)'],
|
||||
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"],
|
||||
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,12 +84,15 @@
|
||||
|
||||
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")
|
||||
@ -107,11 +110,6 @@
|
||||
//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
|
||||
@ -150,18 +148,16 @@
|
||||
<style type="text/css" scoped>
|
||||
.icon-button {
|
||||
height: 40px;
|
||||
width: calc(10% - 7px);
|
||||
width: 14.2%;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.color-button {
|
||||
display: inline-block;
|
||||
width: calc(10% - 7px);
|
||||
height: 30px;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0px 1px 3px 0px #3e3e3e;
|
||||
margin: 7px 7px 0 0;
|
||||
height: 50px;
|
||||
width: 20%;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
}
|
||||
</style>
|
43
client/src/components/CrunchMenu.vue
Normal file
43
client/src/components/CrunchMenu.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>Crunch Menu</p>
|
||||
<div v-for="(item, index) in items">
|
||||
<slot :name="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'CrunchMenu',
|
||||
data () {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
},
|
||||
mounted(){
|
||||
console.log(this)
|
||||
// console.log(this.$slots.default)
|
||||
this.$slots.default.forEach( vnode => {
|
||||
if(vnode.tag && vnode.tag.length > 0){
|
||||
this.items.push(vnode)
|
||||
}
|
||||
})
|
||||
|
||||
console.log(this.items)
|
||||
},
|
||||
methods: {
|
||||
onClickTag(index){
|
||||
console.log('yup')
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css" scoped>
|
||||
</style>
|
@ -70,7 +70,9 @@
|
||||
<template>
|
||||
<div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0">
|
||||
<div class="popup-row color-fade" v-for="item in notifications">
|
||||
<i class="disabled angle left icon"></i>
|
||||
<span>{{ item }}</span>
|
||||
<i class="disabled angle right icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -125,6 +125,7 @@
|
||||
<i class="open folder outline icon"></i>
|
||||
</router-link>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="two wide center aligned bottom aligned column">
|
||||
@ -137,6 +138,7 @@
|
||||
<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,7 +201,7 @@
|
||||
|
||||
<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="sticky note outline icon"></i>Scratch Pad
|
||||
<i class="paper plane outline icon"></i>Quick Note
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
@ -215,12 +217,10 @@
|
||||
|
||||
<div class="menu-section">
|
||||
<div v-on:click="toggleNightMode" class="menu-item menu-button">
|
||||
<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">
|
||||
<span v-if="$store.getters.getIsNightMode">
|
||||
<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.2.2',
|
||||
version: '1.0.5',
|
||||
username: '',
|
||||
collapsed: false,
|
||||
mobile: false,
|
||||
@ -329,7 +329,7 @@
|
||||
.then(response => {
|
||||
|
||||
if(response.data && response.data.id){
|
||||
//Redirect to note page if user is not on it
|
||||
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', 'smile', 'robot', 'hat wizard', 'microchip', 'atom', 'grin tongue squint', 'radiation']
|
||||
const icons = ['cat','crow','dog','dove','dragon','fish','frog','hippo','horse','kiwi bird','otter','spider']
|
||||
const index = ( parseInt(this.version.replace(/\./g,'')) % (icons.length))
|
||||
return icons[index]
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
<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 > 0 ? '#FFF':'#16ab39'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
dur="0.5s"
|
||||
from="0 50 50"
|
||||
to="180 50 50"
|
||||
type="rotate"
|
||||
id="strokeBox"
|
||||
attributeType="XML"
|
||||
begin="rectBox.end"/>
|
||||
</rect>
|
||||
<rect x="25" y="25" :fill="$store.getters.getIsNightMode > 0 ? '#FFF':'#16ab39'" width="50" height="50">
|
||||
<animate
|
||||
attributeName="height"
|
||||
dur="1.3s"
|
||||
attributeType="XML"
|
||||
from="50"
|
||||
to="0"
|
||||
id="rectBox"
|
||||
fill="freeze"
|
||||
begin="0s;strokeBox.end"/>
|
||||
</rect>
|
||||
</svg>
|
||||
<div class="loading-message" v-if="message">{{ message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'LoadingIcon',
|
||||
props:[ 'message' ],
|
||||
data () {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods: {
|
||||
onClickTag(index){
|
||||
console.log('yup')
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css" scoped>
|
||||
.loading-container {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.loading-container svg {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.loading-message {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span>
|
||||
<span class="clickable" @click="confirmDelete()" v-if="click == 0" data-tooltip="Delete Forever" data-inverted="" data-position="top right">
|
||||
<span class="clickable" @click="confirmDelete()" v-if="click == 0" data-tooltip="Delete" data-inverted="" data-position="top right">
|
||||
<i class="trash alternate icon"></i>
|
||||
</span>
|
||||
<span class="clickable" @click="actuallyDelete()" @mouseleave="reset" v-if="click == 1" data-tooltip="Click again to delete." data-position="top right" data-inverted="">
|
||||
|
@ -2,9 +2,9 @@
|
||||
<!-- change class to .master-note-edit to have it popup on the screen -->
|
||||
<div
|
||||
id="InputNotes"
|
||||
class="master-note-edit full-focus"
|
||||
class="master-note-edit"
|
||||
@keyup.esc="close()"
|
||||
:class="[ 'position-'+position ]"
|
||||
:class="[{ 'full-focus':(fullFocusEditor) }, 'position-'+position ]"
|
||||
>
|
||||
|
||||
<!-- Main Menu -->
|
||||
@ -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 :class="{'edit-active':activeToDo}">
|
||||
<div class="edit-button" v-on:click="toggleList('ul')" data-tooltip="Task List" data-position="bottom center" data-inverted>
|
||||
<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 :class="{'edit-active':activeList}">
|
||||
<div class="edit-button" v-on:click="toggleList('ol')" data-tooltip="Numbered List" data-position="bottom center" data-inverted>
|
||||
<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 :style="{'color':activeColor}">
|
||||
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center" data-inverted>
|
||||
<i class="palette icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="edit-button" v-on:click="toggleBold()" data-tooltip="Bold" data-position="bottom center" data-inverted :class="{'edit-active':activeBold}">
|
||||
<div class="edit-button" v-on:click="toggleBold()" data-tooltip="Bold" data-position="bottom center" data-inverted>
|
||||
<i class="bold icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="edit-button" v-on:click="toggleItalic()" data-tooltip="Quote" data-position="bottom center" data-inverted :class="{'edit-active':activeQuote}">
|
||||
<div class="edit-button" v-on:click="toggleItalic()" data-tooltip="Quote" data-position="bottom center" data-inverted>
|
||||
<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 :class="{'edit-active':activeTitle}">
|
||||
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" data-inverted>
|
||||
<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 :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="bottom center" data-inverted>
|
||||
<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>
|
||||
@ -86,6 +86,17 @@
|
||||
|
||||
<div class="edit-divide"></div>
|
||||
|
||||
<!-- protect -->
|
||||
<div class="edit-button" v-if="!isEncrypted"
|
||||
v-on:click="$router.push(`/notes/open/${noteid}/menu/passwordprotect`)" data-tooltip="Password Protect" data-position="bottom center" data-inverted>
|
||||
<i class="shield alternate icon"></i>
|
||||
</div>
|
||||
|
||||
<!-- data-tooltip="Remove Password Protection" -->
|
||||
<div class="edit-button" v-if="isEncrypted && isDecrypted" v-on:click="disableEncryption()" data-tooltip="Close" data-position="bottom center" data-inverted>
|
||||
<i class="unlock icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="edit-button" v-on:click="onToggleArchived()" :data-tooltip="archived == 1?'Move to main list':'Move to Archive'" data-position="bottom center" data-inverted>
|
||||
<span v-if="archived == 1"><i class="green archive icon"></i></span>
|
||||
<span v-if="archived != 1"><i class="archive icon"></i></span>
|
||||
@ -101,8 +112,6 @@
|
||||
<i class="folder icon"></i>
|
||||
</div>
|
||||
|
||||
<span>{{ statusText }}</span>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <span :data-tooltip="`Created: ${$helpers.timeAgo(created)}`">
|
||||
@ -112,20 +121,18 @@
|
||||
|
||||
<div class="bottom-edit-menu"></div>
|
||||
|
||||
<div class="input-container-wrapper" :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}" :style="{ 'background-color':styleObject['noteBackground'] }">
|
||||
<div class="input-container-wrapper" :class="{ 'size-down':(sizeDown == true)}" >
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div v-if="loading" class="loading-note">
|
||||
<div class="ui active dimmer">
|
||||
<div class="ui text loader">{{loadingMessage}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Squire box grows -->
|
||||
<div class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<transition name="fade">
|
||||
<div v-if="loading || forceShowLoading" class="loading-note" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
|
||||
<div class="loading-text">
|
||||
<loading-icon :message="loadingMessage" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- Title input area -->
|
||||
<textarea
|
||||
ref="titleTextarea"
|
||||
@ -137,11 +144,53 @@
|
||||
v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input">
|
||||
</textarea>
|
||||
|
||||
<!-- Squire Box -->
|
||||
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||
<!-- Squire Box - only appears if decrypted -->
|
||||
<div v-show="isDecrypted" id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||
|
||||
<!-- Decrypt note prompt -->
|
||||
<div v-if="isEncrypted && !isDecrypted" class="ui basic padded segment">
|
||||
<div class="ui raised segment">
|
||||
<h3 class="ui center aligned icon header">
|
||||
<i class="green lock alternate icon"></i>
|
||||
|
||||
<span v-if="!lockedOut">
|
||||
This note is encrypted and requires a password to be opened.
|
||||
</span>
|
||||
|
||||
<!-- note is locked for 5 minutes -->
|
||||
<span v-if="lockedOut">
|
||||
To many unlock attempts. Note is locked for 5 minutes.
|
||||
</span>
|
||||
</h3>
|
||||
<!-- Decrypt note -->
|
||||
<div class="ui form" v-if="!lockedOut">
|
||||
<h5 class="ui horizontal divider header" v-if="passwordHint && passwordHint.length > 0">
|
||||
Hint: {{ passwordHint }}
|
||||
</h5>
|
||||
<div class="field">
|
||||
<input :name="`randomThing-${noteid}`" :id="`yupper-${noteid}`"type="password" v-model="password" placeholder="Note Password" v-on:keyup.enter="decryptNote" autofocus ref="decryptNotePrompt">
|
||||
</div>
|
||||
<div class="field">
|
||||
<div v-on:click="decryptNote" class="ui green fluid button" v-if="password.length >= 3">
|
||||
Unlock Note
|
||||
</div>
|
||||
<div class="ui disabled fluid button" v-if="password.length < 3">
|
||||
Unlock Note
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</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 -->
|
||||
@ -218,6 +267,55 @@
|
||||
</div>
|
||||
</side-slide-menu>
|
||||
|
||||
<side-slide-menu v-show="passwordprotect" v-on:close="passwordprotect = false" :fullShadow="true" name="encrypt note">
|
||||
<div class="ui basic segment" v-if="isDecrypted && isEncrypted">
|
||||
<p>Note Decrypted</p>
|
||||
<div class="ui green button" v-on:click="lockNote">Lock Note</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isEncrypted" class="ui basic segment">
|
||||
|
||||
<div class="ui top attached segment">
|
||||
|
||||
<h2><i class="green lock alternate icon"></i>Password protect this Note</h2>
|
||||
<p>Password protection will prevent anyone from reading the text of this note, unless they enter the correct password.</p>
|
||||
<p><b>Only the note text is protected. Title, tags, and files are not encrypted and remain visible without a password.</b></p>
|
||||
<p>The password you select will only be used for this note. You can use the same password on multiple notes. The note will be encrypted using the password entered. A longer password will be more secure.</p>
|
||||
<h4><i class="red icon exclamation triangle"></i> Warning. There is no way to recover a lost password.</h4>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached segment">
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>New Password to lock this note</label>
|
||||
<input :name="`randomThing-${noteid}`" :id="`yupper-${noteid}`"type="password" v-model="password" placeholder="Note Password">
|
||||
</div>
|
||||
<div class="field" v-if="password.length > 3">
|
||||
<label>Confirm Password</label>
|
||||
<input :name="`randomStuff-${noteid}`" :id="`heckye-${noteid}`"type="password" v-model="passwordConfirm" placeholder="Confirm Password">
|
||||
</div>
|
||||
<div class="field" v-if="password.length > 3">
|
||||
<label>Password Hint - visible when unlocking note</label>
|
||||
<input :name="`randomStuzz-${noteid}`" :id="`heckyo-${noteid}`"type="text" v-model="passwordHint" placeholder="Optional Password Hint" v-on:keyup.enter="enableEncryption">
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="passwordConfirm.length > 3 && password != passwordConfirm">
|
||||
<div v-on:click="enableEncryption" class="ui disabled green button">
|
||||
Passwords do not match
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" v-if="passwordConfirm.length > 3 && password == passwordConfirm">
|
||||
<div v-on:click="enableEncryption" class="ui green button">
|
||||
Protect!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</side-slide-menu>
|
||||
|
||||
<!-- Show side shades if user is on desktop only -->
|
||||
<div class="full-focus-shade shade1"
|
||||
:class="{ 'slide-out-left':(sizeDown == true) }"
|
||||
@ -248,13 +346,11 @@
|
||||
'share-note-component': () => import('@/components/ShareNoteComponent.vue'),
|
||||
|
||||
'color-tooltip':require('@/components/TextColorTooltipComponent.vue').default,
|
||||
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default,
|
||||
'loading-icon':require('@/components/LoadingIconComponent.vue').default,
|
||||
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
loading: true,
|
||||
forceShowLoading: true,
|
||||
loadingMessage: 'Loading Note',
|
||||
currentNoteId: 0,
|
||||
modified: false,
|
||||
@ -265,7 +361,7 @@
|
||||
updated: '',
|
||||
shareUsername: null,
|
||||
diffNoteText: '',
|
||||
statusText: 'Saved.',
|
||||
statusText: 'Saved',
|
||||
lastNoteHash: null,
|
||||
saveDebounce: null, //Prevent save from being called numerous times quickly
|
||||
updated: 'Never',
|
||||
@ -279,7 +375,12 @@
|
||||
|
||||
sizeDown: false, //Used to animate close state
|
||||
|
||||
colorPickerLocation: null,
|
||||
|
||||
fullFocusEditor: true, //Initialized editor instance
|
||||
|
||||
//Settings vars
|
||||
showAllSettings: true,
|
||||
lastVisibilityState: null,
|
||||
|
||||
//All the squire settings
|
||||
@ -287,35 +388,38 @@
|
||||
// pastFocusedNode: null,
|
||||
usersOnNote: 0,
|
||||
|
||||
sideMenuOpen: false,
|
||||
tags: false,
|
||||
colors: false,
|
||||
images: false,
|
||||
options: false,
|
||||
colorpicker: false,
|
||||
|
||||
//active button states
|
||||
activeBold: false,
|
||||
activeQuote: false,
|
||||
activeTitle: false,
|
||||
activeList: false,
|
||||
activeToDo: false,
|
||||
activeColor: null,
|
||||
//Encryption options
|
||||
passwordHint: '',
|
||||
password: '', //Field Variables, only for form
|
||||
passwordConfirm: '', //Only a form variable
|
||||
hashedPass: '', //sha-256 password hash, sends to server for decryption
|
||||
isEncrypted: false,
|
||||
isDecrypted: false,
|
||||
passwordprotect: false,
|
||||
decryptAttempts: 0,
|
||||
lockedOut: false,
|
||||
autoLockTimeout: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
noteid:function(newVal, oldVal){
|
||||
|
||||
// if(newVal == this.currentNoteId){
|
||||
// return
|
||||
// }
|
||||
if(newVal == this.currentNoteId){
|
||||
return
|
||||
}
|
||||
|
||||
// if(newVal == oldVal){
|
||||
// return
|
||||
// }
|
||||
if(newVal == oldVal){
|
||||
return
|
||||
}
|
||||
|
||||
// this.currentNoteId = newVal
|
||||
// this.loadNote(this.currentNoteId)
|
||||
this.currentNoteId = newVal
|
||||
this.loadNote(this.currentNoteId)
|
||||
|
||||
},
|
||||
urlData(newVal, oldVal){
|
||||
@ -327,9 +431,9 @@
|
||||
}
|
||||
|
||||
//Reset all note menus on URL change
|
||||
this.sideMenuOpen = false
|
||||
this.colors = false
|
||||
this.tags = false
|
||||
this.passwordprotect = false
|
||||
this.options = false
|
||||
this.images = false
|
||||
|
||||
@ -337,7 +441,7 @@
|
||||
if(newVal.openMenu){
|
||||
//Only modify menu boolean if its defined
|
||||
if(typeof this[newVal.openMenu] == 'boolean'){
|
||||
this.sideMenuOpen = true
|
||||
|
||||
this[newVal.openMenu] = true
|
||||
}
|
||||
}
|
||||
@ -354,23 +458,22 @@
|
||||
},
|
||||
beforeDestroy(){
|
||||
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
this.hashedPass = ''
|
||||
clearTimeout(this.autoLockTimeout)
|
||||
|
||||
// this.$io.emit('leave_room', this.rawTextId)
|
||||
|
||||
this.$bus.$off('new_file_upload')
|
||||
|
||||
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
if(this.editor){
|
||||
this.editor.destroy()
|
||||
}
|
||||
this.editor.destroy()
|
||||
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
setTimeout(()=>{
|
||||
this.forceShowLoading = false
|
||||
}, 500)
|
||||
|
||||
document.addEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
this.$nextTick(() => {
|
||||
@ -385,50 +488,19 @@
|
||||
this.editor = new Squire( this.$refs.squirebox, {blockTag: 'p' })
|
||||
this.setText(this.noteText)
|
||||
|
||||
this.lastNoteHash = this.hashString(this.getText())
|
||||
// console.log('hash on load', this.lastNoteHash)
|
||||
|
||||
//focus on open, not on mobile, it causes the keyboard to pop up, thats annoying
|
||||
//focus on open, not on mobile, thats annoying
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
this.editor.focus()
|
||||
this.editor.moveCursorToEnd()
|
||||
// this.editor.focus()
|
||||
|
||||
if(this.noteTitle.length == 0){
|
||||
this.$refs.titleTextarea.focus()
|
||||
} else {
|
||||
this.editor.focus()
|
||||
this.editor.moveCursorToEnd()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//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 => {
|
||||
|
||||
@ -805,23 +877,20 @@
|
||||
loadNote(noteId){
|
||||
|
||||
//Generate a random loading message
|
||||
let mod = ['Gently','Calmly','Lovingly','Quickly','','','','','','','','','','','','','']
|
||||
let doing = ['Loading','Loading','Getting','Fetching','Grabbing','Sequencing','Organizing','Untangling','Processing','Refining','Extracting','Fusing','Pruning','Expanding','Enlarging','Transfiguring','Quantizing','Ingratiating','Lumping']
|
||||
let thing = ['Note','Note','Note','Note','Data','Text','Document','Algorithm','Buffer','Client','Download','File','Frame','Graphics','Hardware','HTML','Interface','Logic','Mainframe','Memory','Media','Nodes','Network','Chaos']
|
||||
|
||||
let p1 = mod[Math.floor(Math.random() * mod.length)]
|
||||
let p2 = doing[Math.floor(Math.random() * doing.length)]
|
||||
let p3 = thing[Math.floor(Math.random() * thing.length)]
|
||||
this.loadingMessage = `${p1} ${p2} ${p3}`
|
||||
let p1 = doing[Math.floor(Math.random() * doing.length)]
|
||||
let p2 = thing[Math.floor(Math.random() * thing.length)]
|
||||
this.loadingMessage = p1 + ' ' + p2
|
||||
|
||||
//Component is activated with NoteId in place, lookup text with associated ID
|
||||
if(this.$store.getters.getLoggedIn){
|
||||
axios.post('/api/note/get', { 'noteId': this.noteid })
|
||||
axios.post('/api/note/get', { 'noteId': this.noteid, 'password':this.hashedPass })
|
||||
.then(response => {
|
||||
|
||||
//Block notes you don't have access to from opening
|
||||
if(response.data === false){
|
||||
this.$bus.$emit('notification', 'Error opening Note')
|
||||
this.$bus.$emit('notification', 'Invalid Note')
|
||||
this.close(true)
|
||||
return
|
||||
}
|
||||
@ -830,6 +899,7 @@
|
||||
this.currentNoteId = this.noteid
|
||||
this.rawTextId = response.data.rawTextId
|
||||
this.shareUsername = response.data.shareUsername
|
||||
this.passwordHint = response.data.password_hint
|
||||
|
||||
this.created = response.data.created
|
||||
this.updated = response.data.updated
|
||||
@ -841,6 +911,7 @@
|
||||
this.noteText = response.data.text
|
||||
this.diffNoteText = response.data.text
|
||||
|
||||
this.lastNoteHash = this.hashString(response.data.text)
|
||||
//Set up note colors
|
||||
if(response.data.color){
|
||||
this.styleObject = JSON.parse(response.data.color)
|
||||
@ -854,12 +925,29 @@
|
||||
|
||||
this.loading = false
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.isDecrypted = response.data.decrypted
|
||||
this.isEncrypted = response.data.encrypted == 1
|
||||
this.decryptAttempts = response.data.decrypt_attempts_count
|
||||
this.lockedOut = response.data.lockedOut
|
||||
|
||||
|
||||
//If password is required, display a prompt and focus on it
|
||||
if(this.password.length == 0 && this.isEncrypted && !this.isDecrypted){
|
||||
this.$nextTick(() => {
|
||||
if(this.$refs.decryptNotePrompt){
|
||||
// this.editor.moveCursorToEnd()
|
||||
this.$refs.decryptNotePrompt.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
//Adjust note title size after load
|
||||
this.titleResize()
|
||||
|
||||
this.setupWebSockets()
|
||||
this.initSquire()
|
||||
this.startAutolockTimer()
|
||||
})
|
||||
|
||||
})
|
||||
@ -1035,19 +1123,19 @@
|
||||
},
|
||||
onKeyup(){
|
||||
|
||||
this.statusText = 'Modded'
|
||||
this.statusText = 'Save'
|
||||
|
||||
// this.diffText()
|
||||
this.diffText()
|
||||
|
||||
//Each note, save after 15 seconds, focus lost or 30 characters typed.
|
||||
//Each note, save after 5 seconds, focus lost or 30 characters typed.
|
||||
clearTimeout(this.editDebounce)
|
||||
this.editDebounce = setTimeout(() => {
|
||||
this.save()
|
||||
}, 15 * 1000)
|
||||
}, 5000)
|
||||
|
||||
//Save after 50 keystrokes
|
||||
//Save after 30 keystrokes
|
||||
this.keyPressesCounter = (this.keyPressesCounter + 1)
|
||||
if(this.keyPressesCounter > 50){
|
||||
if(this.keyPressesCounter > 30){
|
||||
this.keyPressesCounter = 0
|
||||
this.save()
|
||||
}
|
||||
@ -1059,16 +1147,23 @@
|
||||
|
||||
// return resolve(true)
|
||||
|
||||
|
||||
//Encrypted notes that are not decrypted should not be saved
|
||||
if(this.isEncrypted && !this.isDecrypted){
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
//Don't save note if its hash doesn't change
|
||||
const currentNoteText = this.getText()
|
||||
const currentHash = this.hashString( currentNoteText )
|
||||
if( this.lastNoteHash == currentHash){
|
||||
this.statusText = 'Saved.'
|
||||
if( this.lastNoteHash == this.hashString( currentNoteText )){
|
||||
this.statusText = 'Saved'
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
//If user accidentally clears note, it won't delete it
|
||||
if(currentNoteText == ''){
|
||||
this.statusText = 'Empty'
|
||||
console.log('Prevented from saving empty note.')
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
@ -1079,20 +1174,19 @@
|
||||
'color': JSON.stringify(this.styleObject), //Save little json color object
|
||||
'pinned': this.pinned,
|
||||
'archived': this.archived,
|
||||
'hash': currentHash,
|
||||
'password': this.hashedPass,
|
||||
'hint': this.passwordHint,
|
||||
}
|
||||
|
||||
// console.log('Save Hash', currentHash)
|
||||
|
||||
this.statusText = 'Saving'
|
||||
axios.post('/api/note/update', postData).then( response => {
|
||||
this.statusText = 'Saved.'
|
||||
this.statusText = 'Saved'
|
||||
this.updated = Math.round((+new Date)/1000)
|
||||
this.modified = true
|
||||
|
||||
//Update last saved note hash
|
||||
// this.lastNoteHash = this.hashString( currentNoteText )
|
||||
this.lastNoteHash = currentHash
|
||||
this.lastNoteHash = this.hashString( currentNoteText )
|
||||
this.startAutolockTimer()
|
||||
return resolve(true)
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Save Note') })
|
||||
@ -1100,9 +1194,7 @@
|
||||
},
|
||||
checkForUpdatedNote(){
|
||||
|
||||
//Ignore visibility changes, handle this with socket IO
|
||||
//Just keep it always up to date if user is on note
|
||||
return
|
||||
// return
|
||||
|
||||
//If user leaves page then returns to page, reload the first batch
|
||||
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
|
||||
@ -1114,10 +1206,6 @@
|
||||
updated: this.updated
|
||||
}
|
||||
|
||||
console.log('Focus regained with note open.')
|
||||
console.log('Attempting to fix diff text. fix this. Search spleen')
|
||||
return
|
||||
|
||||
axios.post('/api/note/difftext', postData)
|
||||
.then( ({data}) => {
|
||||
|
||||
@ -1136,15 +1224,18 @@
|
||||
//Track visibility state
|
||||
this.lastVisibilityState = document.visibilityState
|
||||
},
|
||||
hashString(inText){
|
||||
hashString(text){
|
||||
|
||||
let text = this.noteTitle + inText
|
||||
text = this.noteTitle + text
|
||||
|
||||
let hash = 0;
|
||||
var hash = 0;
|
||||
if (text == null || text.length == 0) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
//Simplified for speed
|
||||
// return text.length
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
let char = text.charCodeAt(i);
|
||||
hash = ((hash<<5)-hash)+char;
|
||||
@ -1164,11 +1255,6 @@
|
||||
|
||||
this.save().then( result => {
|
||||
|
||||
//If note was modified, trigger reindex on close
|
||||
if(this.modified){
|
||||
axios.post('/api/note/reindex')
|
||||
}
|
||||
|
||||
this.sizeDown = true
|
||||
//This timeout allows animation to play before closing
|
||||
setTimeout(() => {
|
||||
@ -1181,11 +1267,6 @@
|
||||
},
|
||||
setupWebSockets(){
|
||||
|
||||
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
|
||||
// console.log('Current hash', this.lastNoteHash)
|
||||
// console.log('Incoming Hash', hash)
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
//Tell server to push this note into a room
|
||||
@ -1200,6 +1281,62 @@
|
||||
this.patchText(incomingDiffData)
|
||||
})
|
||||
},
|
||||
decryptNote(){
|
||||
|
||||
const hashed = crypto.createHash('sha256').update(this.password).digest().toString('base64')
|
||||
//Remove plaintext password
|
||||
this.hashedPass = hashed
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
|
||||
this.loadNote()
|
||||
},
|
||||
lockNote(){
|
||||
this.save().then(results => {
|
||||
this.isDecrypted = false
|
||||
this.password = ''
|
||||
this.hashedPass = ''
|
||||
this.passwordprotect = false
|
||||
this.setText('')
|
||||
})
|
||||
},
|
||||
enableEncryption(){
|
||||
|
||||
if(this.noteText == ''){
|
||||
this.noteText = 'Text Typed here is encrypted.'
|
||||
}
|
||||
|
||||
const hashed = crypto.createHash('sha256').update(this.password).digest().toString('base64')
|
||||
//Remove plaintext password
|
||||
this.hashedPass = hashed
|
||||
|
||||
this.lastNoteHash = 0
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
this.passwordprotect = false
|
||||
|
||||
this.save()
|
||||
.then(results => {
|
||||
this.$bus.$emit('notification', 'Password Protection Enabled')
|
||||
this.loadNote()
|
||||
})
|
||||
},
|
||||
disableEncryption(){
|
||||
|
||||
this.lastNoteHash = 0
|
||||
this.isEncrypted = false
|
||||
this.password = ''
|
||||
this.passwordConfirm = ''
|
||||
this.hashedPass = ''
|
||||
this.passwordprotect = false
|
||||
|
||||
//Reload Note
|
||||
this.save()
|
||||
.then(results => {
|
||||
this.loadNote()
|
||||
this.$bus.$emit('notification', 'Password Protection Removed')
|
||||
})
|
||||
},
|
||||
titleResize(){
|
||||
//Resize the title field
|
||||
let element = this.$refs.titleTextarea
|
||||
@ -1208,6 +1345,15 @@
|
||||
element.style.height = 'auto';
|
||||
element.style.height = (element.scrollHeight + padding) +'px';
|
||||
},
|
||||
startAutolockTimer(){
|
||||
//Start autolock timer on encrypted notes that are encrypted and in a decrypted state
|
||||
if(this.isEncrypted && this.isDecrypted){
|
||||
clearTimeout(this.autoLockTimeout)
|
||||
this.autoLockTimeout = setTimeout(() => {
|
||||
this.lockNote()
|
||||
}, (60 * 1000 * 20) ) //Autolock after 20 min
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1247,7 +1393,6 @@
|
||||
background-color: var(--background_color);
|
||||
border: 1px solid var(--menu-accent);;
|
||||
margin: 45px 0 45px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1289,10 +1434,6 @@
|
||||
.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);
|
||||
@ -1348,22 +1489,10 @@
|
||||
.loading-note {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
/*background: var(--background_color);*/
|
||||
/*opacity: 0.;*/
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.loading-text {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 200px;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* One note open, in the middle of the screen */
|
||||
.master-note-edit.position-0 {
|
||||
left: 50%;
|
||||
@ -1373,10 +1502,6 @@
|
||||
left: 15%;
|
||||
right: 15%;
|
||||
}
|
||||
.side-menu-open {
|
||||
left: calc(50% + 10px) !important;
|
||||
right: calc(0% + 10px) !important;
|
||||
}
|
||||
@media only screen and (max-width: 740px) {
|
||||
.input-container-wrapper {
|
||||
left: 0;
|
||||
@ -1493,24 +1618,6 @@
|
||||
right: 150%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fade out transition animation */
|
||||
.fade-enter {
|
||||
/*opacity: 0;*/
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
/*transition: opacity 0.7s;*/
|
||||
}
|
||||
|
||||
.fade-leave {
|
||||
/* opacity: 0; */
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.7s;
|
||||
opacity: 0;
|
||||
}
|
||||
/* animations END */
|
||||
|
||||
</style>
|
@ -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, 'title-view':titleView }"
|
||||
:class="{'currently-open':currentlyOpen, 'bgboy':triggerClosedAnimation}"
|
||||
>
|
||||
|
||||
|
||||
<!-- Show title and snippet below it -->
|
||||
<div class="overflow-hidden note-card-text" @click="cardClicked" v-if="!titleView">
|
||||
<div class="overflow-hidden note-card-text" @click="cardClicked">
|
||||
|
||||
<span class="subtext" v-if="note.shareUsername">
|
||||
Shared by {{ note.shareUsername }}
|
||||
@ -29,10 +29,12 @@
|
||||
|
||||
<!-- Title display -->
|
||||
<span v-if="note.title.length > 0"
|
||||
data-test-id="title"
|
||||
class="big-text"><p>{{ note.title }}</p></span>
|
||||
|
||||
<!-- Sub text display -->
|
||||
<span v-if="note.subtext.length > 0 && !isShowingSearchResults()"
|
||||
data-test-id="subtext"
|
||||
class="small-text"
|
||||
v-html="note.subtext"></span>
|
||||
|
||||
@ -60,64 +62,48 @@
|
||||
|
||||
</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" v-if="!titleView">
|
||||
<div class="tool-bar" @click.self="cardClicked">
|
||||
<div class="icon-bar">
|
||||
|
||||
<!-- <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>
|
||||
<i class="green archive icon"></i>
|
||||
</span> -->
|
||||
|
||||
<span class="tags" v-if="note.tags">
|
||||
<span v-for="tag in (note.tags.split(','))" class="little-tag">{{ tag }}</span>
|
||||
<br>
|
||||
</span>
|
||||
|
||||
<span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
|
||||
<span data-tooltip="Edited" class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
|
||||
{{$helpers.timeAgo(note.updated)}}
|
||||
</span>
|
||||
|
||||
<span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
|
||||
|
||||
<span v-if="!note.trashed">
|
||||
<i class="teeny-button" data-tooltip="Tags" data-inverted v-on:click="toggleTags(true)">
|
||||
<i class="tags icon"></i>
|
||||
</i>
|
||||
|
||||
<i class="teeny-button" data-tooltip="Tags" data-inverted v-on:click="toggleTags(true)">
|
||||
<i class="tags icon"></i>
|
||||
</i>
|
||||
|
||||
<i class="teeny-button"
|
||||
data-tooltip="Archive"
|
||||
:data-tooltip="note.archived ? 'Un-Archive':'Archive' "
|
||||
data-inverted v-on:click="archiveNote">
|
||||
<i class="archive icon" :class="{'green':note.archived}"></i>
|
||||
</i>
|
||||
|
||||
<i class="teeny-button"
|
||||
:data-tooltip="note.pinned ? 'Un-Pin':'Pin' "
|
||||
data-inverted v-on:click="pinNote">
|
||||
<i class="pin icon" :class="{'green':note.pinned}"></i>
|
||||
</i>
|
||||
|
||||
<i class="teeny-button"
|
||||
data-tooltip="Move to Trash"
|
||||
data-inverted v-on:click="trashNote()">
|
||||
<i class="trash icon"></i>
|
||||
</i>
|
||||
</span>
|
||||
|
||||
<!-- Trash note options -->
|
||||
<span v-if="note.trashed">
|
||||
<i class="teeny-button"
|
||||
data-tooltip="Un-Trash"
|
||||
data-inverted v-on:click="trashNote()">
|
||||
<i class="reply icon"></i>
|
||||
</i>
|
||||
<delete-button class="teeny-button" :note-id="note.id" />
|
||||
</span>
|
||||
<i class="teeny-button"
|
||||
data-tooltip="Archive"
|
||||
:data-tooltip="note.archived ? 'Un-Archive':'Archive' "
|
||||
data-inverted v-on:click="archiveNote">
|
||||
<i class="archive icon" :class="{'green':note.archived}"></i>
|
||||
</i>
|
||||
|
||||
<i class="teeny-button"
|
||||
:data-tooltip="note.pinned ? 'Un-Pin':'Pin' "
|
||||
data-inverted v-on:click="pinNote">
|
||||
<i class="pin icon" :class="{'green':note.pinned}"></i>
|
||||
</i>
|
||||
|
||||
<delete-button class="teeny-button" :note-id="note.id" />
|
||||
|
||||
</span>
|
||||
</div>
|
||||
@ -127,8 +113,9 @@
|
||||
<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
|
||||
</div>
|
||||
</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"/>
|
||||
@ -149,7 +136,7 @@
|
||||
|
||||
export default {
|
||||
name: 'NoteTitleDisplayCard',
|
||||
props: [ 'onClick', 'data', 'currentlyOpen', 'textResults', 'attachmentResults', 'tagResults', 'titleView' ],
|
||||
props: [ 'onClick', 'data', 'currentlyOpen', 'textResults', 'attachmentResults', 'tagResults' ],
|
||||
components: {
|
||||
'delete-button': require('@/components/NoteDeleteButtonComponent.vue').default,
|
||||
'note-tag-edit': require('@/components/NoteTagEdit.vue').default,
|
||||
@ -161,17 +148,6 @@
|
||||
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,'')
|
||||
@ -195,7 +171,7 @@
|
||||
let postData = {'pinned': !this.note.pinned, 'noteId':this.note.id}
|
||||
axios.post('/api/note/setpinned', postData)
|
||||
.then(data => {
|
||||
// this.$bus.$emit('update_single_note', this.note.id)
|
||||
this.$bus.$emit('update_single_note', this.note.id)
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Pin Note') })
|
||||
},
|
||||
@ -207,35 +183,20 @@
|
||||
//Show message so no one worries where note went
|
||||
let message = 'Moved to Archive'
|
||||
if(postData.archived != 1){
|
||||
message = 'Moved to main list'
|
||||
message = 'Move to main list'
|
||||
}
|
||||
this.$bus.$emit('notification', message)
|
||||
|
||||
// this.$bus.$emit('update_single_note', this.note.id)
|
||||
this.$bus.$emit('update_single_note', this.note.id)
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Archive Note') })
|
||||
},
|
||||
trashNote(){ //toggleArchived() <- old name
|
||||
let postData = {'trashed': !this.note.trashed, 'noteId':this.note.id}
|
||||
axios.post('/api/note/settrashed', postData)
|
||||
.then(data => {
|
||||
|
||||
//Show message so no one worries where note went
|
||||
let message = 'Moved to Trash'
|
||||
if(postData.trashed == 0){
|
||||
message = 'Moved to main list'
|
||||
}
|
||||
this.$bus.$emit('notification', message)
|
||||
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Trash Note') })
|
||||
},
|
||||
toggleTags(state){
|
||||
|
||||
this.showTagSlideMenu = state
|
||||
|
||||
if(state == false){
|
||||
// this.$bus.$emit('update_single_note', this.note.id)
|
||||
this.$bus.$emit('update_single_note', this.note.id)
|
||||
}
|
||||
|
||||
},
|
||||
@ -355,10 +316,6 @@
|
||||
color: var(--text_color);
|
||||
background-color: var(--background_color);
|
||||
}
|
||||
.subtext {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*Strict font sizes for card display*/
|
||||
.small-text {
|
||||
@ -438,27 +395,6 @@
|
||||
.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;
|
||||
|
@ -6,71 +6,35 @@
|
||||
right: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
.floating-button {
|
||||
position: absolute;
|
||||
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 v-on:mouseenter="extraMenuHover = true" v-on:mouseleave="extraMenuHover = false">
|
||||
<span>
|
||||
|
||||
<div class="ui form">
|
||||
<div class="ui form" v-if="!$store.getters.getIsUserOnMobile">
|
||||
<!-- normal search menu -->
|
||||
<div class="ui left icon fluid input">
|
||||
<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" />
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @keyup.enter="search" placeholder="Search Notes and Files" ref="searchInput"/>
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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="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 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>
|
||||
</div>
|
||||
</div>
|
||||
@ -81,19 +45,14 @@
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
|
||||
data: function(){
|
||||
return {
|
||||
searchTerm:'',
|
||||
searched: false,
|
||||
|
||||
tagSuggestions: [],
|
||||
tagSearchDebounce: null,
|
||||
|
||||
extraMenuHover: false,
|
||||
searchTerm: '',
|
||||
searchTimeout: null,
|
||||
searchDebounceDuration: 300,
|
||||
showFixedSearch: false,
|
||||
}
|
||||
},
|
||||
beforeCreate: function(){
|
||||
@ -103,105 +62,31 @@
|
||||
//search clear
|
||||
this.$bus.$on('reset_fast_filters', () => {
|
||||
this.searchTerm = ''
|
||||
this.tagSuggestions = []
|
||||
})
|
||||
|
||||
},
|
||||
methods: {
|
||||
tagClick(tagId){
|
||||
openFloatingSearch(){
|
||||
this.showFixedSearch = !this.showFixedSearch
|
||||
|
||||
this.$emit('tagClick', tagId)
|
||||
this.tagSuggestions = []
|
||||
this.searchTerm = ''
|
||||
|
||||
},
|
||||
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
|
||||
if(this.showFixedSearch){
|
||||
this.$nextTick( () => {
|
||||
this.searchTerm = ''
|
||||
this.$refs.fixedSearch.focus()
|
||||
})
|
||||
.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){
|
||||
searchKeyUp(){
|
||||
//This event is not triggered on mobile
|
||||
clearTimeout(this.searchTimeout)
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.search()
|
||||
return
|
||||
}
|
||||
}, this.searchDebounceDuration)
|
||||
},
|
||||
search(){
|
||||
this.searched = true
|
||||
|
||||
this.$refs.desktopSearch.focus()
|
||||
//Blur after search on mobile
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$refs.desktopSearch.blur()
|
||||
this.$refs.fixedSearch.blur()
|
||||
}
|
||||
|
||||
this.$bus.$emit('update_search_term', this.searchTerm)
|
||||
},
|
||||
}
|
||||
|
@ -80,14 +80,8 @@
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Load Shared') })
|
||||
},
|
||||
onRevokeAccess(sharedNoteId){
|
||||
|
||||
const postData = {
|
||||
'noteId': this.noteId,
|
||||
'shareUserNoteId': sharedNoteId
|
||||
}
|
||||
|
||||
axios.post('/api/note/shareremoveuser', postData)
|
||||
onRevokeAccess(noteId){
|
||||
axios.post('/api/note/shareremoveuser', {'noteId':noteId})
|
||||
.then( ({data}) => {
|
||||
console.log(data)
|
||||
if(data == true){
|
||||
|
@ -3,7 +3,7 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 50%;
|
||||
right: 55%;
|
||||
bottom: 0;
|
||||
z-index: 1020;
|
||||
overflow: hidden;
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="button-fix">
|
||||
<div class="ui basic button shrinking">
|
||||
|
||||
|
||||
<!-- Dropdown Button -->
|
||||
<span v-if="activeTags.length == 0" v-on:click="openMenu()" class="ui basic button shrinking">
|
||||
<span v-if="activeTags.length == 0" v-on:click="menuOpen = true">
|
||||
<i class="green tags icon"></i>
|
||||
Tags
|
||||
<i class="caret down icon"></i>
|
||||
</span>
|
||||
<!-- Remove Tag button -->
|
||||
<span v-if="activeTags.length > 0" v-on:click="openMenu()" class="ui basic button shrinking">
|
||||
<span v-if="activeTags.length > 0" v-on:click="toggleActive()">
|
||||
<i class="green tag icon"></i>
|
||||
{{ getActiveTag() }}
|
||||
<i class="caret right icon"></i>
|
||||
@ -18,29 +18,13 @@
|
||||
<!-- hidden dropdown menu -->
|
||||
<div class="dropdown-menu" v-if="menuOpen">
|
||||
<div class="ui raised segment">
|
||||
<div class="ui very tight grid">
|
||||
<div class="fourteen wide column">
|
||||
<h2 class="ui header"><i class="small green tags icon"></i>Tags</h2>
|
||||
</div>
|
||||
<div class="two wide middle aligned center aligned column" v-on:click="menuOpen = false">
|
||||
<i class="grey close icon"></i>
|
||||
</div>
|
||||
<div class="sixteen wide middle aligned column" v-if="loadedTags.length == 0">
|
||||
Tags added to Notes will appear here.
|
||||
</div>
|
||||
<div class="row hover-row" v-for="tag in loadedTags" v-on:click="onClick(tag.id)" :class="{'green':(activeTags[0] == tag.id)}">
|
||||
<div class="two wide center aligned column">
|
||||
<i class="grey tag icon"></i>
|
||||
</div>
|
||||
<div class="twelve wide column">
|
||||
{{ ucWords(tag.text) }}
|
||||
</div>
|
||||
<div class="two wide center aligned column">
|
||||
{{tag.usages}}
|
||||
</div>
|
||||
<div class="ui clickable basic label" v-for="tag in tags">
|
||||
<span v-on:click="onClick(tag.id)">
|
||||
{{ ucWords(tag.text) }}
|
||||
<span class="detail">{{tag.usages}}</span>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -50,27 +34,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'TagDisplay',
|
||||
props: [ 'activeTags' ],
|
||||
data () {
|
||||
return {
|
||||
loadedTags: [],
|
||||
menuOpen: false,
|
||||
}
|
||||
},
|
||||
props: [ 'tags', 'activeTags' ],
|
||||
components: {
|
||||
},
|
||||
methods:{
|
||||
openMenu(){
|
||||
this.menuOpen = true
|
||||
axios.post('/api/tag/usertags')
|
||||
.then( ({data}) => {
|
||||
this.loadedTags = data
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Fetch Tags') })
|
||||
},
|
||||
toggleActive(){
|
||||
this.menuOpen = false
|
||||
const current = this.activeTags[0]
|
||||
@ -93,7 +63,7 @@
|
||||
return text
|
||||
}
|
||||
|
||||
this.loadedTags.forEach(tag => {
|
||||
this.tags.forEach(tag => {
|
||||
if( this.activeTags.includes(tag.id) ){
|
||||
text = this.ucWords(tag.text)
|
||||
}
|
||||
@ -102,32 +72,27 @@
|
||||
return text
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
menuOpen: false,
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css">
|
||||
.button-fix {
|
||||
display: inline-block;
|
||||
}
|
||||
.hover-row:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--menu-accent);
|
||||
}
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
/*width: 70vw;*/
|
||||
top: 50px;
|
||||
z-index: 1005;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
/*min-width: 200px;*/
|
||||
/*max-width: 100%;*/
|
||||
width: 340px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: 600px;
|
||||
text-align: left;
|
||||
}
|
||||
.dropdown-menu .button {
|
||||
.dropdown-menu .label {
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
||||
.shade {
|
||||
@ -137,7 +102,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1004;
|
||||
background-color: #0000008a;
|
||||
background-color: transparent;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<!-- Content copied from note -->
|
||||
<!-- https://www.solidscribe.com/#/notes/open/552 -->
|
||||
|
||||
<p><b>Every Note is Encrypted</b><br></p><p>Only you can read your notes. Even if every note in the database was leaked, nothing would be readable. If the government asked for your notes, it would all be gibberish. <br></p><p><br></p><p><b>Some Data is not encrypted</b><br></p><p>Everything isn't encrypted, to keep up ease of use. Files, Tags and Attachments are not encrypted.<br></p><p><br></p><p><b>Searching is somewhat limited</b><br></p><p>Since every note is encrypted, searching is limited. To maintain security, only single words can be searched. Your search index is private and Encrypted.<br></p><p><br></p><p><b>Quick Note</b><br></p><p>The Quick note feature was designed to allow rapid input to a single note. Rather than junking up all your notes with random links, numbers or haikus, you can put them all in one place. <br></p><p>All data pushed to the quick note can still be edited like a normal note.<br></p><p><br></p><p><b>Dark Theme</b><br></p><p>Dark theme was designed to minimize the amount of blue. Less blue entering your eyes is supposed to help you fall asleep.<br></p><p>Most things turn sepia and a filter is applied to images to make them more sepia.<br></p><p>Here is some good research on the topic: <a href="https://justgetflux.com/research.html">https://justgetflux.com/research.html</a><br></p><p><br></p><p><b>Password Protected Notes</b><br></p><p>Note protected with a password are encrypted. This means the data is scrambled and unreadable unless the correct password is used to decrypt them.<br></p><p>If a password is forgotten, it can never be recovered. Passwords are not saved for encrypted notes. If you lose the password to a protected note, that note text is lost. <br></p><p>Only the text of the note is protected. Tags, Files attached to the note, and the title of the note are still visible without a password. You can not search text in a password protected note. But you can search by the title.<br></p><p><br></p><p><b>Links in notes</b><br></p><p>Links put into notes are automatically scraped. This means the data from the link will be scanned to get an image and some text from the website to help make that link more accessible in the future. <br></p><p><br></p><p><b>Files in notes</b><br></p><p>Files can be uploaded to notes. If its an image, the picture will be put into the note.<br></p><p>Images added to notes will have the text pulled out so it can be searched (This isn't super accurate so don't rely to heavily on it.) The text can be updated at any time.<br></p><p><br></p><p><b>Deleting notes</b><br></p><p>When<b> </b>notes are deleted, none of the files related to the note are deleted. <br></p><p><br></p><p><b>Daily Backups</b><br></p><p>All notes are backed up, every night, at midnight. If there is data loss, it can be restored from a backup. If you experience some sort of cataclysmic data loss please contact the system administrator for a copy of your data or a restoration procedure. <br></p>
|
||||
<p><b>Quick Note</b><br></p><p>The Quick note feature was designed to allow rapid input to a single note. Rather than junking up all your notes with random links, numbers or haikus, you can put them all in one place. <br></p><p>All data pushed to the quick note can still be edited like a normal note.<br></p><p><br></p><p><b>Dark Theme</b><br></p><p>Dark theme was designed to minimize the amount of blue. Less blue entering your eyes is supposed to help you fall asleep.<br></p><p>Most things turn sepia and a filter is applied to images to make them more sepia.<br></p><p>Here is some good research on the topic: <a href="https://justgetflux.com/research.html">https://justgetflux.com/research.html</a><br></p><p><br></p><p><b>Password Protected Notes</b><br></p><p>Note protected with a password are encrypted. This means the data is scrambled and unreadable unless the correct password is used to decrypt them.<br></p><p>If a password is forgotten, it can never be recovered. Passwords are not saved for encrypted notes. If you lose the password to a protected note, that note text is lost. <br></p><p>Only the text of the note is protected. Tags, Files attached to the note, and the title of the note are still visible without a password. You can not search text in a password protected note. But you can search by the title.<br></p><p><br></p><p><b>Links in notes</b><br></p><p>Links put into notes are automatically scraped. This means the data from the link will be scanned to get an image and some text from the website to help make that link more accessible in the future. <br></p><p><br></p><p><b>Files in notes</b><br></p><p>Files can be uploaded to notes. If its an image, the picture will be put into the note.<br></p><p>Images added to notes will have the text pulled out so it can be searched (This isn't super accurate so don't rely to heavily on it.) The text can be updated at any time.<br></p><p><br></p><p><b>Deleting notes</b><br></p><p>When<b> </b>notes are deleted, none of the files related to the note are deleted. <br></p><p><br></p><p><b>Daily Backups</b><br></p><p>All notes are backed up, every night, at midnight. If there is data loss, it can be restored from a backup. If you experience some sort of cataclysmic data loss please contact the system administrator for a copy of your data or a restoration procedure. <br></p>
|
||||
|
||||
<!-- content copied from note -->
|
||||
</div>
|
||||
@ -22,14 +22,6 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelpPage',
|
||||
props:[ 'message' ],
|
||||
data () {
|
||||
return {
|
||||
items: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
name: 'HelpPage'
|
||||
}
|
||||
</script>
|
@ -32,7 +32,8 @@
|
||||
100%{ opacity: 0.9; }
|
||||
}
|
||||
.subtext {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
padding: 0 0 0 10px;
|
||||
@ -120,8 +121,9 @@
|
||||
</h2>
|
||||
|
||||
<h3 class="subtext">
|
||||
An easy, encrypted Note App<i class="i cursor icon blinking"></i>
|
||||
Take Notes Like Never Before<i class="i cursor icon blinking"></i>
|
||||
</h3>
|
||||
<p class="green-text">Assuming you have never used a note application previously in your life.</p>
|
||||
|
||||
</div>
|
||||
|
||||
@ -132,21 +134,22 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="eight wide middle aligned column">
|
||||
<h2>Get Started. Only a username is required.</h2>
|
||||
</div>
|
||||
<div class="four wide center aligned column">
|
||||
<router-link class="ui huge green labeled icon button" to="/login">
|
||||
<i class="plug icon"></i>Sign Up
|
||||
<i class="plug icon"></i>Register
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="eight wide middle aligned column">
|
||||
<h2>Only a Username and Password are required.</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide right aligned column">
|
||||
<h2>Solid Scribe is an online note application that focuses on ease of use and security</h2>
|
||||
<h3>Tools to organize and collaborate on notes while maintaining security and respecting your privacy.</h3>
|
||||
<h2>Everyone has knowledge that need to be expressed</h2>
|
||||
<h3>Utilize action potential to create notes by encoding raw brainwaves converted to written language</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
|
||||
@ -158,29 +161,29 @@
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/gardening.svg" alt="Pruning the mind garden">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Tools to organize thousands of notes</h2>
|
||||
<h3>Tag, Pin, Color, Archive, Attach Images and Search notes or links in notes</h3>
|
||||
<h2>Dream it, then do it</h2>
|
||||
<h3>Easily record your unlimited imagination. Ideas, stories, notes, plays, poems anything, that can reasonably be put into text</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered green row">
|
||||
<div class="six wide column">
|
||||
<h2>Privacy through Encryption</h2>
|
||||
<h3>All notes are encrypted. No one can read your notes, even if they steal the data from the database.</h3>
|
||||
<h2>Unbridled Input</h2>
|
||||
<h3>Revolutionary technology allows the use of any keyboard with up to 395 keys</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/secure.svg" alt="marketing mumbo jumbo">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/add.svg" alt="A shpere of newness">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide right aligned column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/cloud.svg" alt="Girl falling into the spiral of digital chaos">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/solution.svg" alt="Hypercube of Solutions">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Extremely accessible</h2>
|
||||
<h3>Works on mobile or desktop browsers. <br>Behaves like an installed app on mobile phones.</h3>
|
||||
<h2>Solutions with the Internet</h2>
|
||||
<h3>With the power to save any combination of letters, you can easily inscribe thoughts</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -191,7 +194,7 @@
|
||||
<h3>Type in a word and find that same word but somewhere else</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/solution.svg" alt="Hypercube of Solutions">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/cloud.svg" alt="Girl falling into the spiral of digital chaos">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -239,7 +242,7 @@
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide right aligned column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/add.svg" alt="A shpere of newness">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/secure.svg" alt="marketing mumbo jumbo">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Data Backups</h2>
|
||||
|
@ -80,17 +80,12 @@
|
||||
|
||||
const token = response.data.token
|
||||
const username = response.data.username
|
||||
const masterKey = response.data.masterKey
|
||||
|
||||
this.$store.commit('setLoginToken', {token, username, masterKey})
|
||||
|
||||
//Setup socket io after user logs in
|
||||
this.$io.emit('user_connect', token)
|
||||
vm.$store.commit('setLoginToken', {token, username})
|
||||
|
||||
//Redirect user to notes section after login
|
||||
this.$router.push('/notes')
|
||||
vm.$router.push('/notes')
|
||||
} else {
|
||||
// this.password = ''
|
||||
this.$bus.$emit('notification', 'Incorrect Username or Password')
|
||||
vm.$store.commit('destroyLoginToken')
|
||||
}
|
||||
|
@ -26,32 +26,31 @@
|
||||
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
|
||||
</div>
|
||||
|
||||
<div class="ui basic icon button shrinking" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0">
|
||||
<i class="trash alternate outline icon"></i>
|
||||
<div class="ui basic button shrinking" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['encryptedNotes'] > 0">
|
||||
<i class="green lock alternate icon"></i>Locked
|
||||
<!-- <span>{{ $store.getters.totals['encryptedNotes'] }}</span> -->
|
||||
</div>
|
||||
|
||||
<tag-display
|
||||
v-if="commonTags.length > 0"
|
||||
:tags="commonTags"
|
||||
:active-tags="searchTags"
|
||||
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">
|
||||
<div class="six wide column" v-if="!$store.getters.getIsUserOnMobile">
|
||||
<search-input
|
||||
v-on:tagClick="tagId => toggleTagFilter(tagId)"
|
||||
v-if="$store.getters.totals && $store.getters.totals['totalNotes']" />
|
||||
</div>
|
||||
|
||||
<div class="eight wide column" v-if="showClear">
|
||||
<!-- <fast-filters /> -->
|
||||
<span class="ui fluid green button" @click="reset">
|
||||
<i class="arrow circle left icon"></i>Show All Notes
|
||||
<span class="ui fluid green button"
|
||||
|
||||
@click="reset">
|
||||
<i class="arrow circle left icon"></i>Back to All Notes
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -59,34 +58,13 @@
|
||||
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column">
|
||||
<h2>Archived Notes</h2>
|
||||
</div>
|
||||
|
||||
<div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1">
|
||||
<h2 >Trash
|
||||
<span>({{ $store.getters.totals['trashedNotes'] }})</span>
|
||||
<div class="ui right floated basic button" data-tooltip="This doesn't work yet">
|
||||
<i class="poo storm icon"></i>
|
||||
Empty Trash
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1">
|
||||
<h2>Shared Notes</h2>
|
||||
</div>
|
||||
<h2 v-if="fastFilters['withLinks'] == 1">Notes with Links</h2>
|
||||
<h2 v-if="fastFilters['withTags'] == 1">Notes with Tags</h2>
|
||||
<h2 v-if="fastFilters['onlyArchived'] == 1">Archived Notes</h2>
|
||||
<h2 v-if="fastFilters['onlyShowSharedNotes'] == 1">Shared Notes</h2>
|
||||
<h2 v-if="fastFilters['onlyShowEncrypted'] == 1">Password Protected Notes</h2>
|
||||
|
||||
<!-- Note title card display -->
|
||||
<div class="sixteen wide column">
|
||||
@ -110,16 +88,12 @@
|
||||
: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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<loading-icon v-if="loadingInProgress" message="Decrypting Notes" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -140,11 +114,15 @@
|
||||
|
||||
<input-notes
|
||||
v-if="activeNoteId1 != null"
|
||||
:key="'active_note_'+activeNoteId1"
|
||||
:noteid="activeNoteId1"
|
||||
:position="activeNote1Position"
|
||||
:url-data="$route.params"
|
||||
ref="note1" />
|
||||
<input-notes
|
||||
v-if="activeNoteId2 != null"
|
||||
:noteid="activeNoteId2"
|
||||
:position="activeNote2Position"
|
||||
ref="note2" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -160,33 +138,30 @@
|
||||
'input-notes': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
|
||||
|
||||
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
||||
// 'fast-filters': require('@/components/FastFilters.vue').default,
|
||||
'fast-filters': require('@/components/FastFilters.vue').default,
|
||||
'search-input': require('@/components/SearchInput.vue').default,
|
||||
'attachment-display': require('@/components/AttachmentDisplayCard').default,
|
||||
'counter':require('@/components/AnimatedCounterComponent.vue').default,
|
||||
'tag-display':require('@/components/TagDisplayComponent.vue').default,
|
||||
'loading-icon':require('@/components/LoadingIconComponent.vue').default,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
initComponent: true,
|
||||
commonTags: [],
|
||||
searchTerm: '',
|
||||
searchResultsCount: 0,
|
||||
searchTags: [],
|
||||
notes: [],
|
||||
highlights: [],
|
||||
searchDebounce: null,
|
||||
fastFilters: {},
|
||||
titleView: false,
|
||||
|
||||
working: false,
|
||||
//Load up notes in batches
|
||||
firstLoadBatchSize: 10, //First set of rapidly loaded notes
|
||||
batchSize: 25, //Size of batch loaded when user scrolls through current batch
|
||||
firstLoadBatchSize: 30, //First set of rapidly loaded notes
|
||||
batchSize: 100, //Size of batch loaded when user scrolls through current batch
|
||||
batchOffset: 0, //Tracks the current batch that has been loaded
|
||||
loadingBatchTimeout: null, //Limit how quickly batches can be loaded
|
||||
loadingInProgress: false,
|
||||
scrollLoadEnabled: true,
|
||||
fetchTags: false,
|
||||
|
||||
//Clear button is not visible
|
||||
showClear: false,
|
||||
@ -218,7 +193,7 @@
|
||||
'sent': ['paper plane outline', 'Shared Notes'],
|
||||
'notes': ['file','Notes'],
|
||||
'highlights': ['paragraph', 'Found In Text'],
|
||||
'trashed': ['poop', 'Trashed Notes']
|
||||
'locked': ['lock', 'Password Protected']
|
||||
},
|
||||
noteSections: {
|
||||
pinned: [],
|
||||
@ -227,7 +202,7 @@
|
||||
sent:[],
|
||||
notes: [],
|
||||
highlights: [],
|
||||
trashed: []
|
||||
locked: []
|
||||
},
|
||||
|
||||
}
|
||||
@ -236,68 +211,40 @@
|
||||
|
||||
this.$parent.loginGateway()
|
||||
|
||||
this.$io.on('new_note_created', noteId => {
|
||||
|
||||
//Do not update note if its open
|
||||
if(this.activeNoteId1 != noteId){
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
this.updateSingleNote(noteId)
|
||||
}
|
||||
})
|
||||
|
||||
this.$io.on('note_attribute_modified', noteId => {
|
||||
//Do not update note if its open
|
||||
if(this.activeNoteId1 != noteId){
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
this.updateSingleNote(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){
|
||||
this.updateSingleNote(noteId)
|
||||
}
|
||||
})
|
||||
|
||||
//Update totals for app
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
|
||||
//Close note event
|
||||
this.$bus.$on('close_active_note', ({position, noteId, modified}) => {
|
||||
|
||||
this.closeNote()
|
||||
this.closeNote(position)
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
if(modified){
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
this.updateSingleNote(parseInt(noteId))
|
||||
}
|
||||
})
|
||||
|
||||
// this.$bus.$on('update_single_note', (noteId) => {
|
||||
// this.updateSingleNote(noteId)
|
||||
// })
|
||||
this.$bus.$on('update_single_note', (noteId) => {
|
||||
this.updateSingleNote(noteId)
|
||||
})
|
||||
|
||||
this.$bus.$on('note_deleted', (noteId) => {
|
||||
//Remove deleted note from set, its deleted
|
||||
|
||||
this.fetchUserTags()
|
||||
Object.keys(this.noteSections).forEach( key => {
|
||||
this.noteSections[key].forEach( (note, index) => {
|
||||
if(note.id == noteId){
|
||||
this.noteSections[key].splice(index,1)
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
this.$bus.$on('update_fast_filters', newFilter => {
|
||||
this.fastFilters = newFilter
|
||||
//Fast filters always return all the results and tags
|
||||
this.search(true, this.batchSize, false).then( () => {
|
||||
// return
|
||||
return this.fetchUserTags()
|
||||
})
|
||||
})
|
||||
|
||||
@ -307,10 +254,9 @@
|
||||
this.search(true, this.batchSize)
|
||||
.then( () => {
|
||||
|
||||
console.log('Search attachments disabled for now')
|
||||
// this.searchAttachments()
|
||||
this.searchAttachments()
|
||||
|
||||
// return
|
||||
return this.fetchUserTags()
|
||||
})
|
||||
})
|
||||
|
||||
@ -329,7 +275,6 @@
|
||||
const id = this.$route.params.id
|
||||
this.openNote(id)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', this.onScroll)
|
||||
|
||||
//Close notes when back button is pressed
|
||||
@ -346,7 +291,7 @@
|
||||
|
||||
this.$bus.$off('note_reload')
|
||||
this.$bus.$off('close_active_note')
|
||||
// this.$bus.$off('update_single_note')
|
||||
this.$bus.$off('update_single_note')
|
||||
this.$bus.$off('note_deleted')
|
||||
this.$bus.$off('update_fast_filters')
|
||||
this.$bus.$off('update_search_term')
|
||||
@ -362,9 +307,6 @@
|
||||
this.reset()
|
||||
},
|
||||
methods: {
|
||||
toggleTitleView(){
|
||||
this.titleView = !this.titleView
|
||||
},
|
||||
showOneColumn(){
|
||||
|
||||
return this.$store.getters.getIsUserOnMobile
|
||||
@ -381,21 +323,66 @@
|
||||
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){
|
||||
if(this.activeNoteId1 == null && this.activeNoteId2 == 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){
|
||||
this.activeNoteId1 = null
|
||||
this.$router.push('/notes')
|
||||
//One note open, close that note
|
||||
if(position == 0){
|
||||
this.activeNoteId1 = null
|
||||
this.activeNoteId2 = null
|
||||
}
|
||||
//Right note closed, thats 1
|
||||
if(position == 1){
|
||||
this.activeNoteId1 = null
|
||||
}
|
||||
if(position == 2){
|
||||
this.activeNoteId2 = null
|
||||
}
|
||||
|
||||
//IF two notes get opened, update ID of open note
|
||||
if(this.activeNoteId1 || this.activeNoteId2){
|
||||
this.$router.push('/notes/open/'+Math.max(this.activeNoteId1, this.activeNoteId2))
|
||||
} else {
|
||||
//No notes are open, just show notes page
|
||||
this.$router.push('/notes')
|
||||
}
|
||||
|
||||
this.activeNote1Position = 0
|
||||
this.activeNote2Position = 0
|
||||
|
||||
},
|
||||
toggleTagFilter(tagId){
|
||||
|
||||
this.searchTags = [tagId]
|
||||
if(this.searchTags.includes(tagId)){
|
||||
this.searchTags.splice( this.searchTags.indexOf(tagId) , 1);
|
||||
} else {
|
||||
this.searchTags.push(tagId)
|
||||
}
|
||||
|
||||
//Reset note set and load up notes and tags
|
||||
if(this.searchTags.length > 0){
|
||||
@ -424,7 +411,7 @@
|
||||
const percentageDown = Math.round( (bottomOfWindow/offsetHeight)*100 )
|
||||
|
||||
//If greater than 80 of the way down the page, load the next batch
|
||||
if(percentageDown >= 65 && this.scrollLoadEnabled){
|
||||
if(percentageDown >= 80){
|
||||
|
||||
this.search(false, this.batchSize, true)
|
||||
}
|
||||
@ -460,20 +447,21 @@
|
||||
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){
|
||||
|
||||
//Fuck this shit, just use web sockets
|
||||
return
|
||||
|
||||
//@TODO - phase this out, update it via socket.io
|
||||
//@TODO - set a timeout on this like 2 minutes or just dont do shit and update it via socket.io
|
||||
//If user leaves page then returns to page, reload the first batch
|
||||
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
|
||||
//Load initial batch, then tags, then other batch
|
||||
this.search(false, this.firstLoadBatchSize)
|
||||
.then( () => {
|
||||
// return
|
||||
return this.fetchUserTags()
|
||||
})
|
||||
}
|
||||
|
||||
@ -483,11 +471,6 @@
|
||||
// @TODO Don't even trigger this if the note wasn't changed
|
||||
updateSingleNote(noteId){
|
||||
|
||||
noteId = parseInt(noteId)
|
||||
|
||||
//Find local note, if it exists; continue
|
||||
|
||||
|
||||
//Lookup one note using passed in ID
|
||||
const postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
@ -497,7 +480,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Note data must be fetched, then sorted into existing note data
|
||||
|
||||
|
||||
axios.post('/api/note/search', postData)
|
||||
.then(results => {
|
||||
|
||||
@ -524,7 +508,7 @@
|
||||
|
||||
//Compare note tags, if they changed, reload tags
|
||||
if(newNote.tag_count != note.tag_count){
|
||||
|
||||
this.fetchUserTags()
|
||||
}
|
||||
|
||||
//go through each prop and update it with new values
|
||||
@ -538,7 +522,6 @@
|
||||
|
||||
this.$nextTick( () => {
|
||||
|
||||
//Trigger close animation on note
|
||||
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
|
||||
this.$refs['note-'+noteId][0].justClosed()
|
||||
}
|
||||
@ -570,19 +553,14 @@
|
||||
|
||||
//Don't double load note batches
|
||||
if(this.loadingInProgress){
|
||||
console.log('Loading in progress, cancel operation')
|
||||
return resolve()
|
||||
}
|
||||
|
||||
//Reset a lot of stuff if we are not merging batches
|
||||
if(!mergeExisting){
|
||||
Object.keys(this.noteSections).forEach( key => {
|
||||
this.noteSections[key] = []
|
||||
})
|
||||
this.batchOffset = 0 // Reset batch offset if we are not merging note batches
|
||||
// this.commonTags = [] //Don't reset tags, if search returns tags, they will be set
|
||||
}
|
||||
this.searchResultsCount = 0
|
||||
|
||||
//Remove all filter limits from previous queries
|
||||
delete this.fastFilters.limitSize
|
||||
@ -611,25 +589,15 @@
|
||||
|
||||
//Perform search - or die
|
||||
this.loadingInProgress = true
|
||||
// console.time('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
axios.post('/api/note/search', postData)
|
||||
.then(response => {
|
||||
|
||||
// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
|
||||
//Save the number of notes just loaded
|
||||
this.batchOffset += response.data.notes.length
|
||||
|
||||
//Enable or disable scroll loading
|
||||
this.scrollLoadEnabled = response.data.notes.length > 0
|
||||
|
||||
//Mush the two new sets of data together (set will be empty is reset is on)
|
||||
// if(response.data.tags.length > 0){
|
||||
// this.commonTags = response.data.tags
|
||||
// }
|
||||
|
||||
if(response.data.total > 0){
|
||||
this.searchResultsCount = response.data.total
|
||||
if(response.data.tags.length > 0){
|
||||
this.commonTags = response.data.tags
|
||||
}
|
||||
|
||||
this.loadingInProgress = false
|
||||
@ -662,73 +630,53 @@
|
||||
//Sort notes into defined sections
|
||||
notes.forEach(note => {
|
||||
|
||||
//Only show trashed notes when trashed
|
||||
if(this.fastFilters.onlyShowTrashed == 1){
|
||||
|
||||
if(note.trashed == 1){
|
||||
this.noteSections.trashed.push(note)
|
||||
}
|
||||
return
|
||||
}
|
||||
if(note.trashed == 1){
|
||||
return
|
||||
}
|
||||
|
||||
//Show archived notes
|
||||
if(this.fastFilters.onlyArchived == 1){
|
||||
|
||||
if(note.pinned == 1 && note.archived == 1){
|
||||
this.noteSections.pinned.push(note)
|
||||
return
|
||||
}
|
||||
if(note.archived == 1){
|
||||
this.noteSections.archived.push(note)
|
||||
}
|
||||
if(note.archived == 1 && this.fastFilters.onlyArchived == 1){
|
||||
this.noteSections.archived.push(note)
|
||||
return
|
||||
}
|
||||
if(note.archived == 1){
|
||||
if(note.shareUsername != null){
|
||||
this.noteSections.shared.push(note)
|
||||
return
|
||||
}
|
||||
|
||||
//Only show sent notes section if shared is selected
|
||||
if(this.fastFilters.onlyShowSharedNotes == 1){
|
||||
|
||||
if(note.shared == 2){
|
||||
this.noteSections.sent.push(note)
|
||||
}
|
||||
if(note.shareUsername != null){
|
||||
this.noteSections.shared.push(note)
|
||||
}
|
||||
if(note.shared == 2 && this.fastFilters.onlyShowSharedNotes == 1){
|
||||
this.noteSections.sent.push(note)
|
||||
return
|
||||
}
|
||||
if(note.encrypted == 1 && this.fastFilters.onlyShowEncrypted == 1){
|
||||
this.noteSections.locked.push(note)
|
||||
return
|
||||
}
|
||||
if(note.note_highlights.length > 0){
|
||||
this.noteSections.highlights.push(note)
|
||||
return
|
||||
}
|
||||
//Show shared notes on main list but not notes shared with you
|
||||
if(note.shareUsername != null){ return }
|
||||
|
||||
// Pinned notes are always first, they can appear in the archive
|
||||
if(note.pinned == 1){
|
||||
this.noteSections.pinned.push(note)
|
||||
return
|
||||
}
|
||||
|
||||
//Push to default note section
|
||||
this.noteSections.notes.push(note)
|
||||
|
||||
return
|
||||
//If the note is not archived, push it.
|
||||
if(note.archived != 1 && this.fastFilters.onlyArchived != 1){
|
||||
this.noteSections.notes.push(note)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
reset(){
|
||||
this.showClear = false
|
||||
this.scrollLoadEnabled = true
|
||||
this.searchTerm = ''
|
||||
this.searchTags = []
|
||||
this.fastFilters = {}
|
||||
this.updateFastFilters(5)
|
||||
this.foundAttachments = [] //Remove all attachments
|
||||
// this.$bus.$emit('reset_fast_filters')
|
||||
this.$bus.$emit('reset_fast_filters')
|
||||
|
||||
//Load initial batch, then tags, then other batch
|
||||
this.search(true, this.firstLoadBatchSize)
|
||||
.then( () => {
|
||||
return this.fetchUserTags()
|
||||
})
|
||||
.then( () => {
|
||||
//Load a larger batch once first batch has loaded
|
||||
return this.search(false, this.batchSize, true)
|
||||
@ -737,11 +685,27 @@
|
||||
//Thats how you promise chain
|
||||
})
|
||||
},
|
||||
fetchUserTags(){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
searchTags: this.searchTags,
|
||||
fastFilters: this.fastFilters,
|
||||
}
|
||||
|
||||
axios.post('/api/tag/usertags', postData)
|
||||
.then( ({data}) => {
|
||||
this.commonTags = data
|
||||
resolve(data)
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Fetch Tags') })
|
||||
})
|
||||
},
|
||||
updateFastFilters(index){
|
||||
|
||||
//clear out tags
|
||||
this.searchTags = []
|
||||
this.loadingInProgress = false
|
||||
|
||||
//A little hacky, brings user to notes page then filters on click
|
||||
if(this.$route.name != 'Note Page'){
|
||||
@ -756,8 +720,7 @@
|
||||
'withTags', // 'Only Show Notes with Tags'
|
||||
'onlyArchived', //'Only Show Archived Notes'
|
||||
'onlyShowSharedNotes', //Only show shared notes
|
||||
'onlyShowTrashed',
|
||||
'notesHome',
|
||||
'onlyShowEncrypted',
|
||||
]
|
||||
|
||||
let filter = {}
|
||||
|
@ -4,27 +4,31 @@
|
||||
|
||||
<div class="ui sixteen wide column">
|
||||
<h2 class="ui header">
|
||||
<i class="sticky note outline icon"></i>
|
||||
<i class="paper plane outline icon"></i>
|
||||
<div class="content">
|
||||
The Scratch Pad
|
||||
<div class="sub header">One place to put random junk</div>
|
||||
Quick
|
||||
<div class="sub header">Rapidly save text</div>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="sixteen wide middle aligned column">
|
||||
|
||||
<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 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>
|
||||
<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 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>
|
||||
</div>
|
||||
|
||||
@ -37,28 +41,25 @@
|
||||
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 (Enter)</div>
|
||||
<div v-on:click="appendQuickNote" class="ui green button">Save</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>
|
||||
Open Note
|
||||
Edit
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="fun" v-html="savedQuickNoteText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -78,7 +79,8 @@
|
||||
newText: '',
|
||||
savedQuickNoteText: '',
|
||||
quickNoteId: null,
|
||||
showNewNoteConfirm: false,
|
||||
pasteToSubmit: true,
|
||||
enterToSubmit: true,
|
||||
}
|
||||
},
|
||||
beforeCreate: function(){
|
||||
@ -87,16 +89,6 @@
|
||||
//
|
||||
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){
|
||||
@ -115,17 +107,6 @@
|
||||
}
|
||||
},
|
||||
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})
|
||||
},
|
||||
@ -138,10 +119,17 @@
|
||||
element.style.height = (element.scrollHeight + padding) +'px';
|
||||
|
||||
//Enter Key submits by default
|
||||
if(event.keyCode == 13){
|
||||
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){
|
||||
this.appendQuickNote()
|
||||
return
|
||||
}
|
||||
},
|
||||
appendQuickNote(){
|
||||
|
||||
@ -154,17 +142,28 @@
|
||||
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( ({data}) => {
|
||||
this.savedQuickNoteText = data.text
|
||||
this.quickNoteId = data.id
|
||||
.then( results => {
|
||||
this.savedQuickNoteText = results.data.text
|
||||
this.quickNoteId = results.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:'Scratch Pad'},
|
||||
meta: {title:'Quick'},
|
||||
component: QuickPage
|
||||
},
|
||||
{
|
||||
|
@ -41,61 +41,38 @@ export default new Vuex.Store({
|
||||
state.token = null
|
||||
state.username = null
|
||||
},
|
||||
toggleNightMode(state, pastTheme){
|
||||
toggleNightMode(state){
|
||||
|
||||
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':{
|
||||
//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 = {
|
||||
'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( themes[currentTheme] ).forEach( attribute => {
|
||||
root.style.setProperty('--'+attribute, themes[currentTheme][attribute])
|
||||
Object.keys(themeColors).forEach( attribute => {
|
||||
root.style.setProperty('--'+attribute, themeColors[attribute])
|
||||
})
|
||||
},
|
||||
detectIsUserOnMobile(state){
|
||||
@ -110,7 +87,6 @@ export default new Vuex.Store({
|
||||
})(navigator.userAgent||navigator.vendor||window.opera, state);
|
||||
},
|
||||
toggleNoteSettingsPane(state){
|
||||
|
||||
state.isNoteSettingsOpen = !state.isNoteSettingsOpen
|
||||
},
|
||||
setSocketIoSocket(state, socket){
|
||||
@ -123,11 +99,11 @@ export default new Vuex.Store({
|
||||
//Save all the totals for the user
|
||||
state.userTotals = totalsObject
|
||||
|
||||
// console.log('-------------')
|
||||
// Object.keys(totalsObject).forEach( key => {
|
||||
// console.log(key + ' -- ' + totalsObject[key])
|
||||
// })
|
||||
}
|
||||
|
||||
},
|
||||
getters: {
|
||||
getUsername: state => {
|
||||
|
@ -4,8 +4,8 @@ let Auth = {}
|
||||
|
||||
const tokenSecretKey = process.env.JSON_KEY
|
||||
|
||||
Auth.createToken = (userId, masterKey) => {
|
||||
const signedData = {'id':userId, 'date':Date.now(), 'masterKey':masterKey}
|
||||
Auth.createToken = (userId) => {
|
||||
const signedData = {'id': userId, 'date':Date.now()}
|
||||
const token = jwt.sign(signedData, tokenSecretKey)
|
||||
return token
|
||||
}
|
||||
|
@ -69,10 +69,6 @@ CryptoString.createSalt = () => {
|
||||
|
||||
return crypto.randomBytes(SALT_BYTE_SIZE).toString('base64')
|
||||
}
|
||||
CryptoString.createSmallSalt = () => {
|
||||
|
||||
return crypto.randomBytes(20).toString('base64')
|
||||
}
|
||||
|
||||
CryptoString.hash = (hashString) => {
|
||||
|
||||
|
@ -37,9 +37,11 @@ var io = require('socket.io')(http, {
|
||||
path:'/socket'
|
||||
});
|
||||
|
||||
//Set socket IO as a global in the app
|
||||
global.SocketIo = io
|
||||
|
||||
// Make io accessible to our router
|
||||
app.use(function(req,res,next){
|
||||
req.io = io;
|
||||
next();
|
||||
});
|
||||
|
||||
io.on('connection', function(socket){
|
||||
|
||||
@ -47,7 +49,7 @@ io.on('connection', function(socket){
|
||||
|
||||
//When a user connects, add them to their own room
|
||||
// This allows the server to emit events to that specific user
|
||||
// access socket.io in the controller with SocketIo global
|
||||
// access socket.io in the controller with req.io
|
||||
socket.on('user_connect', token => {
|
||||
Auth.decodeToken(token)
|
||||
.then(userData => {
|
||||
@ -104,7 +106,7 @@ io.on('connection', function(socket){
|
||||
|
||||
|
||||
http.listen(3001, function(){
|
||||
// console.log('socket.io liseting on port 3001');
|
||||
console.log('socket.io liseting on port 3001');
|
||||
});
|
||||
|
||||
//Enable json body parsing in requests. Allows me to post data in ajax calls
|
||||
@ -122,7 +124,6 @@ app.use(function(req, res, next){
|
||||
Auth.decodeToken(token)
|
||||
.then(userData => {
|
||||
req.headers.userId = userData.id //Update headers for the rest of the application
|
||||
req.headers.masterKey = userData.masterKey
|
||||
next()
|
||||
}).catch(error => {
|
||||
|
||||
@ -134,18 +135,17 @@ app.use(function(req, res, next){
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Test Area
|
||||
const printResults = true
|
||||
let UserTest = require('@models/User')
|
||||
let NoteTest = require('@models/Note')
|
||||
UserTest.keyPairTest('genMan2', '1', printResults)
|
||||
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
||||
.then( message => {
|
||||
if(printResults) console.log(message)
|
||||
})
|
||||
// Test Area
|
||||
|
||||
// Testing Area
|
||||
// let att = require('@models/Attachment')
|
||||
// let testUrl = 'https://dba.stackexchange.com/questions/23908/how-to-search-a-mysql-database-with-encrypted-fields'
|
||||
// testUrl = 'https://www.solidscribe.com/#/'
|
||||
// console.log('About to scrape: ', testUrl)
|
||||
// att.processUrl(61, 3213, testUrl)
|
||||
// .then(results => {
|
||||
// console.log('Scrape happened')
|
||||
// })
|
||||
//
|
||||
//
|
||||
|
||||
//Test
|
||||
app.get(prefix, (req, res) => res.send('The api is running'))
|
||||
@ -178,6 +178,4 @@ var quickNote = require('@routes/quicknoteController')
|
||||
app.use(prefix+'/quick-note', quickNote)
|
||||
|
||||
//Output running status
|
||||
app.listen(port, () => {
|
||||
// console.log(`Listening on port ${port}!`)
|
||||
})
|
||||
app.listen(port, () => console.log(`Listening on port ${port}!`))
|
@ -117,9 +117,6 @@ Attachment.update = (userId, attachmentId, updatedText, noteId) => {
|
||||
|
||||
Attachment.delete = (userId, attachmentId, urlDelete = false) => {
|
||||
|
||||
let attachment = null
|
||||
let noteExists = true
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.promise()
|
||||
.query('SELECT * FROM attachment WHERE id = ? AND user_id = ? LIMIT 1', [attachmentId, userId])
|
||||
@ -130,32 +127,21 @@ Attachment.delete = (userId, attachmentId, urlDelete = false) => {
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
attachment = rows[0][0]
|
||||
|
||||
return db.promise().query('SELECT count(id) as `exists` FROM note WHERE id = ?', [attachment.note_id])
|
||||
|
||||
})
|
||||
|
||||
.then((rows, fields) => {
|
||||
|
||||
noteExists = (rows[0]['exists'] > 0)
|
||||
|
||||
|
||||
|
||||
|
||||
let url = attachment.url
|
||||
const noteId = attachment.note_id
|
||||
//Pull data we want out of
|
||||
let row = rows[0][0]
|
||||
let url = row.url
|
||||
const noteId = row.note_id
|
||||
|
||||
//Try to delete file and thumbnail
|
||||
try {
|
||||
fs.unlinkSync(filePath+attachment.file_location)
|
||||
fs.unlinkSync(filePath+row.file_location)
|
||||
} catch(err) { console.error('File Does not exist') }
|
||||
try {
|
||||
fs.unlinkSync(filePath+'thumb_'+attachment.file_location)
|
||||
fs.unlinkSync(filePath+'thumb_'+row.file_location)
|
||||
} catch(err) { console.error('Thumbnail Does not exist') }
|
||||
|
||||
//Do not delete link attachments, just hide them. They will be deleted if removed from note or if note is deleted
|
||||
if(attachment.attachment_type == 1 && !urlDelete && noteExists){
|
||||
//Do not delete link attachments, just hide them. They will be deleted if removed from note
|
||||
if(row.attachment_type == 1 && !urlDelete){
|
||||
db.promise()
|
||||
.query(`UPDATE attachment SET visible = 0 WHERE id = ?`, [attachmentId])
|
||||
.then((rows, fields) => { })
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,76 +5,45 @@ let Note = require('@models/Note')
|
||||
let QuickNote = module.exports = {}
|
||||
|
||||
|
||||
QuickNote.get = (userId, masterKey) => {
|
||||
QuickNote.get = (userId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
db.promise()
|
||||
.query(`
|
||||
SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1
|
||||
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
|
||||
`, [userId])
|
||||
.then((rows, fields) => {
|
||||
|
||||
//Quick Note is set, return note text
|
||||
if(rows[0][0] != undefined){
|
||||
let noteId = rows[0][0].id
|
||||
Note.get(userId, noteId, masterKey)
|
||||
.then( noteObject => {
|
||||
return resolve(noteObject)
|
||||
if(rows[0].length == 1){
|
||||
resolve({
|
||||
id: rows[0][0].id,
|
||||
text: rows[0][0].text
|
||||
})
|
||||
} else {
|
||||
return resolve(null)
|
||||
}
|
||||
|
||||
|
||||
resolve({
|
||||
id: null,
|
||||
text: 'Enter something to create a quick note.'
|
||||
})
|
||||
})
|
||||
.catch(console.log)
|
||||
})
|
||||
}
|
||||
|
||||
QuickNote.newNote = (userId) => {
|
||||
QuickNote.update = (userId, pushText) => {
|
||||
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 = (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 = '<p>' +
|
||||
let broken = '<blockquote>' +
|
||||
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>' }
|
||||
@ -82,31 +51,51 @@ QuickNote.update = (userId, pushText, masterKey) => {
|
||||
//Return line wrapped in p tags
|
||||
return `${newLine}<span>${clean}</span>`
|
||||
|
||||
}).join('') + '</p><p><br></p>'
|
||||
}).join('') + '</blockquote>'
|
||||
|
||||
QuickNote.get(userId, masterKey)
|
||||
.then(noteObject => {
|
||||
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) => {
|
||||
|
||||
if(noteObject == null){
|
||||
//Quick Note is set, push it the front of existing note
|
||||
if(rows[0].length == 1){
|
||||
|
||||
finalText += broken
|
||||
let d = rows[0][0] //Get row data
|
||||
|
||||
return Note.create(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])
|
||||
//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
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
finalText += (broken + noteObject.text)
|
||||
finalId = noteObject.id
|
||||
return Note.update(userId, noteObject.id, finalText, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey)
|
||||
//Create a new note with the quick text submitted.
|
||||
Note.create(userId, broken, 1)
|
||||
.then( insertId => {
|
||||
resolve({
|
||||
id:insertId,
|
||||
text:broken
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.then( saveResults => {
|
||||
return resolve(true)
|
||||
})
|
||||
.catch(console.log)
|
||||
})
|
||||
|
||||
//Lookup quick note,
|
||||
|
||||
//Note.create(userId, 'Quick Note', 1)
|
||||
|
||||
}
|
@ -9,189 +9,79 @@ const Note = require('@models/Note')
|
||||
|
||||
let ShareNote = module.exports = {}
|
||||
|
||||
const crypto = require('crypto')
|
||||
const cs = require('@helpers/CryptoString')
|
||||
|
||||
ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
|
||||
// Share a note with a user, given the correct username
|
||||
ShareNote.addUser = (userId, noteId, rawTextId, username) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const Note = require('@models/Note')
|
||||
const User = require('@models/User')
|
||||
let shareUserId = null
|
||||
let newNoteShare = null
|
||||
const cleanUser = username.toLowerCase().trim()
|
||||
|
||||
//generate new random salts and password
|
||||
const sharedNoteMasterKey = cs.createSmallSalt()
|
||||
|
||||
let encryptedSharedKey = null //new key for note encrypted with shared users pubic key
|
||||
|
||||
//Current note object
|
||||
let note = null
|
||||
let publicKey = null
|
||||
|
||||
db.promise().query('SELECT id FROM user WHERE id = ?', [shareUserId])
|
||||
//Check that user actually exists
|
||||
db.promise().query(`SELECT id FROM user WHERE LOWER(username) = ?`, [cleanUser])
|
||||
.then((rows, fields) => {
|
||||
|
||||
if(rows[0].length == 0){
|
||||
throw new Error('User Does Not Exist')
|
||||
}
|
||||
|
||||
return Note.get(userId, noteId, masterKey)
|
||||
shareUserId = rows[0][0]['id']
|
||||
|
||||
//Check if note has already been added for user
|
||||
return db.promise()
|
||||
.query('SELECT id FROM note WHERE user_id = ? AND note_raw_text_id = ?', [shareUserId, rawTextId])
|
||||
|
||||
})
|
||||
.then( noteObject => {
|
||||
.then((rows, fields) => {
|
||||
|
||||
if(!noteObject){
|
||||
throw new Error('Note Not Found')
|
||||
if(rows[0].length != 0){
|
||||
throw new Error('User Already Has Note')
|
||||
}
|
||||
|
||||
note = noteObject
|
||||
|
||||
//Lookup note to share with user, clone this data to create users new note
|
||||
return db.promise()
|
||||
.query('SELECT id FROM note WHERE user_id = ? AND note_raw_text_id = ?', [shareUserId, note.rawTextId])
|
||||
|
||||
.query(`SELECT * FROM note WHERE id = ? LIMIT 1`, [noteId])
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
|
||||
if(rows[0].length >= 1){
|
||||
throw new Error('User Already has this note shared with them')
|
||||
}
|
||||
newNoteShare = rows[0][0]
|
||||
|
||||
//All check pass, proceed with sharing note
|
||||
return User.getPublicKey(userId)
|
||||
})
|
||||
.then( userPublicKey => {
|
||||
//Modify note with the share attributes we want
|
||||
delete newNoteShare['id']
|
||||
delete newNoteShare['opened']
|
||||
newNoteShare['share_user_id'] = userId //User who shared the note
|
||||
newNoteShare['user_id'] = shareUserId //User who gets note
|
||||
|
||||
//Get users public key
|
||||
publicKey = userPublicKey
|
||||
//Setup db colums, db values and number of '?' to put into prepared statement
|
||||
let dbColumns = []
|
||||
let dbValues = []
|
||||
let escapeChars = []
|
||||
|
||||
//
|
||||
// Modify note to have a shared password, encrypt text with this password
|
||||
//
|
||||
const sharedNoteSalt = cs.createSmallSalt()
|
||||
//Pull out all the data we need from object to create prepared statemnt
|
||||
Object.keys(newNoteShare).forEach( key => {
|
||||
escapeChars.push('?')
|
||||
dbColumns.push(key)
|
||||
dbValues.push(newNoteShare[key])
|
||||
})
|
||||
|
||||
//Encrypt note text with new password
|
||||
const textObject = JSON.stringify([note.title, note.text])
|
||||
const encryptedText = cs.encrypt(sharedNoteMasterKey, sharedNoteSalt, textObject)
|
||||
|
||||
//Update note raw text with new data
|
||||
//Stick all the note value back into query, insert updated note
|
||||
return db.promise()
|
||||
.query("UPDATE `application`.`note_raw_text` SET `text` = ?, `salt` = ? WHERE (`id` = ?)",
|
||||
[encryptedText, sharedNoteSalt, note.rawTextId])
|
||||
|
||||
.query(`INSERT INTO note (${dbColumns.join()}) VALUES (${escapeChars.join()})`, dbValues)
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
|
||||
//New Encrypted snippet, using new shared password
|
||||
const sharedNoteSnippetSalt = cs.createSmallSalt()
|
||||
const snippet = JSON.stringify([note.title, note.text.substring(0, 500)])
|
||||
const encryptedSnippet = cs.encrypt(sharedNoteMasterKey, sharedNoteSnippetSalt, snippet)
|
||||
|
||||
//Encrypt shared password for this user
|
||||
const encryptedSharedKey = crypto.publicEncrypt(publicKey, Buffer.from(sharedNoteMasterKey, 'utf8')).toString('base64')
|
||||
|
||||
//Update note snippet for current user with public key encoded snippet
|
||||
return db.promise().query('UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 2 WHERE id = ? AND user_id = ?',
|
||||
[encryptedSnippet, sharedNoteSnippetSalt, encryptedSharedKey, noteId, userId])
|
||||
//Update note share status to 2
|
||||
return db.promise()
|
||||
.query('UPDATE note SET shared = 2 WHERE id = ?', [noteId])
|
||||
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
|
||||
return User.getPublicKey(shareUserId)
|
||||
|
||||
})
|
||||
.then(shareUserPublicKey => {
|
||||
|
||||
//New Encrypted snippet, using new shared password
|
||||
const newSnippetSalt = cs.createSmallSalt()
|
||||
const snippet = JSON.stringify([note.title, note.text.substring(0, 500)])
|
||||
const encryptedSnippet = cs.encrypt(sharedNoteMasterKey, newSnippetSalt, snippet)
|
||||
|
||||
//Encrypt shared password for this user
|
||||
const encryptedSharedKey = crypto.publicEncrypt(shareUserPublicKey, Buffer.from(sharedNoteMasterKey, 'utf8')).toString('base64')
|
||||
|
||||
//Insert new note for shared user
|
||||
return db.promise().query(`
|
||||
INSERT INTO note (user_id, note_raw_text_id, created, color, share_user_id, snippet, snippet_salt, encrypted_share_password_key) VALUES (?,?,?,?,?,?,?,?);
|
||||
`, [shareUserId, note.rawTextId, note.created, note.color, userId, encryptedSnippet, newSnippetSalt, encryptedSharedKey])
|
||||
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
|
||||
const sharedUserNoteId = rows[0]['insertId']
|
||||
|
||||
//Emit update count event to user shared with - so they see the note in real time
|
||||
SocketIo.to(sharedUserNoteId).emit('update_counts')
|
||||
|
||||
let success = true
|
||||
return resolve({success, shareUserId, sharedUserNoteId})
|
||||
|
||||
//Success!
|
||||
return resolve({'success':true, shareUserId})
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Shared Note Error')
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
ShareNote.removeUserFromShared = (userId, noteId, shareNoteUserId, masterKey) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let rawTextId = null
|
||||
let removeUserId = null
|
||||
|
||||
db.promise()
|
||||
.query('SELECT note_raw_text_id, user_id FROM note WHERE id = ? AND share_user_id = ?', [shareNoteUserId, userId])
|
||||
.then( (rows, fields) => {
|
||||
|
||||
rawTextId = rows[0][0]['note_raw_text_id']
|
||||
removeUserId = rows[0][0]['user_id']
|
||||
|
||||
//Delete note entry for other user - remove users access
|
||||
//@TODO - this won't remove the note from their search index, it needs to
|
||||
return Note.delete(removeUserId, shareNoteUserId)
|
||||
|
||||
})
|
||||
.then(results => {
|
||||
|
||||
return db.promise().query('SELECT count(*) as count FROM note WHERE note_raw_text_id = ?', [rawTextId])
|
||||
})
|
||||
.then((rows, fields) => {
|
||||
|
||||
//Convert back to normal note if there is only one person with this note
|
||||
if(rows[0][0]['count'] == 1){
|
||||
|
||||
Note.get(userId, noteId, masterKey)
|
||||
.then(noteObject => {
|
||||
|
||||
const salt = cs.createSmallSalt()
|
||||
const snippetSalt = cs.createSmallSalt()
|
||||
|
||||
const snippetObj = JSON.stringify([noteObject.title, noteObject.text.substring(0, 500)])
|
||||
const snippet = cs.encrypt(masterKey, snippetSalt, snippetObj)
|
||||
|
||||
const textObject = JSON.stringify([noteObject.title, noteObject.text])
|
||||
const encryptedText = cs.encrypt(masterKey, salt, textObject)
|
||||
|
||||
db.promise()
|
||||
.query(`UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 0 WHERE id = ? AND user_id = ?`,
|
||||
[snippet, snippetSalt, null, noteId, userId])
|
||||
.then((r,f) => {
|
||||
|
||||
db.promise()
|
||||
.query('UPDATE note_raw_text SET text = ?, salt = ? WHERE id = ?',
|
||||
[encryptedText, salt, noteObject.rawTextId])
|
||||
.then(() => {
|
||||
return resolve(true)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
} else {
|
||||
//Keep note shared
|
||||
return resolve(true)
|
||||
}
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -236,7 +126,7 @@ ShareNote.removeUser = (userId, noteId) => {
|
||||
//Delete note entry for other user - remove users access
|
||||
if(removeUserId && Number.isInteger(removeUserId)){
|
||||
//Delete this users access to the note
|
||||
return Note.delete(removeUserId, noteId, masterKey)
|
||||
return Note.delete(removeUserId, noteId)
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -20,8 +20,26 @@ Tag.userTags = (userId, searchQuery, searchTags, fastFilters) => {
|
||||
WHERE note_tag.user_id = ?
|
||||
`
|
||||
|
||||
//Show shared notes
|
||||
if(fastFilters && fastFilters.onlyShowSharedNotes == 1){
|
||||
query += ' AND note.share_user_id IS NOT NULL' //Show Archived
|
||||
} else {
|
||||
query += ' AND note.share_user_id IS NULL'
|
||||
}
|
||||
|
||||
if(fastFilters && fastFilters.onlyShowEncrypted == 1){
|
||||
query += ' AND note.encrypted = 1' //Show Archived
|
||||
}
|
||||
|
||||
//Show archived notes, only if fast filter is set, default to not archived
|
||||
if(fastFilters && fastFilters.onlyArchived == 1){
|
||||
query += ' AND note.archived = 1' //Show Archived
|
||||
} else {
|
||||
query += ' AND note.archived = 0' //Exclude archived
|
||||
}
|
||||
|
||||
query += ` GROUP BY tag.id
|
||||
ORDER BY LOWER(TRIM(text)) ASC`
|
||||
ORDER BY usages DESC, text ASC`
|
||||
|
||||
|
||||
db.promise()
|
||||
@ -176,24 +194,16 @@ Tag.suggest = (userId, noteId, tagText) => {
|
||||
tagText += '%'
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let params = [userId, tagText]
|
||||
let query = `SELECT tag.id, text FROM note_tag
|
||||
db.promise()
|
||||
.query(`SELECT text FROM note_tag
|
||||
JOIN tag ON note_tag.tag_id = tag.id
|
||||
WHERE note_tag.user_id = ?
|
||||
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)
|
||||
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])
|
||||
.then((rows, fields) => {
|
||||
resolve(rows[0]) //Return new ID
|
||||
})
|
||||
|
@ -1,10 +1,7 @@
|
||||
const crypto = require('crypto')
|
||||
var crypto = require('crypto')
|
||||
|
||||
const Note = require('@models/Note')
|
||||
|
||||
const db = require('@config/database')
|
||||
const Auth = require('@helpers/Auth')
|
||||
const cs = require('@helpers/CryptoString')
|
||||
let db = require('@config/database')
|
||||
let Auth = require('@helpers/Auth')
|
||||
|
||||
let User = module.exports = {}
|
||||
|
||||
@ -19,48 +16,32 @@ User.login = (username, password) => {
|
||||
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
|
||||
.then((rows, fields) => {
|
||||
|
||||
// Create New Account
|
||||
//
|
||||
//Pull out user data from database results
|
||||
const lookedUpUser = rows[0][0];
|
||||
|
||||
//User not found, create a new account with set data
|
||||
if(rows[0].length == 0){
|
||||
User.create(lowerName, password)
|
||||
.then( ({token, userId}) => {
|
||||
return resolve({ token, userId })
|
||||
.then(loginToken => {
|
||||
resolve(loginToken)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Login User
|
||||
//
|
||||
if(rows[0].length == 1){
|
||||
//hash the password and check for a match
|
||||
const salt = new Buffer(lookedUpUser.salt, 'binary')
|
||||
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
|
||||
if(delivered_key.toString('hex') === lookedUpUser.password){
|
||||
|
||||
//Pull out user data from database results
|
||||
const lookedUpUser = rows[0][0]
|
||||
//Passback a json web token
|
||||
const token = Auth.createToken(lookedUpUser.id)
|
||||
resolve(token)
|
||||
|
||||
//hash the password and check for a match
|
||||
// const salt = new Buffer(lookedUpUser.salt, 'binary')
|
||||
const salt = Buffer.from(lookedUpUser.salt, 'binary')
|
||||
crypto.pbkdf2(password, salt, lookedUpUser.iterations, 512, 'sha512', function(err, delivered_key){
|
||||
if(delivered_key.toString('hex') === lookedUpUser.password){
|
||||
} else {
|
||||
|
||||
User.generateMasterKey(lookedUpUser.id, password)
|
||||
.then( result => User.getMasterKey(lookedUpUser.id, password))
|
||||
.then(masterKey => {
|
||||
|
||||
User.generateKeypair(lookedUpUser.id, masterKey)
|
||||
.then(({publicKey, privateKey}) => {
|
||||
|
||||
//Passback a json web token
|
||||
const token = Auth.createToken(lookedUpUser.id, masterKey)
|
||||
resolve({ token: token, userId:lookedUpUser.id })
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
reject('Password does not match database')
|
||||
}
|
||||
})
|
||||
}
|
||||
reject('Password does not match database')
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(console.log)
|
||||
|
||||
@ -90,7 +71,7 @@ User.create = (username, password) => {
|
||||
shasum.update(''+otherRandomInt) //Update Hasd
|
||||
|
||||
const saltString = shasum.digest('hex')
|
||||
const salt = Buffer.from(saltString, 'binary') //Generate Salt hash
|
||||
const salt = new Buffer(saltString, 'binary') //Generate Salt hash
|
||||
const iterations = 25000
|
||||
|
||||
crypto.pbkdf2(password, salt, iterations, 512, 'sha512', function(err, delivered_key) {
|
||||
@ -106,33 +87,25 @@ User.create = (username, password) => {
|
||||
created: currentDate
|
||||
};
|
||||
|
||||
let userId = null
|
||||
let newMasterKey = null
|
||||
|
||||
db.promise()
|
||||
.query('INSERT INTO user SET ?', new_user)
|
||||
.then((rows, fields) => {
|
||||
|
||||
userId = rows[0].insertId
|
||||
return User.generateMasterKey(userId, password)
|
||||
})
|
||||
.then( result => {
|
||||
if(rows[0].affectedRows == 1){
|
||||
|
||||
return User.getMasterKey(userId, password)
|
||||
})
|
||||
.then(masterKey => {
|
||||
newMasterKey = masterKey
|
||||
return User.generateKeypair(userId, newMasterKey)
|
||||
})
|
||||
.then(({publicKey, privateKey}) => {
|
||||
const newUserId = rows[0].insertId
|
||||
const loginToken = Auth.createToken(newUserId)
|
||||
resolve(loginToken)
|
||||
|
||||
const token = Auth.createToken(userId, newMasterKey)
|
||||
return resolve({token, userId})
|
||||
} else {
|
||||
//Emit Error to user
|
||||
reject('New user could not be created')
|
||||
}
|
||||
})
|
||||
.catch(console.log)
|
||||
})
|
||||
} else {
|
||||
return reject('Username already in use.')
|
||||
reject('Username already in use.')
|
||||
}//END user create
|
||||
})
|
||||
.catch(console.log)
|
||||
@ -149,24 +122,22 @@ User.getCounts = (userId) => {
|
||||
|
||||
db.promise().query(
|
||||
`SELECT
|
||||
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
|
||||
SUM(trashed = 1) AS trashedNotes,
|
||||
SUM(share_user_id IS NULL && trashed = 0) AS totalNotes,
|
||||
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes,
|
||||
SUM( (share_user_id != ? && opened IS null && trashed = 0) || (share_user_id != ? && note_raw_text.updated > opened && trashed = 0) ) AS unreadNotes
|
||||
SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes,
|
||||
SUM(archived = 1 && share_user_id IS NULL) AS archivedNotes,
|
||||
SUM(encrypted = 1) AS encryptedNotes,
|
||||
SUM(share_user_id IS NULL) AS totalNotes,
|
||||
SUM(share_user_id != ?) AS sharedToNotes,
|
||||
SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && note_raw_text.updated > opened) ) AS unreadNotes
|
||||
FROM note
|
||||
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
||||
WHERE user_id = ?`, [userId, userId, userId, userId])
|
||||
.then( (rows, fields) => {
|
||||
|
||||
Object.assign(countTotals, rows[0][0]) //combine results
|
||||
//
|
||||
// @TODO - Figured out if this is useful
|
||||
// We want, notes shared with user and note user has shared
|
||||
//
|
||||
|
||||
return db.promise().query(
|
||||
`SELECT count(id) AS sharedFromNotes
|
||||
FROM note WHERE shared = 2 AND user_id = ? AND trashed = 0`, [userId]
|
||||
FROM note WHERE share_user_id = ?`, [userId]
|
||||
)
|
||||
})
|
||||
.then( (rows, fields) => {
|
||||
@ -197,249 +168,3 @@ User.getCounts = (userId) => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
User.generateMasterKey = (userId, password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(!userId || !password){
|
||||
reject('Need userId and password to generate key')
|
||||
}
|
||||
|
||||
db.promise()
|
||||
.query('SELECT count(id) as total FROM user_key WHERE user_id = ?', [userId])
|
||||
.then((rows, fields) => {
|
||||
|
||||
//Entry already exists, you good.
|
||||
if(rows[0][0]['total'] > 0){
|
||||
return resolve(true)
|
||||
// throw new Error('User Encryption key already exists')
|
||||
} else {
|
||||
// Generate user key, its big and random
|
||||
const masterPassword = cs.createSmallSalt()
|
||||
|
||||
//Generate a salt because it wants it
|
||||
const salt = cs.createSmallSalt()
|
||||
|
||||
// Encrypt master password
|
||||
const encryptedMasterPassword = cs.encrypt(password, salt, masterPassword)
|
||||
|
||||
const created = Math.round((+new Date)/1000)
|
||||
|
||||
db.promise()
|
||||
.query(
|
||||
'INSERT INTO user_key (`user_id`, `salt`, `key`, `created`) VALUES (?, ?, ?, ?);',
|
||||
[userId, salt, encryptedMasterPassword, created]
|
||||
)
|
||||
.then(results => {
|
||||
return resolve(true)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Create Master Password Error')
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
User.getMasterKey = (userId, password) => {
|
||||
|
||||
if(!userId || !password){
|
||||
reject('Need userId and password to fetch key')
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
db.promise().query('SELECT * FROM user_key WHERE user_id = ? LIMIT 1', [userId])
|
||||
.then((rows, fields) => {
|
||||
|
||||
const row = rows[0][0]
|
||||
|
||||
if(!rows[0] || rows[0].length == 0 || rows[0][0] == undefined){
|
||||
return reject('Row or salt or something not set')
|
||||
}
|
||||
|
||||
const masterKey = cs.decrypt(password, row['salt'], row['key'])
|
||||
|
||||
if(masterKey == null){
|
||||
return reject('Unable to decrypt key')
|
||||
}
|
||||
|
||||
return resolve(masterKey)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
User.generateKeypair = (userId, masterKey) => {
|
||||
|
||||
let publicKey = null
|
||||
let privateKey = null
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.promise().query('SELECT * FROM user_key WHERE user_id = ?', [userId])
|
||||
.then((rows, fields) => {
|
||||
|
||||
const row = rows[0][0]
|
||||
|
||||
const salt = row['salt']
|
||||
publicKey = row['public_key']
|
||||
privateKey = row['private_key_encrypted']
|
||||
|
||||
if(row['public_key'] == null){
|
||||
const keyPair = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 1024,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem'
|
||||
}
|
||||
})
|
||||
|
||||
publicKey = keyPair.publicKey
|
||||
privateKey = keyPair.privateKey
|
||||
const privateKeyEncrypted = cs.encrypt(masterKey, salt, privateKey)
|
||||
|
||||
db.promise()
|
||||
.query(
|
||||
'UPDATE user_key SET `public_key` = ?, `private_key_encrypted` = ? WHERE user_id = ?;',
|
||||
[publicKey, privateKeyEncrypted, userId]
|
||||
)
|
||||
.then((rows, fields)=>{
|
||||
|
||||
return resolve({publicKey, privateKey})
|
||||
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
//Decrypt private key
|
||||
privateKey = cs.decrypt(masterKey, salt, privateKey)
|
||||
|
||||
return resolve({publicKey, privateKey})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
User.getPublicKey = (userId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.promise().query('SELECT public_key FROM user_key WHERE user_id = ?', [userId])
|
||||
.then((rows, fields) => {
|
||||
|
||||
const row = rows[0][0]
|
||||
return resolve(row['public_key'])
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
User.getPrivateKey = (userId, masterKey) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.promise().query('SELECT salt, private_key_encrypted FROM user_key WHERE user_id = ?', [userId])
|
||||
.then((rows, fields) => {
|
||||
|
||||
const row = rows[0][0]
|
||||
|
||||
const salt = row['salt']
|
||||
privateKey = row['private_key_encrypted']
|
||||
|
||||
//Decrypt private key
|
||||
privateKey = cs.decrypt(masterKey, salt, privateKey)
|
||||
|
||||
return resolve(privateKey)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
User.getByUserName = (username) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.promise().query('SELECT * FROM user WHERE username = ? LIMIT 1', [username.toLowerCase()])
|
||||
.then((rows, fields) => {
|
||||
resolve(rows[0][0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
User.deleteUser = (userId, password) => {
|
||||
|
||||
//Verify user is correct by decryptig master key with password
|
||||
|
||||
let deletePromises = []
|
||||
|
||||
let noteDelete = db.promise().query(`
|
||||
DELETE note, note_raw_text
|
||||
FROM note
|
||||
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
|
||||
WHERE note.user_id = ?
|
||||
`,[userId])
|
||||
deletePromises.push(noteDelete)
|
||||
|
||||
let userDelete = db.promise().query(`
|
||||
DELETE FROM user WHERE id = ?
|
||||
`,[userId])
|
||||
deletePromises.push(userDelete)
|
||||
|
||||
let tables = ['user_key', 'user_encrypted_search_index', 'attachment']
|
||||
tables.forEach(tableName => {
|
||||
|
||||
const query = `DELETE FROM ${tableName} WHERE user_id = ?`
|
||||
const deleteQuery = db.promise().query(query, [userId])
|
||||
deletePromises.push(deleteQuery)
|
||||
})
|
||||
|
||||
return Promise.all(deletePromises)
|
||||
}
|
||||
|
||||
User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let masterKey = null
|
||||
let testUserId = null
|
||||
|
||||
|
||||
const randomUsername = Math.random().toString(36).substring(2, 15);
|
||||
const randomPassword = '1'
|
||||
|
||||
User.login(testUserName, password)
|
||||
.then( ({ token, userId }) => {
|
||||
testUserId = userId
|
||||
|
||||
if(printResults) console.log('Test: Create/Login User '+testUserName+' - Pass')
|
||||
|
||||
return User.getMasterKey(testUserId, password)
|
||||
})
|
||||
.then(newMasterKey => {
|
||||
masterKey = newMasterKey
|
||||
|
||||
if(printResults) console.log('Test: Generate/Decrypt Master Key - Pass')
|
||||
|
||||
return User.generateKeypair(testUserId, masterKey)
|
||||
})
|
||||
.then(({publicKey, privateKey}) => {
|
||||
|
||||
const publicKeyMessage = 'Test: Public key decrypt - Pass'
|
||||
const privateKeyMessage = 'Test: Private key decrypt - Pass'
|
||||
|
||||
//Encrypt Message with private Key
|
||||
const privateKeyEncrypted = crypto.privateEncrypt(privateKey, Buffer.from(privateKeyMessage, 'utf8')).toString('base64')
|
||||
const decryptedPrivate = crypto.publicDecrypt(publicKey, Buffer.from(privateKeyEncrypted, 'base64'))
|
||||
//Conver back to a string
|
||||
if(printResults) console.log(decryptedPrivate.toString('utf8'))
|
||||
|
||||
//Encrypt with public key
|
||||
const pubEncrMsc = crypto.publicEncrypt(publicKey, Buffer.from(publicKeyMessage, 'utf8')).toString('base64')
|
||||
const publicDeccryptMessage = crypto.privateDecrypt(privateKey, Buffer.from(pubEncrMsc, 'base64') )
|
||||
//Convert it back to string
|
||||
if(printResults) console.log(publicDeccryptMessage.toString('utf8'))
|
||||
|
||||
resolve({testUserId, masterKey})
|
||||
})
|
||||
})
|
||||
}
|
@ -1,18 +1,19 @@
|
||||
var express = require('express')
|
||||
var router = express.Router()
|
||||
|
||||
let Note = require('@models/Note')
|
||||
let User = require('@models/User')
|
||||
let ShareNote = require('@models/ShareNote')
|
||||
let Notes = require('@models/Note');
|
||||
let ShareNote = require('@models/ShareNote');
|
||||
|
||||
let userId = null
|
||||
let masterKey = null
|
||||
let socket = null
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function setUserId (req, res, next) {
|
||||
if(req.headers.userId){
|
||||
userId = req.headers.userId
|
||||
masterKey = req.headers.masterKey
|
||||
}
|
||||
if(req.headers.socket){
|
||||
// socket = req.
|
||||
}
|
||||
|
||||
next()
|
||||
@ -22,67 +23,56 @@ router.use(function setUserId (req, res, next) {
|
||||
// Note actions
|
||||
//
|
||||
router.post('/get', function (req, res) {
|
||||
Note.get(userId, req.body.noteId, masterKey)
|
||||
// req.io.emit('welcome_homie', 'Welcome, dont poop from excitement')
|
||||
Notes.get(userId, req.body.noteId, req.body.password)
|
||||
.then( data => {
|
||||
//Join room when user opens note
|
||||
// req.io.join('note_room')
|
||||
res.send(data)
|
||||
})
|
||||
})
|
||||
|
||||
router.post('/delete', function (req, res) {
|
||||
Note.delete(userId, req.body.noteId)
|
||||
Notes.delete(userId, req.body.noteId)
|
||||
.then( data => res.send(data) )
|
||||
})
|
||||
|
||||
router.post('/create', function (req, res) {
|
||||
Note.create(userId, req.body.title, req.body.text, masterKey)
|
||||
Notes.create(userId, req.body.title, req.body.text)
|
||||
.then( id => res.send({id}) )
|
||||
})
|
||||
|
||||
router.post('/update', function (req, res) {
|
||||
Note.update(userId, req.body.noteId, req.body.text, req.body.title, req.body.color, req.body.pinned, req.body.archived, req.body.hash, masterKey)
|
||||
Notes.update(req.io, userId, req.body.noteId, req.body.text, req.body.title, req.body.color, req.body.pinned, req.body.archived, req.body.password, req.body.hint)
|
||||
.then( id => res.send({id}) )
|
||||
})
|
||||
|
||||
router.post('/search', function (req, res) {
|
||||
Note.search(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters, masterKey)
|
||||
.then( NoteAndTags => {
|
||||
res.send(NoteAndTags)
|
||||
Notes.search(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters)
|
||||
.then( notesAndTags => {
|
||||
res.send(notesAndTags)
|
||||
})
|
||||
})
|
||||
|
||||
router.post('/difftext', function (req, res) {
|
||||
Note.getDiffText(userId, req.body.noteId, req.body.text, req.body.updated)
|
||||
Notes.getDiffText(userId, req.body.noteId, req.body.text, req.body.updated)
|
||||
.then( fullDiffText => {
|
||||
//Response should be full diff text
|
||||
res.send(fullDiffText)
|
||||
})
|
||||
})
|
||||
|
||||
router.post('/reindex', function (req, res) {
|
||||
Note.reindex(userId, masterKey)
|
||||
.then( data => {
|
||||
res.send(data)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
//
|
||||
// Update single note attributes
|
||||
//
|
||||
router.post('/setpinned', function (req, res) {
|
||||
Note.setPinned(userId, req.body.noteId, req.body.pinned)
|
||||
Notes.setPinned(userId, req.body.noteId, req.body.pinned)
|
||||
.then( results => {
|
||||
res.send(results)
|
||||
})
|
||||
})
|
||||
router.post('/setarchived', function (req, res) {
|
||||
Note.setArchived(userId, req.body.noteId, req.body.archived)
|
||||
.then( results => {
|
||||
res.send(results)
|
||||
})
|
||||
})
|
||||
router.post('/settrashed', function (req, res) {
|
||||
Note.setTrashed(userId, req.body.noteId, req.body.trashed)
|
||||
Notes.setArchived(userId, req.body.noteId, req.body.archived)
|
||||
.then( results => {
|
||||
res.send(results)
|
||||
})
|
||||
@ -97,20 +87,18 @@ router.post('/getshareusers', function (req, res) {
|
||||
})
|
||||
|
||||
router.post('/shareadduser', function (req, res) {
|
||||
// ShareNote.addUser(userId, req.body.noteId, req.body.rawTextId, req.body.username, masterKey)
|
||||
User.getByUserName(req.body.username)
|
||||
.then( user => {
|
||||
return ShareNote.migrateNoteToShared(userId, req.body.noteId, user.id, masterKey)
|
||||
})
|
||||
ShareNote.addUser(userId, req.body.noteId, req.body.rawTextId, req.body.username)
|
||||
.then( ({success, shareUserId}) => {
|
||||
|
||||
//Emit update count event to user shared with - so they see the note in real time
|
||||
req.io.to(shareUserId).emit('update_counts')
|
||||
|
||||
res.send(success)
|
||||
})
|
||||
})
|
||||
|
||||
router.post('/shareremoveuser', function (req, res) {
|
||||
// (userId, noteId, shareNoteUserId, shareUserId, masterKey)
|
||||
ShareNote.removeUserFromShared(userId, req.body.noteId, req.body.shareUserNoteId, masterKey)
|
||||
ShareNote.removeUser(userId, req.body.noteId)
|
||||
.then(results => res.send(results))
|
||||
})
|
||||
|
||||
@ -118,14 +106,15 @@ router.post('/shareremoveuser', function (req, res) {
|
||||
//
|
||||
// Testing Action
|
||||
//
|
||||
//Reindex all Note. Not a very good function, not public
|
||||
//Reindex all notes. Not a very good function, not public
|
||||
router.get('/reindex5yu43prchuj903mrc', function (req, res) {
|
||||
|
||||
Note.migrateNoteTextToNewTable().then(status => {
|
||||
Notes.migrateNoteTextToNewTable().then(status => {
|
||||
return res.send(status)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
module.exports = router
|
@ -2,15 +2,12 @@ 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()
|
||||
@ -18,19 +15,19 @@ router.use(function setUserId (req, res, next) {
|
||||
|
||||
//Get quick note text
|
||||
router.post('/get', function (req, res) {
|
||||
QuickNote.get(userId, masterKey)
|
||||
QuickNote.get(userId)
|
||||
.then( data => res.send(data) )
|
||||
})
|
||||
|
||||
//Push text to quick note
|
||||
router.post('/update', function (req, res) {
|
||||
QuickNote.update(userId, req.body.pushText, masterKey)
|
||||
QuickNote.update(userId, req.body.pushText)
|
||||
.then( data => res.send(data) )
|
||||
})
|
||||
|
||||
//Push text to quick note
|
||||
router.post('/new', function (req, res) {
|
||||
QuickNote.newNote(userId)
|
||||
//Change quick note to a new note
|
||||
router.post('/change', function (req, res) {
|
||||
QuickNote.change(userId, req.body.noteId)
|
||||
.then( data => res.send(data) )
|
||||
})
|
||||
|
||||
|
@ -2,7 +2,6 @@ var express = require('express')
|
||||
var router = express.Router()
|
||||
|
||||
let User = require('@models/User');
|
||||
const cs = require('@helpers/CryptoString')
|
||||
|
||||
// middleware that is specific to this router
|
||||
router.use(function timeLog (req, res, next) {
|
||||
@ -32,19 +31,19 @@ router.post('/login', function (req, res) {
|
||||
}
|
||||
|
||||
User.login(username, password)
|
||||
.then( ({token, userId}) => {
|
||||
.then(function(loginToken){
|
||||
|
||||
returnData['username'] = username
|
||||
returnData['token'] = token
|
||||
returnData['success'] = true
|
||||
//Return json web token to user
|
||||
returnData['success'] = true
|
||||
returnData['token'] = loginToken
|
||||
returnData['username'] = username
|
||||
|
||||
res.send(returnData)
|
||||
return
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
res.send(returnData)
|
||||
})
|
||||
res.send(returnData)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
res.send(returnData)
|
||||
})
|
||||
})
|
||||
|
||||
// fetch counts of users notes
|
||||
|
Loading…
Reference in New Issue
Block a user