Compare commits

..

3 Commits

Author SHA1 Message Date
Max G
b7d22cb7fc Adding everything to get started on cycle tracking and maybe avid habit clone 2022-09-25 17:17:41 +00:00
Max G
df5e9f8c3b Added paste button and touched up some styles 2022-07-05 05:10:40 +00:00
Max G
7f5f4bea39 Updated marketing images to change with theme
Removed visible attribute that was left over from testing
Removed drag attribute on check boxes, needs better implimentation later. Drag prevented click events
2022-04-03 17:21:05 +00:00
25 changed files with 1812 additions and 370 deletions

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9vBYgeFm2nDnj
HJ+Cq4H96adNLrynoFPs0lSxk3YMXG/mP2sXUpqT3P8S7E6QK55IyU0jtiOoYiV1
bjcTuDrQyZXrtt+Nz0A7vQxtRu8CatXSEG8Vc3y8QrcDT0HfTezHdNkuJXE8cnYv
XSgrZH+cHF996ytOA0lLJWBCHCJH5WIHj5Jziw5dHaLc4mjxSg51xjjqfzLFWQgZ
rOPF6lviAWBFux8RIXXg7nClNvEeyikdraZvVuWFgt89KhN/ePU1Xik/o6++evhT
HxBZqMQHnTp/4h5T25lPGcs0CPPX0afwNSUdPc8yspuSYNcLsWix3oROLMX6cHBa
CTBrtlLzAgMBAAECggEAIBhG7est0dQPfrmCygnVDWyO3mF/jCN0zuStavR0zZZ9
X0dvCBzzBPwnMb5Dc+PM/KcAo3/V/E/N4lVof690U4kmER94JXbfeLt79KhBGfmU
fdpF0C0e9oGaj7bCf9GgsgS0EDhJNV5vW4e4mc6AP5oVFSnIw4OOzGVgKQ61Rc/e
vaalAEz7xGbsYYh2Y43tFnNA6g/qOzs+H8e2Uv+G9mxx7EID1MG3txJJCBpeUeN5
JbHQe254IBy4Ko3+i5Tq3ziYL3WyyElCwK5PiRtAP7WNL5AqFT6Fn4L2fULwkTKL
sjPSgGxt7QHomeW+12n9bst1mOEmsz0hTGrAtIf1UQKBgQDu1u7BLdTt9H+zaOFl
XfhOR3GDZnrIs+F6VuEjdjwNEQLKvOYzGhbgDNRArSETK6f03Jvl7ZC9XYwNC836
J29fHsXVcsMJm8fq69PBSdmMXmOpRfjgALu6DYxK1vBqNQ2ToulCwiWoskKEuPUN
GwVCc/0HcvwZElWZ1UhsB5Xf9wKBgQDLXfMCtsVpfI15w8qEqRCdrCzprHSitcHJ
dCXO72+i9ygMuFtcxo7kgivT0oFUCYmt7Ex+krOlq/xbVLu4sJXWd1FDZ1IeTsIh
cuh4IhSOGJR70S4Q/DzbGUQ08Hu+4hrudaw1Y8Fod0wTERCyOIQiWBfKn3Tab3mk
X29RdGod5QKBgBig5UHaXgijm79+Yy+2vvIjf9sS6DpmAixBZTno6UxXorgRPpOq
bw1vhTueHrkBWXJwhUrycmh0iwqVWwoeoudmHvRhvybwf28EHnPiD6Lf4NsFsiI3
MSSAXSUigOwSyHGe7PrLVmLM7vsMr4hIbwRpPYBVJRXYxCb2zV8GcTgFAoGBALiV
gWhJNE1We6K1jy9xtF8oU2uU2BiHGGkdPuPgd1dXNca13lcK8c9+RwFv42q/bXOr
MpL/3IbW36qV8fzkalvK2LtxIBvaKGHrxgykAjwnGz520nUgPKww9rOGQwsydndR
3whmjrme7jGwH5NjsKrrgkyrBojs/V+wL32jSF3xAoGAPLmI5gamjEo9K25ojNxO
XjA0fIOxUz/rTPS/qYrxcluXibz8eGXDLq8/D3Q4uWDh18ZQYzWVGoN/x2Qv0srz
SHU4AyJo6+asZAe+viOhAtI81B7uM5V4oyEkPaEASPg6+do/to7SFmdcw/XM/p2v
KYVXalAeFhW0wJ4I4z6DkuU=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEWTCCAsGgAwIBAgIRAPWg+zwqGDC6qPGon1qGS0cwDQYJKoZIhvcNAQELBQAw
czEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSQwIgYDVQQLDBttYWJA
bWFydmluIChNYXggR2lhbGFuZWxsYSkxKzApBgNVBAMMIm1rY2VydCBtYWJAbWFy
dmluIChNYXggR2lhbGFuZWxsYSkwHhcNMjIwNjI2MTgxMDE5WhcNMjQwOTI2MTgx
MDE5WjBPMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUx
JDAiBgNVBAsMG21hYkBtYXJ2aW4gKE1heCBHaWFsYW5lbGxhKTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL28FiB4WbacOeMcn4Krgf3pp00uvKegU+zS
VLGTdgxcb+Y/axdSmpPc/xLsTpArnkjJTSO2I6hiJXVuNxO4OtDJleu2343PQDu9
DG1G7wJq1dIQbxVzfLxCtwNPQd9N7Md02S4lcTxydi9dKCtkf5wcX33rK04DSUsl
YEIcIkflYgePknOLDl0dotziaPFKDnXGOOp/MsVZCBms48XqW+IBYEW7HxEhdeDu
cKU28R7KKR2tpm9W5YWC3z0qE3949TVeKT+jr756+FMfEFmoxAedOn/iHlPbmU8Z
yzQI89fRp/A1JR09zzKym5Jg1wuxaLHehE4sxfpwcFoJMGu2UvMCAwEAAaOBizCB
iDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgw
FoAUzspSlchxoKIVJLKOhPGrcTFjRyowQAYDVR0RBDkwN4IMbWFydmluLmxvY2Fs
gglsb2NhbGhvc3SHBMCoAaSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI
hvcNAQELBQADggGBAAaTOkCKiVwsapOAuiEt8kG7HS/r4HG+drLzhb2dUFYfqxpz
mfRlRfFA88JN8nyFtOPcpoeOEaTPMi0hxq0rOw9zHPga5kz6LRAUJeADPgA4pw2S
fYT1CEbPMknmHQyhVODKNZN05l3vWC2CL2SDs9lirGVrzmfg7kZ0im8hc81GQgo+
MsfnC3AT1r1rzMqGLWiBHM8BjeGGwgqjjFZmxoHuGw+0CuV2TZfkZlNoJRtlRtyV
xlkUuRkVDYbLmHLMz7n9+ItOy8epKLToFpIXyhGR+ehAzYyyJeh2SCaboJ71lU+h
+90GQPl20ajWzLtwTsZHEAehHu4l/JLWleNaQh3nVdllHyzvU3IR/C7hhVv6im+q
/KiwDR3W8LqIOGJsemca5iu73EXd1d5UU49alIPm1Ko+Z22X/WMPj74+9CNW65DV
7ebM17NNQgr4tEJdXF3IYwaZ0Epv1/Y7v6bAXT8V2mdjtXfRBwu3HyySl22a9Y4R
h7svqj31cb0ubXEfrA==
-----END CERTIFICATE-----

View File

