Compare commits
84 Commits
dev
...
789a4e47d4
Author | SHA1 | Date | |
---|---|---|---|
|
789a4e47d4 | ||
|
952a1dd1b1 | ||
|
e5c117bbdb | ||
|
178a7dfc2c | ||
|
f12be22765 | ||
|
b7d22cb7fc | ||
|
df5e9f8c3b | ||
|
7f5f4bea39 | ||
|
c972430ef4 | ||
|
6d0187ee0a | ||
|
00500ecc33 | ||
|
848c86327a | ||
|
d4be0d6471 | ||
|
c99828dbad | ||
|
217f052e63 | ||
|
4e93bf23fb | ||
|
02899b3b75 | ||
|
bcc7d60fd3 | ||
|
df4afeafc6 | ||
|
1d891ea734 | ||
|
3447b2e0e6 | ||
|
e7d1cc7bc9 | ||
|
47fff0e1ee | ||
|
cca89a60d8 | ||
|
a56ade5b08 | ||
|
39f9a16fff | ||
|
6740200a33 | ||
|
e4fae23623 | ||
|
56d4664d0d | ||
|
d349fb8328 | ||
|
09cccf1983 | ||
|
97e7b011d9 | ||
|
fc1f3f81fe | ||
|
9c4fff7913 | ||
|
b0eee636b5 | ||
|
2861042485 | ||
|
1005913c0b | ||
|
c8033588dd | ||
|
bcb31e9af5 | ||
|
596e57eaf0 | ||
|
d91b0735fd | ||
|
71f909fb76 | ||
|
a44bca204c | ||
|
7c15427b3d | ||
|
ed4a5e5291 | ||
|
c11f1b1b6f | ||
|
0b5675e000 | ||
|
9309ea0821 | ||
|
5975ab6d68 | ||
|
3d6e527e3a | ||
|
88a0c7b26a | ||
|
1b14a8fd31 | ||
|
4cc6014581 | ||
|
196224d0b8 | ||
|
795f1b7d76 | ||
|
1600bd132c | ||
|
2a379f8a4e | ||
|
3ed26bcc03 | ||
|
282cbfe7bc | ||
|
b50aecdfca | ||
|
98f4695739 | ||
|
984ac6ccff | ||
|
f63c0c0d60 | ||
|
a478cbe11c | ||
|
99b69c234f | ||
|
f0b6d7b85e | ||
|
596703a963 | ||
|
21f606b480 | ||
|
b961a69a91 | ||
|
8d3762e106 | ||
|
b2f241dbba | ||
|
8833a213a7 | ||
|
f833845452 | ||
|
05152cd5a4 | ||
|
cf3289aac6 | ||
|
acf72ca67e | ||
|
7f93925f74 | ||
|
d2c1dedffb | ||
|
003c7e32b1 | ||
|
de646cf1de | ||
|
2828cc9462 | ||
|
f99d6ed430 | ||
|
4216c1825e | ||
|
8d07a8e11a |
@@ -1,24 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Take all variables in .env and turn them into local variables for this script
|
|
||||||
source ~/.env
|
|
||||||
|
|
||||||
BACKUPDIR="/home/mab/databaseBackupSolidScribe"
|
BACKUPDIR="/home/mab/databaseBackupSolidScribe"
|
||||||
|
|
||||||
mkdir -p $BACKUPDIR
|
mkdir -p $BACKUPDIR
|
||||||
cd $BACKUPDIR
|
cd $BACKUPDIR
|
||||||
|
|
||||||
NOW=$(date +"%Y-%m-%d_%H-%M")
|
NOW=$(date +"%Y-%m-%d_%H-%M")
|
||||||
ssh mab@solidscribe.com -p 13328 "mysqldump --all-databases --single-transaction --user root -p$PROD_DB_PASS" > "backup-$NOW.sql"
|
ssh mab@solidscribe.com -p 13328 "mysqldump --all-databases --single-transaction --user root -pRootPass1234!" > "backup-$NOW.sql"
|
||||||
gzip "backup-$NOW.sql"
|
gzip "backup-$NOW.sql"
|
||||||
|
|
||||||
# cp "backup-$NOW.sql" "/mnt/Windows Data/DatabaseBackups/backup-$NOW.sql"
|
# cp "backup-$NOW.sql" "/mnt/Windows Data/DatabaseBackups/backup-$NOW.sql"
|
||||||
|
|
||||||
echo "Database Backup Complete on $NOW"
|
echo "Database Backup Complete on $NOW"
|
||||||
|
|
||||||
# Delete all but last 8 files
|
|
||||||
ls -tp | grep -v '/$' | tail -n +9 | tr '\n' '\0' | xargs -0 rm --
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Restore DB
|
# Restore DB
|
||||||
##
|
##
|
||||||
@@ -31,4 +25,4 @@ ls -tp | grep -v '/$' | tail -n +9 | tr '\n' '\0' | xargs -0 rm --
|
|||||||
# Crontab setup
|
# Crontab setup
|
||||||
##
|
##
|
||||||
|
|
||||||
# 0 2 * * * /bin/bash /home/mab/ss/backupDatabase.sh 1> /home/mab/databaseBackupLog.txt
|
# 0 2 * * * /bin/bash /home/mab/ss/backupDatabase.sh 1> /home/mab/databaseBackupLog.txt
|
@@ -17,14 +17,13 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
/* overflow-x: hidden;*/
|
overflow-x: hidden;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
background: green;
|
background: #FFFFFF;
|
||||||
font-family: 'Roboto', system-ui, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
font-family: 'Roboto', system-ui, -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.4285em;
|
line-height: 1.4285em;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -96,7 +95,7 @@ body {
|
|||||||
font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
#app {
|
#app {
|
||||||
/* background: var(--body_bg_color);*/
|
background: var(--body_bg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.segment {
|
.ui.segment {
|
||||||
@@ -147,9 +146,6 @@ body {
|
|||||||
.ui.dividing.header {
|
.ui.dividing.header {
|
||||||
border-bottom-color: var(--dark_border_color);
|
border-bottom-color: var(--dark_border_color);
|
||||||
}
|
}
|
||||||
.ui.dividing.header > .sub.header {
|
|
||||||
color: var(--dark_border_color);
|
|
||||||
}
|
|
||||||
.ui.icon.input > i.icon {
|
.ui.icon.input > i.icon {
|
||||||
color: var(--text_color);
|
color: var(--text_color);
|
||||||
}
|
}
|
||||||
@@ -184,15 +180,10 @@ i.green.icon.icon.icon.icon, i.green.icon.icon.icon.icon.icon {
|
|||||||
.button {
|
.button {
|
||||||
box-shadow: 2px 2px 4px -2px rgba(40, 40, 40, 0.89) !important;
|
box-shadow: 2px 2px 4px -2px rgba(40, 40, 40, 0.89) !important;
|
||||||
transition: all 0.9s ease;
|
transition: all 0.9s ease;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.button:hover {
|
.button:hover {
|
||||||
box-shadow: 3px 2px 3px -2px rgba(40, 40, 40, 0.95) !important;
|
box-shadow: 3px 2px 5px -2px rgba(40, 40, 40, 0.95) !important;
|
||||||
}
|
}
|
||||||
.button:active {
|
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.green.buttons, .ui.green.button, .ui.green.button:hover {
|
.ui.green.buttons, .ui.green.button, .ui.green.button:hover {
|
||||||
background-color: var(--main-accent);
|
background-color: var(--main-accent);
|
||||||
}
|
}
|
||||||
@@ -592,15 +583,6 @@ padding-right: 10px;
|
|||||||
color: var(--main-accent);
|
color: var(--main-accent);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove indent line on mobile */
|
|
||||||
.note-card-text > ol > ol,
|
|
||||||
.squire-box > ol > ol,
|
|
||||||
.note-card-text > ul > ul,
|
|
||||||
.squire-box > ul > ul
|
|
||||||
{
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-height: 75px;
|
max-height: 100px;
|
||||||
}
|
}
|
||||||
.image-placeholder:after {
|
.image-placeholder:after {
|
||||||
content: 'No Image';
|
content: 'No Image';
|
||||||
@@ -89,14 +89,7 @@
|
|||||||
<!-- image and text -->
|
<!-- image and text -->
|
||||||
<div class="six wide center aligned middle aligned column">
|
<div class="six wide center aligned middle aligned column">
|
||||||
<a :href="linkUrl" target="_blank" >
|
<a :href="linkUrl" target="_blank" >
|
||||||
<img v-if="item.file_location" class="attachment-image"
|
<img v-if="item.file_location" class="attachment-image" :src="`/api/static/thumb_${item.file_location}`">
|
||||||
onerror="
|
|
||||||
this.onerror=null;
|
|
||||||
this.src='/api/static/assets/marketing/void.svg';
|
|
||||||
this.classList.add('image-placeholder');
|
|
||||||
this.insertAdjacentText('afterend', 'Image not found');
|
|
||||||
"
|
|
||||||
:src="`/api/static/thumb_${item.file_location}`">
|
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<img class="image-placeholder" loading="lazy" src="/api/static/assets/marketing/void.svg">
|
<img class="image-placeholder" loading="lazy" src="/api/static/assets/marketing/void.svg">
|
||||||
No Image
|
No Image
|
||||||
@@ -117,16 +110,11 @@
|
|||||||
<a class="link" :href="linkUrl" target="_blank">{{linkText}}</a>
|
<a class="link" :href="linkUrl" target="_blank">{{linkText}}</a>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<div v-if="item.note_id" class="ui small compact basic button" v-on:click="openNote">
|
<div class="ui small compact basic button" v-on:click="openNote">
|
||||||
<i class="file outline icon"></i>
|
<i class="file outline icon"></i>
|
||||||
Open Note
|
Open Note
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!item.note_id" class="ui small compact basic disabled button">
|
<div class="ui small compact basic button" v-on:click="openEditAttachments"
|
||||||
<i class="angle double up icon"></i>
|
|
||||||
Pushed from Web
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="item.note_id" class="ui small compact basic button" v-on:click="openEditAttachments"
|
|
||||||
:class="{ 'disabled':this.searchParams.noteId }">
|
:class="{ 'disabled':this.searchParams.noteId }">
|
||||||
<i class="folder open outline icon"></i>
|
<i class="folder open outline icon"></i>
|
||||||
Note Files
|
Note Files
|
||||||
@@ -183,9 +171,6 @@
|
|||||||
this.checkKeyup()
|
this.checkKeyup()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updated: function(){
|
|
||||||
this.checkKeyup()
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
checkKeyup(){
|
checkKeyup(){
|
||||||
let elm = this.$refs.edit
|
let elm = this.$refs.edit
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: block;
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 900;
|
z-index: 111;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: rgba(0,0,0,0.7);
|
background-color: rgba(0,0,0,0.7);
|
||||||
z-index: 899;
|
z-index: 100;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.top-menu-bar {
|
.top-menu-bar {
|
||||||
@@ -87,7 +87,6 @@
|
|||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.place-holder {
|
.place-holder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -527,11 +526,8 @@
|
|||||||
location.reload(true)
|
location.reload(true)
|
||||||
},
|
},
|
||||||
getVersionIcon(){
|
getVersionIcon(){
|
||||||
if(!this.version){
|
|
||||||
return 'radiation alternate'
|
|
||||||
}
|
|
||||||
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', 'ghost', 'dna', 'burn', 'brain', 'moon', 'torii gate']
|
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', 'ghost', 'dna', 'burn', 'brain', 'moon', 'torii gate']
|
||||||
const index = ( parseInt(String(this.version).replace(/\./g,'')) % (icons.length))
|
const index = ( parseInt(this.version.replace(/\./g,'')) % (icons.length))
|
||||||
return icons[index]
|
return icons[index]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<div>
|
<div>
|
||||||
|
|
||||||
<!-- thicc form display -->
|
<!-- thicc form display -->
|
||||||
<div v-if="!thin" class="ui large form" v-on:keyup.enter="register">
|
<div v-if="!thin" class="ui large form" v-on:keyup.enter="register()">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui input">
|
<div class="ui input">
|
||||||
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
|
<input ref="nameForm" v-model="username" type="text" name="email" placeholder="Username or E-mail">
|
||||||
@@ -15,11 +15,6 @@
|
|||||||
<input v-model="password" type="password" name="password" placeholder="Password">
|
<input v-model="password" type="password" name="password" placeholder="Password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<div class="ui input">
|
|
||||||
<input v-model="password2" type="password" name="password2" placeholder="Re-type Password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field" v-if="require2FA">
|
<div class="field" v-if="require2FA">
|
||||||
<div class="ui input">
|
<div class="ui input">
|
||||||
<input v-model="authToken" ref="authForm" type="text" name="authToken" placeholder="Authorization Token">
|
<input v-model="authToken" ref="authForm" type="text" name="authToken" placeholder="Authorization Token">
|
||||||
@@ -29,10 +24,17 @@
|
|||||||
<div class="ui fluid buttons">
|
<div class="ui fluid buttons">
|
||||||
|
|
||||||
|
|
||||||
<div v-on:click="register" class="ui green button" :class="{ 'disabled':(username.length == 0 || password.length == 0)}">
|
<div v-on:click="register()" class="ui green button" :class="{ 'disabled':(username.length == 0 || password.length == 0)}">
|
||||||
<i class="plug icon"></i>
|
<i class="plug icon"></i>
|
||||||
Sign Up
|
Sign Up
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="or"></div>
|
||||||
|
|
||||||
|
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="login()" class="ui button">
|
||||||
|
<i class="power icon"></i>
|
||||||
|
Login
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,12 +49,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Thin form display -->
|
<!-- Thin form display -->
|
||||||
<div v-if="thin" class="ui small form" v-on:keyup.enter="login">
|
<div v-if="thin" class="ui small form" v-on:keyup.enter="login()">
|
||||||
|
|
||||||
<div v-if="!require2FA" class="field"><!-- hide this field if someone is logging in with 2FA -->
|
<div v-if="!require2FA" class="field"><!-- hide this field if someone is logging in with 2FA -->
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="ui sixteen wide center aligned column">
|
<div class="ui sixteen wide center aligned column">
|
||||||
<div v-on:click="register" class="ui green button">
|
<div v-on:click="register()" class="ui green button">
|
||||||
<i class="plug icon"></i>
|
<i class="plug icon"></i>
|
||||||
Sign Up Now!
|
Sign Up Now!
|
||||||
</div>
|
</div>
|
||||||
@@ -85,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div v-on:click="login" class="ui fluid button">
|
<div v-on:click="login()" class="ui fluid button">
|
||||||
<i class="power icon"></i>
|
<i class="power icon"></i>
|
||||||
Login
|
Login
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +128,6 @@
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
password2: '',
|
|
||||||
authToken: '',
|
authToken: '',
|
||||||
require2FA: false,
|
require2FA: false,
|
||||||
}
|
}
|
||||||
@@ -159,21 +160,13 @@
|
|||||||
},
|
},
|
||||||
register(){
|
register(){
|
||||||
|
|
||||||
let error = false
|
if( this.username.length == 0 || this.password.length == 0 ){
|
||||||
|
|
||||||
if( this.username.length == 0 || this.password.length == 0 || this.password2.length == 0 ){
|
if(this.$route.name == 'LoginPage'){
|
||||||
|
this.$bus.$emit('notification', 'Both a Username and Password are Required')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.$bus.$emit('notification', 'All fields are required.')
|
|
||||||
error = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if( this.password !== this.password2 ){
|
|
||||||
|
|
||||||
this.$bus.$emit('notification', 'Passwords must be identical.')
|
|
||||||
error = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if(error){
|
|
||||||
//Login section
|
//Login section
|
||||||
this.$router.push('/login')
|
this.$router.push('/login')
|
||||||
return
|
return
|
||||||
|
@@ -1,431 +0,0 @@
|
|||||||
<style type="text/css" scoped>
|
|
||||||
.an-graph {
|
|
||||||
background: #fefefe;
|
|
||||||
}
|
|
||||||
.inactive.segment {
|
|
||||||
|
|
||||||
}
|
|
||||||
.active.segment {
|
|
||||||
outline: 4px solid cyan;
|
|
||||||
outline-offset: -5px;
|
|
||||||
outline-style: dashed;
|
|
||||||
max-height: 2000px;
|
|
||||||
}
|
|
||||||
.not-padded {
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-right: -5px;
|
|
||||||
margin-bottom: -10px;
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
.sticky-boy {
|
|
||||||
position: fixed;
|
|
||||||
top: -1px;
|
|
||||||
right: 10px;
|
|
||||||
z-index: 100;
|
|
||||||
width: 70%;
|
|
||||||
background: orange;
|
|
||||||
}
|
|
||||||
.animate-height {
|
|
||||||
transition: max-height 0.8s linear;
|
|
||||||
max-height: 450px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<div class="ui very compact grid" :class="{'sticky-boy':editGraphs}">
|
|
||||||
<div class="sixteen wide column" v-if="!editGraphs">
|
|
||||||
<div class="ui basic padded segment">
|
|
||||||
<!-- Just a space to keep things clickable -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sixteen wide column">
|
|
||||||
<dix class="ui basic segment" v-if="!editGraphs">
|
|
||||||
<div class="ui button" v-on:click="toggleEditGraphs">
|
|
||||||
<i class="edit icon"></i>
|
|
||||||
<span>Add/Edit Graphs</span>
|
|
||||||
</div>
|
|
||||||
</dix>
|
|
||||||
|
|
||||||
|
|
||||||
<div v-if="editGraphs">
|
|
||||||
<div class="ui green button" v-on:click="addGraph()">
|
|
||||||
<i class="plus icon"></i>
|
|
||||||
New Graph
|
|
||||||
</div>
|
|
||||||
<div class="ui basic button" v-on:click="toggleEditGraphs">
|
|
||||||
<i class="check circle icon"></i>
|
|
||||||
Done Editing Graphs
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="(graph, index) in graphs" :class="`ui not-padded ${editGraphs?'active ':'inactive '}segment animate-height`">
|
|
||||||
|
|
||||||
<!-- Edit options -->
|
|
||||||
<div class="ui small header" v-if="editGraphs">
|
|
||||||
<div class="ui grid">
|
|
||||||
<div class="eight wide column">
|
|
||||||
<b>Graph #{{ index+1 }}</b>
|
|
||||||
</div>
|
|
||||||
<div class="eight wide right aligned column">
|
|
||||||
<span class="ui tiny compact inverted red button" v-on:click="removeGraph(index)">
|
|
||||||
Remove Graph
|
|
||||||
<i class="close icon"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="ui center aligned dividing header">
|
|
||||||
{{ getGraphTitle(graph) }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div v-if="graph?.type == PILL_CALENDAR">
|
|
||||||
<PillCalendarGraph
|
|
||||||
:graph="graph"
|
|
||||||
:tempChartDays="tempChartDays"
|
|
||||||
:userFields="userFields"
|
|
||||||
:cycleData="cycleData"
|
|
||||||
:edit-graphs="editGraphs"
|
|
||||||
:showZeroValues="graph?.options?.showZeroValues"
|
|
||||||
:showTextValues="graph?.options?.showTextValues"
|
|
||||||
:connectDays="graph?.options?.connectDays"
|
|
||||||
:hideValues="graph?.options?.hideValues"
|
|
||||||
:hideIcons="graph?.options?.hideIcons"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div v-if="editGraphs" class="ui segment">
|
|
||||||
<p>Calendar Graph Toggles</p>
|
|
||||||
<div v-on:click="toggelValue(index, 'hideIcons')"class="ui button">
|
|
||||||
<span v-if="graph?.options?.hideIcons">Show</span><span v-else>Hide</span> Icons
|
|
||||||
</div>
|
|
||||||
<div v-on:click="toggelValue(index, 'hideValues')"class="ui button">
|
|
||||||
<span v-if="graph?.options?.hideValues">Show</span><span v-else>Hide</span> Values
|
|
||||||
</div>
|
|
||||||
<div v-on:click="toggelValue(index, 'showZeroValues')"class="ui button">
|
|
||||||
<span v-if="!graph?.options?.showZeroValues">Show</span><span v-else>Hide</span> Lowest Value
|
|
||||||
</div>
|
|
||||||
<div v-on:click="toggelValue(index, 'showTextValues')"class="ui button">
|
|
||||||
<span v-if="!graph?.options?.showTextValues">Show</span><span v-else>Hide</span> Text Value
|
|
||||||
</div>
|
|
||||||
<div v-on:click="toggelValue(index, 'connectDays')"class="ui button">
|
|
||||||
<span v-if="!graph?.options?.connectDays">Connect</span><span v-else>Disconnect</span> Days
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="graph?.type == LAST_DONE">
|
|
||||||
Last done not implemented
|
|
||||||
</div>
|
|
||||||
<div v-if="!graph.fieldIds || graph.fieldIds && graph.fieldIds.length == 0">
|
|
||||||
<h5>Blank Graph</h5>
|
|
||||||
<span v-if="!editGraphs">Click "Edit Graphs" then,</span>
|
|
||||||
Select Graph type and Metrics to display
|
|
||||||
</div>
|
|
||||||
<div v-if="graph?.type == undefined && graph.fieldIds && graph.fieldIds.length > 0">
|
|
||||||
<div :id="`graphdiv${index}`" style="width: 100%; min-height: 320px;"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui segment" v-if="editGraphs">
|
|
||||||
|
|
||||||
<!-- change graph type -->
|
|
||||||
<div v-for="(graphType, graphId) in graphTypesDef" class="ui buttons">
|
|
||||||
<div class="ui tiny button" v-on:click="changeGraphType(index, graphId)" :class="{'green':(String(graphId) == String(graph?.type))}">
|
|
||||||
{{ graphType }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="fieldId in fields">
|
|
||||||
<span v-if="graph.fieldIds && graph.fieldIds.includes(fieldId)" v-on:click="toggleGraphField(fieldId, index)">
|
|
||||||
<i class="green check square icon"></i>
|
|
||||||
</span>
|
|
||||||
<span v-else v-on:click="toggleGraphField(fieldId, index)">
|
|
||||||
<i class="square outline icon"></i>
|
|
||||||
</span>
|
|
||||||
<i :class="`${$parent.getFieldColor(fieldId)} ${$parent.getFieldIcon(fieldId)} icon`"></i>
|
|
||||||
<b>{{ userFields[fieldId]?.label }}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="ui very compact grid" :class="{'sticky-boy':editGraphs}">
|
|
||||||
<div class="sixteen wide column" v-if="!editGraphs">
|
|
||||||
<div class="ui basic padded segment">
|
|
||||||
<!-- Just a space to keep things clickable -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sixteen wide column">
|
|
||||||
<dix class="ui basic segment" v-if="!editGraphs">
|
|
||||||
<div class="ui button" v-on:click="toggleEditGraphs">
|
|
||||||
<i class="edit icon"></i>
|
|
||||||
<span>Add/Edit Graphs</span>
|
|
||||||
</div>
|
|
||||||
</dix>
|
|
||||||
|
|
||||||
|
|
||||||
<div v-if="editGraphs">
|
|
||||||
<div class="ui green button" v-on:click="addGraph()">
|
|
||||||
<i class="plus icon"></i>
|
|
||||||
New Graph
|
|
||||||
</div>
|
|
||||||
<div class="ui basic button" v-on:click="toggleEditGraphs">
|
|
||||||
<i class="check circle icon"></i>
|
|
||||||
Done Editing Graphs
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Anchor for scrolling to the bottom of graphs -->
|
|
||||||
<div ref="anchor"></div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
const PILL_CALENDAR = 'pillCalendar'
|
|
||||||
const LAST_DONE = 'lastDone'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MetricTrackingGraphs',
|
|
||||||
props: [
|
|
||||||
'tempChartDays', // Number of days to display
|
|
||||||
'fields', // field IDs for display/order
|
|
||||||
'userFields', // field values defined by user
|
|
||||||
'graphs', // Graph data defined by user
|
|
||||||
'cycleData', // ALL user data
|
|
||||||
'calendar', // Date data for currently open day
|
|
||||||
'editGraphs' // boolean for edit or not edit graphs
|
|
||||||
],
|
|
||||||
components: {
|
|
||||||
'PillCalendarGraph':require('@/components/Metrictracking/PillCalendarGraph.vue').default,
|
|
||||||
},
|
|
||||||
data: function(){
|
|
||||||
return {
|
|
||||||
graphTypesDef:{
|
|
||||||
// [LAST_DONE]: 'Last Done',
|
|
||||||
'undefined':'Line Graph (Default)',
|
|
||||||
[PILL_CALENDAR]:'Calendar Graph',
|
|
||||||
},
|
|
||||||
localGraphData:[],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeCreate() {
|
|
||||||
// Constants
|
|
||||||
this.PILL_CALENDAR = PILL_CALENDAR
|
|
||||||
this.LAST_DONE = LAST_DONE
|
|
||||||
|
|
||||||
// Include JS libraries
|
|
||||||
let graphsScript = document.createElement('script')
|
|
||||||
graphsScript.setAttribute('src', '//cdnjs.cloudflare.com/ajax/libs/dygraph/2.1.0/dygraph.min.js')
|
|
||||||
document.head.appendChild(graphsScript)
|
|
||||||
},
|
|
||||||
mounted(){
|
|
||||||
this.localGraphData = this.graphs
|
|
||||||
|
|
||||||
this.graphCurrentData()
|
|
||||||
},
|
|
||||||
updated(){
|
|
||||||
// update graphs here? Or watch graphs prop
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
// whenever question changes, this function will run
|
|
||||||
userFields(newFields, oldFields) {
|
|
||||||
// console.log([newFields, oldFields])
|
|
||||||
if( JSON.stringify(oldFields) == "{}" ){
|
|
||||||
this.graphCurrentData()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tempChartDays(newDays, oldDays){
|
|
||||||
if( newDays != oldDays ){
|
|
||||||
this.graphCurrentData()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
saveGraphs(){
|
|
||||||
this.$emit('saveGraphs', this.localGraphData)
|
|
||||||
},
|
|
||||||
toggleEditGraphs(){
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// scroll last graph into view
|
|
||||||
this.$refs.anchor.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
inline: 'center'
|
|
||||||
})
|
|
||||||
}, 800)
|
|
||||||
|
|
||||||
this.$emit('toggleEditGraphs')
|
|
||||||
},
|
|
||||||
changeGraphType(index, newType){
|
|
||||||
console.log(index + ' change to ' + newType)
|
|
||||||
this.localGraphData[index]['type'] = newType
|
|
||||||
this.saveGraphs()
|
|
||||||
},
|
|
||||||
addGraph(){
|
|
||||||
this.localGraphData.push({})
|
|
||||||
this.saveGraphs()
|
|
||||||
},
|
|
||||||
removeGraph(index){
|
|
||||||
this.localGraphData.splice(index, 1)
|
|
||||||
this.saveGraphs()
|
|
||||||
},
|
|
||||||
toggelValue(graphIndex, optionName){
|
|
||||||
|
|
||||||
if(!this.localGraphData[graphIndex].options){
|
|
||||||
this.localGraphData[graphIndex].options = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.localGraphData[graphIndex].options[optionName]){
|
|
||||||
this.localGraphData[graphIndex].options[optionName] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
this.localGraphData[graphIndex].options[optionName] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(this.localGraphData[graphIndex].options[optionName])
|
|
||||||
|
|
||||||
this.saveGraphs()
|
|
||||||
|
|
||||||
},
|
|
||||||
toggleGraphField(fieldId, graphIndex){
|
|
||||||
|
|
||||||
if(!Array.isArray(this.localGraphData[graphIndex].fieldIds)){
|
|
||||||
this.localGraphData[graphIndex].fieldIds = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const inSetCheck = this.localGraphData[graphIndex]?.fieldIds.indexOf(fieldId)
|
|
||||||
|
|
||||||
if(inSetCheck == -1){
|
|
||||||
this.localGraphData[graphIndex]?.fieldIds.push(fieldId)
|
|
||||||
}
|
|
||||||
if(inSetCheck > -1){
|
|
||||||
this.localGraphData[graphIndex]?.fieldIds.splice(inSetCheck,1)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveGraphs()
|
|
||||||
|
|
||||||
},
|
|
||||||
getGraphTitle(graph){
|
|
||||||
|
|
||||||
const graphFields = graph?.fieldIds || []
|
|
||||||
let fieldTitles = []
|
|
||||||
graphFields.forEach(fieldId => {
|
|
||||||
fieldTitles.push(this.userFields[fieldId]?.label)
|
|
||||||
})
|
|
||||||
|
|
||||||
// console.log(fieldTitles)
|
|
||||||
const title = fieldTitles.join(', ')
|
|
||||||
|
|
||||||
return title
|
|
||||||
},
|
|
||||||
graphCurrentData(){
|
|
||||||
|
|
||||||
// try again if dygraphs isn't loaded
|
|
||||||
if( typeof(window.Dygraph) != 'function' ){
|
|
||||||
setTimeout(() => {
|
|
||||||
this.graphCurrentData()
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const graphOptions = {
|
|
||||||
interactionModel: {},
|
|
||||||
// pointClickCallback: function(e, pt){
|
|
||||||
// console.log(e)
|
|
||||||
// console.log(pt)
|
|
||||||
// console.log(this.getValue(pt.idx, 0))
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Excel date format YYYYMMDD
|
|
||||||
const convertToExcelDate = (dateCode) => {
|
|
||||||
return dateCode
|
|
||||||
.split('.')
|
|
||||||
.reverse()
|
|
||||||
.map(item => String(item).padStart(2,0))
|
|
||||||
.join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate set of keys for graph length
|
|
||||||
let dataKeys = Object.keys(this.cycleData)
|
|
||||||
dataKeys = dataKeys.splice(0, this.tempChartDays)
|
|
||||||
console.log(dataKeys)
|
|
||||||
|
|
||||||
|
|
||||||
// build CSV data for each graph
|
|
||||||
this.graphs.forEach((graph,index) => {
|
|
||||||
|
|
||||||
// only chart line graphs with dygraphs
|
|
||||||
if( graph.type != undefined ){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if( !graph.fieldIds ){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSV or path to a CSV file.
|
|
||||||
let dataString = ""
|
|
||||||
|
|
||||||
// Lookup graph field titles
|
|
||||||
let graphLabels = ['Date']
|
|
||||||
graph.fieldIds.forEach(fieldId => {
|
|
||||||
const graphLabel = this.userFields[fieldId]?.label
|
|
||||||
const escapedLabel = graphLabel.replaceAll(',','')
|
|
||||||
graphLabels.push(escapedLabel)
|
|
||||||
})
|
|
||||||
dataString += graphLabels.join(',') + '\n'
|
|
||||||
|
|
||||||
|
|
||||||
// build each row, for each day
|
|
||||||
for (var i = 0; i < dataKeys.length; i++) {
|
|
||||||
|
|
||||||
let nextFragment = []
|
|
||||||
// push date code to first column
|
|
||||||
nextFragment.push(convertToExcelDate(dataKeys[i]))
|
|
||||||
|
|
||||||
graph.fieldIds.forEach(fieldId => {
|
|
||||||
|
|
||||||
const currentEntry = this.cycleData[dataKeys[i]]
|
|
||||||
let currentValue = currentEntry[fieldId]
|
|
||||||
|
|
||||||
// setup correct float graphing
|
|
||||||
if(fieldId == 'BT'){
|
|
||||||
// parse temp to fixed length float 00.00
|
|
||||||
currentValue = parseFloat(currentValue).toFixed(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if( currentValue == undefined ){
|
|
||||||
currentValue = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
nextFragment.push(currentValue)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
dataString += nextFragment.join(',') + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let graphDiv = document.getElementById("graphdiv"+index)
|
|
||||||
const g = new Dygraph(graphDiv, dataString ,graphOptions)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -1,548 +0,0 @@
|
|||||||
<style type="text/css" scoped>
|
|
||||||
div.calendar {
|
|
||||||
width: calc(100% - 4px);
|
|
||||||
min-height: 350px;
|
|
||||||
display: flex;
|
|
||||||
margin: 5px 8px 15px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
.day {
|
|
||||||
flex: 0 0 calc(14.28% - 2px);
|
|
||||||
min-height: 50px;
|
|
||||||
border: 1px solid var(--border_color);
|
|
||||||
font-size: 1.2em;
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
line-height: 1em;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
.today {
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.active-entry {
|
|
||||||
outline: #07f4f4;
|
|
||||||
outline-style: none;
|
|
||||||
outline-width: medium;
|
|
||||||
outline-style: none;
|
|
||||||
outline-offset: -1px;
|
|
||||||
outline-style: solid;
|
|
||||||
outline-width: 3px;
|
|
||||||
}
|
|
||||||
.day ~ .has-data {
|
|
||||||
|
|
||||||
}
|
|
||||||
.day ~ .no-data {
|
|
||||||
background: #c7c7c787;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
.day > .number {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 5px;
|
|
||||||
z-index: 10;
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
.day > .sex {
|
|
||||||
font-size: 0.7em;
|
|
||||||
border-radius: 5px;
|
|
||||||
background: rgba(249, 0, 0, 0.15);
|
|
||||||
color: white;
|
|
||||||
padding: 0 0 0 4px;
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
height: 26px;
|
|
||||||
}
|
|
||||||
.day > .period {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1px;
|
|
||||||
left: 1px;
|
|
||||||
right: 1px;
|
|
||||||
height: 5px;
|
|
||||||
background: red;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
.day > .mucus {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
min-height: 10px;
|
|
||||||
background: #abecff7d;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.day > .notes {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.pill {
|
|
||||||
width: calc(100% - 8px);
|
|
||||||
min-height: 2px;
|
|
||||||
margin: 0 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
background: rgb(50 218 255 / 44%);
|
|
||||||
border-radius: 40px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1em;
|
|
||||||
position: relative;
|
|
||||||
color: white;
|
|
||||||
font-size: 0.7em;
|
|
||||||
padding: 2px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.pill.did-last {
|
|
||||||
margin-left: 0;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
width: calc(100% - 5px);
|
|
||||||
}
|
|
||||||
.pill.did-next {
|
|
||||||
margin-right: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
width: calc(100% - 5px);
|
|
||||||
}
|
|
||||||
.pill.did-next.did-last {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* .last-high:after {
|
|
||||||
content: '';
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 15px solid transparent;
|
|
||||||
border-bottom: 3px solid transparent;
|
|
||||||
border-left: 10px solid rgb(50 218 255 / 44%);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: -13px;
|
|
||||||
}
|
|
||||||
.next-high:before {
|
|
||||||
content: '';
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-top: 15px solid transparent;
|
|
||||||
border-bottom: 3px solid transparent;
|
|
||||||
border-right: 10px solid rgb(50 218 255 / 44%);
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: -13px;
|
|
||||||
}*/
|
|
||||||
.big-day {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 2px;
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.zero-day {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
.icon-spacer {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: greenyellow;
|
|
||||||
width: 20px;
|
|
||||||
height: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.past-entries {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
/* padding: 0 10px;*/
|
|
||||||
overflow-x: scroll;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
.past-entry {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid;
|
|
||||||
border-color: var(--dark_border_color);
|
|
||||||
color: var(--text_color);
|
|
||||||
flex-grow: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
min-width: 40px;
|
|
||||||
min-height: 40px;
|
|
||||||
margin: 5px 0 10px;
|
|
||||||
line-height: 2.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-list {
|
|
||||||
width: 100%;
|
|
||||||
height: 80px;
|
|
||||||
background-color: green;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
overflow-x: scroll;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
.day-list-item {
|
|
||||||
flex-grow: 1;
|
|
||||||
border: 1px solid black;
|
|
||||||
width: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pill.red { background-color: #db2828 }
|
|
||||||
.pill.orange { background-color: #f2711c }
|
|
||||||
.pill.yellow { background-color: #fbbd08 }
|
|
||||||
.pill.olive { background-color: #b5cc18 }
|
|
||||||
.pill.green { background-color: #21ba45 }
|
|
||||||
.pill.teal { background-color: #00b5ad }
|
|
||||||
.pill.blue { background-color: #2185d0 }
|
|
||||||
.pill.violet { background-color: #6435c9 }
|
|
||||||
.pill.purple { background-color: #a333c8 }
|
|
||||||
.pill.pink { background-color: #e03997 }
|
|
||||||
.pill.brown { background-color: #a5673f }
|
|
||||||
.pill.grey { background-color: #767676 }
|
|
||||||
.pill.black { background-color: #1b1c1d }
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="calendar">
|
|
||||||
|
|
||||||
<div v-for="day in calendar.weekdays" class="day">
|
|
||||||
{{ day }}
|
|
||||||
</div>
|
|
||||||
<div v-for="day in calendar.days" class="day"
|
|
||||||
:class="{
|
|
||||||
'today':day == calendar.today,
|
|
||||||
'active-entry':calendar.dateCode == `${day}.${calendar.month}.${calendar.year}`,
|
|
||||||
'has-data':cycleData[`${day}.${calendar.month}.${calendar.year}`],
|
|
||||||
'no-data':showDayDataColor(day),
|
|
||||||
}">
|
|
||||||
<!-- v-on:click="openDayData(`${day}.${calendar.month}.${calendar.year}`)" -->
|
|
||||||
<span class="number">{{ day }}</span>
|
|
||||||
<!-- {{ `${day}.${calendar.month}.${calendar.year}` }} -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<span class="pill-container" v-for="(entry, dateCode) in getChartData" v-if="dateCode == `${day}.${calendar.month}.${calendar.year}`">
|
|
||||||
<span
|
|
||||||
v-for="(dayData, fieldId) in entry"
|
|
||||||
v-if="showZeroValuesCheck(dayData.value, fieldId)"
|
|
||||||
class="pill"
|
|
||||||
:class="[$parent.$parent.getFieldColor(fieldId), {
|
|
||||||
'did-next':dayData.didNext,
|
|
||||||
'did-last':dayData.didLast,
|
|
||||||
'last-high':dayData.lastHigh,
|
|
||||||
'next-high':dayData.nextHigh,
|
|
||||||
}]">
|
|
||||||
<!-- 'zero-day':dayData.value == lowestGraphValue, -->
|
|
||||||
<!-- <i v-if="dayData.value != 0" :class="`tiny ${$parent.$parent.getFieldColor(fieldId)} ${$parent.$parent.getFieldIcon(fieldId)} icon`"></i> -->
|
|
||||||
<!-- <span v-else class="icon-spacer"></span>
|
|
||||||
:style="{height:(Math.round(dayData.value*5)+'px')}"
|
|
||||||
|
|
||||||
-->
|
|
||||||
<span v-if="dayData.value > lowestGraphValue-1" class="big-day">
|
|
||||||
<i v-if="!hideIcons" :class="`tiny white ${$parent.$parent.getFieldIcon(fieldId)} icon`"></i>
|
|
||||||
<span v-if="!hideValues">
|
|
||||||
{{ getDayValue(fieldId, dayData.value) }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<!-- <span v-for="fieldId in graph.fieldIds"></span> -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
|
|
||||||
// let chartData = {}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: [
|
|
||||||
'graph', // options associated with this graph
|
|
||||||
'userFields', // all field attributes
|
|
||||||
'tempChartDays', // number of days to display
|
|
||||||
'cycleData', // all users metric data
|
|
||||||
'editGraphs', // display additional edit options
|
|
||||||
// Graph options
|
|
||||||
'showZeroValues', // Hide graph data with value of zero
|
|
||||||
'showTextValues', // Show button text or button value
|
|
||||||
'connectDays', // Calculates next and previous day connections.
|
|
||||||
'hideValues', // Hide all values on the graph
|
|
||||||
'hideIcons', // option to hide icons
|
|
||||||
],
|
|
||||||
data: function(){
|
|
||||||
return {
|
|
||||||
openModel:true,
|
|
||||||
calendar: {
|
|
||||||
dateObject: null,
|
|
||||||
dateCode: null,
|
|
||||||
monthName: '',
|
|
||||||
dayName:'',
|
|
||||||
daysAgo:0,
|
|
||||||
month: '',
|
|
||||||
year: '',
|
|
||||||
days: [],
|
|
||||||
weekdays: ['S','M','T','W','T','F','S'],
|
|
||||||
today: 0,
|
|
||||||
},
|
|
||||||
chartDateCodes: [], // array of date codes in chart
|
|
||||||
listDateCodes: [],
|
|
||||||
dayList: true,
|
|
||||||
lowestGraphValue: 0,
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted(){
|
|
||||||
this.setupCalendar(new Date())
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
getChartData(){
|
|
||||||
|
|
||||||
let chartData = {}
|
|
||||||
let chartValues = []
|
|
||||||
|
|
||||||
// iterate every day in month by day code
|
|
||||||
this.chartDateCodes.forEach((chartDayCode, codeIndex) => {
|
|
||||||
|
|
||||||
// lookup data for that day
|
|
||||||
const cycleDayData = this.cycleData[chartDayCode]
|
|
||||||
|
|
||||||
// if chart data is set for this day
|
|
||||||
if( cycleDayData && Object.keys(cycleDayData).length > 0){
|
|
||||||
chartData[chartDayCode] = {}
|
|
||||||
|
|
||||||
// go over each field to be displayed on graph
|
|
||||||
this.graph.fieldIds.forEach((graphFieldId) => {
|
|
||||||
|
|
||||||
if( cycleDayData[graphFieldId] == undefined ){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// track all chart values
|
|
||||||
chartValues.push(cycleDayData[graphFieldId])
|
|
||||||
|
|
||||||
chartData[chartDayCode][graphFieldId] = {
|
|
||||||
didLast: false,
|
|
||||||
lastHigh: false,
|
|
||||||
didNext: false,
|
|
||||||
nextHigh: false,
|
|
||||||
value: cycleDayData[graphFieldId]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.lowestGraphValue = Math.min(...chartValues)
|
|
||||||
|
|
||||||
// determine next and previous states for display
|
|
||||||
this.chartDateCodes.forEach((chartDayCode, codeIndex) => {
|
|
||||||
if(chartData[chartDayCode] && this.connectDays){
|
|
||||||
|
|
||||||
const previousDateCode = this.chartDateCodes[codeIndex-1]
|
|
||||||
const nextDateCode = this.chartDateCodes[codeIndex+1]
|
|
||||||
|
|
||||||
Object.keys(chartData[chartDayCode]).forEach((graphFieldId) => {
|
|
||||||
|
|
||||||
const currentValue = chartData[chartDayCode][graphFieldId].value
|
|
||||||
|
|
||||||
// check for previous entry
|
|
||||||
if( chartData[previousDateCode] && chartData[previousDateCode][graphFieldId] ){
|
|
||||||
|
|
||||||
chartData[chartDayCode][graphFieldId].didLast = true
|
|
||||||
|
|
||||||
// set low value flag
|
|
||||||
const lastHigh = chartData[previousDateCode][graphFieldId].value > 0
|
|
||||||
chartData[chartDayCode][graphFieldId].lastHigh = lastHigh && currentValue == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for next entry
|
|
||||||
if( chartData[nextDateCode] && chartData[nextDateCode][graphFieldId] ){
|
|
||||||
|
|
||||||
chartData[chartDayCode][graphFieldId].didNext = true
|
|
||||||
|
|
||||||
// set low value flag
|
|
||||||
const nextHigh = chartData[nextDateCode][graphFieldId].value > 0
|
|
||||||
chartData[chartDayCode][graphFieldId].nextHigh = nextHigh && currentValue == 0
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// console.log(chartData)
|
|
||||||
|
|
||||||
return chartData
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showZeroValuesCheck(dayValue, fieldId){
|
|
||||||
|
|
||||||
// if graph type is boolean or there are two options
|
|
||||||
let isBooleanField = this.userFields[fieldId].type == 'boolean'
|
|
||||||
if(this.userFields[fieldId].customOptions){
|
|
||||||
let options = this.userFields[fieldId].customOptions
|
|
||||||
|
|
||||||
isBooleanField = options.split(',').length == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isBooleanField && !this.showZeroValues){
|
|
||||||
|
|
||||||
const parsedValue = this.getDayValue(fieldId, dayValue)
|
|
||||||
if(parsedValue == 'Yes'){
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return this.showZeroValues || dayValue > this.lowestGraphValue
|
|
||||||
},
|
|
||||||
getDayValue(fieldId, value){
|
|
||||||
|
|
||||||
if( !this.showTextValues ){
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = 'error, Yes, No'
|
|
||||||
|
|
||||||
if(this.userFields[fieldId].customOptions){
|
|
||||||
options = this.userFields[fieldId].customOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const values = options.split(',')
|
|
||||||
const selection = String(values[value]).trim()
|
|
||||||
|
|
||||||
return selection
|
|
||||||
},
|
|
||||||
displayDayFromCode(dateCode){
|
|
||||||
|
|
||||||
const parts = dateCode.split('.')
|
|
||||||
return `${parts[0]}`
|
|
||||||
},
|
|
||||||
showDayDataColor(day){
|
|
||||||
// Determine if day has any data set
|
|
||||||
if(day == ''){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !(this.cycleData[`${day}.${this.calendar.month}.${this.calendar.year}`])
|
|
||||||
},
|
|
||||||
generateDateCode(date){
|
|
||||||
|
|
||||||
const dateSetup = [
|
|
||||||
date.getDate(), // 1-31 (Day)
|
|
||||||
date.getMonth()+1, // 0-11 (Month)
|
|
||||||
date.getFullYear(), // 1888-2022 (Year)
|
|
||||||
]
|
|
||||||
|
|
||||||
return dateSetup.join('.')
|
|
||||||
},
|
|
||||||
setupCalendar(date){
|
|
||||||
|
|
||||||
// visualize each day change
|
|
||||||
this.working = true
|
|
||||||
setTimeout(() => {
|
|
||||||
this.working = false
|
|
||||||
}, 500)
|
|
||||||
|
|
||||||
if(!date && this.dateObject){
|
|
||||||
date = this.dateObject
|
|
||||||
}
|
|
||||||
if(!date){
|
|
||||||
date = new Date()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.calendar.dateObject = date
|
|
||||||
|
|
||||||
this.calendar.dateCode = this.generateDateCode(date)
|
|
||||||
|
|
||||||
// calculate days ago since current date
|
|
||||||
const now = new Date()
|
|
||||||
const diffSeconds = Math.floor((now - date) / 1000) // subtract unix timestamps, convert MS to S
|
|
||||||
const dayInterval = diffSeconds / 86400 // seconds in a day
|
|
||||||
this.calendar.daysAgo = Math.floor(dayInterval)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------
|
|
||||||
// setup calendar display
|
|
||||||
var y = date.getFullYear()
|
|
||||||
var m = date.getMonth()
|
|
||||||
|
|
||||||
var firstDay = new Date(y, m, 1);
|
|
||||||
var lastDay = new Date(y, m + 1, 0);
|
|
||||||
|
|
||||||
function getDaysInMonth(year, month) {
|
|
||||||
return new Date(year, month, 0).getDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentYear = date.getFullYear();
|
|
||||||
const currentMonth = date.getMonth() + 1;
|
|
||||||
this.calendar.monthName = date.toLocaleString("en-US", { month: "long" });
|
|
||||||
this.calendar.dayName = date.toLocaleString("en-US", { weekday: "long" });
|
|
||||||
this.calendar.year = currentYear
|
|
||||||
const daysInCurrentMonth = getDaysInMonth(currentYear, currentMonth);
|
|
||||||
|
|
||||||
const monthStartDay = firstDay.getDay()
|
|
||||||
let days = Array(monthStartDay).fill(""); // Pad days to start on correct weekday
|
|
||||||
for (let i = 0; i < daysInCurrentMonth; i++) {
|
|
||||||
days.push(i+1)
|
|
||||||
}
|
|
||||||
this.calendar.days = days
|
|
||||||
|
|
||||||
// set today
|
|
||||||
this.calendar.today = date.getDate()
|
|
||||||
this.calendar.month = date.getMonth()+1
|
|
||||||
|
|
||||||
// setup date codes for key matching on calendar
|
|
||||||
this.calendar.days.forEach((day) => {
|
|
||||||
if( day !== "" ){
|
|
||||||
let dateDay = new Date(y, m, day);
|
|
||||||
let dayCode = this.generateDateCode(dateDay)
|
|
||||||
this.chartDateCodes.push(dayCode)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// generate past date codes for list
|
|
||||||
for (let i = 0; i < this.tempChartDays; i++) {
|
|
||||||
|
|
||||||
const now = new Date()
|
|
||||||
const pastDate = now.setDate(now.getDate() - i)
|
|
||||||
const pastDateObj = new Date(pastDate)
|
|
||||||
const newCode = this.generateDateCode(pastDateObj)
|
|
||||||
this.listDateCodes.push(newCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// return codes.reverse()
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
October 2022
|
|
||||||
S M T W T F S
|
|
||||||
1 2 3 4 5 6
|
|
||||||
7 8 9
|
|
||||||
*/
|
|
||||||
|
|
||||||
// -------
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -1,164 +0,0 @@
|
|||||||
<style type="text/css" scoped>
|
|
||||||
.modal-content {
|
|
||||||
position: fixed;
|
|
||||||
top: 40%;
|
|
||||||
left: 50%;
|
|
||||||
/* bring your own prefixes */
|
|
||||||
transform: translate(-50%, -40%);
|
|
||||||
z-index: 300;
|
|
||||||
padding: 1em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 50%;
|
|
||||||
max-height: 100%;
|
|
||||||
/*overflow: hidden;*/
|
|
||||||
overflow-y: scroll;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.modal-content.fullscreen {
|
|
||||||
width: 96%;
|
|
||||||
height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.close-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
z-index: 320;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Shrink button text for mobile */
|
|
||||||
@media only screen and (max-width: 740px) {
|
|
||||||
.modal-content {
|
|
||||||
width: 100%;
|
|
||||||
/* padding-bottom: 55px;*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content.right-side {
|
|
||||||
width: 60%;
|
|
||||||
max-height: none;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
left: auto;
|
|
||||||
transform: translate(0, 0);
|
|
||||||
}
|
|
||||||
.close-container-right-side {
|
|
||||||
position: fixed;
|
|
||||||
top: 5px;
|
|
||||||
left: calc(60% + 2px);
|
|
||||||
z-index: 320;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shade {
|
|
||||||
position: fixed;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #0000007d;
|
|
||||||
z-index: 299;
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-out-top {
|
|
||||||
animation: fade-out-top 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-out {
|
|
||||||
animation: fade-out 0.3s ease-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-out-top {
|
|
||||||
0% {
|
|
||||||
/*transform: translate(-50%, -50%);*/
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
/*transform: translate(-50%, -70%);*/
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-out {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in {
|
|
||||||
/*animation: fade-in 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;*/
|
|
||||||
}
|
|
||||||
@keyframes fade-in {
|
|
||||||
0% {
|
|
||||||
transform: translate(-50%, -70%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-if="openModel">
|
|
||||||
<div class="modal-content" :class="{ 'fade-out-top':(animateOut), 'fade-in':(!animateOut), 'fullscreen':(fullscreen)}">
|
|
||||||
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
<!-- full screen close button -->
|
|
||||||
<div class="close-container" v-if="fullscreen && clickOutClose !== false">
|
|
||||||
<div class="ui green icon button" v-on:click="closeModel">
|
|
||||||
<i class="close icon"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="shade" v-on:click="closeModel" v-on:mouseenter=" hoverOutClose?closeModel():null " :class="{ 'fade-out':(animateOut) }"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: [
|
|
||||||
'fullscreen', //Make the model really big
|
|
||||||
'clickOutClose', //Set to false to prevent closing of modal by clicking out
|
|
||||||
'hoverOutClose', //Close if cursor leaves modal
|
|
||||||
],
|
|
||||||
data: function(){
|
|
||||||
return {
|
|
||||||
openModel:true,
|
|
||||||
animateOut:false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
closeModel(){
|
|
||||||
|
|
||||||
//Don't allow closing by clicking out
|
|
||||||
if(this.clickOutClose === false){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set stups to close model, animate out
|
|
||||||
this.animateOut = true
|
|
||||||
setTimeout( () => {
|
|
||||||
this.openModel = false
|
|
||||||
this.$emit('close')
|
|
||||||
|
|
||||||
//Once close event is sent, reset to default state
|
|
||||||
this.animateOut = false
|
|
||||||
this.openModel = true
|
|
||||||
|
|
||||||
}, 800)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -172,13 +172,8 @@
|
|||||||
|
|
||||||
<span class="status-menu" v-on:click=" hash=0; save()">
|
<span class="status-menu" v-on:click=" hash=0; save()">
|
||||||
|
|
||||||
<span v-if="idleNote" data-position="left center" data-tooltip="Idle: Awaiting Changes">
|
|
||||||
<i class="vertically flipped grey wifi icon"></i>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-if="diffsApplied > 0">
|
<span v-if="diffsApplied > 0">
|
||||||
<i class="blue wave square icon"></i>
|
+{{ diffsApplied }} Unsaved Changes
|
||||||
+{{ diffsApplied }}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="left center">
|
<span v-if="usersOnNote > 1" :data-tooltip="`Viewers`" data-position="left center">
|
||||||
@@ -351,10 +346,6 @@
|
|||||||
:class="{ 'fade-me-out':sizeDown }"
|
:class="{ 'fade-me-out':sizeDown }"
|
||||||
v-on:click="closeButtonAction()"></div> -->
|
v-on:click="closeButtonAction()"></div> -->
|
||||||
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -368,8 +359,6 @@
|
|||||||
const dmp = new DiffMatchPatch.diff_match_patch()
|
const dmp = new DiffMatchPatch.diff_match_patch()
|
||||||
|
|
||||||
import SquireButtonFunctions from '@/mixins/SquireButtonFunctions.js'
|
import SquireButtonFunctions from '@/mixins/SquireButtonFunctions.js'
|
||||||
|
|
||||||
let rawNoteText = '' // Used for comparing and generating diffs
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NoteInputPanel',
|
name: 'NoteInputPanel',
|
||||||
@@ -401,6 +390,7 @@
|
|||||||
created: '',
|
created: '',
|
||||||
updated: '',
|
updated: '',
|
||||||
shareUsername: null,
|
shareUsername: null,
|
||||||
|
// diffNoteText: '',
|
||||||
statusText: 'saved',
|
statusText: 'saved',
|
||||||
lastNoteHash: null,
|
lastNoteHash: null,
|
||||||
saveDebounce: null, //Prevent save from being called numerous times quickly
|
saveDebounce: null, //Prevent save from being called numerous times quickly
|
||||||
@@ -412,11 +402,13 @@
|
|||||||
pinned: 0,
|
pinned: 0,
|
||||||
archived: 0,
|
archived: 0,
|
||||||
attachmentCount: 0,
|
attachmentCount: 0,
|
||||||
attachments: [],
|
|
||||||
styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
|
styleObject: { 'noteText':null,'noteBackground':null, 'noteIcon':null, 'iconColor':null }, //Style object. Determines colors and badges
|
||||||
|
|
||||||
sizeDown: false, //Used to animate close state
|
sizeDown: false, //Used to animate close state
|
||||||
|
|
||||||
|
//Settings vars
|
||||||
|
lastVisibilityState: null,
|
||||||
|
|
||||||
//All the squire settings
|
//All the squire settings
|
||||||
editor: null,
|
editor: null,
|
||||||
usersOnNote: 0,
|
usersOnNote: 0,
|
||||||
@@ -432,14 +424,10 @@
|
|||||||
//Diff text/sync text variables
|
//Diff text/sync text variables
|
||||||
diffTextTimeout: null,
|
diffTextTimeout: null,
|
||||||
diffsApplied: null,
|
diffsApplied: null,
|
||||||
idleNote: true, // If note is idle, get updates from server
|
|
||||||
idleNoteTimeout: null,
|
|
||||||
reloadNoteDebounce: null,
|
|
||||||
|
|
||||||
//Used to restore caret position
|
//Used to restore caret position
|
||||||
lastRange: null,
|
lastRange: null,
|
||||||
startOffset: 0,
|
startOffset: 0,
|
||||||
childIndex: null,
|
|
||||||
|
|
||||||
//Tag Display
|
//Tag Display
|
||||||
allTags: [],
|
allTags: [],
|
||||||
@@ -495,12 +483,9 @@
|
|||||||
|
|
||||||
this.$bus.$off('new_file_upload')
|
this.$bus.$off('new_file_upload')
|
||||||
|
|
||||||
this.destroyAttachmentStyles()
|
|
||||||
|
|
||||||
this.destroyWebSockets()
|
this.destroyWebSockets()
|
||||||
|
|
||||||
window.removeEventListener('blur', this.windowBlurEvent)
|
document.removeEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||||
window.removeEventListener('focus', this.windowFocusEvent)
|
|
||||||
|
|
||||||
//Obliterate squire instance
|
//Obliterate squire instance
|
||||||
this.editor.destroy()
|
this.editor.destroy()
|
||||||
@@ -516,9 +501,7 @@
|
|||||||
this.forceShowLoading = true
|
this.forceShowLoading = true
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
window.addEventListener('blur', this.windowBlurEvent)
|
document.addEventListener('visibilitychange', this.checkForUpdatedNote)
|
||||||
window.addEventListener('focus', this.windowFocusEvent)
|
|
||||||
// this.logNoteInteraction()
|
|
||||||
|
|
||||||
//Init squire as early as possible
|
//Init squire as early as possible
|
||||||
if(this.editor && this.editor.destroy){
|
if(this.editor && this.editor.destroy){
|
||||||
@@ -587,31 +570,33 @@
|
|||||||
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initSquireEvents(){
|
initSquire(){
|
||||||
|
|
||||||
//Set up squire and load note text
|
//Set up squire and load note text
|
||||||
this.setText(this.noteText)
|
this.setText(this.noteText)
|
||||||
|
|
||||||
// Use squire box HTML for diff/patch changes
|
|
||||||
rawNoteText = document.getElementById('squire-id').innerHTML
|
|
||||||
|
|
||||||
//focus on open, not on mobile, it causes the keyboard to pop up, thats annoying
|
//focus on open, not on mobile, it causes the keyboard to pop up, thats annoying
|
||||||
if(!this.$store.getters.getIsUserOnMobile){
|
if(!this.$store.getters.getIsUserOnMobile){
|
||||||
this.editor.focus()
|
this.editor.focus()
|
||||||
this.editor.moveCursorToEnd()
|
this.editor.moveCursorToEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set up websockets after squire is set up
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setupWebSockets()
|
||||||
|
}, 500)
|
||||||
|
|
||||||
this.editor.addEventListener('cursor', e => {
|
this.editor.addEventListener('cursor', e => {
|
||||||
|
|
||||||
this.saveCaretPosition(e)
|
//Save range to replace cursor if someone else makes an update
|
||||||
|
this.lastRange = e.range
|
||||||
|
this.startOffset = parseInt(e.range.startOffset)
|
||||||
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
//Change button states on editor when element is active
|
//Change button states on editor when element is active
|
||||||
//eg; Bold button turns green when on bold text
|
//eg; Bold button turns green when on bold text
|
||||||
this.editor.addEventListener('pathChange', e => {
|
this.editor.addEventListener('pathChange', e => this.pathChangeEvent(e))
|
||||||
this.pathChangeEvent(e)
|
|
||||||
this.diffText(e)
|
|
||||||
})
|
|
||||||
|
|
||||||
//Click Event - Open links when clicked in editor or toggle checks
|
//Click Event - Open links when clicked in editor or toggle checks
|
||||||
this.editor.addEventListener('click', e => {
|
this.editor.addEventListener('click', e => {
|
||||||
@@ -696,20 +681,10 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//Bind event handlers
|
||||||
this.editor.addEventListener('keyup', event => {
|
this.editor.addEventListener('keyup', event => {
|
||||||
|
|
||||||
this.onKeyup(event)
|
this.onKeyup(event)
|
||||||
this.diffText(event)
|
|
||||||
this.logNoteInteraction()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.editor.addEventListener('focus', e => {
|
|
||||||
this.logNoteInteraction()
|
|
||||||
// this.diffText(e)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.editor.addEventListener('blur', e => {
|
|
||||||
this.idleNote = true
|
|
||||||
this.diffText(e)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// this.editor.addEventListener("dragstart", e => {
|
// this.editor.addEventListener("dragstart", e => {
|
||||||
@@ -717,6 +692,12 @@
|
|||||||
// console.log(e)
|
// console.log(e)
|
||||||
// if(){}
|
// if(){}
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
//Show and hide additional toolbars
|
||||||
|
// this.editor.addEventListener('focus', e => {
|
||||||
|
// })
|
||||||
|
// this.editor.addEventListener('blur', e => {
|
||||||
|
// })
|
||||||
},
|
},
|
||||||
openEditAttachment(){
|
openEditAttachment(){
|
||||||
|
|
||||||
@@ -779,18 +760,13 @@
|
|||||||
//Setup all responsive vue data
|
//Setup all responsive vue data
|
||||||
this.setupLoadedNoteData(response)
|
this.setupLoadedNoteData(response)
|
||||||
|
|
||||||
|
this.loading = false
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
||||||
//Adjust note title size after load
|
//Adjust note title size after load
|
||||||
this.titleResize()
|
this.titleResize()
|
||||||
this.initSquireEvents()
|
this.initSquire()
|
||||||
|
|
||||||
//Set up websockets after squire is set up
|
|
||||||
setTimeout(() => {
|
|
||||||
this.initWebsocketEvents()
|
|
||||||
|
|
||||||
this.loading = false
|
|
||||||
}, 500)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -810,11 +786,14 @@
|
|||||||
this.created = response.data.created
|
this.created = response.data.created
|
||||||
this.updated = response.data.updated
|
this.updated = response.data.updated
|
||||||
this.lastInteractionTimestamp = +new Date
|
this.lastInteractionTimestamp = +new Date
|
||||||
this.noteTitle = response.data.title || ''
|
this.noteTitle = ''
|
||||||
|
if(response.data.title){
|
||||||
|
this.noteTitle = response.data.title
|
||||||
|
}
|
||||||
|
|
||||||
this.noteText = response.data.text
|
this.noteText = response.data.text
|
||||||
this.lastNoteHash = this.hashString( response.data.text )
|
this.lastNoteHash = this.hashString( response.data.text )
|
||||||
|
// this.diffNoteText = response.data.text
|
||||||
|
|
||||||
//Setup note tags
|
//Setup note tags
|
||||||
this.allTags = response.data.tags ? response.data.tags.split(','):[]
|
this.allTags = response.data.tags ? response.data.tags.split(','):[]
|
||||||
@@ -828,186 +807,86 @@
|
|||||||
this.pinned = response.data.pinned
|
this.pinned = response.data.pinned
|
||||||
}
|
}
|
||||||
this.archived = response.data.archived
|
this.archived = response.data.archived
|
||||||
|
|
||||||
// Fetch attachmets if the count changed
|
|
||||||
if(response.data.attachment_count > 0){
|
|
||||||
this.getAttachments()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.attachmentCount = response.data.attachment_count
|
this.attachmentCount = response.data.attachment_count
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
},
|
|
||||||
generateSelector(el){
|
|
||||||
|
|
||||||
if (!(el instanceof Element))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var path = [];
|
|
||||||
while (el.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
var selector = el.nodeName.toLowerCase();
|
|
||||||
if (el.id) {
|
|
||||||
selector += '#' + el.id;
|
|
||||||
path.unshift(selector);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
var sib = el, nth = 1;
|
|
||||||
while (sib = sib.previousElementSibling) {
|
|
||||||
if (sib.nodeName.toLowerCase() == selector)
|
|
||||||
nth++;
|
|
||||||
}
|
|
||||||
if (nth != 1)
|
|
||||||
selector += ":nth-of-type("+nth+")";
|
|
||||||
}
|
|
||||||
path.unshift(selector);
|
|
||||||
el = el.parentNode;
|
|
||||||
}
|
|
||||||
return path.join(" > ");
|
|
||||||
},
|
},
|
||||||
//Called on squire event for keyup
|
//Called on squire event for keyup
|
||||||
diffText(event){
|
diffText(event){
|
||||||
// console.log(event.type)
|
|
||||||
|
|
||||||
const diffEvents = ['keyup','pathChange', 'click']
|
//Diff the changed lines only
|
||||||
|
|
||||||
// only process changes on certain events
|
let oldText = this.noteText
|
||||||
if( !diffEvents.includes(event?.type) ){
|
// let newText = this.getText()
|
||||||
return
|
let newText = document.getElementById('squire-id').innerHTML
|
||||||
|
|
||||||
|
const diff = dmp.diff_main(oldText, newText)
|
||||||
|
// dmp.diff_cleanupSemantic(diff)
|
||||||
|
const patch_list = dmp.patch_make(oldText, newText, diff);
|
||||||
|
const patch_text = dmp.patch_toText(patch_list);
|
||||||
|
|
||||||
|
if(patch_text == ''){ return }
|
||||||
|
|
||||||
|
//Save computed diff text
|
||||||
|
this.noteText = newText
|
||||||
|
|
||||||
|
let newPatch = {
|
||||||
|
id: this.rawTextId,
|
||||||
|
diff: patch_text,
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimeout(this.diffTextTimeout)
|
this.$io.emit('note_diff', newPatch)
|
||||||
this.diffTextTimeout = setTimeout(() => {
|
|
||||||
|
|
||||||
// Current Editor Text
|
|
||||||
const liveEditorElm = document.getElementById('squire-id')
|
|
||||||
|
|
||||||
// virtual element for selecting div
|
|
||||||
let virtualEditorElm = document.createElement('div')
|
|
||||||
virtualEditorElm.innerHTML = rawNoteText
|
|
||||||
|
|
||||||
// element at cursor
|
|
||||||
const elmAtCaret = window.getSelection().getRangeAt(0).startContainer.parentNode
|
|
||||||
|
|
||||||
// Remove beginngin selector from path, make it more generic
|
|
||||||
const path = this.generateSelector(elmAtCaret).replace('div#squire-id > ','')
|
|
||||||
let workingPath = ''
|
|
||||||
|
|
||||||
// default to entire note text, select down if path
|
|
||||||
let selectedDivText = virtualEditorElm
|
|
||||||
let newSelectedDivText = liveEditorElm
|
|
||||||
|
|
||||||
if( path != ''){
|
|
||||||
|
|
||||||
const pathParts = path.split(' > ')
|
|
||||||
let testedPathParts = []
|
|
||||||
let workingPathParts = []
|
|
||||||
|
|
||||||
for (var i = 0; i < pathParts.length; i++) {
|
|
||||||
|
|
||||||
testedPathParts.push(pathParts[i])
|
|
||||||
let currentTestPath = testedPathParts.join(' > ')
|
|
||||||
// console.log('elm test ',i,currentTestPath)
|
|
||||||
let elmTest = virtualEditorElm.querySelector(currentTestPath)
|
|
||||||
|
|
||||||
if(!elmTest){
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
workingPathParts.push(pathParts[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
workingPath = workingPathParts.join(' > ')
|
|
||||||
|
|
||||||
if(workingPath){
|
|
||||||
// Select text from virtual editor text
|
|
||||||
selectedDivText = selectedDivText.querySelector(workingPath)
|
|
||||||
// select text from current editor text
|
|
||||||
newSelectedDivText = newSelectedDivText.querySelector(workingPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldDivText = selectedDivText.innerHTML
|
|
||||||
const newDivText = newSelectedDivText.innerHTML
|
|
||||||
|
|
||||||
if(oldDivText == newDivText){ return }
|
|
||||||
|
|
||||||
const diff = dmp.diff_main(oldDivText, newDivText)
|
|
||||||
const patch_list = dmp.patch_make(oldDivText, newDivText, diff)
|
|
||||||
const patch_text = dmp.patch_toText(patch_list)
|
|
||||||
|
|
||||||
// save raw text for future diffs
|
|
||||||
rawNoteText = liveEditorElm.innerHTML
|
|
||||||
|
|
||||||
let newPatch = {
|
|
||||||
id: this.rawTextId,
|
|
||||||
diff: patch_text,
|
|
||||||
path: path,
|
|
||||||
// testing metrics
|
|
||||||
'old text':oldDivText,
|
|
||||||
'new text':newDivText,
|
|
||||||
'starting path':path,
|
|
||||||
'working path':workingPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('Sending out patch', newPatch)
|
|
||||||
|
|
||||||
this.$io.emit('note_diff', newPatch)
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
},
|
},
|
||||||
patchText(incomingPatchs){
|
patchText(incomingPatchs){
|
||||||
// console.log('incoming patches ', incomingPatchs)
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const editorElement = document.getElementById('squire-id')
|
if(incomingPatchs == null){ return resolve(true) }
|
||||||
|
if(incomingPatchs.length == 0){ return resolve(true) }
|
||||||
|
|
||||||
// iterate over incoming patches because they apply to specific divs
|
// let currentText = this.getText()
|
||||||
|
let currentText = document.getElementById('squire-id').innerHTML
|
||||||
|
|
||||||
|
//Convert text of all new patches into patches array
|
||||||
|
let patches = []
|
||||||
incomingPatchs.forEach(patch => {
|
incomingPatchs.forEach(patch => {
|
||||||
|
|
||||||
// default to parent element, change to child if set
|
if(patch.time <= this.updated){
|
||||||
let editedElement = editorElement
|
return
|
||||||
if(patch.path){
|
|
||||||
editedElement = editorElement.querySelector(patch.path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !editedElement ){
|
patches.push(...dmp.patch_fromText(patch.diff))
|
||||||
editedElement = editorElement
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert patch from text and then apply to selected element
|
|
||||||
const patches = dmp.patch_fromText(patch.diff)
|
|
||||||
const patchResults = dmp.patch_apply(patches, editedElement.innerHTML)
|
|
||||||
|
|
||||||
// console.log('Patch results')
|
|
||||||
// console.log([patch.path, editedElement.innerHTML, patchResults[0]])
|
|
||||||
|
|
||||||
// patch changed directly into editor
|
|
||||||
editedElement.innerHTML = patchResults[0]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// save editor HTML after change for future comparisons
|
if(patches.length == 0){
|
||||||
rawNoteText = editorElement.innerHTML
|
|
||||||
|
|
||||||
// update hash on patch
|
|
||||||
this.lastNoteHash = this.hashString( rawNoteText )
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
return resolve(true)
|
return resolve(true)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
var results = dmp.patch_apply(patches, currentText);
|
||||||
|
let newText = results[0]
|
||||||
|
|
||||||
|
this.noteText = newText
|
||||||
|
// this.editor.setHTML(newText)
|
||||||
|
document.getElementById('squire-id').innerHTML = newText
|
||||||
|
|
||||||
|
return resolve(true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onKeyup(event){
|
onKeyup(event){
|
||||||
|
|
||||||
this.statusText = 'modified'
|
this.statusText = 'modified'
|
||||||
this.idleNote = false
|
|
||||||
|
// Small debounce on diff generation
|
||||||
|
clearTimeout(this.diffTextTimeout)
|
||||||
|
this.diffTextTimeout = setTimeout(() => {
|
||||||
|
this.diffText()
|
||||||
|
}, 25)
|
||||||
|
|
||||||
//Save after x seconds
|
//Save after x seconds
|
||||||
clearTimeout(this.editDebounce)
|
clearTimeout(this.editDebounce)
|
||||||
this.editDebounce = setTimeout(() => {
|
this.editDebounce = setTimeout(() => {
|
||||||
this.save()
|
this.save()
|
||||||
}, 4 * 1000)
|
}, 5 * 1000)
|
||||||
|
|
||||||
//Save after x keystrokes
|
//Save after x keystrokes
|
||||||
this.keyPressesCounter = (this.keyPressesCounter + 1)
|
this.keyPressesCounter = (this.keyPressesCounter + 1)
|
||||||
@@ -1040,7 +919,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//tell websockets to truncate history at this save
|
//tell websockets to truncate history at this save
|
||||||
this.lastNoteHash = currentHash //Update last saved note hash
|
|
||||||
this.$io.emit('truncate_diffs_at_save', {'rawTextId':this.rawTextId, 'hash':currentHash })
|
this.$io.emit('truncate_diffs_at_save', {'rawTextId':this.rawTextId, 'hash':currentHash })
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
@@ -1061,54 +939,44 @@
|
|||||||
this.modified = true
|
this.modified = true
|
||||||
this.diffsApplied = 0
|
this.diffsApplied = 0
|
||||||
|
|
||||||
|
//Update last saved note hash
|
||||||
|
this.lastNoteHash = currentHash
|
||||||
return resolve(true)
|
return resolve(true)
|
||||||
})
|
})
|
||||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Save Note') })
|
.catch(error => { this.$bus.$emit('notification', 'Failed to Save Note') })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
loadNoteNextFromServer(){
|
checkForUpdatedNote(){
|
||||||
|
|
||||||
clearTimeout(this.reloadNoteDebounce)
|
const now = +new Date
|
||||||
this.reloadNoteDebounce = setTimeout(() => {
|
//Only check every 3 seconds
|
||||||
|
const checkForUpdateTimeout = now - this.lastInteractionTimestamp > (2 * 1000)
|
||||||
// flash note text to show the update
|
|
||||||
// this.setText('')
|
|
||||||
|
|
||||||
|
//If user leaves page then returns to page, reload the first batch
|
||||||
|
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible' && checkForUpdateTimeout){
|
||||||
|
|
||||||
//Focus Regained on Note, check for update
|
//Focus Regained on Note, check for update
|
||||||
axios.post('/api/note/get', { 'noteId': this.noteid })
|
axios.post('/api/note/get', { 'noteId': this.noteid })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
||||||
this.setupLoadedNoteData(response)
|
const serverTextHash = this.hashString( response.data.text )
|
||||||
|
|
||||||
//Manually set squire text to show
|
if(this.lastNoteHash != serverTextHash){
|
||||||
this.setText(this.noteText)
|
// console.log('note was changed UPDATE THAT BITCH!!!!')
|
||||||
|
this.setupLoadedNoteData(response)
|
||||||
|
|
||||||
|
//Manually set squire text to show
|
||||||
|
this.setText(this.noteText)
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
},
|
|
||||||
windowFocusEvent(){
|
|
||||||
|
|
||||||
//Only check if its been greater than a few seconds
|
|
||||||
const now = +new Date
|
|
||||||
const checkForUpdateTimeout = now - this.lastInteractionTimestamp > (3 * 1000)
|
|
||||||
|
|
||||||
//If user leaves page then returns to page, reload the first batch
|
|
||||||
if(checkForUpdateTimeout){
|
|
||||||
|
|
||||||
this.loadNoteNextFromServer()
|
|
||||||
this.lastInteractionTimestamp = now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
//Keep track of visibility change and last interaction time
|
||||||
windowBlurEvent(){
|
this.lastVisibilityState = document.visibilityState
|
||||||
|
|
||||||
this.idleNote = true
|
|
||||||
this.lastInteractionTimestamp = +new Date
|
this.lastInteractionTimestamp = +new Date
|
||||||
|
|
||||||
},
|
},
|
||||||
hashString(inText){
|
hashString(inText){
|
||||||
|
|
||||||
@@ -1162,14 +1030,11 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
destroyWebSockets(){
|
destroyWebSockets(){
|
||||||
this.$io.removeListener('past_diffs')
|
// this.$io.removeListener('past_diffs')
|
||||||
this.$io.removeListener('update_user_count')
|
// this.$io.removeListener('update_user_count')
|
||||||
this.$io.removeListener('incoming_diff')
|
// this.$io.removeListener('incoming_diff')
|
||||||
this.$io.removeListener('update_note_attachments')
|
|
||||||
|
|
||||||
clearTimeout(this.idleNoteTimeout)
|
|
||||||
},
|
},
|
||||||
initWebsocketEvents(){
|
setupWebSockets(){
|
||||||
|
|
||||||
//Tell server to push this note into a room
|
//Tell server to push this note into a room
|
||||||
this.$io.emit('join_room', this.rawTextId )
|
this.$io.emit('join_room', this.rawTextId )
|
||||||
@@ -1185,79 +1050,36 @@
|
|||||||
this.diffsApplied = diffSinceLastUpdate.length
|
this.diffsApplied = diffSinceLastUpdate.length
|
||||||
// console.log('Got Diffs Total -> ', diffSinceLastUpdate)
|
// console.log('Got Diffs Total -> ', diffSinceLastUpdate)
|
||||||
}
|
}
|
||||||
// console.log(diffSinceLastUpdate)
|
|
||||||
this.patchText(diffSinceLastUpdate)
|
this.patchText(diffSinceLastUpdate)
|
||||||
.then(() => {
|
|
||||||
this.restoreCaretPosition()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.$io.on('incoming_diff', incomingDiff => {
|
this.$io.on('incoming_diff', incomingDiff => {
|
||||||
|
|
||||||
|
//Save current caret position
|
||||||
|
//Find index of child element based on past range
|
||||||
|
const element = window.getSelection().getRangeAt(0).startContainer.parentNode
|
||||||
|
const textLines = document.getElementById('squire-id').children
|
||||||
|
const childIndex = [...textLines].indexOf(element)
|
||||||
|
|
||||||
this.patchText([incomingDiff])
|
this.patchText([incomingDiff])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.restoreCaretPosition()
|
|
||||||
|
if(childIndex == -1){
|
||||||
|
console.log('Cursor position lost. Div being updated was lost.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset caret position
|
||||||
|
//Find child index of old range and create a new one
|
||||||
|
let allChildren = document.getElementById('squire-id').children
|
||||||
|
const newLine = allChildren[childIndex].firstChild
|
||||||
|
let range = document.createRange()
|
||||||
|
range.setStart(newLine, this.startOffset)
|
||||||
|
range.setEnd(newLine, this.startOffset)
|
||||||
|
this.editor.setSelection(range)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
|
|
||||||
|
|
||||||
const sameIdCheck = (this.idleNote && this.noteid == noteId)
|
|
||||||
const differentHashCheck = (hash != this.lastNoteHash)
|
|
||||||
|
|
||||||
// if hashes do not match, reload text from server
|
|
||||||
if(sameIdCheck && differentHashCheck){
|
|
||||||
this.loadNoteNextFromServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
this.$io.on('update_note_attachments', () => {
|
|
||||||
this.getAttachments()
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
logNoteInteraction(){
|
|
||||||
|
|
||||||
this.idleNote = false
|
|
||||||
clearTimeout(this.idleNoteTimeout)
|
|
||||||
this.idleNoteTimeout = setTimeout(() => {
|
|
||||||
this.idleNote = true
|
|
||||||
}, 5000)
|
|
||||||
|
|
||||||
},
|
|
||||||
saveCaretPosition(event){
|
|
||||||
|
|
||||||
//Find index of child element based on past range
|
|
||||||
const element = window.getSelection().getRangeAt(0).startContainer.parentNode
|
|
||||||
|
|
||||||
//Save range to replace cursor if someone else makes an update
|
|
||||||
this.lastRange = this.generateSelector(element)
|
|
||||||
this.startOffset = parseInt(event.range.startOffset) || 0
|
|
||||||
|
|
||||||
return
|
|
||||||
},
|
|
||||||
restoreCaretPosition(){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// This code is intended to restore caret position to previous location
|
|
||||||
// when a third party updates the note.
|
|
||||||
|
|
||||||
if(!this.lastRange){ return resolve(true) }
|
|
||||||
|
|
||||||
const editorElement = document.getElementById('squire-id')
|
|
||||||
const lastElement = editorElement.querySelector(this.lastRange)
|
|
||||||
|
|
||||||
if( !lastElement ){ return resolve(true) }
|
|
||||||
|
|
||||||
let range = document.createRange()
|
|
||||||
range.setStart(lastElement.firstChild, this.startOffset)
|
|
||||||
range.setEnd(lastElement.firstChild, this.startOffset)
|
|
||||||
|
|
||||||
// Set range in editor element
|
|
||||||
this.editor.setSelection(range)
|
|
||||||
|
|
||||||
return resolve(true)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
titleResize(){
|
titleResize(){
|
||||||
//Resize the title field
|
//Resize the title field
|
||||||
@@ -1268,88 +1090,6 @@
|
|||||||
element.style.height = (element.scrollHeight) +'px'
|
element.style.height = (element.scrollHeight) +'px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyAttachmentStyles(){
|
|
||||||
// Remove attachment preview styles
|
|
||||||
var head = document.head
|
|
||||||
var styleElement = document.getElementById('attachmentGeneratedStyles'+this.noteid)
|
|
||||||
if(styleElement){
|
|
||||||
head.removeChild(styleElement)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getAttachments(){
|
|
||||||
|
|
||||||
axios.post('/api/attachment/search', {'noteId':this.noteid})
|
|
||||||
.then( results => {
|
|
||||||
|
|
||||||
|
|
||||||
// generate new style group
|
|
||||||
var style = document.createElement('style')
|
|
||||||
style.id = 'attachmentGeneratedStyles'+this.noteid
|
|
||||||
style.type = 'text/css'
|
|
||||||
|
|
||||||
// iterate attachments and build unique style for each
|
|
||||||
let attachmentStyles = []
|
|
||||||
results.data.forEach(attachment => {
|
|
||||||
|
|
||||||
// thumbnail location
|
|
||||||
const bgurl = `/api/static/thumb_${attachment.file_location}`
|
|
||||||
let padding = '2px 0 0'
|
|
||||||
|
|
||||||
// increase padding if there is a valid file
|
|
||||||
if(attachment.file_location){
|
|
||||||
padding = '13px 0 13px 155px'
|
|
||||||
}
|
|
||||||
|
|
||||||
// unescaped characters will break content attribute
|
|
||||||
const strippedText = attachment.text
|
|
||||||
.replace(/'/g, "\\'") //Escape ' s
|
|
||||||
.replace(/\n/g, '\\A') //Escape new lines
|
|
||||||
|
|
||||||
// strip down URL, *= matches anywhere is string
|
|
||||||
const substringsToRemove = ['https://','http://','www.']
|
|
||||||
var pattern = new RegExp(substringsToRemove.join('|'), 'g');
|
|
||||||
const strippedurl = attachment.url
|
|
||||||
.replace(pattern, '') // remove url protocol
|
|
||||||
.replace(/&.*/, '') // remove anything after &
|
|
||||||
|
|
||||||
const cleanStyle = `
|
|
||||||
.squire-box a[href*="${strippedurl}" i]::before {
|
|
||||||
content: '${strippedText}';
|
|
||||||
display: inline-block;
|
|
||||||
padding: ${padding};
|
|
||||||
pointer-events: none;
|
|
||||||
font-size: 1.3em !important;
|
|
||||||
width: 100%;
|
|
||||||
background-position: left center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 140px auto;
|
|
||||||
background-image: url(${bgurl});
|
|
||||||
border-bottom: solid 1px var(--main-accent);
|
|
||||||
margin-bottom: -10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.squire-box a[href*="${strippedurl}" i] {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
.replace(/\t|\r|\n/gm, "") // Remove tabs, new lines, returns
|
|
||||||
.replace(/\s+/g, ' ') // remove double spaces
|
|
||||||
|
|
||||||
attachmentStyles.push(cleanStyle)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Destroy just before creating new to prevent page jumping
|
|
||||||
this.destroyAttachmentStyles()
|
|
||||||
|
|
||||||
// push new styles into <head>
|
|
||||||
style.innerHTML = attachmentStyles.join(' ')
|
|
||||||
document.head.appendChild(style)
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch(error => { console.log(error);this.$bus.$emit('notification', 'Failed to Search Attachments') })
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1362,11 +1102,6 @@
|
|||||||
z-index: 1019;
|
z-index: 1019;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.status-menu span + span {
|
|
||||||
border-left: 1px solid #ccc;
|
|
||||||
margin-left: 4px;
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-color-bar {
|
.font-color-bar {
|
||||||
/*width: calc(100% - 8px);*/
|
/*width: calc(100% - 8px);*/
|
||||||
@@ -1634,7 +1369,6 @@
|
|||||||
}
|
}
|
||||||
.edit-button {
|
.edit-button {
|
||||||
padding: 6px 0px 0;
|
padding: 6px 0px 0;
|
||||||
flex-grow: 1;
|
|
||||||
}
|
}
|
||||||
.edit-button > span:not(.ui) {
|
.edit-button > span:not(.ui) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@@ -108,14 +108,7 @@
|
|||||||
|
|
||||||
<div v-if="getThumbs.length > 0">
|
<div v-if="getThumbs.length > 0">
|
||||||
<div class="tiny-thumb-box" v-on:click="openEditAttachment">
|
<div class="tiny-thumb-box" v-on:click="openEditAttachment">
|
||||||
<img v-for="thumb in getThumbs"
|
<img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`">
|
||||||
class="tiny-thumb"
|
|
||||||
:src="`/api/static/thumb_${thumb}`"
|
|
||||||
onerror="
|
|
||||||
this.onerror=null;
|
|
||||||
this.src='/api/static/assets/marketing/void.svg';
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -227,12 +220,10 @@
|
|||||||
},
|
},
|
||||||
pinNote(){ //togglePinned() <- old name
|
pinNote(){ //togglePinned() <- old name
|
||||||
this.showWorking = true
|
this.showWorking = true
|
||||||
this.note.pinned = this.note.pinned == 1 ? 0:1
|
let postData = {'pinned': !this.note.pinned, 'noteId':this.note.id}
|
||||||
let postData = {'pinned': this.note.pinned, 'noteId':this.note.id}
|
|
||||||
axios.post('/api/note/setpinned', postData)
|
axios.post('/api/note/setpinned', postData)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.showWorking = false
|
this.showWorking = false
|
||||||
// this event is triggered by the server after note is saved
|
|
||||||
// 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') })
|
.catch(error => { this.$bus.$emit('notification', 'Failed to Pin Note') })
|
||||||
@@ -248,10 +239,11 @@
|
|||||||
//Show message so no one worries where note went
|
//Show message so no one worries where note went
|
||||||
let message = 'Moved to Archive'
|
let message = 'Moved to Archive'
|
||||||
if(postData.archived != 1){
|
if(postData.archived != 1){
|
||||||
message = 'Moved out of Archive'
|
message = 'Moved to main list'
|
||||||
}
|
}
|
||||||
this.$bus.$emit('notification', message)
|
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') })
|
.catch(error => { this.$bus.$emit('notification', 'Failed to Archive Note') })
|
||||||
},
|
},
|
||||||
@@ -266,10 +258,9 @@
|
|||||||
//Show message so no one worries where note went
|
//Show message so no one worries where note went
|
||||||
let message = 'Moved to Trash'
|
let message = 'Moved to Trash'
|
||||||
if(postData.trashed == 0){
|
if(postData.trashed == 0){
|
||||||
message = 'Moved out of Trash'
|
message = 'Moved to main list'
|
||||||
}
|
}
|
||||||
this.$bus.$emit('notification', message)
|
this.$bus.$emit('notification', message)
|
||||||
this.$bus.$emit('update_single_note', this.note.id)
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(error => { this.$bus.$emit('notification', 'Failed to Trash Note') })
|
.catch(error => { this.$bus.$emit('notification', 'Failed to Trash Note') })
|
||||||
@@ -285,28 +276,23 @@
|
|||||||
},
|
},
|
||||||
justClosed(){
|
justClosed(){
|
||||||
|
|
||||||
// Dont do anything when not is closed.
|
|
||||||
// Its already saved, this will make interface feel snappy
|
|
||||||
|
|
||||||
// Scroll note into view
|
// Scroll note into view
|
||||||
// this.$el.scrollIntoView({
|
this.$el.scrollIntoView({
|
||||||
// behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
// block: 'center',
|
block: 'center',
|
||||||
// inline: 'center'
|
inline: 'center'
|
||||||
// })
|
})
|
||||||
|
|
||||||
// this.$bus.$emit('notification','Note Saved')
|
//After scroll, trigger green outline animation
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
// //After scroll, trigger green outline animation
|
this.triggerClosedAnimation = true
|
||||||
// setTimeout(() => {
|
setTimeout(()=>{
|
||||||
|
//After 3 seconds, hide it
|
||||||
|
this.triggerClosedAnimation = false
|
||||||
|
}, 1500)
|
||||||
|
|
||||||
// this.triggerClosedAnimation = true
|
}, 500)
|
||||||
// setTimeout(()=>{
|
|
||||||
// //After 3 seconds, hide it
|
|
||||||
// this.triggerClosedAnimation = false
|
|
||||||
// }, 1500)
|
|
||||||
|
|
||||||
// }, 500)
|
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="button-fix">
|
<div>
|
||||||
<div class="ui right floated basic shrinking icon button" v-on:click="showPasteInputArea">
|
<div class="ui right floated basic shrinking icon button" v-on:click="showPasteInputArea">
|
||||||
<i class="green paste icon"></i>
|
<i class="paste icon"></i>
|
||||||
Paste
|
Paste
|
||||||
</div>
|
</div>
|
||||||
<div class="shade" v-if="showPasteArea" @click.prevent="close">
|
<div class="shade" v-if="showPasteArea" @click.prevent="close">
|
||||||
|
@@ -35,6 +35,7 @@
|
|||||||
<i class="search icon"></i>
|
<i class="search icon"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="floating-button" v-if="searchTerm.length > 0">
|
<div class="floating-button" v-if="searchTerm.length > 0">
|
||||||
<i class="big link grey close icon" v-on:click="clear()"></i>
|
<i class="big link grey close icon" v-on:click="clear()"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -172,16 +172,15 @@ const SquireButtonFunctions = {
|
|||||||
|
|
||||||
//Fetch the container
|
//Fetch the container
|
||||||
let container = document.getElementById('squire-id')
|
let container = document.getElementById('squire-id')
|
||||||
this.$router.go(-1)
|
|
||||||
|
|
||||||
setTimeout(()=>{
|
Array.from( container.getElementsByClassName('active') ).forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
})
|
||||||
|
|
||||||
Array.from( container.getElementsByClassName('active') ).forEach(item => {
|
//Close menu if user is on mobile, then sort list
|
||||||
item.classList.remove('active');
|
if(this.$store.getters.getIsUserOnMobile){
|
||||||
})
|
this.$router.go(-1)
|
||||||
|
}
|
||||||
},600)
|
|
||||||
|
|
||||||
},
|
},
|
||||||
deleteCompletedListItems(){
|
deleteCompletedListItems(){
|
||||||
//
|
//
|
||||||
@@ -191,57 +190,53 @@ const SquireButtonFunctions = {
|
|||||||
//Fetch the container
|
//Fetch the container
|
||||||
let container = document.getElementById('squire-id')
|
let container = document.getElementById('squire-id')
|
||||||
|
|
||||||
//Close menu if user is on mobile, then sort list
|
//Go through each item, on first level, look for Unordered Lists
|
||||||
this.$router.go(-1)
|
container.childNodes.forEach( (node) => {
|
||||||
|
if(node.nodeName == 'UL'){
|
||||||
|
|
||||||
setTimeout(()=>{
|
//Create two categories, done and not done list items
|
||||||
|
let undoneElements = document.createDocumentFragment()
|
||||||
|
|
||||||
//Go through each item, on first level, look for Unordered Lists
|
//Go through each item in each list we found
|
||||||
container.childNodes.forEach( (node) => {
|
node.childNodes.forEach( (checkListItem, index) => {
|
||||||
if(node.nodeName == 'UL'){
|
|
||||||
|
|
||||||
//Create two categories, done and not done list items
|
//Skip Embedded lists, they are handled with the list item above them. Keep lists with intented items together
|
||||||
let undoneElements = document.createDocumentFragment()
|
if(checkListItem.nodeName == 'UL'){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//Go through each item in each list we found
|
//Check if list item has active class
|
||||||
node.childNodes.forEach( (checkListItem, index) => {
|
const checkedItem = checkListItem.classList.contains('active')
|
||||||
|
|
||||||
//Skip Embedded lists, they are handled with the list item above them. Keep lists with intented items together
|
//Check if the next item is a list, Keep lists with intented items together
|
||||||
if(checkListItem.nodeName == 'UL'){
|
let sublist = null
|
||||||
return
|
if(node.childNodes[index+1] && node.childNodes[index+1].nodeName == 'UL'){
|
||||||
|
sublist = node.childNodes[index+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
//Push checked items and their sub lists to the done set
|
||||||
|
if(!checkedItem){
|
||||||
|
|
||||||
|
undoneElements.appendChild( checkListItem.cloneNode(true) )
|
||||||
|
if(sublist){
|
||||||
|
undoneElements.appendChild( sublist.cloneNode(true) )
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if list item has active class
|
}
|
||||||
const checkedItem = checkListItem.classList.contains('active')
|
|
||||||
|
|
||||||
//Check if the next item is a list, Keep lists with intented items together
|
})
|
||||||
let sublist = null
|
|
||||||
if(node.childNodes[index+1] && node.childNodes[index+1].nodeName == 'UL'){
|
|
||||||
sublist = node.childNodes[index+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
//Push checked items and their sub lists to the done set
|
//Remove all HTML from node, push unfinished items, then finished below them
|
||||||
if(!checkedItem){
|
node.innerHTML = null
|
||||||
|
node.appendChild(undoneElements)
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
undoneElements.appendChild( checkListItem.cloneNode(true) )
|
//Close menu if user is on mobile, then sort list
|
||||||
if(sublist){
|
if(this.$store.getters.getIsUserOnMobile){
|
||||||
undoneElements.appendChild( sublist.cloneNode(true) )
|
this.$router.go(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
//Remove all HTML from node, push unfinished items, then finished below them
|
|
||||||
node.innerHTML = null
|
|
||||||
node.appendChild(undoneElements)
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}, 600)
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
sortList(){
|
sortList(){
|
||||||
//
|
//
|
||||||
@@ -251,65 +246,61 @@ const SquireButtonFunctions = {
|
|||||||
//Fetch the container
|
//Fetch the container
|
||||||
let container = document.getElementById('squire-id')
|
let container = document.getElementById('squire-id')
|
||||||
|
|
||||||
//Close menu if user is on mobile
|
//Go through each item, on first level, look for Unordered Lists
|
||||||
this.$router.go(-1)
|
container.childNodes.forEach( (node) => {
|
||||||
|
if(node.nodeName == 'UL'){
|
||||||
|
|
||||||
setTimeout(()=>{
|
//Create two categories, done and not done list items
|
||||||
|
let doneElements = document.createDocumentFragment()
|
||||||
|
let undoneElements = document.createDocumentFragment()
|
||||||
|
|
||||||
//Go through each item, on first level, look for Unordered Lists
|
//Go through each item in each list we found
|
||||||
container.childNodes.forEach( (node) => {
|
node.childNodes.forEach( (checkListItem, index) => {
|
||||||
if(node.nodeName == 'UL'){
|
|
||||||
|
|
||||||
//Create two categories, done and not done list items
|
//Skip Embedded lists, they are handled with the list item above them. Keep lists with intented items together
|
||||||
let doneElements = document.createDocumentFragment()
|
if(checkListItem.nodeName == 'UL'){
|
||||||
let undoneElements = document.createDocumentFragment()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//Go through each item in each list we found
|
//Check if list item has active class
|
||||||
node.childNodes.forEach( (checkListItem, index) => {
|
const checkedItem = checkListItem.classList.contains('active')
|
||||||
|
|
||||||
//Skip Embedded lists, they are handled with the list item above them. Keep lists with intented items together
|
//Check if the next item is a list, Keep lists with intented items together
|
||||||
if(checkListItem.nodeName == 'UL'){
|
let sublist = null
|
||||||
return
|
if(node.childNodes[index+1] && node.childNodes[index+1].nodeName == 'UL'){
|
||||||
|
sublist = node.childNodes[index+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
//Push checked items and their sub lists to the done set
|
||||||
|
if(checkedItem){
|
||||||
|
|
||||||
|
doneElements.appendChild( checkListItem.cloneNode(true) )
|
||||||
|
if(sublist){
|
||||||
|
doneElements.appendChild( sublist.cloneNode(true) )
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check if list item has active class
|
} else {
|
||||||
const checkedItem = checkListItem.classList.contains('active')
|
|
||||||
|
|
||||||
//Check if the next item is a list, Keep lists with intented items together
|
undoneElements.appendChild( checkListItem.cloneNode(true) )
|
||||||
let sublist = null
|
if(sublist){
|
||||||
if(node.childNodes[index+1] && node.childNodes[index+1].nodeName == 'UL'){
|
undoneElements.appendChild( sublist.cloneNode(true) )
|
||||||
sublist = node.childNodes[index+1]
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Push checked items and their sub lists to the done set
|
})
|
||||||
if(checkedItem){
|
|
||||||
|
|
||||||
doneElements.appendChild( checkListItem.cloneNode(true) )
|
//Remove all HTML from node, push unfinished items, then finished below them
|
||||||
if(sublist){
|
node.innerHTML = null
|
||||||
doneElements.appendChild( sublist.cloneNode(true) )
|
node.appendChild(undoneElements)
|
||||||
}
|
node.appendChild(doneElements)
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
} else {
|
//Close menu if user is on mobile
|
||||||
|
if(this.$store.getters.getIsUserOnMobile){
|
||||||
undoneElements.appendChild( checkListItem.cloneNode(true) )
|
this.$router.go(-1)
|
||||||
if(sublist){
|
}
|
||||||
undoneElements.appendChild( sublist.cloneNode(true) )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
//Remove all HTML from node, push unfinished items, then finished below them
|
|
||||||
node.innerHTML = null
|
|
||||||
node.appendChild(undoneElements)
|
|
||||||
node.appendChild(doneElements)
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
},600)
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
calculateMath(){
|
calculateMath(){
|
||||||
//
|
//
|
||||||
@@ -319,9 +310,6 @@ const SquireButtonFunctions = {
|
|||||||
//Fetch the container
|
//Fetch the container
|
||||||
let container = document.getElementById('squire-id')
|
let container = document.getElementById('squire-id')
|
||||||
|
|
||||||
//Close menu if user is on mobile, then sort list
|
|
||||||
this.$router.go(-1)
|
|
||||||
|
|
||||||
// simple function that trys to evaluate javascript
|
// simple function that trys to evaluate javascript
|
||||||
const shittyMath = (string) => {
|
const shittyMath = (string) => {
|
||||||
//Remove all chars but math chars
|
//Remove all chars but math chars
|
||||||
@@ -334,39 +322,38 @@ const SquireButtonFunctions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(()=>{
|
//Go through each item, on first level, look for Unordered Lists
|
||||||
|
container.childNodes.forEach( (node) => {
|
||||||
|
|
||||||
//Go through each item, on first level, look for Unordered Lists
|
const line = node.innerText.trim()
|
||||||
container.childNodes.forEach( (node) => {
|
|
||||||
|
|
||||||
const line = node.innerText.trim()
|
// = sign exists and its the last character in the string
|
||||||
|
if(line.indexOf('=') != -1 && (line.length-1) == line.indexOf('=')){
|
||||||
|
|
||||||
// = sign exists and its the last character in the string
|
//Pull out everything before the formula and try to evaluate it
|
||||||
if(line.indexOf('=') != -1 && (line.length-1) == line.indexOf('=')){
|
const formula = line.split('=').shift()
|
||||||
|
const output = shittyMath(formula)
|
||||||
|
|
||||||
//Pull out everything before the formula and try to evaluate it
|
//If its a number and didn't throw an error, update the line
|
||||||
const formula = line.split('=').shift()
|
if(!isNaN(output) && output != null){
|
||||||
const output = shittyMath(formula)
|
|
||||||
|
|
||||||
//If its a number and didn't throw an error, update the line
|
//Since there is HTML in the line, splice in the number after the = sign
|
||||||
if(!isNaN(output) && output != null){
|
let equalLocation = node.innerHTML.indexOf('=')
|
||||||
|
let newLine = node.innerHTML.slice(0, equalLocation+1).trim()
|
||||||
|
newLine += ` ${output}`
|
||||||
|
newLine += node.innerHTML.slice(equalLocation+1).trim()
|
||||||
|
|
||||||
//Since there is HTML in the line, splice in the number after the = sign
|
//Slam in that new HTML with the output
|
||||||
let equalLocation = node.innerHTML.indexOf('=')
|
node.innerHTML = newLine
|
||||||
let newLine = node.innerHTML.slice(0, equalLocation+1).trim()
|
|
||||||
newLine += ` ${output}`
|
|
||||||
newLine += node.innerHTML.slice(equalLocation+1).trim()
|
|
||||||
|
|
||||||
//Slam in that new HTML with the output
|
|
||||||
node.innerHTML = newLine
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
|
||||||
},600)
|
})
|
||||||
|
|
||||||
|
//Close menu if user is on mobile, then sort list
|
||||||
|
if(this.$store.getters.getIsUserOnMobile){
|
||||||
|
this.$router.go(-1)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setText(inText){
|
setText(inText){
|
||||||
|
|
||||||
|
@@ -8,13 +8,6 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
Files
|
Files
|
||||||
<div class="sub header">Uploaded Files and Websites from notes.</div>
|
<div class="sub header">Uploaded Files and Websites from notes.</div>
|
||||||
<div class="sub header">
|
|
||||||
<i class="green angle double up icon icon"></i>
|
|
||||||
<router-link
|
|
||||||
to="/bookmarklet">
|
|
||||||
Push any website to solid scribe
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@@ -42,23 +35,6 @@
|
|||||||
<i class="copy icon"></i>
|
<i class="copy icon"></i>
|
||||||
Other Files
|
Other Files
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<router-link
|
|
||||||
v-if="$store.getters.totals && $store.getters.totals['archivedNotes']"
|
|
||||||
exact-active-class="green"
|
|
||||||
class="ui basic button shrinking"
|
|
||||||
to="/attachments/type/archived">
|
|
||||||
<i class="archive icon"></i>
|
|
||||||
Archived
|
|
||||||
</router-link>
|
|
||||||
<router-link
|
|
||||||
v-if="$store.getters.totals && $store.getters.totals['trashedNotes']"
|
|
||||||
exact-active-class="green"
|
|
||||||
class="ui basic button shrinking"
|
|
||||||
to="/attachments/type/trashed">
|
|
||||||
<i class="trash icon"></i>
|
|
||||||
Trashed
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']"
|
v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']"
|
||||||
@@ -132,11 +108,6 @@
|
|||||||
//Load more attachments on scroll
|
//Load more attachments on scroll
|
||||||
window.addEventListener('scroll', this.onScroll)
|
window.addEventListener('scroll', this.onScroll)
|
||||||
|
|
||||||
this.$io.on('update_note_attachments', () => {
|
|
||||||
this.reset()
|
|
||||||
this.searchAttachments()
|
|
||||||
})
|
|
||||||
|
|
||||||
//Mount notes on load if note ID is set
|
//Mount notes on load if note ID is set
|
||||||
this.searchAttachments()
|
this.searchAttachments()
|
||||||
},
|
},
|
||||||
@@ -144,8 +115,6 @@
|
|||||||
|
|
||||||
//Remove scroll event on destroy
|
//Remove scroll event on destroy
|
||||||
window.removeEventListener('scroll', this.onScroll)
|
window.removeEventListener('scroll', this.onScroll)
|
||||||
|
|
||||||
this.$io.removeListener('update_note_attachments')
|
|
||||||
},
|
},
|
||||||
watch:{
|
watch:{
|
||||||
$route (to, from){
|
$route (to, from){
|
||||||
|
@@ -1,66 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-container squire-box">
|
|
||||||
|
|
||||||
<h2 class="ui header">
|
|
||||||
<i class="green angle double up icon icon"></i>
|
|
||||||
<div class="content">
|
|
||||||
Push URL to Solid Scribe - Bookmarklet
|
|
||||||
<div class="sub header">Push any website to your file list.</div>
|
|
||||||
</div>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p>A bookmarklet is a small piece of code that can be run from a bookmark.</p>
|
|
||||||
<p>Use the bookmarklet below to push URLs of website to solid scribe for later</p>
|
|
||||||
<p>The bookmarklet works in a secure way and won't leak any data.</p>
|
|
||||||
<p>To install the bookmarklet, all you need to do is drag it to your bookmarks bar.</p>
|
|
||||||
|
|
||||||
<h2>
|
|
||||||
Drag the link below to your bookmarks.
|
|
||||||
</h2>
|
|
||||||
<h3>
|
|
||||||
<a :href="`${(bookmarkletscript)}`" class="ui huge text">Push to SolidScribe</a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
data: function(){
|
|
||||||
return {
|
|
||||||
loading: true,
|
|
||||||
bookmarkletscript:'',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeCreate: function(){
|
|
||||||
// Perform Login check
|
|
||||||
this.$parent.loginGateway()
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted: function(){
|
|
||||||
this.getBookmarklet()
|
|
||||||
},
|
|
||||||
beforeDestroy(){
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getBookmarklet(){
|
|
||||||
|
|
||||||
this.loading = true
|
|
||||||
axios.post('/api/attachment/getbookmarklet')
|
|
||||||
.then( results => {
|
|
||||||
|
|
||||||
this.bookmarkletscript = results.data
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch(error => { this.$bus.$emit('notification', 'Failed to get bookmarklet') })
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -12,12 +12,7 @@
|
|||||||
animation: fadeorama 16s ease infinite;
|
animation: fadeorama 16s ease infinite;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
|
|
||||||
text-shadow:
|
text-shadow: 1px 1px 2px black;
|
||||||
1px 1px 1px rgba(69,69,69,0.1),
|
|
||||||
-1px -1px 1px rgba(69,69,69,0.1),
|
|
||||||
-1px 1px 1px rgba(69,69,69,0.1),
|
|
||||||
1px -1px 1px rgba(69,69,69,0.1)
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
.shine {
|
.shine {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -497,8 +492,6 @@
|
|||||||
<a target="_blank" href="https://www.maxg.cc">Solid Scribe was created by Max Gialanella</a>
|
<a target="_blank" href="https://www.maxg.cc">Solid Scribe was created by Max Gialanella</a>
|
||||||
</h3>
|
</h3>
|
||||||
<p><a target="_blank" href="https://www.maxg.cc">Check out my Resume</a></p>
|
<p><a target="_blank" href="https://www.maxg.cc">Check out my Resume</a></p>
|
||||||
<p>OR</p>
|
|
||||||
<p><a target="_blank" href="http://blog.maxg.cc">Check out my Programming Blog</a></p>
|
|
||||||
<p>
|
<p>
|
||||||
I was tired of all my data being owned by big companies, having it farmed out for marketing, and leaving the contents of my life exposed to corporations.
|
I was tired of all my data being owned by big companies, having it farmed out for marketing, and leaving the contents of my life exposed to corporations.
|
||||||
</p>
|
</p>
|
||||||
|
@@ -11,12 +11,6 @@
|
|||||||
<div class="six wide column" v-if="$store.getters.totals && $store.getters.totals['totalNotes']">
|
<div class="six wide column" v-if="$store.getters.totals && $store.getters.totals['totalNotes']">
|
||||||
<search-input />
|
<search-input />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sixteen wide column" v-if="$store.getters.totals && $store.getters.totals['showTrackMetricsButton']">
|
|
||||||
<router-link class="ui fluid green button" to="/metrictrack">
|
|
||||||
<i class="calendar check outlin icon"></i>Metric Track
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ten wide column" :class="{ 'sixteen wide column':$store.getters.getIsUserOnMobile }">
|
<div class="ten wide column" :class="{ 'sixteen wide column':$store.getters.getIsUserOnMobile }">
|
||||||
|
|
||||||
@@ -29,13 +23,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tag-display
|
<tag-display
|
||||||
v-if="$store.getters.totals && Object.keys($store.getters.totals['tags'] || {}).length"
|
|
||||||
:user-tags="$store.getters.totals['tags']"
|
|
||||||
:active-tags="searchTags"
|
:active-tags="searchTags"
|
||||||
v-on:tagClick="tagId => toggleTagFilter(tagId)"
|
v-on:tagClick="tagId => toggleTagFilter(tagId)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<paste-button />
|
<paste-button />
|
||||||
|
|
||||||
|
<span class="ui grey text text-fix">
|
||||||
|
Active Sessions {{ $store.getters.getActiveSessions }}
|
||||||
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -112,7 +108,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ui small green button" v-on:click="collapseFloatingList = true">
|
<div class="ui small green button" v-on:click="collapseFloatingList = true">
|
||||||
<i class="caret square left outline icon"></i>
|
<i class="caret square left outline icon"></i>
|
||||||
Hide List
|
Hide Menu
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -129,7 +125,7 @@
|
|||||||
:data="note"
|
:data="note"
|
||||||
:title-view="titleView || isFloatingList"
|
:title-view="titleView || isFloatingList"
|
||||||
:currently-open="openNotes.includes(note.id)"
|
:currently-open="openNotes.includes(note.id)"
|
||||||
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated + note.archived + note.pinned + note.trashed"
|
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,8 +152,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="show-hidden-note-list-button"
|
<div class="show-hidden-note-list-button" v-if="collapseFloatingList" v-on:click="collapseFloatingList = false">
|
||||||
v-if="collapseFloatingList && openNotes.length > 0" v-on:click="collapseFloatingList = false">
|
|
||||||
<i class="caret square right outline icon"></i>
|
<i class="caret square right outline icon"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -567,20 +562,16 @@
|
|||||||
// @TODO Don't even trigger this if the note wasn't changed
|
// @TODO Don't even trigger this if the note wasn't changed
|
||||||
updateSingleNote(noteId, focuseAndAnimate = true){
|
updateSingleNote(noteId, focuseAndAnimate = true){
|
||||||
|
|
||||||
// console.log('updating single note', noteId)
|
|
||||||
|
|
||||||
noteId = parseInt(noteId)
|
noteId = parseInt(noteId)
|
||||||
|
|
||||||
//Find local note, if it exists; continue
|
//Find local note, if it exists; continue
|
||||||
let note = null
|
let note = null
|
||||||
if(this.$refs['note-'+noteId]?.[0]?.note){
|
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){
|
||||||
note = this.$refs['note-'+noteId][0].note
|
note = this.$refs['note-'+noteId][0].note
|
||||||
//Show that note is working on updating
|
//Show that note is working on updating
|
||||||
this.$refs['note-'+noteId][0].showWorking = true
|
this.$refs['note-'+noteId][0].showWorking = true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rebuildNoteCategorise()
|
|
||||||
// return
|
|
||||||
|
|
||||||
//Lookup one note using passed in ID
|
//Lookup one note using passed in ID
|
||||||
const postData = {
|
const postData = {
|
||||||
|
@@ -13,7 +13,6 @@ const NotesPage = () => import(/* webpackChunkName: "NotesPage" */ '@/pages/Note
|
|||||||
const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage')
|
const QuickPage = () => import(/* webpackChunkName: "QuickPage" */ '@/pages/QuickPage')
|
||||||
const AttachmentsPage = () => import(/* webpackChunkName: "AttachmentsPage" */ '@/pages/AttachmentsPage')
|
const AttachmentsPage = () => import(/* webpackChunkName: "AttachmentsPage" */ '@/pages/AttachmentsPage')
|
||||||
const OverviewPage = () => import(/* webpackChunkName: "OverviewPage" */ '@/pages/OverviewPage')
|
const OverviewPage = () => import(/* webpackChunkName: "OverviewPage" */ '@/pages/OverviewPage')
|
||||||
const BookmarkletPage = () => import(/* webpackChunkName: "BookmarkletPage" */ '@/pages/BookmarkletPage')
|
|
||||||
const NotFoundPage = () => import(/* webpackChunkName: "404Page" */ '@/pages/NotFoundPage')
|
const NotFoundPage = () => import(/* webpackChunkName: "404Page" */ '@/pages/NotFoundPage')
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
@@ -68,12 +67,6 @@ export default new Router({
|
|||||||
meta: {title:'Terms'},
|
meta: {title:'Terms'},
|
||||||
component: TermsPage
|
component: TermsPage
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/bookmarklet',
|
|
||||||
name: 'Bookmarklet',
|
|
||||||
meta: {title:'Bookmarklet'},
|
|
||||||
component: BookmarkletPage
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
@@ -127,7 +120,7 @@ export default new Router({
|
|||||||
path: '/metrictrack',
|
path: '/metrictrack',
|
||||||
name: 'Metric Tracking',
|
name: 'Metric Tracking',
|
||||||
meta: {title:'Metric Tracking'},
|
meta: {title:'Metric Tracking'},
|
||||||
component: () => import(/* webpackChunkName: "MetrictrackingPage" */ '@/pages/MetrictrackingPage')
|
component: () => import(/* webpackChunkName: "CycletrackingPage" */ '@/pages/CycletrackingPage')
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -9,8 +9,7 @@ export default new Vuex.Store({
|
|||||||
username: null,
|
username: null,
|
||||||
nightMode: false,
|
nightMode: false,
|
||||||
isUserOnMobile: false,
|
isUserOnMobile: false,
|
||||||
fetchTotalsTimeout: null,
|
userTotals: null,
|
||||||
userTotals: null, // {} // setting this to object breaks reactivity
|
|
||||||
activeSessions: 0,
|
activeSessions: 0,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
@@ -44,7 +43,7 @@ export default new Vuex.Store({
|
|||||||
'menu-text': '#5e6268',
|
'menu-text': '#5e6268',
|
||||||
},
|
},
|
||||||
'black':{
|
'black':{
|
||||||
'body_bg_color': 'rgb(12 4 30)',
|
'body_bg_color': 'linear-gradient(135deg, rgba(0,0,0,1) 0%, rgba(23,12,46,1) 100%)',
|
||||||
//'#0f0f0f',//'#000',
|
//'#0f0f0f',//'#000',
|
||||||
'small_element_bg_color': '#000',
|
'small_element_bg_color': '#000',
|
||||||
'text_color': '#FFF',
|
'text_color': '#FFF',
|
||||||
@@ -101,23 +100,8 @@ export default new Vuex.Store({
|
|||||||
state.socket = socket
|
state.socket = socket
|
||||||
},
|
},
|
||||||
setUserTotals(state, totalsObject){
|
setUserTotals(state, totalsObject){
|
||||||
|
//Save all the totals for the user
|
||||||
if(!state.userTotals){
|
state.userTotals = totalsObject
|
||||||
state.userTotals = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retain old values loaded on initial, extended options load
|
|
||||||
let oldMissingValues = {}
|
|
||||||
Object.keys(state.userTotals).forEach(key => {
|
|
||||||
if(!totalsObject[key] && totalsObject[key] !== 0){
|
|
||||||
oldMissingValues[key] = state.userTotals[key]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// combine old settings with updated settings
|
|
||||||
let oldAndNew = Object.assign(oldMissingValues, totalsObject)
|
|
||||||
|
|
||||||
state.userTotals = oldAndNew
|
|
||||||
|
|
||||||
//Set computer version from server
|
//Set computer version from server
|
||||||
const currentVersion = localStorage.getItem('currentVersion')
|
const currentVersion = localStorage.getItem('currentVersion')
|
||||||
@@ -141,11 +125,6 @@ export default new Vuex.Store({
|
|||||||
setActiveSessions(state, countData){
|
setActiveSessions(state, countData){
|
||||||
//Count of the number of active socket.io sessions for this user
|
//Count of the number of active socket.io sessions for this user
|
||||||
state.activeSessions = countData
|
state.activeSessions = countData
|
||||||
},
|
|
||||||
hideMetricTrackingReminder(state){
|
|
||||||
if(state.userTotals){
|
|
||||||
state.userTotals['showTrackMetricsButton'] = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@@ -176,24 +155,17 @@ export default new Vuex.Store({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
fetchAndUpdateUserTotals ({ commit, state }) {
|
fetchAndUpdateUserTotals ({ commit }) {
|
||||||
clearTimeout(state.fetchTotalsTimeout)
|
axios.post('/api/user/totals')
|
||||||
state.fetchTotalsTimeout = setTimeout(() => {
|
.then( ({data}) => {
|
||||||
// load extended options on initial load
|
commit('setUserTotals', data)
|
||||||
let postData = {
|
})
|
||||||
extendedOptions: !state.userTotals
|
.catch( error => {
|
||||||
|
if(error.response && error.response.status == 400){
|
||||||
|
commit('destroyLoginToken')
|
||||||
|
location.reload()
|
||||||
}
|
}
|
||||||
axios.post('/api/user/totals', postData)
|
})
|
||||||
.then( ({data}) => {
|
|
||||||
commit('setUserTotals', data)
|
|
||||||
})
|
|
||||||
.catch( error => {
|
|
||||||
if(error.response && error.response.status == 400){
|
|
||||||
commit('destroyLoginToken')
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
97
configs/dev nginx sites available default.cfg
Normal file
97
configs/dev nginx sites available default.cfg
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#
|
||||||
|
# Working dev server config
|
||||||
|
#
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name 192.168.1.164;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
server {
|
||||||
|
|
||||||
|
listen 443 ssl;
|
||||||
|
|
||||||
|
ssl_certificate /home/mab/ss/client/certs/nginx-selfsigned.crt;
|
||||||
|
ssl_certificate_key /home/mab/ss/client/certs/nginx-selfsigned.key;
|
||||||
|
ssl_dhparam /home/mab/ss/client/certs/dhparam.pem;
|
||||||
|
|
||||||
|
ssl_session_cache shared:SSL:1m;
|
||||||
|
ssl_session_timeout 5m;
|
||||||
|
ssl_protocols TLSV1.1 TLSV1.2 TLSV1.3;
|
||||||
|
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/httpslocalhost.access.log;
|
||||||
|
error_log /var/log/nginx/httpslocalhost.error.log;
|
||||||
|
|
||||||
|
client_max_body_size 20M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass https://127.0.0.1:8081;
|
||||||
|
proxy_set_header Host localhost;
|
||||||
|
proxy_set_header X-Forwarded-Host localhost;
|
||||||
|
proxy_set_header X-Forwarded-Server localhost;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_connect_timeout 90s;
|
||||||
|
proxy_read_timeout 90s;
|
||||||
|
proxy_send_timeout 90s;
|
||||||
|
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /sockjs-node {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
proxy_pass https://127.0.0.1:8081;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /socket {
|
||||||
|
proxy_pass http://127.0.0.1:3001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Prod settings to serve static index
|
||||||
|
# location / {
|
||||||
|
# autoindex on;
|
||||||
|
# #try_files $uri $uri/ /index.html;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# location / {
|
||||||
|
# #autoindex on
|
||||||
|
#
|
||||||
|
# proxy_pass http://127.0.0.1:8444;
|
||||||
|
# proxy_http_version 1.1;
|
||||||
|
# proxy_set_header Upgrade $http_upgrade;
|
||||||
|
# proxy_set_header Connection 'upgrade';
|
||||||
|
# proxy_set_header Host $host;
|
||||||
|
# proxy_cache_bypass $http_upgrade;
|
||||||
|
# }
|
@@ -1,11 +0,0 @@
|
|||||||
const path = '../../'
|
|
||||||
const prefix = '/$1'
|
|
||||||
module.exports = {
|
|
||||||
moduleNameMapper: {
|
|
||||||
"@root/(.*)": ".",
|
|
||||||
"@models/(.*)": path+"server/models"+prefix,
|
|
||||||
"@routes/(.*)": path+"server/routes"+prefix,
|
|
||||||
"@helpers/(.*)": path+"server/helpers"+prefix,
|
|
||||||
"@config/(.*)": path+"server/config"+prefix,
|
|
||||||
}
|
|
||||||
}
|
|
6486
package-lock.json
generated
6486
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "personal-internet",
|
"name": "personal-internet",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Encrypted note taking applications",
|
"description": "Personal or Private net",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Max",
|
"author": "Max",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@@ -33,8 +33,5 @@
|
|||||||
"@routes": "server/routes",
|
"@routes": "server/routes",
|
||||||
"@helpers": "server/helpers",
|
"@helpers": "server/helpers",
|
||||||
"@config": "server/config"
|
"@config": "server/config"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"jest": "^29.7.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
//Import mysql2 package
|
//Import mysql2 package
|
||||||
const mysql = require('mysql2');
|
const mysql = require('mysql2');
|
||||||
const os = require('os') //Used to get path of home directory
|
|
||||||
const result = require('dotenv').config({ path:(os.homedir()+'/.env') })
|
|
||||||
|
|
||||||
// Create the connection pool.
|
// Create the connection pool.
|
||||||
const pool = mysql.createPool({
|
const pool = mysql.createPool({
|
||||||
|
@@ -72,8 +72,6 @@ CryptoString.createSalt = () => {
|
|||||||
|
|
||||||
return crypto.randomBytes(SALT_BYTE_SIZE).toString('base64')
|
return crypto.randomBytes(SALT_BYTE_SIZE).toString('base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a small random salt
|
|
||||||
CryptoString.createSmallSalt = () => {
|
CryptoString.createSmallSalt = () => {
|
||||||
|
|
||||||
return crypto.randomBytes(20).toString('base64')
|
return crypto.randomBytes(20).toString('base64')
|
||||||
|
@@ -6,7 +6,7 @@ let SiteScrape = module.exports = {}
|
|||||||
|
|
||||||
const removeWhitespace = /\s+/g
|
const removeWhitespace = /\s+/g
|
||||||
|
|
||||||
const commonWords = ['just','start','what','these','how', 'was', 'being','can','way','share','facebook','twitter','reddit','be','have','do','say','get','make','go','know','take','see','come','think','look','want',
|
const commonWords = ['share','facebook','twitter','reddit','be','have','do','say','get','make','go','know','take','see','come','think','look','want',
|
||||||
'give','use','find','tell','ask','work','seem','feel','try','leave','call','good','new','first','last','long','great','little','own','other','old',
|
'give','use','find','tell','ask','work','seem','feel','try','leave','call','good','new','first','last','long','great','little','own','other','old',
|
||||||
'right','big','high','different','small','large','next','early','young','important','few','public','bad','same','able','to','of','in','for','on',
|
'right','big','high','different','small','large','next','early','young','important','few','public','bad','same','able','to','of','in','for','on',
|
||||||
'with','at','by','from','up','about','into','over','after','the','and','a','that','I','it','not','he','as','you','this','but','his','they','her',
|
'with','at','by','from','up','about','into','over','after','the','and','a','that','I','it','not','he','as','you','this','but','his','they','her',
|
||||||
@@ -162,28 +162,19 @@ SiteScrape.getKeywords = ($) => {
|
|||||||
|
|
||||||
majorContent += $('[class*=content]').text()
|
majorContent += $('[class*=content]').text()
|
||||||
.replace(removeWhitespace, " ") //Remove all whitespace
|
.replace(removeWhitespace, " ") //Remove all whitespace
|
||||||
// .replace(/\W\s/g, '') //Remove all non alphanumeric characters
|
.replace(/\W\s/g, '') //Remove all non alphanumeric characters
|
||||||
.substring(0,6000) //Limit to 6000 characters
|
.substring(0,3000) //Limit to 3000 characters
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/[^A-Za-z0-9- ]/g, '');
|
|
||||||
|
|
||||||
|
|
||||||
console.log(majorContent)
|
|
||||||
|
|
||||||
//Count frequency of each word in scraped text
|
//Count frequency of each word in scraped text
|
||||||
let frequency = {}
|
let frequency = {}
|
||||||
majorContent.split(' ').forEach(word => {
|
majorContent.split(' ').forEach(word => {
|
||||||
// Exclude short or common words
|
if(commonWords.includes(word)){
|
||||||
if(commonWords.includes(word) || word.length <= 2){
|
return //Exclude certain words
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if(!frequency[word]){
|
if(!frequency[word]){
|
||||||
frequency[word] = 0
|
frequency[word] = 0
|
||||||
}
|
}
|
||||||
// Skip some plurals
|
|
||||||
if(frequency[word+'s'] || frequency[word+'es']){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
frequency[word]++
|
frequency[word]++
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -201,7 +192,7 @@ SiteScrape.getKeywords = ($) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let finalWords = []
|
let finalWords = []
|
||||||
for(let i=0; i<6; i++){
|
for(let i=0; i<5; i++){
|
||||||
if(sortable[i] && sortable[i][0]){
|
if(sortable[i] && sortable[i][0]){
|
||||||
finalWords.push(sortable[i][0])
|
finalWords.push(sortable[i][0])
|
||||||
}
|
}
|
||||||
|
@@ -20,8 +20,6 @@ const helmet = require('helmet')
|
|||||||
const express = require('express')
|
const express = require('express')
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use( helmet() )
|
app.use( helmet() )
|
||||||
// allow for the parsing of url encoded forms
|
|
||||||
app.use(express.urlencoded({ extended: true }));
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -116,12 +114,29 @@ io.on('connection', function(socket){
|
|||||||
|
|
||||||
//Emit all sorted diffs to user
|
//Emit all sorted diffs to user
|
||||||
socket.emit('past_diffs', noteDiffs[rawTextId])
|
socket.emit('past_diffs', noteDiffs[rawTextId])
|
||||||
|
} else {
|
||||||
|
socket.emit('past_diffs', null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersInRoom = io.sockets.adapter.rooms[rawTextId]
|
const usersInRoom = io.sockets.adapter.rooms[rawTextId]
|
||||||
if(usersInRoom){
|
if(usersInRoom){
|
||||||
//Update users in room count
|
//Update users in room count
|
||||||
io.to(rawTextId).emit('update_user_count', usersInRoom.length)
|
io.to(rawTextId).emit('update_user_count', usersInRoom.length)
|
||||||
|
|
||||||
|
//Debugging text - prints out notes in limbo
|
||||||
|
let noteDiffKeys = Object.keys(noteDiffs)
|
||||||
|
let totalDiffs = 0
|
||||||
|
noteDiffKeys.forEach(diffSetKey => {
|
||||||
|
if(noteDiffs[diffSetKey]){
|
||||||
|
totalDiffs += noteDiffs[diffSetKey].length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//Debugging Text
|
||||||
|
if(noteDiffKeys.length > 0){
|
||||||
|
console.log('Total notes in limbo -> ', noteDiffKeys.length)
|
||||||
|
console.log('Total Diffs for all notes -> ', totalDiffs)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -147,13 +162,31 @@ io.on('connection', function(socket){
|
|||||||
|
|
||||||
noteDiffs[noteId].push(data)
|
noteDiffs[noteId].push(data)
|
||||||
|
|
||||||
// Go over each user in this note-room
|
//Remove duplicate diffs if they exist
|
||||||
|
for (var i = noteDiffs[noteId].length - 1; i >= 0; i--) {
|
||||||
|
|
||||||
|
let pastDiff = noteDiffs[noteId][i]
|
||||||
|
|
||||||
|
for (var j = noteDiffs[noteId].length - 1; j >= 0; j--) {
|
||||||
|
let currentDiff = noteDiffs[noteId][j]
|
||||||
|
|
||||||
|
if(i == j){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentDiff.diff == pastDiff.diff || currentDiff.time == pastDiff.time){
|
||||||
|
console.log('Removing Duplicate')
|
||||||
|
noteDiffs[noteId].splice(i,1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Each user joins a room when they open the app.
|
||||||
io.in(noteId).clients((error, clients) => {
|
io.in(noteId).clients((error, clients) => {
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
//Go through each client in note-room and send them the diff
|
//Go through each client in note room and send them the diff
|
||||||
clients.forEach(socketId => {
|
clients.forEach(socketId => {
|
||||||
// only send off diff if user
|
|
||||||
if(socketId != socket.id){
|
if(socketId != socket.id){
|
||||||
io.to(socketId).emit('incoming_diff', data)
|
io.to(socketId).emit('incoming_diff', data)
|
||||||
}
|
}
|
||||||
@@ -180,6 +213,7 @@ io.on('connection', function(socket){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
noteDiffs[checkpoint.rawTextId] = diffSet.slice(0, sliceTo)
|
noteDiffs[checkpoint.rawTextId] = diffSet.slice(0, sliceTo)
|
||||||
|
|
||||||
if(noteDiffs[checkpoint.rawTextId].length == 0){
|
if(noteDiffs[checkpoint.rawTextId].length == 0){
|
||||||
@@ -201,7 +235,7 @@ io.on('connection', function(socket){
|
|||||||
|
|
||||||
|
|
||||||
http.listen(ports.socketIo, function(){
|
http.listen(ports.socketIo, function(){
|
||||||
console.log(`Socke.io: Listening on port ${ports.socketIo}`)
|
console.log(`Socke.io: Listening on port ${ports.socketIo}!`)
|
||||||
});
|
});
|
||||||
|
|
||||||
//Enable json body parsing in requests. Allows me to post data in ajax calls
|
//Enable json body parsing in requests. Allows me to post data in ajax calls
|
||||||
@@ -242,21 +276,17 @@ app.use(function(req, res, next){
|
|||||||
|
|
||||||
|
|
||||||
// Test Area
|
// Test Area
|
||||||
// const printResults = true
|
const printResults = true
|
||||||
// let UserTest = require('@models/User')
|
let UserTest = require('@models/User')
|
||||||
// let NoteTest = require('@models/Note')
|
let NoteTest = require('@models/Note')
|
||||||
// let AuthTest = require('@helpers/Auth')
|
let AuthTest = require('@helpers/Auth')
|
||||||
// Auth.test()
|
Auth.test()
|
||||||
// UserTest.keyPairTest('genMan30', '1', printResults)
|
UserTest.keyPairTest('genMan30', '1', printResults)
|
||||||
// .then( ({testUserId, masterKey}) =>
|
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
|
||||||
// NoteTest.test(testUserId, masterKey, printResults))
|
.then( message => {
|
||||||
// .then( message => {
|
if(printResults) console.log(message)
|
||||||
// if(printResults) console.log(message)
|
Auth.testTwoFactor()
|
||||||
// Auth.testTwoFactor()
|
})
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// console.log(error)
|
|
||||||
// })
|
|
||||||
|
|
||||||
//Test
|
//Test
|
||||||
app.get('/api', (req, res) => res.send('Solidscribe /API is up and running'))
|
app.get('/api', (req, res) => res.send('Solidscribe /API is up and running'))
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
let db = require('@config/database')
|
let db = require('@config/database')
|
||||||
|
|
||||||
let SiteScrape = require('@helpers/SiteScrape')
|
let SiteScrape = require('@helpers/SiteScrape')
|
||||||
const cs = require('@helpers/CryptoString')
|
|
||||||
|
|
||||||
let Attachment = module.exports = {}
|
let Attachment = module.exports = {}
|
||||||
|
|
||||||
@@ -48,44 +47,25 @@ Attachment.textSearch = (userId, searchTerm) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeShared) => {
|
Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeShared) => {
|
||||||
console.log([userId, noteId, attachmentType, offset, setSize, includeShared])
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let params = [userId]
|
let params = [userId]
|
||||||
let query = `
|
let query = `
|
||||||
SELECT attachment.*, note.share_user_id FROM attachment
|
SELECT attachment.*, note.share_user_id FROM attachment
|
||||||
LEFT JOIN note ON (attachment.note_id = note.id)
|
JOIN note ON (attachment.note_id = note.id)
|
||||||
WHERE attachment.user_id = ? AND visible = 1
|
WHERE attachment.user_id = ? AND visible = 1 `
|
||||||
`
|
|
||||||
|
|
||||||
if(noteId && noteId > 0){
|
if(noteId && noteId > 0){
|
||||||
//
|
|
||||||
// Show everything if note ID is present
|
|
||||||
//
|
|
||||||
query += 'AND attachment.note_id = ? '
|
query += 'AND attachment.note_id = ? '
|
||||||
params.push(noteId)
|
params.push(noteId)
|
||||||
|
|
||||||
} else {
|
|
||||||
//
|
|
||||||
// Other filters if NO note id
|
|
||||||
//
|
|
||||||
|
|
||||||
if(attachmentType == 'links'){
|
|
||||||
query += 'AND attachment_type = 1 '
|
|
||||||
}
|
|
||||||
if(attachmentType == 'files'){
|
|
||||||
query += 'AND attachment_type > 1 '
|
|
||||||
}
|
|
||||||
|
|
||||||
query += `AND note.archived = ${ attachmentType == 'archived' ? '1':'0' } `
|
|
||||||
query += `AND note.trashed = ${ attachmentType == 'trashed' ? '1':'0' } `
|
|
||||||
|
|
||||||
if(!attachmentType){
|
|
||||||
// Null note ID means it was pushed by bookmarklet
|
|
||||||
query += 'OR attachment.note_id IS NULL '
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(attachmentType == 'links'){
|
||||||
|
query += 'AND attachment_type = 1 '
|
||||||
|
}
|
||||||
|
if(attachmentType == 'files'){
|
||||||
|
query += 'AND attachment_type > 1 '
|
||||||
|
}
|
||||||
|
|
||||||
if(!noteId){
|
if(!noteId){
|
||||||
const sharedOrNot = includeShared ? ' NOT ':' '
|
const sharedOrNot = includeShared ? ' NOT ':' '
|
||||||
@@ -99,8 +79,6 @@ Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeSha
|
|||||||
const parsedSetSize = parseInt(setSize, 10) || 20
|
const parsedSetSize = parseInt(setSize, 10) || 20
|
||||||
query += ` LIMIT ${limitOffset}, ${parsedSetSize}`
|
query += ` LIMIT ${limitOffset}, ${parsedSetSize}`
|
||||||
|
|
||||||
console.log(query)
|
|
||||||
|
|
||||||
db.promise()
|
db.promise()
|
||||||
.query(query, params)
|
.query(query, params)
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
@@ -110,6 +88,18 @@ Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeSha
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Returns all attachments
|
||||||
|
Attachment.forNote = (userId, noteId) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.promise()
|
||||||
|
.query(`SELECT * FROM attachment WHERE user_id = ? AND note_id = ? AND visible = 1 ORDER BY last_indexed DESC;`, [userId, noteId])
|
||||||
|
.then((rows, fields) => {
|
||||||
|
resolve(rows[0]) //Return all attachments found by query
|
||||||
|
})
|
||||||
|
.catch(console.log)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Attachment.urlForNote = (userId, noteId) => {
|
Attachment.urlForNote = (userId, noteId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.promise()
|
db.promise()
|
||||||
@@ -185,7 +175,6 @@ Attachment.delete = (userId, attachmentId, urlDelete = false) => {
|
|||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,13 +291,9 @@ Attachment.scanTextForWebsites = (io, userId, noteId, noteText) => {
|
|||||||
//Once everything is done being scraped, emit new attachment events
|
//Once everything is done being scraped, emit new attachment events
|
||||||
SocketIo.to(userId).emit('update_counts')
|
SocketIo.to(userId).emit('update_counts')
|
||||||
|
|
||||||
// Tell user to update attachments with scraped text
|
|
||||||
SocketIo.to(userId).emit('update_note_attachments')
|
|
||||||
|
|
||||||
solrAttachmentText += freshlyScrapedText
|
solrAttachmentText += freshlyScrapedText
|
||||||
resolve(solrAttachmentText)
|
resolve(solrAttachmentText)
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -336,13 +321,9 @@ Attachment.scrapeUrlsCreateAttachments = (userId, noteId, foundUrls) => {
|
|||||||
|
|
||||||
//All URLs have been scraped, return data
|
//All URLs have been scraped, return data
|
||||||
if(processedCount == foundUrls.length){
|
if(processedCount == foundUrls.length){
|
||||||
console.log('All urls scraped')
|
resolve(scrapedText)
|
||||||
return resolve(scrapedText)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
|
||||||
console.log('Site Scrape error', error)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -352,8 +333,8 @@ Attachment.downloadFileFromUrl = (url) => {
|
|||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
if(!url){
|
if(url == null || url == undefined || url == ''){
|
||||||
return resolve(null)
|
resolve(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
||||||
@@ -361,7 +342,8 @@ Attachment.downloadFileFromUrl = (url) => {
|
|||||||
let fileName = random+'_scrape'
|
let fileName = random+'_scrape'
|
||||||
let thumbPath = 'thumb_'+fileName
|
let thumbPath = 'thumb_'+fileName
|
||||||
|
|
||||||
console.log('Scraping image url', url)
|
console.log('Scraping image url')
|
||||||
|
console.log(url)
|
||||||
|
|
||||||
console.log('Getting ready to scrape ', url)
|
console.log('Getting ready to scrape ', url)
|
||||||
|
|
||||||
@@ -399,7 +381,7 @@ Attachment.downloadFileFromUrl = (url) => {
|
|||||||
|
|
||||||
Attachment.processUrl = (userId, noteId, url) => {
|
Attachment.processUrl = (userId, noteId, url) => {
|
||||||
|
|
||||||
const scrapeTime = 5*1000;
|
const scrapeTime = 20*1000;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
@@ -452,12 +434,9 @@ Attachment.processUrl = (userId, noteId, url) => {
|
|||||||
const keywords = SiteScrape.getKeywords($)
|
const keywords = SiteScrape.getKeywords($)
|
||||||
|
|
||||||
var desiredSearchText = ''
|
var desiredSearchText = ''
|
||||||
desiredSearchText += pageTitle
|
desiredSearchText += pageTitle + "\n"
|
||||||
if(keywords){
|
desiredSearchText += keywords
|
||||||
desiredSearchText += "\n " + keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Results from site scrape-------------')
|
|
||||||
console.log({
|
console.log({
|
||||||
pageTitle,
|
pageTitle,
|
||||||
hostname,
|
hostname,
|
||||||
@@ -507,142 +486,40 @@ Attachment.processUrl = (userId, noteId, url) => {
|
|||||||
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('Scrape pooped out')
|
// console.log('Scrape pooped out')
|
||||||
console.log('Issue with scrape', error.statusCode)
|
// console.log('Issue with scrape')
|
||||||
clearTimeout(requestTimeout)
|
console.log(error)
|
||||||
return resolve('No site text')
|
// resolve('')
|
||||||
})
|
})
|
||||||
|
|
||||||
requestTimeout = setTimeout( () => {
|
requestTimeout = setTimeout( () => {
|
||||||
console.log('Cancel the request, its taking to long.')
|
console.log('Cancel the request, its taking to long.')
|
||||||
request.cancel()
|
request.cancel()
|
||||||
return resolve('Request Timeout')
|
|
||||||
|
desiredSearchText = 'No Description for -> '+url
|
||||||
|
|
||||||
|
created = Math.round((+new Date)/1000)
|
||||||
|
db.promise()
|
||||||
|
.query(`UPDATE attachment SET
|
||||||
|
text = ?,
|
||||||
|
last_indexed = ?,
|
||||||
|
WHERE id = ?
|
||||||
|
`, [desiredSearchText, created, insertedId])
|
||||||
|
.then((rows, fields) => {
|
||||||
|
resolve(desiredSearchText) //Return found text
|
||||||
|
})
|
||||||
|
.catch(console.log)
|
||||||
|
|
||||||
|
//Create attachment in DB with scrape text and provided data
|
||||||
|
// db.promise()
|
||||||
|
// .query(`INSERT INTO attachment
|
||||||
|
// (note_id, user_id, attachment_type, text, url, last_indexed)
|
||||||
|
// VALUES (?, ?, ?, ?, ?, ?)`, [noteId, userId, 1, desiredSearchText, url, created])
|
||||||
|
// .then((rows, fields) => {
|
||||||
|
// resolve(desiredSearchText) //Return found text
|
||||||
|
// })
|
||||||
|
// .catch(console.log)
|
||||||
|
|
||||||
}, scrapeTime )
|
}, scrapeTime )
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
Attachment.generatePushKey = (userId) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
db.promise()
|
|
||||||
.query("SELECT pushkey FROM user WHERE id = ? LIMIT 1", [userId])
|
|
||||||
.then((rows, fields) => {
|
|
||||||
|
|
||||||
const pushKey = rows[0][0].pushkey
|
|
||||||
|
|
||||||
// push key exists
|
|
||||||
if(pushKey && pushKey.length > 0){
|
|
||||||
|
|
||||||
return resolve(pushKey)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// generate and save a new key
|
|
||||||
const newPushKey = cs.createSmallSalt()
|
|
||||||
db.promise()
|
|
||||||
.query('UPDATE user SET pushkey = ? WHERE id = ? LIMIT 1', [newPushKey,userId])
|
|
||||||
.then((rows, fields) => {
|
|
||||||
|
|
||||||
return resolve(newPushKey)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Attachment.deletePushKey = (userId) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
db.promise()
|
|
||||||
.query('UPDATE user SET pushkey = null WHERE id = ? LIMIT 1', [userId])
|
|
||||||
.then((rows, fields) => {
|
|
||||||
|
|
||||||
return resolve(rows[0].affectedRows == 1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Attachment.getPushkeyBookmarklet = (userId) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
Attachment.generatePushKey(userId)
|
|
||||||
.then( pushKey => {
|
|
||||||
|
|
||||||
let bookmarklet = Attachment.generateBookmarkletText(pushKey)
|
|
||||||
return resolve(bookmarklet)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Attachment.pushUrl = (pushkey,url) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
let userId = null
|
|
||||||
pushkey = pushkey.replace(/ /g, '+')
|
|
||||||
|
|
||||||
db.promise()
|
|
||||||
.query("SELECT id FROM user WHERE pushkey = ? LIMIT 1", [pushkey])
|
|
||||||
.then((rows, fields) => {
|
|
||||||
|
|
||||||
if(rows[0].length == 0){
|
|
||||||
return resolve(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
userId = rows[0][0].id
|
|
||||||
return Attachment.scrapeUrlsCreateAttachments(userId, null, [url])
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
|
|
||||||
if(typeof SocketIo != 'undefined'){
|
|
||||||
//Once everything is done being scraped, emit new attachment events
|
|
||||||
SocketIo.to(userId).emit('update_counts')
|
|
||||||
|
|
||||||
// Tell user to update attachments with scraped text
|
|
||||||
SocketIo.to(userId).emit('update_note_attachments')
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolve(true)
|
|
||||||
})
|
|
||||||
.catch(console.log)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Attachment.generateBookmarkletText = (pushKey) => {
|
|
||||||
|
|
||||||
const endpoint = '/api/public/pushmebaby'
|
|
||||||
let url = 'https://www.solidscribe.com' + endpoint
|
|
||||||
if(process.env.NODE_ENV === 'development'){
|
|
||||||
// url = 'https://192.168.1.164' + endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate each line with a semi-colon, super important, since spaces are removed.
|
|
||||||
|
|
||||||
// document.getElementById(id).remove();
|
|
||||||
url += '?pushkey='+encodeURIComponent(pushKey)
|
|
||||||
const bookmarkletV3 = `
|
|
||||||
javascript: (() => {
|
|
||||||
var p = encodeURIComponent(window.location.href);
|
|
||||||
var n = "`+url+`&url="+p;
|
|
||||||
window.open(n, '_blank', 'noopener=noopener');
|
|
||||||
window.focus();
|
|
||||||
|
|
||||||
var k = document.createElement("div");
|
|
||||||
k.setAttribute("style", "position:fixed;right:10px;top:10px;z-index:222222;border-radius:4px;font-size:1.3em;padding:20px 15px;background: #8f51be;color:white;");
|
|
||||||
k.innerHTML = "Posted URL to your Solid Scribe account";
|
|
||||||
|
|
||||||
document.body.appendChild(k);
|
|
||||||
|
|
||||||
setTimeout(()=>{
|
|
||||||
k.remove();
|
|
||||||
},5000);
|
|
||||||
|
|
||||||
})();
|
|
||||||
`
|
|
||||||
|
|
||||||
return bookmarkletV3
|
|
||||||
.replace(/\t|\r|\n/gm, "") // Remove tabs, new lines, returns
|
|
||||||
.replace(/\s+/g, ' ') // remove double spaces
|
|
||||||
.trim()
|
|
||||||
}
|
}
|
@@ -2,7 +2,7 @@ let db = require('@config/database')
|
|||||||
|
|
||||||
let Note = require('@models/Note')
|
let Note = require('@models/Note')
|
||||||
|
|
||||||
let MetricTracking = module.exports = {};
|
let MetricTracking = module.exports = {}
|
||||||
|
|
||||||
|
|
||||||
MetricTracking.get = (userId, masterKey) => {
|
MetricTracking.get = (userId, masterKey) => {
|
||||||
@@ -23,36 +23,31 @@ MetricTracking.get = (userId, masterKey) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return resolve('no data')
|
//Or create a new note
|
||||||
|
let finalId = null
|
||||||
|
return Note.create(userId, 'Metric Tracking', '', masterKey)
|
||||||
|
.then(insertedId => {
|
||||||
|
finalId = insertedId
|
||||||
|
db.promise().query('UPDATE note SET quick_note = 2 WHERE id = ? AND user_id = ?',[insertedId, userId])
|
||||||
|
.then((rows, fields) => {
|
||||||
|
|
||||||
|
const note = Note.get(userId, finalId, masterKey)
|
||||||
|
.then(noteData => {
|
||||||
|
return resolve(noteData)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
MetricTracking.create = (userId, masterKey) => {
|
MetricTracking.save = (userId, cycleData, masterKey) => {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let finalId = null
|
|
||||||
return Note.create(userId, 'Metric Tracking', '', masterKey)
|
|
||||||
.then(insertedId => {
|
|
||||||
finalId = insertedId
|
|
||||||
db.promise().query('UPDATE note SET quick_note = 2 WHERE id = ? AND user_id = ?',[insertedId, userId])
|
|
||||||
.then((rows, fields) => {
|
|
||||||
|
|
||||||
const note = Note.get(userId, finalId, masterKey)
|
|
||||||
.then(noteData => {
|
|
||||||
return resolve(noteData)
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(console.log)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MetricTracking.save = (userId, metricData, masterKey) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let finalId = null
|
let finalId = null
|
||||||
@@ -60,7 +55,7 @@ MetricTracking.save = (userId, metricData, masterKey) => {
|
|||||||
MetricTracking.get(userId, masterKey)
|
MetricTracking.get(userId, masterKey)
|
||||||
.then(noteObject => {
|
.then(noteObject => {
|
||||||
|
|
||||||
return Note.update(userId, noteObject.id, metricData, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey)
|
return Note.update(userId, noteObject.id, cycleData, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey)
|
||||||
|
|
||||||
})
|
})
|
||||||
.then( saveResults => {
|
.then( saveResults => {
|
||||||
|
@@ -17,7 +17,6 @@ const fs = require('fs')
|
|||||||
const gm = require('gm')
|
const gm = require('gm')
|
||||||
|
|
||||||
Note.test = (userId, masterKey, printResults) => {
|
Note.test = (userId, masterKey, printResults) => {
|
||||||
return false;
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
|
||||||
@@ -163,10 +162,6 @@ Note.test = (userId, masterKey, printResults) => {
|
|||||||
return resolve('Test: Complete ---')
|
return resolve('Test: Complete ---')
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
return reject(error)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +193,7 @@ Note.create = (userId, noteTitle = '', noteText = '', masterKey) => {
|
|||||||
})
|
})
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
if(typeof SocketIo != 'undefined'){
|
if(SocketIo){
|
||||||
SocketIo.to(userId).emit('new_note_created', rows[0].insertId)
|
SocketIo.to(userId).emit('new_note_created', rows[0].insertId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +341,7 @@ Note.reindex = (userId, masterKey, removeId = null) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
||||||
if(masterKey == null || note.salt == null){
|
if(masterKey == null || note.salt == null){
|
||||||
console.log('Error indexing note - master key or salt missing', note.id)
|
console.log('Error indexing note', note.id)
|
||||||
return resolve(true)
|
return resolve(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,13 +390,13 @@ Note.reindex = (userId, masterKey, removeId = null) => {
|
|||||||
|
|
||||||
return Promise.all(reindexQueue)
|
return Promise.all(reindexQueue)
|
||||||
})
|
})
|
||||||
.then(updatePromiseResults => {
|
.then(rawSearchIndex => {
|
||||||
|
|
||||||
const created = Math.round((+new Date)/1000)
|
const created = Math.round((+new Date)/1000)
|
||||||
const jsonSearchIndex = JSON.stringify(searchIndex)
|
const jsonSearchIndex = JSON.stringify(searchIndex)
|
||||||
const encryptedJsonIndex = cs.encrypt(masterKey, searchIndexSalt, jsonSearchIndex)
|
const encryptedJsonIndex = cs.encrypt(masterKey, searchIndexSalt, jsonSearchIndex)
|
||||||
|
|
||||||
db.promise().query("UPDATE user_encrypted_search_index SET `index` = ?, `last_update` = ? WHERE (`user_id` = ?) LIMIT 1",
|
return db.promise().query("UPDATE user_encrypted_search_index SET `index` = ?, `last_update` = ? WHERE (`user_id` = ?) LIMIT 1",
|
||||||
[encryptedJsonIndex, created, userId])
|
[encryptedJsonIndex, created, userId])
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
@@ -411,7 +406,6 @@ Note.reindex = (userId, masterKey, removeId = null) => {
|
|||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
// console.log('Indexd Note Count: ' + rows[0]['affectedRows'])
|
// console.log('Indexd Note Count: ' + rows[0]['affectedRows'])
|
||||||
// @TODO - Return number of reindexed notes
|
|
||||||
resolve(true)
|
resolve(true)
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -513,13 +507,13 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
|
|||||||
})
|
})
|
||||||
.then((rows, fields) => {
|
.then((rows, fields) => {
|
||||||
|
|
||||||
if(typeof SocketIo != 'undefined'){
|
if(SocketIo){
|
||||||
SocketIo.to(userId).emit('new_note_text_saved', {noteId, hash})
|
SocketIo.to(userId).emit('new_note_text_saved', {noteId, hash})
|
||||||
|
|
||||||
//Async attachment reindex
|
|
||||||
Attachment.scanTextForWebsites(SocketIo, userId, noteId, noteText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Async attachment reindex
|
||||||
|
Attachment.scanTextForWebsites(SocketIo, userId, noteId, noteText)
|
||||||
|
|
||||||
//Send back updated response
|
//Send back updated response
|
||||||
resolve(rows[0])
|
resolve(rows[0])
|
||||||
})
|
})
|
||||||
@@ -745,13 +739,12 @@ Note.get = (userId, noteId, masterKey) => {
|
|||||||
|
|
||||||
const nowTime = Math.round((+new Date)/1000)
|
const nowTime = Math.round((+new Date)/1000)
|
||||||
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
|
db.promise().query(`UPDATE note SET opened = ? WHERE (id = ?)`, [nowTime, noteId])
|
||||||
.then(results => {
|
|
||||||
//Return note data
|
//Return note data
|
||||||
// delete noteData.salt //remove salt from return data
|
// delete noteData.salt //remove salt from return data
|
||||||
// delete noteData.encrypted_share_password_key
|
// delete noteData.encrypted_share_password_key
|
||||||
noteData.lockedOut = noteLockedOut
|
noteData.lockedOut = noteLockedOut
|
||||||
resolve(noteData)
|
resolve(noteData)
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@@ -9,8 +9,7 @@ const speakeasy = require('speakeasy')
|
|||||||
|
|
||||||
let User = module.exports = {}
|
let User = module.exports = {}
|
||||||
|
|
||||||
const version = '3.8.0'
|
const version = '3.6.2'
|
||||||
// 3.7.3 - diff/patch update
|
|
||||||
|
|
||||||
//Login a user, if that user does not exist create them
|
//Login a user, if that user does not exist create them
|
||||||
//Issues login token
|
//Issues login token
|
||||||
@@ -194,19 +193,19 @@ User.register = (username, password) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
|
//Counts notes, pinned notes, archived notes, shared notes, unread notes, total files and types
|
||||||
User.getCounts = (userId, extendedOptions) => {
|
User.getCounts = (userId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
let countTotals = {
|
let countTotals = {
|
||||||
tags: {}
|
tags: {}
|
||||||
}
|
}
|
||||||
// const userHash = cs.hash(String(userId)).toString('base64')
|
const userHash = cs.hash(String(userId)).toString('base64')
|
||||||
|
|
||||||
db.promise().query(
|
db.promise().query(
|
||||||
`SELECT
|
`SELECT
|
||||||
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
|
SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
|
||||||
SUM(trashed = 1) AS trashedNotes,
|
SUM(trashed = 1) AS trashedNotes,
|
||||||
SUM(share_user_id IS NULL && trashed = 0 AND quick_note < 2) AS totalNotes,
|
SUM(share_user_id IS NULL && trashed = 0) AS totalNotes,
|
||||||
SUM(share_user_id IS NOT null && opened IS null && trashed = 0) AS youGotMailCount,
|
SUM(share_user_id IS NOT null && opened IS null && trashed = 0) AS youGotMailCount,
|
||||||
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes
|
SUM(share_user_id != ? && trashed = 0) AS sharedToNotes
|
||||||
FROM note
|
FROM note
|
||||||
@@ -280,38 +279,7 @@ User.getCounts = (userId, extendedOptions) => {
|
|||||||
|
|
||||||
countTotals['currentVersion'] = version
|
countTotals['currentVersion'] = version
|
||||||
|
|
||||||
// Allow for extended options set on page load
|
resolve(countTotals)
|
||||||
if(extendedOptions){
|
|
||||||
|
|
||||||
db.promise().query(
|
|
||||||
`SELECT updated FROM note
|
|
||||||
JOIN note_raw_text ON note_raw_text.id = note.note_raw_text_id
|
|
||||||
WHERE note.quick_note = 2
|
|
||||||
AND user_id = ?`, [userId])
|
|
||||||
.then( (rows, fields) => {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(rows[0][0] && rows[0][0].updated){
|
|
||||||
const lastOpened = rows[0][0].updated
|
|
||||||
const timeDiff = Math.round(((+new Date) - (lastOpened))/1000)
|
|
||||||
const hoursInSeconds = (12 * 60 * 60) //12 hours
|
|
||||||
|
|
||||||
// Show metric tracking button if its been 12 hours since last entry
|
|
||||||
if(lastOpened && timeDiff > hoursInSeconds){
|
|
||||||
countTotals['showTrackMetricsButton'] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
resolve(countTotals)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
resolve(countTotals)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -553,12 +521,6 @@ User.revokeActiveSessions = (userId, sessionId) => {
|
|||||||
|
|
||||||
User.deleteUser = (userId, password) => {
|
User.deleteUser = (userId, password) => {
|
||||||
|
|
||||||
if(!userId || !password){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
return resolve('Missing User ID or Password. No Action Taken.')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//Verify user is correct by decryptig master key with password
|
//Verify user is correct by decryptig master key with password
|
||||||
|
|
||||||
let deletePromises = []
|
let deletePromises = []
|
||||||
@@ -590,4 +552,78 @@ User.deleteUser = (userId, password) => {
|
|||||||
//Remove all note attachments and files
|
//Remove all note attachments and files
|
||||||
|
|
||||||
return Promise.all(deletePromises)
|
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'
|
||||||
|
const secondPassword = '2'
|
||||||
|
|
||||||
|
User.register(testUserName, password)
|
||||||
|
.then( ({ token, userId }) => {
|
||||||
|
testUserId = userId
|
||||||
|
|
||||||
|
if(printResults) console.log('Test: Register 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'))
|
||||||
|
|
||||||
|
return User.login(testUserName, password)
|
||||||
|
})
|
||||||
|
.then( ({token, userId}) => {
|
||||||
|
|
||||||
|
if(printResults) console.log('Test: Login New User - Pass')
|
||||||
|
|
||||||
|
return User.changePassword(testUserId, randomPassword, secondPassword)
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(passwordChangeResults => {
|
||||||
|
|
||||||
|
if(printResults) console.log('Test: Password Change - ', passwordChangeResults?'Pass':'Fail')
|
||||||
|
|
||||||
|
return User.login(testUserName, secondPassword)
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(reLogin => {
|
||||||
|
|
||||||
|
if(printResults) console.log('Test: Login With new Password - Pass')
|
||||||
|
|
||||||
|
return User.getMasterKey(testUserId, secondPassword)
|
||||||
|
})
|
||||||
|
.then(newMasterKey => {
|
||||||
|
|
||||||
|
masterKey = newMasterKey
|
||||||
|
|
||||||
|
resolve({testUserId, masterKey})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
@@ -35,6 +35,11 @@ router.post('/textsearch', function (req, res) {
|
|||||||
.then( data => res.send(data) )
|
.then( data => res.send(data) )
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.post('/get', function (req, res) {
|
||||||
|
Attachment.forNote(userId, req.body.noteId)
|
||||||
|
.then( data => res.send(data) )
|
||||||
|
})
|
||||||
|
|
||||||
router.post('/update', function (req, res) {
|
router.post('/update', function (req, res) {
|
||||||
Attachment.update(userId, req.body.attachmentId, req.body.updatedText, req.body.noteId)
|
Attachment.update(userId, req.body.attachmentId, req.body.updatedText, req.body.noteId)
|
||||||
.then( result => {
|
.then( result => {
|
||||||
@@ -60,26 +65,5 @@ router.post('/upload', upload.single('file'), function (req, res, next) {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
//
|
|
||||||
// Push URL to attachments
|
|
||||||
// push action on - public controller
|
|
||||||
//
|
|
||||||
|
|
||||||
// get push key
|
|
||||||
router.post('/getbookmarklet', function (req, res) {
|
|
||||||
|
|
||||||
Attachment.getPushkeyBookmarklet(userId)
|
|
||||||
.then( data => res.send(data) )
|
|
||||||
})
|
|
||||||
|
|
||||||
// generate new push key
|
|
||||||
router.post('/generatepushkey', function (req, res) {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// delete push key
|
|
||||||
router.post('/deletepushkey', function (req, res) {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
@@ -25,16 +25,12 @@ router.use(function setUserId (req, res, next) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//Get quick note text
|
||||||
router.post('/get', function (req, res) {
|
router.post('/get', function (req, res) {
|
||||||
MetricTracking.get(userId, masterKey)
|
MetricTracking.get(userId, masterKey)
|
||||||
.then( data => res.send(data) )
|
.then( data => res.send(data) )
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/create', function (req, res) {
|
|
||||||
MetricTracking.create(userId, masterKey)
|
|
||||||
.then( data => res.send(data) )
|
|
||||||
})
|
|
||||||
|
|
||||||
//Push text to quick note
|
//Push text to quick note
|
||||||
router.post('/save', function (req, res) {
|
router.post('/save', function (req, res) {
|
||||||
MetricTracking.save(userId, req.body.cycleData, masterKey)
|
MetricTracking.save(userId, req.body.cycleData, masterKey)
|
||||||
|
@@ -4,7 +4,6 @@ const rateLimit = require('express-rate-limit')
|
|||||||
|
|
||||||
const Note = require('@models/Note')
|
const Note = require('@models/Note')
|
||||||
const User = require('@models/User')
|
const User = require('@models/User')
|
||||||
const Attachment = require('@models/Attachment')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -57,29 +56,6 @@ router.post('/register', registerLimiter, function (req, res) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
//
|
|
||||||
// Public Pushme Action
|
|
||||||
//
|
|
||||||
const pushMeLimiter = rateLimit({
|
|
||||||
windowMs: 30 * 60 * 1000, //30 min window
|
|
||||||
max: 50, // start blocking after x requests
|
|
||||||
message:'Error'
|
|
||||||
})
|
|
||||||
router.get('/pushmebaby', pushMeLimiter, function (req, res) {
|
|
||||||
|
|
||||||
|
|
||||||
Attachment.pushUrl(req.query.pushkey, req.query.url)
|
|
||||||
.then((() => {
|
|
||||||
const jsCode = `
|
|
||||||
<script>
|
|
||||||
window.close();
|
|
||||||
</script>
|
|
||||||
<h1>Posting URL</h1>
|
|
||||||
`;
|
|
||||||
res.header('Content-Security-Policy', "script-src 'unsafe-inline'");
|
|
||||||
res.set('Content-Type', 'text/html');
|
|
||||||
res.send(Buffer.from(jsCode));
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
@@ -53,7 +53,7 @@ router.post('/revokesessions', function(req, res) {
|
|||||||
|
|
||||||
// fetch counts of users notes
|
// fetch counts of users notes
|
||||||
router.post('/totals', function (req, res) {
|
router.post('/totals', function (req, res) {
|
||||||
User.getCounts(req.headers.userId, req.body.extendedOptions)
|
User.getCounts(req.headers.userId)
|
||||||
.then( countsObject => res.send( countsObject ))
|
.then( countsObject => res.send( countsObject ))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -1,100 +0,0 @@
|
|||||||
const Attachment = require('../../models/Attachment')
|
|
||||||
const User = require('../../models/User')
|
|
||||||
|
|
||||||
const testUserName = 'jestTestUserAttachment'
|
|
||||||
const password = 'Beans19934!!!'
|
|
||||||
|
|
||||||
let newUserId = null
|
|
||||||
let masterKey = null
|
|
||||||
let newPushKey = null
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
|
|
||||||
// Find and Delete Previous Test user, log in, get key
|
|
||||||
return User.getByUserName(testUserName)
|
|
||||||
.then((user) => {
|
|
||||||
return User.deleteUser(user?.id, password)
|
|
||||||
})
|
|
||||||
.then((results) => {
|
|
||||||
|
|
||||||
return User.register(testUserName, password)
|
|
||||||
})
|
|
||||||
.then(({ token, userId }) => {
|
|
||||||
newUserId = userId
|
|
||||||
|
|
||||||
return User.getMasterKey(userId, password)
|
|
||||||
})
|
|
||||||
.then((newMasterKey) => {
|
|
||||||
masterKey = newMasterKey
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.catch(((error) => {
|
|
||||||
console.log(error)
|
|
||||||
}))
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
test('Test Generate Push Key', () => {
|
|
||||||
|
|
||||||
return Attachment.generatePushKey(newUserId)
|
|
||||||
.then( (pushKey) => {
|
|
||||||
newPushKey = pushKey
|
|
||||||
return Attachment.generatePushKey(newUserId)
|
|
||||||
})
|
|
||||||
.then( (pushKey) => {
|
|
||||||
// expect a long, defined pushkey
|
|
||||||
expect(pushKey).toBeDefined()
|
|
||||||
expect(pushKey?.length).toBeGreaterThan(20)
|
|
||||||
expect(pushKey).toMatch(newPushKey)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test('Test get Push Key Bookmarklet', () => {
|
|
||||||
|
|
||||||
return Attachment.getPushkeyBookmarklet(newUserId)
|
|
||||||
.then(( bookmarklet => {
|
|
||||||
// Expect a bookmarklet containting URL encoded pushkey from above
|
|
||||||
const keyCheck = bookmarklet.includes(encodeURIComponent(newPushKey))
|
|
||||||
|
|
||||||
expect(bookmarklet).toBeDefined()
|
|
||||||
expect(keyCheck).toBe(true)
|
|
||||||
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
test('Test Push URL', () => {
|
|
||||||
|
|
||||||
let url = 'https://www.solidscribe.com'
|
|
||||||
|
|
||||||
return Attachment.pushUrl(newPushKey, url)
|
|
||||||
.then(( results => {
|
|
||||||
|
|
||||||
return Attachment.textSearch(newUserId, 'scribe')
|
|
||||||
|
|
||||||
}))
|
|
||||||
.then((results) => {
|
|
||||||
|
|
||||||
expect(results.length == 1).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test Delete Push Key', () => {
|
|
||||||
|
|
||||||
return Attachment.deletePushKey(newUserId)
|
|
||||||
.then(( results => {
|
|
||||||
// Expect a true bool
|
|
||||||
expect(results).toBe(true)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(done => {
|
|
||||||
// Close Database
|
|
||||||
const db = require('../../config/database')
|
|
||||||
db.end()
|
|
||||||
done()
|
|
||||||
})
|
|
@@ -1,117 +0,0 @@
|
|||||||
const Note = require('../../models/Note')
|
|
||||||
const User = require('../../models/User')
|
|
||||||
|
|
||||||
const testUserName = 'jestTestUserNote'
|
|
||||||
const password = 'Beans1234!!!'
|
|
||||||
const secondPassword = 'Rice1234!!!'
|
|
||||||
|
|
||||||
let newUserId = null
|
|
||||||
let masterKey = null
|
|
||||||
|
|
||||||
let testNoteId = 0
|
|
||||||
let testNoteId2 = 0
|
|
||||||
|
|
||||||
|
|
||||||
const searchWord1 = 'beans'
|
|
||||||
const searchWord2 = 'RICE'
|
|
||||||
const updatedNoteText = 'Some Note Text for Testing more '+searchWord2+' is nice'
|
|
||||||
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
|
|
||||||
// Find and Delete Previous Test user, log in, get key
|
|
||||||
return User.getByUserName(testUserName)
|
|
||||||
.then((user) => {
|
|
||||||
return User.deleteUser(user?.id, password)
|
|
||||||
})
|
|
||||||
.then((results) => {
|
|
||||||
|
|
||||||
return User.register(testUserName, password)
|
|
||||||
})
|
|
||||||
.then(({ token, userId }) => {
|
|
||||||
newUserId = userId
|
|
||||||
|
|
||||||
return User.getMasterKey(userId, password)
|
|
||||||
})
|
|
||||||
.then((newMasterKey) => {
|
|
||||||
masterKey = newMasterKey
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.catch(((error) => {
|
|
||||||
console.log(error)
|
|
||||||
}))
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Create Note', () => {
|
|
||||||
const noteTitle = 'Test Note'
|
|
||||||
const noteText = 'Some Note Text for Testing'
|
|
||||||
|
|
||||||
return Note.create(newUserId, noteTitle, noteText, masterKey)
|
|
||||||
.then((noteId) => {
|
|
||||||
testNoteId = noteId
|
|
||||||
expect(noteId).toBeGreaterThan(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Create Another Note', () => {
|
|
||||||
const noteTitle = 'Test Note2'
|
|
||||||
const noteText = 'Some Note Text for Testing more '+searchWord1
|
|
||||||
|
|
||||||
return Note.create(newUserId, noteTitle, noteText, masterKey)
|
|
||||||
.then((noteId) => {
|
|
||||||
testNoteId2 = noteId
|
|
||||||
expect(noteId).toBeGreaterThan(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Update a note', () => {
|
|
||||||
|
|
||||||
return Note.update(newUserId, testNoteId, updatedNoteText, 'title', 0, 0, 0, 'hash', masterKey)
|
|
||||||
.then((results) => {
|
|
||||||
expect(results.changedRows).toEqual(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Decrypt a note', () => {
|
|
||||||
|
|
||||||
return Note.get(newUserId, testNoteId, masterKey)
|
|
||||||
.then((noteData) => {
|
|
||||||
expect(noteData.text).toMatch(updatedNoteText)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Update note search index', () => {
|
|
||||||
return Note.reindex(newUserId, masterKey)
|
|
||||||
.then((results) => {
|
|
||||||
expect(results).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Search Encrypted Index', () => {
|
|
||||||
const searchString = `${searchWord1} ${searchWord2}`
|
|
||||||
|
|
||||||
return Note.encryptedIndexSearch(newUserId, searchString, null, masterKey)
|
|
||||||
.then(({ids}) => {
|
|
||||||
// Make sure beans is in one note and rice is in updated text
|
|
||||||
expect(ids.length).toEqual(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Search Encrypted Index no results', () => {
|
|
||||||
|
|
||||||
return Note.encryptedIndexSearch(newUserId, 'zzz', null, masterKey)
|
|
||||||
.then(({ids}) => {
|
|
||||||
// Make sure beans is in one note and rice is in updated text
|
|
||||||
expect(ids.length).toEqual(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
afterAll(done => {
|
|
||||||
// Close Database
|
|
||||||
const db = require('../../config/database')
|
|
||||||
db.end()
|
|
||||||
done()
|
|
||||||
})
|
|
@@ -1,67 +0,0 @@
|
|||||||
const Note = require('../../models/Note')
|
|
||||||
const User = require('../../models/User')
|
|
||||||
const ShareNote = require('../../models/ShareNote')
|
|
||||||
|
|
||||||
const testUserName = 'jestTestUserNote'
|
|
||||||
const password = 'Beans1234!!!'
|
|
||||||
let newUserId = null
|
|
||||||
let masterKey = null
|
|
||||||
|
|
||||||
const testUserName2 = 'jestTestUserDude'
|
|
||||||
const password2 = 'Rice1234!!!'
|
|
||||||
let newUserId2 = null
|
|
||||||
let masterKey2 = null
|
|
||||||
|
|
||||||
|
|
||||||
let testNoteId = 0
|
|
||||||
let testNoteId2 = 0
|
|
||||||
// let sharedNoteId = 0 //ID of note shared with user
|
|
||||||
const shareUserId = 61
|
|
||||||
const searchWord1 = 'beans'
|
|
||||||
const searchWord2 = 'RICE'
|
|
||||||
const updatedNoteText = 'Some Note Text for Testing more '+searchWord2+' is nice'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
|
|
||||||
// Find and Delete Previous Test user, log in, get key
|
|
||||||
return
|
|
||||||
User.getByUserName(testUserName)
|
|
||||||
.then(user => {
|
|
||||||
User.deleteUser(user?.id, password)
|
|
||||||
})
|
|
||||||
.then(user => {
|
|
||||||
User.getByUserName(testUserName2)
|
|
||||||
})
|
|
||||||
.then(user => {
|
|
||||||
User.deleteUser(user?.id, password)
|
|
||||||
})
|
|
||||||
.then((results) => {
|
|
||||||
|
|
||||||
return User.register(testUserName, password)
|
|
||||||
})
|
|
||||||
.then(({ token, userId }) => {
|
|
||||||
newUserId = userId
|
|
||||||
|
|
||||||
return User.getMasterKey(userId, password)
|
|
||||||
})
|
|
||||||
.then((newMasterKey) => {
|
|
||||||
masterKey = newMasterKey
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.catch(((error) => {
|
|
||||||
console.log(error)
|
|
||||||
}))
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
afterAll(done => {
|
|
||||||
// Close Database
|
|
||||||
const db = require('../../config/database')
|
|
||||||
db.end()
|
|
||||||
done()
|
|
||||||
})
|
|
@@ -1,112 +0,0 @@
|
|||||||
const User = require('../../models/User')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
const testUserName = 'jestTestUser'
|
|
||||||
const password = 'Beans1234!!!'
|
|
||||||
const secondPassword = 'Rice1234!!!'
|
|
||||||
|
|
||||||
let testUserId = null
|
|
||||||
let masterKey = null
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
|
|
||||||
// Find and Delete Previous Test user
|
|
||||||
return User.getByUserName(testUserName)
|
|
||||||
.then((user) => {
|
|
||||||
return User.deleteUser(user?.id, password)
|
|
||||||
})
|
|
||||||
.then((results) => {
|
|
||||||
|
|
||||||
return results
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test User Registration', () => {
|
|
||||||
|
|
||||||
return User.register(testUserName, password)
|
|
||||||
.then((({ token, userId }) => {
|
|
||||||
|
|
||||||
testUserId = userId
|
|
||||||
|
|
||||||
expect(token).toBeDefined()
|
|
||||||
expect(userId).toBeGreaterThan(0)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test decrypting user masterKey', () => {
|
|
||||||
|
|
||||||
return User.getMasterKey(testUserId, password)
|
|
||||||
.then((newMasterKey) => {
|
|
||||||
masterKey = newMasterKey
|
|
||||||
|
|
||||||
expect(masterKey).toBeDefined()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test generating public and private key pair', () => {
|
|
||||||
|
|
||||||
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
|
|
||||||
expect(decryptedPrivate.toString('utf8')).toMatch(privateKeyMessage)
|
|
||||||
|
|
||||||
//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
|
|
||||||
expect(publicDeccryptMessage.toString('utf8')).toMatch(publicKeyMessage)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test Logging in User', () => {
|
|
||||||
|
|
||||||
return User.login(testUserName, password)
|
|
||||||
.then(({token, userId}) => {
|
|
||||||
expect(token).toBeDefined()
|
|
||||||
expect(userId).toBeGreaterThan(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test Changing Password', () => {
|
|
||||||
return User.changePassword(testUserId, password, secondPassword)
|
|
||||||
.then((passwordChangeResults) => {
|
|
||||||
|
|
||||||
expect(passwordChangeResults).toBe(true)
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test Login with wrong password', () => {
|
|
||||||
|
|
||||||
return User.login(testUserName, password)
|
|
||||||
.then(({token, userId}) => {
|
|
||||||
|
|
||||||
expect(token).toBeNull()
|
|
||||||
expect(userId).toBeNull()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Test decrypting masterKey with new Password', () => {
|
|
||||||
return User.getMasterKey(testUserId, secondPassword)
|
|
||||||
.then((newMasterKey) => {
|
|
||||||
|
|
||||||
expect(newMasterKey).toBeDefined()
|
|
||||||
expect(newMasterKey.length).toBe(28)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(done => {
|
|
||||||
// Close Database
|
|
||||||
const db = require('../../config/database')
|
|
||||||
db.end()
|
|
||||||
done()
|
|
||||||
})
|
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"background_color": "purple",
|
|
||||||
"description": "Take Notes",
|
|
||||||
"display": "fullscreen",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/api/static/assets/favicon.ico",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Notes",
|
|
||||||
"short_name": "Notes",
|
|
||||||
"start_url": "/#/notes"
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
Binary file not shown.
20
updatedomain.sh
Executable file
20
updatedomain.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Send updated dynamic IP address to Namecheap, in order to update subdomains.
|
||||||
|
# This uses curl (separate pkg) to send the change; Namecheap automatically detects source IP if the ip field (like domain, password) ..
|
||||||
|
# is not specified.
|
||||||
|
|
||||||
|
# info helper
|
||||||
|
info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; }
|
||||||
|
|
||||||
|
info "Starting IP update for subdomains"
|
||||||
|
|
||||||
|
PASSWORD="12a35d53c2c54d6281265e3e086e541b"
|
||||||
|
HOST="maxg.cc"
|
||||||
|
SUBDOMAIN="x.maxg.cc"
|
||||||
|
|
||||||
|
|
||||||
|
#echo "https://dynamicdns.park-your-domain.com/update?host=$SUBDOMAIN&domain=$HOST&password=$PASSWORD"
|
||||||
|
curl "https://dynamicdns.park-your-domain.com/update?host=$SUBDOMAIN&domain=$HOST&password=$PASSWORD"
|
||||||
|
|
||||||
|
info "IP update done"
|
Reference in New Issue
Block a user