* Delete Crunch Menu Component
* Disabled Quick Note * Note crunches over when menu is open * Added a cool loader * Remomoved locked notes * Added full note encryption * Added encrypted search index * Added encrypted shared notes * Made search bar have a clear and search button * Tags only loade when clicking on the tags menu * Tweaked home page to be a little more sane * built out some gigantic test cases * simplified a lot of things to make entire app easier to maintain
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
<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>
|
@@ -199,11 +199,11 @@
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="menu-section" v-if="loggedIn">
|
||||
<!-- <div class="menu-section" v-if="loggedIn">
|
||||
<router-link v-if="loggedIn" exact-active-class="active" class="menu-item menu-button" to="/quick">
|
||||
<i class="paper plane outline icon"></i>Quick Note
|
||||
</router-link>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="menu-section" v-if="!loggedIn">
|
||||
<router-link v-if="!loggedIn" class="menu-item menu-button" exact-active-class="active" to="/">
|
||||
@@ -257,7 +257,7 @@
|
||||
},
|
||||
data: function(){
|
||||
return {
|
||||
version: '1.0.5',
|
||||
version: '2.1.0',
|
||||
username: '',
|
||||
collapsed: false,
|
||||
mobile: false,
|
||||
|
67
client/src/components/LoadingIconComponent.vue
Normal file
67
client/src/components/LoadingIconComponent.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="loading-container">
|
||||
<svg version="1.1" id="L6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
|
||||
<rect fill="none" :stroke="$store.getters.getIsNightMode?'#FFF':'#16ab39'" stroke-width="4" x="25" y="25" width="50" height="50" rx="5">
|
||||
<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?'#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>
|
@@ -2,9 +2,9 @@
|
||||
<!-- change class to .master-note-edit to have it popup on the screen -->
|
||||
<div
|
||||
id="InputNotes"
|
||||
class="master-note-edit"
|
||||
class="master-note-edit full-focus"
|
||||
@keyup.esc="close()"
|
||||
:class="[{ 'full-focus':(fullFocusEditor) }, 'position-'+position ]"
|
||||
:class="[ 'position-'+position ]"
|
||||
>
|
||||
|
||||
<!-- Main Menu -->
|
||||
@@ -100,6 +100,8 @@
|
||||
<div class="edit-button" v-on:click="openEditAttachment" data-tooltip="Files" data-position="bottom center" data-inverted>
|
||||
<i class="folder icon"></i>
|
||||
</div>
|
||||
|
||||
<span>{{ statusText }}</span>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -110,19 +112,20 @@
|
||||
|
||||
<div class="bottom-edit-menu"></div>
|
||||
|
||||
<div class="input-container-wrapper" :class="{ 'size-down':(sizeDown == true)}" >
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div v-if="loading" class="loading-note">
|
||||
<div class="loading-text">
|
||||
Decrypting Note &
|
||||
{{loadingMessage}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-container-wrapper" :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true)}" >
|
||||
|
||||
<!-- 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"
|
||||
@@ -134,45 +137,8 @@
|
||||
v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input">
|
||||
</textarea>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Squire Box -->
|
||||
<div id="squire-id" class="squire-box" ref="squirebox" placeholder="Note Text"></div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -287,11 +253,13 @@
|
||||
'share-note-component': () => import('@/components/ShareNoteComponent.vue'),
|
||||
|
||||
'color-tooltip':require('@/components/TextColorTooltipComponent.vue').default,
|
||||
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default
|
||||
'nm-button':require('@/components/NoteMenuButtonComponent.vue').default,
|
||||
'loading-icon':require('@/components/LoadingIconComponent.vue').default,
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
loading: true,
|
||||
forceShowLoading: true,
|
||||
loadingMessage: 'Loading Note',
|
||||
currentNoteId: 0,
|
||||
modified: false,
|
||||
@@ -315,13 +283,8 @@
|
||||
styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
|
||||
|
||||
sizeDown: false, //Used to animate close state
|
||||
|
||||
colorPickerLocation: null,
|
||||
|
||||
fullFocusEditor: true, //Initialized editor instance
|
||||
|
||||
//Settings vars
|
||||
showAllSettings: true,
|
||||
lastVisibilityState: null,
|
||||
|
||||
//All the squire settings
|
||||
@@ -329,23 +292,12 @@
|
||||
// pastFocusedNode: null,
|
||||
usersOnNote: 0,
|
||||
|
||||
sideMenuOpen: false,
|
||||
tags: false,
|
||||
colors: false,
|
||||
images: false,
|
||||
options: false,
|
||||
colorpicker: false,
|
||||
|
||||
//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: {
|
||||
@@ -372,9 +324,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
|
||||
|
||||
@@ -382,7 +334,7 @@
|
||||
if(newVal.openMenu){
|
||||
//Only modify menu boolean if its defined
|
||||
if(typeof this[newVal.openMenu] == 'boolean'){
|
||||
|
||||
this.sideMenuOpen = true
|
||||
this[newVal.openMenu] = true
|
||||
}
|
||||
}
|
||||
@@ -399,22 +351,23 @@
|
||||
},
|
||||
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)
|
||||
|
||||
this.editor.destroy()
|
||||
if(this.editor){
|
||||
this.editor.destroy()
|
||||
}
|
||||
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
setTimeout(()=>{
|
||||
this.forceShowLoading = false
|
||||
}, 500)
|
||||
|
||||
document.addEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||
|
||||
this.$nextTick(() => {
|
||||
@@ -429,6 +382,9 @@
|
||||
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, thats annoying
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
// this.editor.focus()
|
||||
@@ -826,12 +782,12 @@
|
||||
|
||||
//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, 'password':this.hashedPass })
|
||||
axios.post('/api/note/get', { 'noteId': this.noteid })
|
||||
.then(response => {
|
||||
|
||||
//Block notes you don't have access to from opening
|
||||
if(response.data === false){
|
||||
this.$bus.$emit('notification', 'Invalid Note')
|
||||
this.$bus.$emit('notification', 'Error opening Note')
|
||||
this.close(true)
|
||||
return
|
||||
}
|
||||
@@ -840,7 +796,6 @@
|
||||
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
|
||||
@@ -852,7 +807,6 @@
|
||||
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)
|
||||
@@ -866,29 +820,12 @@
|
||||
|
||||
this.loading = false
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1064,7 +1001,7 @@
|
||||
},
|
||||
onKeyup(){
|
||||
|
||||
this.statusText = 'Save'
|
||||
this.statusText = ''
|
||||
|
||||
this.diffText()
|
||||
|
||||
@@ -1088,23 +1025,16 @@
|
||||
|
||||
// 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()
|
||||
if( this.lastNoteHash == this.hashString( currentNoteText )){
|
||||
const currentHash = this.hashString( currentNoteText )
|
||||
if( this.lastNoteHash == currentHash){
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1115,10 +1045,11 @@
|
||||
'color': JSON.stringify(this.styleObject), //Save little json color object
|
||||
'pinned': this.pinned,
|
||||
'archived': this.archived,
|
||||
'password': this.hashedPass,
|
||||
'hint': this.passwordHint,
|
||||
'hash': currentHash,
|
||||
}
|
||||
|
||||
console.log('Save Hash', currentHash)
|
||||
|
||||
this.statusText = 'Saving'
|
||||
axios.post('/api/note/update', postData).then( response => {
|
||||
this.statusText = 'Saved'
|
||||
@@ -1126,8 +1057,8 @@
|
||||
this.modified = true
|
||||
|
||||
//Update last saved note hash
|
||||
this.lastNoteHash = this.hashString( currentNoteText )
|
||||
this.startAutolockTimer()
|
||||
// this.lastNoteHash = this.hashString( currentNoteText )
|
||||
this.lastNoteHash = currentHash
|
||||
return resolve(true)
|
||||
})
|
||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Save Note') })
|
||||
@@ -1135,7 +1066,9 @@
|
||||
},
|
||||
checkForUpdatedNote(){
|
||||
|
||||
// return
|
||||
//Ignore visibility changes, handle this with socket IO
|
||||
//Just keep it always up to date if user is on note
|
||||
return
|
||||
|
||||
//If user leaves page then returns to page, reload the first batch
|
||||
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
|
||||
@@ -1169,18 +1102,15 @@
|
||||
//Track visibility state
|
||||
this.lastVisibilityState = document.visibilityState
|
||||
},
|
||||
hashString(text){
|
||||
hashString(inText){
|
||||
|
||||
text = this.noteTitle + text
|
||||
let text = this.noteTitle + inText
|
||||
|
||||
var hash = 0;
|
||||
let 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;
|
||||
@@ -1217,6 +1147,11 @@
|
||||
},
|
||||
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
|
||||
@@ -1231,62 +1166,6 @@
|
||||
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
|
||||
@@ -1295,15 +1174,6 @@
|
||||
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>
|
||||
@@ -1343,6 +1213,7 @@
|
||||
background-color: var(--background_color);
|
||||
border: 1px solid var(--menu-accent);;
|
||||
margin: 45px 0 45px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1438,18 +1309,18 @@
|
||||
}
|
||||
.loading-note {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
bottom: 20%;
|
||||
background: transparent;
|
||||
color: #5e6268;;
|
||||
font-size: 1.3em;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
background: var(--background_color);
|
||||
/*opacity: 0.;*/
|
||||
z-index: 1;
|
||||
}
|
||||
.loading-text {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
top: 200px;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@@ -1464,6 +1335,10 @@
|
||||
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;
|
||||
@@ -1580,6 +1455,24 @@
|
||||
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>
|
@@ -6,6 +6,12 @@
|
||||
right: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
.floating-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 4px;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<span>
|
||||
@@ -13,12 +19,18 @@
|
||||
<div class="ui form" v-if="!$store.getters.getIsUserOnMobile">
|
||||
<!-- normal search menu -->
|
||||
<div class="ui left icon fluid input">
|
||||
<input v-model="searchTerm" @keyup.enter="search" placeholder="Search Notes and Files" ref="searchInput"/>
|
||||
<input ref="desktopSearch" v-model="searchTerm" @keyup.enter="search" placeholder="Search Notes and Files" />
|
||||
<i class="search icon"></i>
|
||||
</div>
|
||||
<div class="floating-button" v-if="searchTerm.length > 0 && !searched">
|
||||
<div class="ui green compact button" v-on:click="search()">Search</div>
|
||||
</div>
|
||||
<div class="floating-button" v-if="searchTerm.length > 0 && searched">
|
||||
<div class="ui grey compact button" v-on:click="clear()">Clear</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Only show button on mobile -->
|
||||
<span class="ui basic icon button" v-on:click="openFloatingSearch" v-if="$store.getters.getIsUserOnMobile">
|
||||
<i class="green search icon"></i>
|
||||
</span>
|
||||
@@ -50,9 +62,8 @@
|
||||
data: function(){
|
||||
return {
|
||||
searchTerm: '',
|
||||
searchTimeout: null,
|
||||
searchDebounceDuration: 300,
|
||||
showFixedSearch: false,
|
||||
searched: false,
|
||||
}
|
||||
},
|
||||
beforeCreate: function(){
|
||||
@@ -76,17 +87,22 @@
|
||||
})
|
||||
}
|
||||
},
|
||||
searchKeyUp(){
|
||||
//This event is not triggered on mobile
|
||||
clearTimeout(this.searchTimeout)
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.search()
|
||||
}, this.searchDebounceDuration)
|
||||
clear(){
|
||||
this.searched = false
|
||||
this.searchTerm = ''
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
this.$refs.desktopSearch.focus()
|
||||
}
|
||||
this.$bus.$emit('note_reload')
|
||||
},
|
||||
search(){
|
||||
this.searched = true
|
||||
if(this.$store.getters.getIsUserOnMobile){
|
||||
this.$refs.fixedSearch.blur()
|
||||
}
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
this.$refs.desktopSearch.focus()
|
||||
}
|
||||
this.$bus.$emit('update_search_term', this.searchTerm)
|
||||
},
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 55%;
|
||||
right: 50%;
|
||||
bottom: 0;
|
||||
z-index: 1020;
|
||||
overflow: hidden;
|
||||
|
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="ui basic button shrinking">
|
||||
<div class="button-fix">
|
||||
|
||||
|
||||
<!-- Dropdown Button -->
|
||||
<span v-if="activeTags.length == 0" v-on:click="menuOpen = true">
|
||||
<span v-if="activeTags.length == 0" v-on:click="openMenu()" class="ui basic button shrinking">
|
||||
<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="toggleActive()">
|
||||
<span v-if="activeTags.length > 0" v-on:click="openMenu()" class="ui basic button shrinking">
|
||||
<i class="green tag icon"></i>
|
||||
{{ getActiveTag() }}
|
||||
<i class="caret right icon"></i>
|
||||
@@ -18,13 +18,26 @@
|
||||
<!-- hidden dropdown menu -->
|
||||
<div class="dropdown-menu" v-if="menuOpen">
|
||||
<div class="ui raised segment">
|
||||
<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 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="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,13 +47,27 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
export default {
|
||||
name: 'TagDisplay',
|
||||
props: [ 'tags', 'activeTags' ],
|
||||
props: [ 'activeTags' ],
|
||||
data () {
|
||||
return {
|
||||
loadedTags: [],
|
||||
menuOpen: false,
|
||||
}
|
||||
},
|
||||
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]
|
||||
@@ -63,7 +90,7 @@
|
||||
return text
|
||||
}
|
||||
|
||||
this.tags.forEach(tag => {
|
||||
this.loadedTags.forEach(tag => {
|
||||
if( this.activeTags.includes(tag.id) ){
|
||||
text = this.ucWords(tag.text)
|
||||
}
|
||||
@@ -72,27 +99,32 @@
|
||||
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: 0;
|
||||
right: 0;
|
||||
max-width: 600px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
/*min-width: 200px;*/
|
||||
/*max-width: 100%;*/
|
||||
width: 340px;
|
||||
text-align: left;
|
||||
}
|
||||
.dropdown-menu .label {
|
||||
.dropdown-menu .button {
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
||||
.shade {
|
||||
@@ -102,7 +134,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1004;
|
||||
background-color: transparent;
|
||||
background-color: #0000008a;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<!-- Content copied from note -->
|
||||
<!-- https://www.solidscribe.com/#/notes/open/552 -->
|
||||
|
||||
<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>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>
|
||||
|
||||
<!-- content copied from note -->
|
||||
</div>
|
||||
|
@@ -32,8 +32,7 @@
|
||||
100%{ opacity: 0.9; }
|
||||
}
|
||||
.subtext {
|
||||
border-bottom: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
padding: 0 0 0 10px;
|
||||
@@ -121,9 +120,8 @@
|
||||
</h2>
|
||||
|
||||
<h3 class="subtext">
|
||||
Take Notes Like Never Before<i class="i cursor icon blinking"></i>
|
||||
An easy, encrypted Note App<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>
|
||||
|
||||
@@ -134,22 +132,21 @@
|
||||
</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>Register
|
||||
<i class="plug icon"></i>Sign Up
|
||||
</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>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>
|
||||
<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>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
|
||||
@@ -161,29 +158,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>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>
|
||||
<h2>Tools to organize thousands of notes</h2>
|
||||
<h3>Tag, Pin, Color, Archive, Attach Images and Search notes or links in notes</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered green row">
|
||||
<div class="six wide column">
|
||||
<h2>Unbridled Input</h2>
|
||||
<h3>Revolutionary technology allows the use of any keyboard with up to 395 keys</h3>
|
||||
<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>
|
||||
</div>
|
||||
<div class="six wide 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>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide right aligned 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 class="six wide column">
|
||||
<h2>Solutions with the Internet</h2>
|
||||
<h3>With the power to save any combination of letters, you can easily inscribe thoughts</h3>
|
||||
<h2>Extremely accessible</h2>
|
||||
<h3>Works on mobile or desktop browsers. <br>Behaves like an installed app on mobile phones.</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +191,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/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>
|
||||
|
||||
@@ -242,7 +239,7 @@
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide right aligned 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 class="six wide column">
|
||||
<h2>Data Backups</h2>
|
||||
|
@@ -26,14 +26,7 @@
|
||||
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
|
||||
</div>
|
||||
|
||||
<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)"
|
||||
/>
|
||||
@@ -47,10 +40,8 @@
|
||||
|
||||
<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>Back to All Notes
|
||||
<span class="ui fluid green button" @click="reset">
|
||||
<i class="arrow circle left icon"></i>Show All Notes
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +49,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h2 v-if="searchTerm.length > 0 && !loadingInProgress">
|
||||
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
|
||||
</h2>
|
||||
|
||||
<h2 v-if="fastFilters['withLinks'] == 1">Notes with Links</h2>
|
||||
<h2 v-if="fastFilters['withTags'] == 1">Notes with Tags</h2>
|
||||
@@ -94,6 +87,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<loading-icon v-if="loadingInProgress" message="Decrypting Notes" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -143,12 +139,14 @@
|
||||
'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: [],
|
||||
@@ -161,7 +159,6 @@
|
||||
batchOffset: 0, //Tracks the current batch that has been loaded
|
||||
loadingBatchTimeout: null, //Limit how quickly batches can be loaded
|
||||
loadingInProgress: false,
|
||||
fetchTags: false,
|
||||
scrollLoadEnabled: true,
|
||||
|
||||
//Clear button is not visible
|
||||
@@ -193,8 +190,7 @@
|
||||
'shared': ['envelope outline', 'Received Notes'],
|
||||
'sent': ['paper plane outline', 'Shared Notes'],
|
||||
'notes': ['file','Notes'],
|
||||
'highlights': ['paragraph', 'Found In Text'],
|
||||
'locked': ['lock', 'Password Protected']
|
||||
'highlights': ['paragraph', 'Found In Text']
|
||||
},
|
||||
noteSections: {
|
||||
pinned: [],
|
||||
@@ -202,8 +198,7 @@
|
||||
shared:[],
|
||||
sent:[],
|
||||
notes: [],
|
||||
highlights: [],
|
||||
locked: []
|
||||
highlights: []
|
||||
},
|
||||
|
||||
}
|
||||
@@ -212,6 +207,13 @@
|
||||
|
||||
this.$parent.loginGateway()
|
||||
|
||||
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
|
||||
//Do not update note if its open
|
||||
if(this.activeNoteId1 != noteId){
|
||||
console.log('notePage: update display of note ', noteId)
|
||||
}
|
||||
})
|
||||
|
||||
//Update totals for app
|
||||
this.$store.dispatch('fetchAndUpdateUserTotals')
|
||||
|
||||
@@ -230,7 +232,7 @@
|
||||
|
||||
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){
|
||||
@@ -245,7 +247,7 @@
|
||||
this.fastFilters = newFilter
|
||||
//Fast filters always return all the results and tags
|
||||
this.search(true, this.batchSize, false).then( () => {
|
||||
return this.fetchUserTags()
|
||||
// return
|
||||
})
|
||||
})
|
||||
|
||||
@@ -258,7 +260,7 @@
|
||||
console.log('Search attachments disabled for now')
|
||||
// this.searchAttachments()
|
||||
|
||||
return this.fetchUserTags()
|
||||
// return
|
||||
})
|
||||
})
|
||||
|
||||
@@ -381,11 +383,7 @@
|
||||
},
|
||||
toggleTagFilter(tagId){
|
||||
|
||||
if(this.searchTags.includes(tagId)){
|
||||
this.searchTags.splice( this.searchTags.indexOf(tagId) , 1);
|
||||
} else {
|
||||
this.searchTags.push(tagId)
|
||||
}
|
||||
this.searchTags = [tagId]
|
||||
|
||||
//Reset note set and load up notes and tags
|
||||
if(this.searchTags.length > 0){
|
||||
@@ -458,13 +456,16 @@
|
||||
},
|
||||
visibiltyChangeAction(event){
|
||||
|
||||
//Fuck this shit, just use web sockets
|
||||
return
|
||||
|
||||
//@TODO - phase this out, 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 this.fetchUserTags()
|
||||
// return
|
||||
})
|
||||
}
|
||||
|
||||
@@ -511,7 +512,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
|
||||
@@ -556,14 +557,19 @@
|
||||
|
||||
//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
|
||||
@@ -592,11 +598,11 @@
|
||||
|
||||
//Perform search - or die
|
||||
this.loadingInProgress = true
|
||||
console.time('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
// console.time('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
axios.post('/api/note/search', postData)
|
||||
.then(response => {
|
||||
|
||||
console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
|
||||
|
||||
//Save the number of notes just loaded
|
||||
this.batchOffset += response.data.notes.length
|
||||
@@ -605,8 +611,12 @@
|
||||
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.tags.length > 0){
|
||||
// this.commonTags = response.data.tags
|
||||
// }
|
||||
|
||||
if(response.data.total > 0){
|
||||
this.searchResultsCount = response.data.total
|
||||
}
|
||||
|
||||
this.loadingInProgress = false
|
||||
@@ -653,10 +663,6 @@
|
||||
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
|
||||
@@ -685,9 +691,8 @@
|
||||
//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)
|
||||
})
|
||||
@@ -695,23 +700,6 @@
|
||||
//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
|
||||
|
Reference in New Issue
Block a user