@ -290,7 +290,7 @@ i.green.icon.icon.icon.icon {
border: none; border: none;
/*height: calc(100% - 69px);*/ /*height: calc(100% - 69px);*/
min-height: 500px; min-height: 300px;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
/*margin-bottom: 15px;*/ /*margin-bottom: 15px;*/
@ -309,6 +309,9 @@ i.green.icon.icon.icon.icon {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: 1100px; max-width: 1100px;
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
} }
.squire-box::selection, .squire-box::selection,
.squire-box::-moz-selection { .squire-box::-moz-selection {
@ -372,8 +375,37 @@ i.green.icon.icon.icon.icon {
.squire-box ol, .squire-box ol,
.note-card-text ul, .note-card-text ul,
.squire-box ul { .squire-box ul {
margin: 8px 0 0 0; margin: 3px 0;
display: block;
} }
/* Add border 1 indent level */
.note-card-text > ol > ol,
.squire-box > ol > ol,
.note-card-text > ul > ul,
.squire-box > ul > ul
{
border-left: 1px solid var(--border_color);
}
.note-card-text ol > ol,
.squire-box ol > ol,
.note-card-text ul > ul,
.squire-box ul > ul {
list-style-type: upper-alpha;
}
ol {
counter-reset: item;
}
ol li {
display: block;
}
ol li:before {
content: counters(item, ".") ".";
counter-increment: item;
padding-right: 10px;
}
.note-card-text ul > li, .note-card-text ul > li,
.squire-box ul > li { .squire-box ul > li {
position: relative; position: relative;
@ -500,10 +532,6 @@ i.green.icon.icon.icon.icon {
/* adjust checkboxes for mobile. Make them a little bigger, easier to click */ /* adjust checkboxes for mobile. Make them a little bigger, easier to click */
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.squire-box {
min-height: calc(100vh - 122px);
}
.ui.button.shrinking { .ui.button.shrinking {
font-size: 0.85714286rem; font-size: 0.85714286rem;
margin: 0 3px; margin: 0 3px;
@ -931,3 +959,13 @@ i.green.icon.icon.icon.icon {
opacity: 0; opacity: 0;
} }
} }
.shade {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.7);
z-index: 1000;
}

View File

@ -19,7 +19,7 @@
padding: 1em 5px; padding: 1em 5px;
cursor: pointer; cursor: pointer;
} }
.popup-row > span { .popup-row > p {
/*width: calc(100% - 50px);*/ /*width: calc(100% - 50px);*/
display: inline-block; display: inline-block;
text-align: left; text-align: left;
@ -85,6 +85,18 @@
animation: progressBar 3s linear; animation: progressBar 3s linear;
animation-fill-mode: both; animation-fill-mode: both;
} }
.time-display {
display: inline-block;
width: calc(100% - 25px);
/*text-align: right;*/
color: white;
font-size: 0.7em;
margin: 0 0 0 25px;
}
.text-display {
display: inline-block;
width: 100%;
}
@keyframes progressBar { @keyframes progressBar {
0% { width: 0; } 0% { width: 0; }
@ -101,7 +113,11 @@
<div class="meter"> <div class="meter">
<span><span class="progress"></span></span> <span><span class="progress"></span></span>
</div> </div>
<span><i class="small info circle icon"></i>{{ item }}</span> <p class="text-display">
<i class="small info circle icon"></i>
{{ item.text }}
<span class="time-display">{{ item.time }}</span>
</p>
</div> </div>
</div> </div>
</template> </template>
@ -119,8 +135,8 @@
} }
}, },
beforeMount(){ beforeMount(){
this.$bus.$on('notification', info => { this.$bus.$on('notification', notificationText => {
this.displayNotification(info) this.displayNotification(notificationText)
}) })
}, },
mounted(){ mounted(){
@ -131,8 +147,17 @@
}, },
methods: { methods: {
displayNotification(newNotification){ displayNotification(notificationText){
this.notifications.push(newNotification)
const date = new Date()
const time = date.toLocaleTimeString()
const notification = {
text: notificationText,
time: time
}
this.notifications.unshift(notification)
clearTimeout(this.totalTimeout) clearTimeout(this.totalTimeout)
this.totalTimeout = setTimeout(() => { this.totalTimeout = setTimeout(() => {
this.dismiss() this.dismiss()

View File

@ -4,6 +4,7 @@
width: 180px; width: 180px;
display: block; display: block;
float: left; float: left;
overflow: hidden;
} }
.global-menu { .global-menu {
width: 180px; width: 180px;
@ -106,6 +107,7 @@
text-align: center; text-align: center;
color: #8c80ae; color: #8c80ae;
cursor: pointer; cursor: pointer;
background-color: var(--menu-background);
} }
.mobile-button { .mobile-button {

View File

@ -1,133 +1,134 @@
<template> <template>
<!-- change class to .master-note-edit to have it popup on the screen. <!-- change class to .master-note-edit to have it popup on the screen.
@keyup.esc="closeButtonAction()" --> @keyup.esc="closeButtonAction()" -->
<div <div class="master-note-edit">
id="InputNotes"
class="master-note-edit"
>
<!-- Giant Edit Note Menu --> <!-- Edit Menus -->
<div class="edit-menu" :class="{ 'slide-out-top':(sizeDown == true) }">
<!-- edit spacer is disabled, it is helpful if menu gets bigger. It adds a left margin, starting the icons at the edge of the note --> <div class="menu-top-half" :class="{ 'hide-text':(openNotes > 1) }">
<div class="edit-spacer"></div>
<div class="menu-top-half">
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center">
<i class="font icon"></i>
<div class="font-color-bar" :style="{'background':lastUsedColor}"></div>
</div>
<div class="edit-button" v-on:click="toggleBold()" :data-tooltip="`Bold\n(CTRL + b)`" data-position="bottom center" :class="{'edit-active':activeBold}">
<i class="bold icon"></i>
</div>
<div class="edit-button" v-on:click="toggleItalic()" :data-tooltip="`Italic\n(CRTL + i)`" data-position="bottom center" :class="{'edit-active':activeItalics}">
<i class="italic icon"></i>
</div>
<div class="edit-button" v-on:click="toggleUnderline()" :data-tooltip="`Underline\n(CRTL + u)`" data-position="bottom center" :class="{'edit-active':activeUnderline}">
<i class="underline icon"></i>
</div>
<div class="edit-button" v-on:click="modifyCode('1.4em')" data-tooltip="Quote" data-position="bottom center" :class="{'edit-active':activeCode}">
<i class="quote right icon"></i>
</div>
<div class="edit-button" v-on:click="modifyFont('0.9em')" data-tooltip="Sub Title" data-position="bottom center" :class="{'edit-active':activeSubTitle}">
<i class="small text height icon"></i>
</div>
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" :class="{'edit-active':activeTitle}">
<i class="text height icon"></i>
</div>
<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center">
<i class="remove format icon"></i>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="editor.increaseQuoteLevel()" :data-tooltip="`Indent\n(TAB)`" data-position="bottom center">
<i class="indent icon"></i>
</div>
<div class="edit-button" v-on:click="editor.decreaseQuoteLevel()" :data-tooltip="`Un-Indent\n(SHIFT + TAB)`" data-position="bottom center">
<i class="outdent icon"></i>
</div>
<div class="edit-button" v-on:click="colorpicker = true" data-tooltip="Text Color" data-position="bottom center">
<i class="font icon"></i>
<div class="font-color-bar" :style="{'background':lastUsedColor}"></div>
<span>Color</span>
</div> </div>
<div class="menu-bottom-half"> <div class="edit-button" v-on:click="toggleBold()" :data-tooltip="`Bold\n(CTRL + b)`" data-position="bottom center" :class="{'edit-active':activeBold}">
<i class="bold icon"></i>
<!-- <span>Bold</span>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/table`)" data-tooltip="Insert Table" data-position="bottom center">
<i class="border all icon"></i>
</div> -->
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="toggleList('ul')" :data-tooltip="`Task List\n(CTRL + SHIFT + 8)`" data-position="bottom center" :class="{'edit-active':activeToDo}">
<i class="tasks icon"></i>
</div>
<div class="edit-button" v-on:click="toggleList('ol')" :data-tooltip="`Ordered List\n(CTRL + SHIFT + 9)`" data-position="bottom center" :class="{'edit-active':activeList}">
<i class="list ol icon"></i>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="bottom center">
<i class="grip lines icon"></i>
</div>
<div class="edit-divide"></div>
<!-- <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="bottom center" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
<i class="paint brush icon"></i>
</div> -->
<!-- <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" data-tooltip="Tags" data-position="bottom center">
<i class="tags icon"></i>
</div> -->
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/images`)" data-tooltip="Images" data-position="bottom center">
<i class="image icon"></i>
</div>
<file-upload-button
data-tooltip="Upload File" data-position="bottom center"
class="edit-button"
:noteId="noteid" />
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/options`)" data-tooltip="More Options" data-position="bottom center">
&nbsp;&nbsp;
<i class="ellipsis horizontal icon"></i>
&nbsp;&nbsp;
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="bottom center">
<i class="reply icon"></i>
</div>
<div class="edit-button done-button" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center">
<!-- <i class="green close icon"></i> -->
<span class="ui green text">Done</span>
</div>
</div> </div>
<div class="edit-button" v-on:click="toggleItalic()" :data-tooltip="`Italic\n(CRTL + i)`" data-position="bottom center" :class="{'edit-active':activeItalics}">
<i class="italic icon"></i>
<span>Italic</span>
</div>
<div class="edit-button" v-on:click="toggleUnderline()" :data-tooltip="`Underline\n(CRTL + u)`" data-position="bottom center" :class="{'edit-active':activeUnderline}">
<i class="underline icon"></i>
<span>Underline</span>
</div>
<div class="edit-button" v-on:click="modifyCode('1.4em')" data-tooltip="Quote" data-position="bottom center" :class="{'edit-active':activeCode}">
<i class="quote right icon"></i>
<span>Quote</span>
</div>
<div class="edit-button" v-on:click="modifyFont('0.9em')" data-tooltip="Sub Title" data-position="bottom center" :class="{'edit-active':activeSubTitle}">
<i class="small text height icon"></i>
<span>Small Text</span>
</div>
<div class="edit-button" v-on:click="modifyFont('1.4em')" data-tooltip="Title" data-position="bottom center" :class="{'edit-active':activeTitle}">
<i class="text height icon"></i>
<span>Title</span>
</div>
<div class="edit-button" v-on:click="removeFormatting()" data-tooltip="Remove Formatting" data-position="bottom center">
<i class="remove format icon"></i>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="indentText" :data-tooltip="`Indent\n(TAB)`" data-position="bottom center">
<i class="indent icon"></i>
<span>Indent</span>
</div>
<div class="edit-button" v-on:click="outdentText" :data-tooltip="`Un-Indent\n(SHIFT + TAB)`" data-position="bottom center">
<i class="outdent icon"></i>
</div>
</div> </div>
<!-- invisible menu item, creates BG for bottom menu --> <div class="menu-bottom-half" :class="{ 'hide-text':(openNotes > 1) }">
<div class="bottom-edit-menu"></div>
<!--
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/table`)" data-tooltip="Insert Table" data-position="bottom center">
<i class="border all icon"></i>
</div> -->
<div class="edit-button" v-on:click="toggleList('ul')" :data-tooltip="`Task List\n(CTRL + SHIFT + 8)`" data-position="top center" :class="{'edit-active':activeToDo}">
<i class="tasks icon"></i>
<span>To-Do</span>
</div>
<div class="edit-button" v-on:click="toggleList('ol')" :data-tooltip="`Ordered List\n(CTRL + SHIFT + 9)`" data-position="top center" :class="{'edit-active':activeList}">
<i class="list ol icon"></i>
<span>List</span>
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="insertDivide()" data-tooltip="Insert Divide" data-position="top center">
<i class="grip lines icon"></i>
<span>Divide</span>
</div>
<div class="edit-divide"></div>
<!--
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/colors`)" data-tooltip="Note Color" data-position="top center" :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText']}">
<i class="paint brush icon"></i>
</div> -->
<!-- <div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" data-tooltip="Tags" data-position="top center">
<i class="tags icon"></i>
</div> -->
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/images`)" data-tooltip="Images" data-position="top center">
<i class="image icon"></i>
<span>Images</span>
</div>
<file-upload-button
data-tooltip="Upload File" data-position="top center"
class="edit-button"
:noteId="noteid" />
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="$router.push(`/notes/open/${noteid}/menu/options`)" data-tooltip="More Options" data-position="top center">
&nbsp;&nbsp;
<i class="ellipsis horizontal icon"></i>
&nbsp;&nbsp;
</div>
<div class="edit-divide"></div>
<div class="edit-button" v-on:click="undoCustom()" :data-tooltip="`Undo\n(CTRL + z)`" data-position="top center">
<i class="reply icon"></i>
</div>
<div class="edit-button done-button" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="top center">
<!-- <i class="green close icon"></i> -->
<span class="ui green text">Done</span>
</div>
</div>
<div class="input-container-wrapper" <div class="input-container-wrapper"
:class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}"> :class="{ 'side-menu-open':sideMenuOpen }">
<!-- Squire box grows --> <!-- Squire box grows -->
<div id="text-box-container" class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground']}"> <div class="note-wrapper">
<!-- Loading indicator --> <!-- Loading indicator -->
<transition name="fade"> <transition name="fade">
@ -155,14 +156,9 @@
</div> </div>
<!-- tags on the side, only show on desktop --> <!-- tags on the side, only show on desktop -->
<div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }" <div class="note-mini-tag-area"
v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)"
:style="{ 'background-color':styleObject['noteBackground'] }"> :style="{ 'background-color':styleObject['noteBackground'], 'color':styleObject['noteText'] }">
<span>
{{ lastVisibilityState }}
</span>
<span class="add-mini-tag" v-if="allTags.length == 0"> <span class="add-mini-tag" v-if="allTags.length == 0">
<i class="tags icon"></i>Add Tags <i class="tags icon"></i>Add Tags
@ -346,9 +342,9 @@
</side-slide-menu> </side-slide-menu>
<!-- Show side shades if user is on desktop only --> <!-- Show side shades if user is on desktop only -->
<div class="full-focus-shade shade1" <!-- <div class="full-focus-shade shade1"
:class="{ 'fade-me-out':sizeDown }" :class="{ 'fade-me-out':sizeDown }"
v-on:click="closeButtonAction()"></div> v-on:click="closeButtonAction()"></div> -->
</div> </div>
</template> </template>
@ -365,8 +361,8 @@
import SquireButtonFunctions from '@/mixins/SquireButtonFunctions.js' import SquireButtonFunctions from '@/mixins/SquireButtonFunctions.js'
export default { export default {
name: 'InputNotes', name: 'NoteInputPanel',
props: [ 'noteid', 'position', 'openMenu', 'urlData' ], props: [ 'noteid', 'position', 'openMenu', 'urlData', 'openNotes'],
components:{ components:{
'note-tag-edit': () => import('@/components/NoteTagEdit.vue'), 'note-tag-edit': () => import('@/components/NoteTagEdit.vue'),
'color-picker': () => import('@/components/ColorPicker.vue'), 'color-picker': () => import('@/components/ColorPicker.vue'),
@ -456,7 +452,7 @@
this.table = false this.table = false
//If a menu value is set, open it //If a menu value is set, open it
if(newVal.openMenu){ if(newVal.openMenu && newVal.id == this.noteid){
//Only modify menu boolean if its defined //Only modify menu boolean if its defined
if(typeof this[newVal.openMenu] == 'boolean'){ if(typeof this[newVal.openMenu] == 'boolean'){
this.sideMenuOpen = true this.sideMenuOpen = true
@ -474,6 +470,12 @@
this.save() this.save()
} }
}) })
this.$bus.$on('close_note_by_id', (noteId) => {
if(noteId == this.noteid){
this.closeButtonAction()
}
})
}, },
beforeDestroy(){ beforeDestroy(){
@ -488,6 +490,7 @@
//Obliterate squire instance //Obliterate squire instance
this.editor.destroy() this.editor.destroy()
// trigger save actions and reindex
this.close() this.close()
}, },
@ -646,9 +649,9 @@
if(keyCode == 'Tab'){ if(keyCode == 'Tab'){
if(event.shiftKey){ if(event.shiftKey){
this.editor.decreaseQuoteLevel() this.outdentText()
} else { } else {
this.editor.increaseQuoteLevel() this.indentText()
} }
event.preventDefault() event.preventDefault()
@ -684,6 +687,12 @@
this.onKeyup(event) this.onKeyup(event)
}) })
// this.editor.addEventListener("dragstart", e => {
// console.log('Dragging')
// console.log(e)
// if(){}
// });
//Show and hide additional toolbars //Show and hide additional toolbars
// this.editor.addEventListener('focus', e => { // this.editor.addEventListener('focus', e => {
// }) // })
@ -986,12 +995,14 @@
return hash; return hash;
}, },
closeButtonAction(){ closeButtonAction(playAnimation = false){
this.sizeDown = true this.sizeDown = playAnimation
const animationTimeout = (playAnimation ? 300 : 0)
//This timeout allows animation to play before closing //This timeout allows animation to play before closing
setTimeout(() => { setTimeout(() => {
this.$router.push('/notes') // this.$router.push('/notes')
}, 300) this.close()
}, animationTimeout)
}, },
close(){ close(){
@ -1019,9 +1030,9 @@
}) })
}, },
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')
}, },
setupWebSockets(){ setupWebSockets(){
@ -1131,42 +1142,41 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: var(--menu-accent); background-color: var(--small_element_bg_color);
z-index: 999; z-index: 999;
cursor: pointer; cursor: pointer;
opacity: 0.9; opacity: 0.6;
} }
/* squire styles */ /* squire styles */
.input-container-wrapper { .input-container-wrapper {
position: fixed; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 15%; left: 0;
right: 15%; right: 0;
z-index: 1005;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
scrollbar-width: none; scrollbar-width: none;
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
overscroll-behavior: contain; overscroll-behavior: contain;
background-color: var(--border_color);
} }
.note-wrapper { .note-wrapper {
background-color: var(--small_element_bg_color); background-color: var(--border_color);
border: 1px solid var(--menu-accent);
margin: 45px 0 45px 0;
position: relative; position: relative;
margin: 50px auto;
max-width: 1100px;
} }
.note-mini-tag-area { .note-mini-tag-area {
width: 100%; width: 100%;
padding: 5px 15px 0 15px; padding: 5px 15px 0 15px;
cursor: pointer; cursor: pointer;
margin-left: auto;
margin-right: auto;
max-width: 1100px;
position: relative; position: relative;
background: var(--small_element_bg_color);
} }
.add-mini-tag { .add-mini-tag {
color: var(--border_color); color: var(--border_color);
@ -1192,22 +1202,29 @@
Edit Menu Styles START Edit Menu Styles START
*/ */
.edit-menu { .menu-top-half, .menu-bottom-half {
position: fixed; display: flex;
top: 0; justify-content: center;
position: absolute;
z-index: 1001;
background-color: green;
border-radius: 3px;
padding: 5px 5px;
background-color: var(--menu-background);
left: 0; left: 0;
right: 0; right: 0;
width: 100%;
display: block;
background-color: var(--small_element_bg_color);
z-index: 1019;
padding: 5px 0;
border: none;
border-bottom: 1px solid var(--menu-accent);
text-align: center;
} }
.menu-top-half, .menu-bottom-half { .menu-top-half{
display: inline-block; top: 0;
}
.menu-bottom-half {
bottom: 0;
}
.menu-top-half.hide-text .edit-button > span:not(.ui),
.menu-bottom-half.hide-text .edit-button > span:not(.ui)
{
display: none;
} }
.edit-button { .edit-button {
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
@ -1215,14 +1232,15 @@
display: inline-block; display: inline-block;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
font-size: 1.4em; font-size: 1em;
box-shadow: 0 0 1px 0 #c4c4c4; box-shadow: 0 0 1px 0 #c4c4c4;
margin: 0 3px 0; margin: 0 3px 0;
padding: 6px 0 0 0; padding: 6px 12px 0;
text-align: center; text-align: center;
min-width: 32px; min-width: 25px;
min-height: 32px; min-height: 30px;
/*flex-basis: 100%;*/
white-space: nowrap;
} }
.edit-button > i { .edit-button > i {
font-size: 1em; font-size: 1em;
@ -1241,19 +1259,29 @@
} }
.edit-divide { .edit-divide {
display: inline-block; display: inline-block;
background-color: var(--menu-accent);
height: 15px; height: 15px;
width: 1px; width: 7px;
margin: 0 8px;
padding: 0; padding: 0;
} }
@media only screen and (max-width: 740px) { @media only screen and (max-width: 740px) {
.edit-spacer {
display: none;
}
.edit-button { .edit-button {
font-size: 1.2em; font-size: 1.2em;
} }
.menu-top-half, .menu-bottom-half {
padding: 3px 2px;
left: 0;
right: 0;
transform: none;
border-radius: 0;
}
.menu-bottom-half {
position: fixed;
z-index: 100000;
}
.note-wrapper {
margin-bottom: 100px;
margin-top: 38px;
}
} }
/* /*
@ -1328,68 +1356,22 @@
left: calc(50% + 10px) !important; left: calc(50% + 10px) !important;
right: calc(0% + 10px) !important; right: calc(0% + 10px) !important;
} }
@media only screen and (max-width: 740px) { /*weird inbetween size for tables*/
@media only screen and (max-width: 875px) {
.master-note-edit { }
left: 0; @media only screen and (max-width: 830px) {
right: 0;
}
.input-container-wrapper {
left: 0;
right: 0;
top: 35px;
bottom: 37px;
background-color: var(--small_element_bg_color);
}
.note-wrapper {
margin: 0;
border: none;
}
.shade1, .shade2 { .shade1, .shade2 {
right: 150%; right: 150%;
} }
.edit-menu {
background-color: var(--dark_border_color);
}
/*menu overwrites */
.bottom-edit-menu {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
display: block;
/*background-color: var(--small_element_bg_color);*/
background-color: var(--dark_border_color);
z-index: 1012;
border: none;
border-top: 1px solid var(--menu-accent);
min-height: 37px;
}
.menu-top-half, .menu-bottom-half {
display: flex;
justify-content: space-around;
}
.edit-divide { .edit-divide {
display: none; display: none;
} }
.menu-bottom-half {
z-index: 1005;
position: fixed;
bottom: 4px;
left: 0;
right: 0;
text-align: center;
}
.edit-button { .edit-button {
flex-basis: 100%; padding: 6px 0px 0;
margin: 0 2px;
} }
.done-button { .edit-button > span:not(.ui) {
flex-basis: 150%; display: none;
font-size: 13px;
} }
} }

View File

@ -9,7 +9,7 @@
<!-- Show title and snippet below it --> <!-- Show title and snippet below it -->
<div class="overflow-hidden note-card-text" @click="cardClicked" v-if="!titleView"> <div class="overflow-hidden note-card-text" @click.stop="cardClicked" v-if="!titleView">
<span v-if="note.title == '' && note.subtext == ''"> <span v-if="note.title == '' && note.subtext == ''">
Empty Note Empty Note
@ -24,7 +24,7 @@
class="big-text"><p>{{ note.title }}</p></span> class="big-text"><p>{{ note.title }}</p></span>
<span class="tags" v-if="note.tags"> <span class="tags" v-if="note.tags">
<span v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span> <span v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click.stop="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span>
<br> <br>
</span> </span>
@ -444,10 +444,10 @@
.note-title-display-card { .note-title-display-card {
position: relative; position: relative;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
/*The subtle shadow*/ /*The subtle shadow*/
/*box-shadow: 0px 1px 2px 1px rgba(210, 211, 211, 0.46);*/
box-shadow: 2px 2px 6px 0 rgba(0,0,0,.15); box-shadow: 2px 2px 6px 0 rgba(0,0,0,.15);
transition: box-shadow ease 0.5s, transform linear 0.1s; transition: box-shadow, border-color ease 0.5s, transform linear 0.5s;
margin: 5px; margin: 5px;
/*padding: 0.7em 1em;*/ /*padding: 0.7em 1em;*/
border-radius: .28571429rem; border-radius: .28571429rem;
@ -472,9 +472,8 @@
max-height: 450px; max-height: 450px;
} }
.note-title-display-card:hover { .note-title-display-card:hover {
/*box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);*/ box-shadow: 0 8px 15px rgba(0,0,0,0.3);
/*transform: translateY(-2px);*/ border-color: var(--main-accent);
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
} }
.note-title-display-card.title-view { .note-title-display-card.title-view {
width: 100%; width: 100%;

View File

@ -0,0 +1,116 @@
<template>
<div>
<div class="ui right floated basic shrinking icon button" v-on:click="showPasteInputArea">
<i class="paste icon"></i>
Paste
</div>
<div class="shade" v-if="showPasteArea" @click.prevent="close">
<div class="ui stackable grid full-height" @click.prevent="close">
<div class="four wide column"></div>
<div class="eight wide middle aligned center aligned column">
<div class="ui raised segment">
<div class="ui dividing header">
<i class="green paste icon"></i>
Paste & automatically Save
</div>
<div class="ui fluid action input">
<input
id="pastetextarea"
type="text"
ref="pastearea"
@paste.prevent="onPaste"
@keyup.enter.prevent="onEnter"
placeholder="Paste Here">
<button class="ui green labeled icon button" @click.prevent="onEnter">
<i class="save icon"></i>
Save
</button>
</div>
</div>
</div>
<div class="four wide column"></div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'PasteButton',
props: {},
data () {
return {
showPasteArea: false,
}
},
methods: {
close(){
this.showPasteArea = false
},
onEnter(e){
const text = this.$refs.pastearea.value
this.saveText(text)
},
onPaste(e){
// Get pasted data via clipboard API
const clipboardData = e.clipboardData || window.clipboardData
const pastedData = String(clipboardData.getData('Text')).trim()
this.saveText(pastedData)
},
saveText(text){
this.showPasteArea = false
if(!text){
this.$bus.$emit('notification', 'Nothing to save.')
return;
}
axios.post('/api/quick-note/update', { 'pushText':text } )
.then( response => {
this.$bus.$emit('notification', 'Saved To Scratch Pad')
})
.catch(error => {
this.$bus.$emit('notification', 'Failed to Save')
})
},
showPasteInputArea(){
// Show text area and focus its contents
this.showPasteArea = true
this.$nextTick(() => {
const aux = document.getElementById('pastetextarea')
aux.focus();
})
// auto hide after 1 Minute
setTimeout(() => {
this.showPasteArea = false
}, 60*1000)
},
}
}
</script>
<style scoped lang="css">
.paste-text-container {
background-color: green;
position: absolute;
width: 50vw;
height: 80vh;
display: inline-block;
}
.full-height {
height: 100vh;
}
</style>

View File

@ -1,9 +1,9 @@
<style type="text/css" scoped> <style type="text/css" scoped>
.slide-container { .slide-container {
position: fixed; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 50%; right: 0;
bottom: 0; bottom: 0;
z-index: 1020; z-index: 1020;
overflow: hidden; overflow: hidden;
@ -98,11 +98,9 @@
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
<div class="slide-shadow" :class="{'full-shadow':fullShadow}" v-on:click="close"></div> <!-- <div class="slide-shadow" :class="{'full-shadow':fullShadow}" v-on:click="close"></div> -->
</div> </div>
<!-- </transition> --> <!-- </transition> -->

File diff suppressed because one or more lines are too long

View File

@ -113,6 +113,7 @@
<style type="text/css"> <style type="text/css">
.button-fix { .button-fix {
display: inline-block; display: inline-block;
float: left;
} }
.hover-row:hover { .hover-row:hover {
cursor: pointer; cursor: pointer;

View File

@ -357,9 +357,21 @@ const SquireButtonFunctions = {
}, },
setText(inText){ setText(inText){
this.editor.setHTML(inText) this.editor.setHTML(inText)
// this.noteText = this.editor._getHTML() // this.noteText = this.editor._getHTML()
// this.diffNoteText = this.editor._getHTML() // this.diffNoteText = this.editor._getHTML()
//Make sure all list items have draggable property
let container = document.getElementById('squire-id')
let listItems = container.getElementsByTagName('li')
for(let itemIndex in listItems){
// console.log(listItems[itemIndex])
// listItems[itemIndex].setAttribute('draggable','true')
}
// console.log(listItems)
}, },
getText(){ getText(){
@ -392,6 +404,26 @@ const SquireButtonFunctions = {
this.$router.go(-1) this.$router.go(-1)
}, },
indentText(){
// Lists use increase list level, increase quote breaks numbering
if(this.activeList || this.activeToDo){
this.editor.increaseListLevel()
return
}
this.editor.increaseQuoteLevel()
},
outdentText(){
// Lists use increase list level, increase quote breaks numbering
if(this.activeList || this.activeToDo){
this.editor.decreaseListLevel()
return
}
this.editor.decreaseQuoteLevel()
},
}, },
} }

View File

@ -36,6 +36,15 @@
Other Files Other Files
</router-link> </router-link>
<router-link
v-if="$store.getters.totals && $store.getters.totals['sharedToNotes']"
exact-active-class="green"
class="ui basic button shrinking"
to="/attachments/type/shared">
<i class="send icon"></i>
Show Shared
</router-link>
</div> </div>
<div class="sixteen wide column" v-if="searchParams.noteId"> <div class="sixteen wide column" v-if="searchParams.noteId">
@ -165,6 +174,12 @@
this.searchParams.attachmentType = this.$route.params.type this.searchParams.attachmentType = this.$route.params.type
} }
// include files from shared notes or selected notes
this.searchParams.includeShared = false
if(this.$route.params.type == 'shared'){
this.searchParams.includeShared = true
}
//Set noteId in if in URL //Set noteId in if in URL
if(this.$route.params.id){ if(this.$route.params.id){
this.searchParams.noteId = this.$route.params.id this.searchParams.noteId = this.$route.params.id

View File

@ -149,23 +149,23 @@
<!-- All marketing images if you need to review --> <!-- All marketing images if you need to review -->
<div v-if="false" class="sixteen wide column"> <div v-if="false" class="sixteen wide column">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/add.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/add.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/gardening.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/gardening.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/growth.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/growth.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/icecream.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/icecream.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/investing.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/investing.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/onboarding.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/onboarding.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/robot.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/robot.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/solution.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/solution.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/watching.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/watching.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/cloud.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/cloud.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/grandma.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/grandma.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/hamburger.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/hamburger.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/idea.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/idea.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/notebook.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/notebook.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/plan.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/plan.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/secure.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/secure.svg">
<img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg" alt=""> <img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg">
</div> </div>
<!-- Go to notes button --> <!-- Go to notes button -->
@ -200,7 +200,7 @@
<!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> --> <!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> -->
</div> </div>
<div class="four wide column"> <div class="four wide column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas"> <svg-displayer file="idea" alt="Explosion of New Ideas" />
</div> </div>
</div> </div>
@ -392,7 +392,7 @@
</div> </div>
<div class="six wide column"> <div class="six wide column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/onboarding.svg" alt=""> <svg-displayer file="onboarding" alt="Observe this chart" />
</div> </div>
</div> </div>
@ -400,8 +400,7 @@
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<div class="four wide right aligned column"> <div class="four wide right aligned column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/secure.svg" alt="marketing mumbo jumbo"> <svg-displayer file="secure" alt="So dang secure" />
</div> </div>
<div class="six wide column"> <div class="six wide column">
<h2>Only you can read your notes. </h2> <h2>Only you can read your notes. </h2>
@ -415,13 +414,13 @@
<h3>Works on mobile or desktop browsers. <br>Behaves like an installed app on mobile phones.</h3> <h3>Works on mobile or desktop browsers. <br>Behaves like an installed app on mobile phones.</h3>
</div> </div>
<div class="four wide right aligned column"> <div class="four wide right aligned column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/cloud.svg" alt="Girl falling into the spiral of digital chaos"> <svg-displayer file="cloud" alt="Girl falling into the spiral of digital chaos" />
</div> </div>
</div> </div>
<div class="middle aligned centered row"> <div class="middle aligned centered row">
<div class="four wide right aligned column"> <div class="four wide right aligned column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/robot.svg" alt="Shrunken man near giant tablet"> <svg-displayer file="robot" alt="Murder Robot in office environment" />
</div> </div>
<div class="six wide column"> <div class="six wide column">
<h2>Secure Data Sharing</h2> <h2>Secure Data Sharing</h2>
@ -440,7 +439,7 @@
<a href="https://pi-hole.net/" target="_blank">Pi-hole</a> on the network.</h3> <a href="https://pi-hole.net/" target="_blank">Pi-hole</a> on the network.</h3>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/icecream.svg" alt="Emergence of a 4th dimensional being perceived as a large ice cream "> <svg-displayer file="icecream" alt="Emergence of a 4th dimensional being perceived as a large ice cream" />
</div> </div>
</div> </div>
@ -512,7 +511,7 @@
<p>Awesomely Generic Marketing Images - <a target="_blank" href="https://undraw.co/">https://unDraw.co/</a></p> <p>Awesomely Generic Marketing Images - <a target="_blank" href="https://undraw.co/">https://unDraw.co/</a></p>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<img loading="lazy" width="100%" src="/api/static/assets/marketing/watching.svg" alt="Drinking the blood of the elderly"> <svg-displayer file="watching" alt="Drinking the blood of the elderly" />
</div> </div>
</div> </div>
@ -531,6 +530,7 @@ export default {
components: { components: {
'login-form':require('@/components/LoginFormComponent.vue').default, 'login-form':require('@/components/LoginFormComponent.vue').default,
'logo':require('@/components/LogoComponent.vue').default, 'logo':require('@/components/LogoComponent.vue').default,
'svg-displayer':require('@/components/SvgDisplayer.vue').default,
}, },
data(){ data(){
return { return {
@ -543,7 +543,7 @@ export default {
'#00b5ad', //Teal '#00b5ad', //Teal
'#2185d0', //Blue '#2185d0', //Blue
'#7128b9', //Violet '#7128b9', //Violet
'#a333c8', // "Purple" '#a333c8', //Purple
'#e03997', //Pink '#e03997', //Pink
'#db2828', //Red '#db2828', //Red
'#f2711c', //Orange '#f2711c', //Orange

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="page-container" v-on:scroll="onScroll"> <div class="page-container">
<div class="ui grid" ref="content"> <div class="ui grid" ref="content">
<div class="sixteen wide column"> <div class="sixteen wide column">
<!-- :class="{ 'sixteen wide column':showOneColumn(), 'sixteen wide column':!showOneColumn() }" --> <!-- :class="{ 'sixteen wide column':showOneColumn 'sixteen wide column':!showOneColumn}" -->
<div class="ui stackable grid"> <div class="ui stackable grid">
@ -27,21 +27,12 @@
v-on:tagClick="tagId => toggleTagFilter(tagId)" v-on:tagClick="tagId => toggleTagFilter(tagId)"
/> />
<span> <paste-button />
<span class="ui grey text text-fix">
Active Sessions {{ $store.getters.getActiveSessions }} Active Sessions {{ $store.getters.getActiveSessions }}
</span> </span>
<div class="ui right floated basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
<span v-if="titleView">
<i class="th icon"></i> Tiles
</span>
<span v-if="!titleView">
<i class="list icon"></i> List
</span>
</div>
</div> </div>
<div class="eight wide column" v-if="showClear"> <div class="eight wide column" v-if="showClear">
@ -107,7 +98,19 @@
</h3> </h3>
<!-- Go to one wide column, do not do this on mobile interface --> <!-- Go to one wide column, do not do this on mobile interface -->
<div :class="{'one-column':( showOneColumn() )}"> <div :class="{'one-column':( showOneColumn), 'floating-list':( isFloatingList ), 'hidden-floating-list':(collapseFloatingList)}" v-on:scroll="onScroll">
<div class="ui basic fitted right aligned segment" v-if="isFloatingList">
<div class="ui small basic green left floated button" v-on:click="closeAllNotes()" v-if="openNotes.length > 1">
<i class="times circle outline icon"></i>
Close All
</div>
<div class="ui small green button" v-on:click="collapseFloatingList = true">
<i class="caret square left outline icon"></i>
Hide Menu
</div>
</div>
<!-- render each section based on notes in set --> <!-- render each section based on notes in set -->
<div v-for="section,index in noteSections" v-if="section.length > 0" class="note-card-section"> <div v-for="section,index in noteSections" v-if="section.length > 0" class="note-card-section">
@ -120,8 +123,8 @@
:ref="'note-'+note.id" :ref="'note-'+note.id"
:onClick="openNote" :onClick="openNote"
:data="note" :data="note"
:title-view="titleView" :title-view="titleView || isFloatingList"
:currently-open="activeNoteId1 == note.id" :currently-open="openNotes.includes(note.id)"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated" :key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
/> />
</div> </div>
@ -149,13 +152,21 @@
</div> </div>
<div class="show-hidden-note-list-button" v-if="collapseFloatingList" v-on:click="collapseFloatingList = false">
<i class="caret square right outline icon"></i>
</div>
<note-input-panel <!-- flexbox note container evenly spaces open notes -->
v-if="activeNoteId1 != null" <div class="note-panel-container" :class="{ 'note-panel-fullwidth':collapseFloatingList}" v-if="openNotes.length">
:key="activeNoteId1" <note-input-panel
:noteid="activeNoteId1" v-for="noteId in openNotes"
:url-data="$route.params" v-if="noteId != null"
/> :key="noteId"
:noteid="noteId"
:url-data="$route.params"
:open-notes="openNotes.length"
/>
</div>
</div> </div>
</template> </template>
@ -165,7 +176,7 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
name: 'SearchBar', name: 'NotesPage',
components: { components: {
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'), 'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
@ -176,6 +187,7 @@
'attachment-display': require('@/components/AttachmentDisplayCard').default, 'attachment-display': require('@/components/AttachmentDisplayCard').default,
'tag-display':require('@/components/TagDisplayComponent.vue').default, 'tag-display':require('@/components/TagDisplayComponent.vue').default,
'loading-icon':require('@/components/LoadingIconComponent.vue').default, 'loading-icon':require('@/components/LoadingIconComponent.vue').default,
'paste-button':require('@/components/PasteButton.vue').default,
}, },
data () { data () {
return { return {
@ -185,6 +197,8 @@
searchResultsCount: 0, searchResultsCount: 0,
searchTags: [], searchTags: [],
notes: [], notes: [],
openNotes: [],
collapseFloatingList: false,
highlights: [], highlights: [],
searchDebounce: null, searchDebounce: null,
fastFilters: {}, fastFilters: {},
@ -246,35 +260,34 @@
this.$io.on('new_note_created', noteId => { this.$io.on('new_note_created', noteId => {
//Do not update note if its open // Push new note to top of list and animate
if(this.activeNoteId1 != noteId){ this.updateSingleNote(noteId)
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
}
}) })
this.$io.on('note_attribute_modified', noteId => { this.$io.on('note_attribute_modified', noteId => {
const drawFocus = !this.openNotes.includes(parseInt(noteId))
this.updateSingleNote(noteId, drawFocus)
//Do not update note if its open //Do not update note if its open
if(this.activeNoteId1 != noteId){ if(this.openNotes.includes(parseInt(noteId))){
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
} }
}) })
//Update title cards when new note text is saved //Update title cards when new note text is saved
this.$io.on('new_note_text_saved', ({noteId, hash}) => { this.$io.on('new_note_text_saved', ({noteId, hash}) => {
//Do not update note if its open const drawFocus = !this.openNotes.includes(parseInt(noteId))
if(this.activeNoteId1 != noteId){ this.updateSingleNote(noteId, drawFocus)
this.updateSingleNote(noteId, true)
}
}) })
this.$bus.$on('update_single_note', (noteId) => { this.$bus.$on('update_single_note', (noteId) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){ const drawFocus = !this.openNotes.includes(parseInt(noteId))
this.updateSingleNote(noteId) this.updateSingleNote(noteId, drawFocus)
}
}) })
//Update totals for app //Update totals for app
@ -282,19 +295,7 @@
//Close note event //Close note event
this.$bus.$on('close_active_note', ({noteId, modified}) => { this.$bus.$on('close_active_note', ({noteId, modified}) => {
this.closeNote(noteId, modified)
if(modified){
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
}
//A note has been closed
if(this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified
this.updateSingleNote(noteId, modified)
}) })
this.$bus.$on('note_deleted', (noteId) => { this.$bus.$on('note_deleted', (noteId) => {
@ -346,6 +347,8 @@
} }
}) })
// Window scroll needed when scrolling full page.
// second scroll event added on note-list for floating view scroll detection
window.addEventListener('scroll', this.onScroll) window.addEventListener('scroll', this.onScroll)
//Close notes when back button is pressed //Close notes when back button is pressed
@ -354,8 +357,6 @@
//update note on visibility change //update note on visibility change
// document.addEventListener('visibilitychange', this.visibiltyChangeAction); // document.addEventListener('visibilitychange', this.visibiltyChangeAction);
//Find previously stored notes, cache for 20 hours, load them and compare
}, },
beforeDestroy(){ beforeDestroy(){
window.removeEventListener('scroll', this.onScroll) window.removeEventListener('scroll', this.onScroll)
@ -374,9 +375,9 @@
}, },
mounted() { mounted() {
//Open note on load if ID is set //Open note on PAGE LOAD if ID is set
if(this.$route.params.id > 1){ if(this.$route.params.id > 1){
this.activeNoteId1 = this.$route.params.id this.openNote(this.$route.params.id)
} }
//Loads initial batch and tags //Loads initial batch and tags
@ -385,18 +386,21 @@
}, },
watch: { watch: {
'$route.params.id': function(id){ '$route.params.id': function(id){
//Open note on ID, null id will close note this.openNote(id)
this.activeNoteId1 = id
}, },
'$route' (to, from) { '$route' (to, from) {
// Reload the notes if returning to this page // Reload the notes if returning to this page
if(to.fullPath == '/notes' && !from.fullPath.includes('/notes/open/')){ if(to.fullPath == '/notes' && !from.fullPath.includes('/notes/open/')){
this.reset() this.reset()
} }
// Close all notes if returning to /notes page
if(to.fullPath == '/notes' && from.fullPath.includes('/notes/open/')){
this.closeAllNotes()
}
//Lookup tags set in URL //Lookup tags set in URL
if(to.params.tag && this.$store.getters.totals && this.$store.getters.totals['tags'][to.params.tag]){ if(to.params.tag && this.$store.getters.totals && this.$store.getters.totals['tags'][to.params.tag]){
@ -409,30 +413,96 @@
} }
} }
}, },
methods: { computed: {
toggleTitleView(){ isFloatingList(){
this.titleView = !this.titleView
//If note 1 or 2 is open, show floating column
return (this.openNotes.length > 0)
}, },
showOneColumn(){ showOneColumn(){
return this.$store.getters.getIsUserOnMobile return this.$store.getters.getIsUserOnMobile
//If note 1 or 2 is open, show one column. Or if the user is on mobile }
return (this.activeNoteId1 != null || this.activeNoteId2 != null) && },
!this.$store.getters.getIsUserOnMobile methods: {
},
openNote(id, event = null){ openNote(id, event = null){
//
const intId = parseInt(id)
if(this.openNotes.includes(intId)){
console.log('Open already open note?')
// const openIndex = this.openNotes.indexOf(intId)
// if(openIndex != -1){
// console.log('Open note and remove it ', intId + ' on index ' + openIndex)
// this.openNotes.splice(openIndex, 1)
// }
// this.$bus.$emit('close_note_by_id', intId)
return
}
//Don't open note if a link is clicked in display card //Don't open note if a link is clicked in display card
if(event && event.target && event.target.nodeName){ if(event && event.target && event.target.nodeName){
const nodeClick = event.target.nodeName const nodeClick = event.target.nodeName
if(nodeClick == 'A'){ return } if(nodeClick == 'A'){ return }
} }
//Open note if a link was not clicked // Push note to stack if not open
this.$router.push('/notes/open/'+id) if(Number.isInteger(intId) && !this.openNotes.includes(intId)){
this.openNotes.push(intId)
}
this.$nextTick(() => {
// change route if open ID is not the same as current ID
if(this.$route.params.id != id){
console.log('Open note, change route -> route id ' + this.$route.params.id + ' note id ->' + id + ', ' +(this.$route.params.id == id))
this.$router.push('/notes/open/'+id)
}
})
return return
}, },
closeNote(noteId, modified){
console.log('close note', this.$route.fullPath)
const openIndex = this.openNotes.indexOf(noteId)
if(openIndex != -1){
console.log('Removing note id ', noteId + ' on index ' + openIndex)
this.openNotes.splice(openIndex, 1)
}
// //A note has been closed
// if(this.$route.fullPath != '/notes'){
// this.$router.push('/notes')
// }
if(this.openNotes.length == 0 && this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
if(modified){
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified
this.updateSingleNote(noteId, modified)
}
console.log('closeNote(): Open notes length ', this.openNotes.length)
},
closeAllNotes(){
console.log('Close all notes ------------')
for (let i = this.openNotes.length - 1; i >= 0; i--) {
console.log('Close all notes -> ' + this.openNotes[i])
this.closeNote(this.openNotes[i])
}
console.log('----------------')
},
toggleTagFilter(tagId){ toggleTagFilter(tagId){
this.searchTags = [tagId] this.searchTags = [tagId]
@ -488,7 +558,6 @@
} }
this.lastVisibilityState = document.visibilityState this.lastVisibilityState = document.visibilityState
}, },
// @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){
@ -524,6 +593,7 @@
return return
} }
// if old note data and new note data exists
if(note && newNote){ if(note && newNote){
//go through each prop and update it with new values //go through each prop and update it with new values
@ -532,7 +602,7 @@
}) })
//Push new note to front if its modified or we want it to //Push new note to front if its modified or we want it to
if( focuseAndAnimate || note.updated != newNote.updated ){ if( note.updated != newNote.updated ){
// Find note, in section, move to front // Find note, in section, move to front
Object.keys(this.noteSections).forEach( key => { Object.keys(this.noteSections).forEach( key => {
@ -546,6 +616,9 @@
}) })
}) })
}
if( focuseAndAnimate ){
this.$nextTick( () => { this.$nextTick( () => {
//Trigger close animation on note //Trigger close animation on note
this.$refs['note-'+noteId][0].justClosed() this.$refs['note-'+noteId][0].justClosed()
@ -809,6 +882,11 @@
</script> </script>
<style type="text/css" scoped> <style type="text/css" scoped>
.text-fix {
padding: 8px 0 0 15px;
display: inline-block;
color: var(--menu-accent);
}
.detail { .detail {
float: right; float: right;
} }
@ -827,15 +905,130 @@
padding: 15px 0 0; padding: 15px 0 0;
} }
.loading-section { .loading-section {
position: fixed; color: var(--main-accent);
bottom: 40px; box-shadow: 0 1px 3px 0 var(--main-accent);
padding: 0 10px;
right: 5px;
box-shadow: 0 1px 3px 0 #656565;
border-radius: 6px; border-radius: 6px;
background-color: var(--small_element_bg_color); background-color: var(--small_element_bg_color);
opacity: 0.9; display: inline-block;
font-size: 0.7em; width: 100%;
margin: 15px 0;
}
.floating-list {
z-index: 1000;
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 25%;
height: 100vh;
background-color: var(--small_element_bg_color);
padding: 15px 5px 0px 10px;
overflow-y: scroll;
overflow-x: hidden;
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
background-color: var(--border_color);
}
.floating-list::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
.note-panel-container {
position: fixed;
width: 75%;
height: 100vh;
background: gray;
top: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: stretch;
align-content: stretch;
z-index: 1000;
}
.note-panel-fullwidth {
width: 100% !important;
}
.note-panel-container > div {
flex: 1;
position: relative;
}
.hidden-floating-list {
left: -1000px !important;
}
.show-hidden-note-list-button {
position: fixed;
top: 25px;
left: 0;
min-width: 45px;
background-color: var(--main-accent);
color: var(--text_color);
display: block;
z-index: 1100;
cursor: pointer;
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
padding: 8px 0px 8px 13px;
text-align: left;
font-size: 1.4em;
}
@media (min-width:320px) { /* smartphones, iPhone, portrait 480x320 phones */
.floating-list {
left: -1000px;
}
.note-panel-container {
width: 100%;
}
}
@media (min-width:481px) { /* portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide. */
.floating-list {
left: 0px;
}
.note-panel-container {
width: 75%;
}
}
@media (min-width:641px) { /* portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones */
}
@media (min-width:961px) { /* tablet, landscape iPad, lo-res laptops ands desktops */
}
@media (min-width:1025px) { /* big landscape tablets, laptops, and desktops */
}
@media (min-width:1281px) { /* hi-res laptops and desktops */
}
@media (min-width:2000px) { /* BIG hi-res laptops and desktops */
.floating-list {
left: 180px;
width: calc(30% - 180px);
}
.note-panel-container {
width: 70%;
}
}
.master-note-edit {
position: absolute;
width: 100%;
background: var(--small_element_bg_color);
left: 0;
top: 0;
bottom: 0;
overflow: hidden;
}
.master-note-edit + .master-note-edit {
border-left: 2px solid var(--main-accent);
border-left: 5px solid var(--border_color);
} }

View File

@ -0,0 +1,761 @@
<template>
<div class="page-container">
<div class="ui grid" ref="content">
<div class="sixteen wide column">
<!-- :class="{ 'sixteen wide column':showOneColumn(), 'sixteen wide column':!showOneColumn() }" -->
<div class="ui stackable grid">
<div class="six wide column" v-if="$store.getters.totals && $store.getters.totals['totalNotes']">
<search-input />
</div>
<div class="ten wide column" :class="{ 'sixteen wide column':$store.getters.getIsUserOnMobile }">
<div class="ui basic button shrinking"
v-on:click="updateFastFilters(3)"
v-if="$store.getters.totals && ($store.getters.totals['youGotMailCount'] > 0)"
style="position: relative;">
<i class="green mail icon"></i>Inbox
<span class="tiny circular floating ui green label">+{{ $store.getters.totals['youGotMailCount'] }}</span>
</div>
<tag-display
:active-tags="searchTags"
v-on:tagClick="tagId => toggleTagFilter(tagId)"
/>
<div class="ui basic shrinking icon button" v-on:click="toggleTitleView()" v-if="$store.getters.totals && $store.getters.totals['totalNotes'] > 0">
<i v-if="titleView" class="th icon"></i>
<i v-if="!titleView" class="bars icon"></i>
</div>
</div>
<div class="eight wide column" v-if="showClear">
<!-- <fast-filters /> -->
<span class="ui fluid green button" @click="reset">
<i class="arrow circle left icon"></i>Show All Notes
</span>
</div>
</div>
</div>
<div class="sixteen wide column" v-if="searchTerm.length > 0 && !loadingInProgress">
<h2 class="ui header">
<div class="content">
{{ searchResultsCount.toLocaleString() }} notes with keyword "{{ searchTerm }}"
<div v-if="searchResultsCount == 0" class="sub header">
Search can only find key words. Try a single word search.
</div>
</div>
</h2>
</div>
<div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column">
<h2>Archived Notes</h2>
</div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1">
<h2>Trash
<span>({{ $store.getters.totals['trashedNotes'] }})</span>
<div class="ui right floated basic button" data-tooltip="This doesn't work yet">
<i class="poo storm icon"></i>
Empty Trash
</div>
</h2>
</div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1">
<h2>Shared Notes</h2>
</div>
<div class="sixteen wide column" v-if="tagSuggestions.length > 0">
<h5 class="ui tiny dividing header"><i class="green tags icon"></i> Tags ({{ tagSuggestions.length }})</h5>
<div class="ui clickable green label" v-for="tag in tagSuggestions" v-on:click="tagId => toggleTagFilter(tag.id)">
<i class="tag icon"></i>
{{ tag.text }}
</div>
</div>
<!-- found attachments -->
<div class="sixteen wide column" v-if="foundAttachments.length > 0">
<h5 class="ui tiny dividing header"><i class="green folder open outline icon"></i> Files ({{ foundAttachments.length }})</h5>
<attachment-display
v-for="item in foundAttachments"
:item="item"
:key="item.id"
:search-params="{}"
/>
</div>
<!-- Note title card display -->
<div class="sixteen wide column">
<h3 v-if="$store.getters.totals && $store.getters.totals['totalNotes'] == 0 && fastFilters['notesHome'] == 1">
No Notes Yet. <br>Thats ok.<br><br> <br>
<img loading="lazy" width="25%" src="/api/static/assets/marketing/hamburger.svg" alt="Create a new note"><br>
Create one when you feel ready.
</h3>
<!-- Go to one wide column, do not do this on mobile interface -->
<div :class="{'one-column':( showOneColumn() )}">
<!-- render each section based on notes in set -->
<div v-for="section,index in noteSections" v-if="section.length > 0" class="note-card-section">
<h5 class="ui tiny dividing header"><i :class="`green ${sectionData[index][0]} icon`"></i>{{ sectionData[index][1] }}</h5>
<div class="note-card-display-area">
<note-title-display-card
v-on:tagClick="tagId => toggleTagFilter(tagId)"
v-for="note in section"
:ref="'note-'+note.id"
:onClick="openNote"
:data="note"
:title-view="titleView"
:currently-open="activeNoteId1 == note.id"
:key="note.id + note.color + '-' +note.title.length + '-' +note.subtext.length + '-' + note.tag_count + note.updated"
/>
</div>
</div>
<loading-icon v-if="loadingInProgress" message="Decrypting Notes" />
</div>
</div>
</div>
<note-input-panel
v-if="activeNoteId1 != null"
:key="activeNoteId1"
:noteid="activeNoteId1"
:url-data="$route.params"
/>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'SearchBar',
components: {
'note-input-panel': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
// 'fast-filters': require('@/components/FastFilters.vue').default,
'search-input': require('@/components/SearchInput.vue').default,
'attachment-display': require('@/components/AttachmentDisplayCard').default,
'counter':require('@/components/AnimatedCounterComponent.vue').default,
'tag-display':require('@/components/TagDisplayComponent.vue').default,
'loading-icon':require('@/components/LoadingIconComponent.vue').default,
},
data () {
return {
initComponent: true,
tagSuggestions:[],
searchTerm: '',
searchResultsCount: 0,
searchTags: [],
notes: [],
highlights: [],
searchDebounce: null,
fastFilters: {},
titleView: false,
//Load up notes in batches
firstLoadBatchSize: 10, //First set of rapidly loaded notes
batchSize: 25, //Size of batch loaded when user scrolls through current batch
batchOffset: 0, //Tracks the current batch that has been loaded
loadingBatchTimeout: null, //Limit how quickly batches can be loaded
loadingInProgress: false,
scrollLoadEnabled: true,
//Clear button is not visible
showClear: false,
initialPostData: null,
//Currently open notes in app
activeNoteId1: null,
activeNoteId2: null,
//Position determines how note is Positioned
activeNote1Position: 0,
activeNote2Position: 0,
lastVisibilityState: null,
foundAttachments: [],
sectionData: {
'pinned': ['thumbtack', 'Pinned'],
'archived': ['archive', 'Archived'],
'shared': ['envelope outline', 'Inbox'],
'sent': ['paper plane outline', 'Sent Notes'],
'notes': ['file','Notes'],
'highlights': ['paragraph', 'Found In Text'],
'trashed': ['poop', 'Trashed Notes'],
'tagged': ['tag', 'Tagged'],
},
noteSections: {
pinned: [],
archived: [],
shared:[],
sent:[],
notes: [],
highlights: [],
trashed: [],
tagged:[],
},
}
},
beforeMount(){
this.$parent.loginGateway()
this.$io.on('new_note_created', noteId => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
}
})
this.$io.on('note_attribute_modified', noteId => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId, false)
}
})
//Update title cards when new note text is saved
this.$io.on('new_note_text_saved', ({noteId, hash}) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId, true)
}
})
this.$bus.$on('update_single_note', (noteId) => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.updateSingleNote(noteId)
}
})
//Update totals for app
this.$store.dispatch('fetchAndUpdateUserTotals')
//Close note event
this.$bus.$on('close_active_note', ({noteId, modified}) => {
if(modified){
console.log('Just closed Note -> ' + noteId + ', modified -> ', modified)
}
//A note has been closed
if(this.$route.fullPath != '/notes'){
this.$router.push('/notes')
}
this.$store.dispatch('fetchAndUpdateUserTotals')
//Focus and animate if modified
this.updateSingleNote(noteId, modified)
})
this.$bus.$on('note_deleted', (noteId) => {
//Remove deleted note from set, its deleted
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key].forEach( (note, index) => {
if(note.id == noteId){
this.noteSections[key].splice(index,1)
this.$store.dispatch('fetchAndUpdateUserTotals')
return
}
})
})
})
this.$bus.$on('update_fast_filters', filterIndex => {
this.updateFastFilters(filterIndex)
})
//Event to update search from other areas
this.$bus.$on('update_search_term', sentInSearchTerm => {
this.searchTerm = sentInSearchTerm
this.search(true, this.batchSize)
.then( () => {
this.searchAttachments()
const postData = {
'tagText':this.searchTerm.trim()
}
this.tagSuggestions = []
axios.post('/api/tag/suggest', postData)
.then( response => {
this.tagSuggestions = response.data
})
// return
})
})
//Reload page content - don't trigger if load is in progress
this.$bus.$on('note_reload', () => {
if(!this.loadingInProgress){
this.reset()
}
})
window.addEventListener('scroll', this.onScroll)
//Close notes when back button is pressed
// window.addEventListener('hashchange', this.hashChangeAction)
//update note on visibility change
// document.addEventListener('visibilitychange', this.visibiltyChangeAction);
},
beforeDestroy(){
window.removeEventListener('scroll', this.onScroll)
// document.removeEventListener('visibilitychange', this.visibiltyChangeAction)
this.$bus.$off('note_reload')
this.$bus.$off('close_active_note')
// this.$bus.$off('update_single_note')
this.$bus.$off('note_deleted')
this.$bus.$off('update_fast_filters')
this.$bus.$off('update_search_term')
//We want to remove event listeners, but something here is messing them up and preventing ALL event listeners from working
// this.$off() // Remove all event listeners
// this.$bus.$off()
},
mounted() {
//Open note on load if ID is set
if(this.$route.params.id > 1){
this.activeNoteId1 = this.$route.params.id
}
//Loads initial batch and tags
this.reset()
},
watch: {
'$route.params.id': function(id){
//Open note on ID, null id will close note
this.activeNoteId1 = id
}
},
methods: {
toggleTitleView(){
this.titleView = !this.titleView
},
showOneColumn(){
return this.$store.getters.getIsUserOnMobile
//If note 1 or 2 is open, show one column. Or if the user is on mobile
return (this.activeNoteId1 != null || this.activeNoteId2 != null) &&
!this.$store.getters.getIsUserOnMobile
},
openNote(id, event = null){
//Don't open note if a link is clicked in display card
if(event && event.target && event.target.nodeName){
const nodeClick = event.target.nodeName
if(nodeClick == 'A'){ return }
}
//Open note if a link was not clicked
this.$router.push('/notes/open/'+id)
return
},
toggleTagFilter(tagId){
this.searchTags = [tagId]
//Reset note set and load up notes and tags
if(this.searchTags.length > 0){
this.search(true, this.batchSize)
return
}
//If no tags are selected, reset entire page
this.reset()
},
onScroll(e){
clearTimeout(this.loadingBatchTimeout)
this.loadingBatchTimeout = setTimeout(() => {
//Detect distance scrolled down the page
const scrolledDown = window.pageYOffset + window.innerHeight
//Get height of div to properly detect scroll distance down
const height = document.getElementById('app').scrollHeight
//Load if less than 500px from the bottom
if(((height - scrolledDown) < 500) && this.scrollLoadEnabled && !this.loadingInProgress){
this.search(false, this.batchSize, true)
}
}, 30)
return
},
visibiltyChangeAction(event){
//Fuck this shit, just use web sockets
return
//@TODO - phase this out, update it via socket.io
//If user leaves page then returns to page, reload the first batch
if(this.lastVisibilityState == 'hidden' && document.visibilityState == 'visible'){
//Load initial batch, then tags, then other batch
this.search(false, this.firstLoadBatchSize)
.then( () => {
// return
})
}
this.lastVisibilityState = document.visibilityState
},
// @TODO Don't even trigger this if the note wasn't changed
updateSingleNote(noteId, focuseAndAnimate = true){
noteId = parseInt(noteId)
//Find local note, if it exists; continue
let note = null
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0] && this.$refs['note-'+noteId][0].note){
note = this.$refs['note-'+noteId][0].note
//Show that note is working on updating
this.$refs['note-'+noteId][0].showWorking = true
}
//Lookup one note using passed in ID
const postData = {
searchQuery: this.searchTerm,
searchTags: this.searchTags,
fastFilters:{
noteIdSet:[noteId]
}
}
//Note data must be fetched, then sorted into existing note data
axios.post('/api/note/search', postData)
.then(results => {
//Pull note data out of note set
let newNote = results.data.notes[0]
if(newNote === undefined){
return
}
if(note && newNote){
//go through each prop and update it with new values
Object.keys(newNote).forEach(prop => {
note[prop] = newNote[prop]
})
//Push new note to front if its modified or we want it to
if( focuseAndAnimate || note.updated != newNote.updated ){
// Find note, in section, move to front
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key].forEach( (searchNote, index) => {
if(searchNote.id == noteId){
//Remove note from location and push to front
this.noteSections[key].splice(index, 1)
this.noteSections[key].unshift(note)
return
}
})
})
this.$nextTick( () => {
//Trigger close animation on note
this.$refs['note-'+noteId][0].justClosed()
this.$refs['note-'+noteId][0].showWorking = false
})
}
}
//New notes don't exist in list, push them to the front
if(note == null){
this.noteSections.notes.unshift(newNote)
//Trigger close animation on note
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].justClosed()
this.$refs['note-'+noteId][0].showWorking = false
}
}
if(this.$refs['note-'+noteId] && this.$refs['note-'+noteId][0]){
this.$refs['note-'+noteId][0].showWorking = false
}
//Trigger section rebuild
this.rebuildNoteCategorise()
})
.catch(error => {
console.log(error)
this.$bus.$emit('notification', 'Failed to Update Note')
})
},
searchAttachments(){
axios.post('/api/attachment/textsearch', {'searchTerm':this.searchTerm})
.then(results => {
this.foundAttachments = results.data
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Search Attachments') })
},
search(showLoading = true, notesInNextLoad = 10, mergeExisting = false){
return new Promise((resolve, reject) => {
//Don't double load note batches
if(this.loadingInProgress){
console.log('Loading already in progress')
return resolve(false)
}
//Reset a lot of stuff if we are not merging batches
if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
this.batchOffset = 0 // Reset batch offset if we are not merging note batches
}
this.searchResultsCount = 0
//Remove all filter limits from previous queries
delete this.fastFilters.limitSize
delete this.fastFilters.limitOffset
let postData = {
searchQuery: this.searchTerm,
searchTags: this.searchTags,
fastFilters: this.fastFilters,
}
//Save initial post data on first load
if(this.initialPostData == null){
this.initialPostData = JSON.stringify(postData)
}
//If post data is not the same as initial, show clear button
if(JSON.stringify(postData) != this.initialPostData){
this.showClear = true
}
if(notesInNextLoad && notesInNextLoad > 0){
//Create limit based off of the number of notes already loaded
postData.fastFilters.limitSize = notesInNextLoad
postData.fastFilters.limitOffset = this.batchOffset
}
//Perform search - or die
this.loadingInProgress = true
axios.post('/api/note/search', postData)
.then(response => {
// console.timeEnd('Fetch TitleCard Batch '+notesInNextLoad)
//Save the number of notes just loaded
this.batchOffset += response.data.notes.length
//Enable or disable scroll loading
this.scrollLoadEnabled = response.data.notes.length > 0
if(response.data.total > 0){
this.searchResultsCount = response.data.total
}
this.loadingInProgress = false
this.generateNoteCategories(response.data.notes, mergeExisting)
return resolve(true)
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Search Notes') })
})
},
rebuildNoteCategorise(){
let currentNotes = []
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key].forEach( note => {
currentNotes.push(note)
})
})
this.generateNoteCategories(currentNotes, false)
},
generateNoteCategories(notes, mergeExisting){
// Place each note in a category based on certain attributes and fast filters
//Reset all sections if we are not merging existing
if(!mergeExisting){
Object.keys(this.noteSections).forEach( key => {
this.noteSections[key] = []
})
}
//Sort notes into defined sections
notes.forEach(note => {
if(this.searchTerm.length > 0){
if(note.pinned == 1){
this.noteSections.pinned.push(note)
return
}
//Push to default note section
this.noteSections.notes.push(note)
return
}
//Display all tags in tag section
if(this.searchTags.length >= 1){
this.noteSections.tagged.push(note)
return
}
//Only show trashed notes when trashed
if(this.fastFilters.onlyShowTrashed == 1){
if(note.trashed == 1){
this.noteSections.trashed.push(note)
}
return
}
if(note.trashed == 1){
return
}
//Show archived notes
if(this.fastFilters.onlyArchived == 1){
if(note.pinned == 1 && note.archived == 1){
this.noteSections.pinned.push(note)
return
}
if(note.archived == 1){
this.noteSections.archived.push(note)
}
return
}
if(note.archived == 1){ return }
//Only show sent notes section if shared is selected
if(this.fastFilters.onlyShowSharedNotes == 1){
if(note.shared == 2){
this.noteSections.sent.push(note)
}
if(note.shareUsername != null){
this.noteSections.shared.push(note)
}
return
}
//Show shared notes on main list but not notes shared with you
if(note.shareUsername != null){ return }
// Pinned notes are always first, they can appear in the archive
if(note.pinned == 1){
this.noteSections.pinned.push(note)
return
}
//Push to default note section
this.noteSections.notes.push(note)
return
})
},
reset(){
this.showClear = false
this.scrollLoadEnabled = true
this.searchTerm = ''
this.searchTags = []
this.tagSuggestions = []
this.fastFilters = {}
this.foundAttachments = [] //Remove all attachments
this.updateFastFilters(5) //This loads notes
},
updateFastFilters(index){
//clear out tags
this.searchTags = []
this.tagSuggestions = []
this.loadingInProgress = false
this.searchTerm = ''
this.$bus.$emit('reset_fast_filters') //Clear out search
const options = [
'withLinks', // 'Only Show Notes with Links'
'withTags', // 'Only Show Notes with Tags'
'onlyArchived', //'Only Show Archived Notes'
'onlyShowSharedNotes', //Only show shared notes
'onlyShowTrashed',
'notesHome',
]
let filter = {}
filter[options[index]] = 1
this.fastFilters = filter
//Fetch First batch of notes with new filter
this.search(true, this.firstLoadBatchSize, false)
.then( r => this.search(false, this.batchSize, true))
}
}
}
</script>
<style type="text/css" scoped>
.detail {
float: right;
}
.note-card-display-area {
display: flex;
flex-wrap: wrap;
}
.display-area-title {
width: 100%;
display: inline-block;
}
.note-card-section {
/*padding-bottom: 15px;*/
}
.note-card-section + .note-card-section {
padding: 15px 0 0;
}
</style>

View File

@ -48,7 +48,7 @@ export default new Vuex.Store({
'small_element_bg_color': '#000', 'small_element_bg_color': '#000',
'text_color': '#FFF', 'text_color': '#FFF',
'dark_border_color': '#555',//'#ACACAC', //Lighter color to accent elemnts user can interact with 'dark_border_color': '#555',//'#ACACAC', //Lighter color to accent elemnts user can interact with
'border_color': '#555', 'border_color': '#0b0110',
'menu-accent': '#626262', 'menu-accent': '#626262',
'menu-text': '#d9d9d9', 'menu-text': '#d9d9d9',
}, },

View File

@ -1,3 +1,5 @@
const fs = require('fs')
module.exports = { module.exports = {
pwa: { pwa: {
name: 'SolidScribe', name: 'SolidScribe',
@ -13,6 +15,10 @@ module.exports = {
disableHostCheck: true, disableHostCheck: true,
proxy: 'http://localhost:8081', proxy: 'http://localhost:8081',
public: 'marvin.local', public: 'marvin.local',
https: {
key: fs.readFileSync('./certs/192.168.1.164+4-key.pem'),
cert: fs.readFileSync('./certs/192.168.1.164+4.pem'),
},
}, },
} }

View File

@ -0,0 +1,158 @@
#
# Working dev server config
#
server {
listen 443 ssl;
ssl_certificate /home/mab/ss/client/certs/192.168.1.164+4.pem;
ssl_certificate_key /home/mab/ss/client/certs/192.168.1.164+4-key.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;
}
}
##
## Working Copy below --------------------------------------------
##
server {
listen 443 ssl;
ssl_certificate /home/mab/ss/client/certs/192.168.1.164+4.pem;
ssl_certificate_key /home/mab/ss/client/certs/192.168.1.164+4-key.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_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:3001;
proxy_redirect off;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
}
}
# 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;
# }

View File

@ -46,14 +46,17 @@ Attachment.textSearch = (userId, searchTerm) => {
}) })
} }
Attachment.search = (userId, noteId, attachmentType, offset, setSize) => { Attachment.search = (userId, noteId, attachmentType, offset, setSize, includeShared) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let params = [userId] let params = [userId]
let query = 'SELECT * FROM attachment WHERE user_id = ? AND visible = 1 ' let query = `
SELECT attachment.*, note.share_user_id FROM attachment
JOIN note ON (attachment.note_id = note.id)
WHERE attachment.user_id = ? AND visible = 1 `
if(noteId && noteId > 0){ if(noteId && noteId > 0){
query += 'AND note_id = ? ' query += 'AND attachment.note_id = ? '
params.push(noteId) params.push(noteId)
} }
@ -64,11 +67,16 @@ Attachment.search = (userId, noteId, attachmentType, offset, setSize) => {
query += 'AND attachment_type > 1 ' query += 'AND attachment_type > 1 '
} }
if(!noteId){
const sharedOrNot = includeShared ? ' NOT ':' '
query += `AND note.share_user_id IS${sharedOrNot}NULL `
}
query += 'ORDER BY last_indexed DESC ' query += 'ORDER BY last_indexed DESC '
const limitOffset = parseInt(offset, 10) || 0 //Either parse int, or use zero const limitOffset = parseInt(offset, 10) || 0 //Either parse int, or use zero
const parsedSetSize = parseInt(setSize, 10) || 20 //Either parse int, or use zero const parsedSetSize = parseInt(setSize, 10) || 20
query += ` LIMIT ${limitOffset}, ${parsedSetSize}` query += ` LIMIT ${limitOffset}, ${parsedSetSize}`
db.promise() db.promise()

View File

@ -442,6 +442,10 @@ Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, has
}) })
.then((rows, fields) => { .then((rows, fields) => {
if(!rows[0] || !rows[0][0] || !rows[0][0]['note_raw_text_id']){
return reject(false)
}
const textId = rows[0][0]['note_raw_text_id'] const textId = rows[0][0]['note_raw_text_id']
let salt = rows[0][0]['salt'] let salt = rows[0][0]['salt']
let snippetSalt = rows[0][0]['snippet_salt'] let snippetSalt = rows[0][0]['snippet_salt']
@ -658,6 +662,9 @@ Note.delete = (userId, noteId, masterKey = null) => {
}) })
} }
//
// Returns noteData
//
Note.get = (userId, noteId, masterKey) => { Note.get = (userId, noteId, masterKey) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -13,11 +13,14 @@ QuickNote.get = (userId, masterKey) => {
SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1`, [userId]) SELECT note.id FROM note WHERE quick_note = 1 AND user_id = ? LIMIT 1`, [userId])
.then((rows, fields) => { .then((rows, fields) => {
//Quick Note is set, return note text //Quick Note is set, return note object
if(rows[0][0] != undefined){ if(rows[0][0] != undefined){
let noteId = rows[0][0].id let noteId = rows[0][0].id
return resolve({'noteId':noteId}) const note = Note.get(userId, noteId, masterKey)
.then(noteData => {
return resolve(noteData)
})
} else { } else {
//Or create a new note and get the id //Or create a new note and get the id
@ -81,7 +84,7 @@ QuickNote.update = (userId, pushText, masterKey) => {
.replace(/&[#A-Za-z0-9]+;/g,'') //Rip out all HTML entities .replace(/&[#A-Za-z0-9]+;/g,'') //Rip out all HTML entities
.replace(/<[^>]+>/g, '') //Rip out all HTML tags .replace(/<[^>]+>/g, '') //Rip out all HTML tags
//Turn links into actual linx //Turn links into actual link
clean = QuickNote.makeUrlLink(clean) clean = QuickNote.makeUrlLink(clean)
if(clean == ''){ clean = '&nbsp;' } if(clean == ''){ clean = '&nbsp;' }
@ -114,7 +117,7 @@ QuickNote.update = (userId, pushText, masterKey) => {
} }
}) })
.then( saveResults => { .then( saveResults => {
return resolve(true) return resolve(saveResults)
}) })
}) })

View File

@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
let User = module.exports = {} let User = module.exports = {}
const version = '3.4.2' const version = '3.5.0'
//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
@ -255,7 +255,7 @@ User.getCounts = (userId) => {
WHERE user_id = ? WHERE user_id = ?
GROUP BY tag_id GROUP BY tag_id
ORDER BY uses DESC ORDER BY uses DESC
LIMIT 5 LIMIT 16
`, [userId]) `, [userId])
}).then( (rows, fields) => { }).then( (rows, fields) => {

View File

@ -26,7 +26,7 @@ router.use(function setUserId (req, res, next) {
}) })
router.post('/search', function (req, res) { router.post('/search', function (req, res) {
Attachment.search(userId, req.body.noteId, req.body.attachmentType, req.body.offset, req.body.setSize) Attachment.search(userId, req.body.noteId, req.body.attachmentType, req.body.offset, req.body.setSize, req.body.includeShared)
.then( data => res.send(data) ) .then( data => res.send(data) )
}) })

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB