From 148b822d499710e790ae7e33779a5da8432cacc5 Mon Sep 17 00:00:00 2001 From: Max G <admin@internet.com> Date: Thu, 27 Jan 2022 04:48:19 +0000 Subject: [PATCH] Tons of littele interface changes and cleanups Massive update to image scraper with much better image getter Lots of little ui updates for mobile --- client/src/Helpers.js | 2 +- client/src/assets/semantic-helper.css | 2 +- client/src/assets/squire.js | 9 +- client/src/components/ColorPicker.vue | 95 +++++---- client/src/components/NoteInputPanel.vue | 199 +++++++----------- .../src/components/NoteTitleDisplayCard.vue | 151 ++++++++----- client/src/pages/HomePage.vue | 34 +-- client/src/pages/NotesPage.vue | 15 +- dontSync.txt | 3 +- server/helpers/Auth.js | 3 +- server/helpers/SiteScrape.js | 93 ++++++-- server/index.js | 2 - server/models/Attachment.js | 25 ++- server/models/Note.js | 5 +- server/models/Tag.js | 27 +++ server/models/User.js | 2 +- server/routes/tagController.js | 6 + 17 files changed, 400 insertions(+), 273 deletions(-) diff --git a/client/src/Helpers.js b/client/src/Helpers.js index 0b22d71..2e45431 100644 --- a/client/src/Helpers.js +++ b/client/src/Helpers.js @@ -53,7 +53,7 @@ helpers.timeAgo = (time) => { if (typeof format[2] == 'string') { return format[list_choice] } else { - return Math.floor(seconds / format[2]) + ' ' + format[1]// + ' ' + token + return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token } } } diff --git a/client/src/assets/semantic-helper.css b/client/src/assets/semantic-helper.css index 8d8a050..37b5c88 100644 --- a/client/src/assets/semantic-helper.css +++ b/client/src/assets/semantic-helper.css @@ -43,7 +43,7 @@ html { height:100%; padding: 0; margin: 0; - background: none; + background: var(--body_bg_color); } a:hover { text-decoration: underline; diff --git a/client/src/assets/squire.js b/client/src/assets/squire.js index 29529ae..c5c8f82 100644 --- a/client/src/assets/squire.js +++ b/client/src/assets/squire.js @@ -2117,7 +2117,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) { break; } } - data = data.replace( /^[ \t\r\n]+/g, sibling ? ' ' : '' ); + data = data.replace( /^[ \r\n]+/g, sibling ? ' ' : '' ); } if ( endsWithWS ) { walker.currentNode = child; @@ -2132,7 +2132,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) { break; } } - data = data.replace( /[ \t\r\n]+$/g, sibling ? ' ' : '' ); + data = data.replace( /[ \r\n]+$/g, sibling ? ' ' : '' ); } if ( data ) { child.data = data; @@ -2693,7 +2693,8 @@ var sanitizeToDOMFragment = function ( html, isPaste, self ) { ALLOW_UNKNOWN_PROTOCOLS: true, WHOLE_DOCUMENT: false, RETURN_DOM: true, - RETURN_DOM_FRAGMENT: true + RETURN_DOM_FRAGMENT: true, + FORCE_BODY: false }) : null; return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment(); }; @@ -5011,4 +5012,4 @@ if ( typeof exports === 'object' ) { } } -}( document ) ); \ No newline at end of file +}( document ) ); diff --git a/client/src/components/ColorPicker.vue b/client/src/components/ColorPicker.vue index c6aef64..612af09 100644 --- a/client/src/components/ColorPicker.vue +++ b/client/src/components/ColorPicker.vue @@ -1,54 +1,59 @@ <template> - <div :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}"> - <div class="ui basic segment"> + <div> + <div class="ui grid"> - <div class="ui sixteen wide center aligned column"> - <div class="ui fluid button" v-on:click="clearStyles"> + <div class="ui sixteen wide column"> + <div class="ui dividing header"> + Reset Background Color and Icon + </div> + <div class="ui labeled basic icon button" v-on:click="clearStyles"> <i class="refresh icon"></i> - Clear All Styles + Reset </div> </div> - <div class="row"> - <div class="sixteen wide column"> - <br> - <p>Note Color</p> - <div v-for="color in colors" - class="color-button" - :style="{ backgroundColor:color }" - v-on:click="chosenColor(color)" - ></div> + <div class="sixteen wide column rounded" :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}"> + <div class="ui dividing header" :style="{ 'color':allStyles['noteText']}"> + <i class="fill drip icon"></i> + Background Color + </div> + <div v-for="color in colors" + class="color-button" + :style="{ backgroundColor:color }" + v-on:click="chosenColor(color)" + ></div> + </div> + + <div class="sixteen wide column"> + <div class="ui dividing header"> + <span v-if="allStyles.noteIcon" > + <i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i> + </span> + Note Icon + </div> + <div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" > + <i :class="`large ${icon} icon`"></i> </div> </div> - <div class="row"> - <div class="sixteen wide column"> - <p>Note Icon - <span v-if="allStyles.noteIcon" > - <i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i> - </span> - </p> - <div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" > - <i :class="`large ${icon} icon`" :style="{ 'color':allStyles.iconColor }"></i> - </div> + <div class="sixteen wide column"> + <div class="ui dividing header"> + <span v-if="allStyles.noteIcon" > + <i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i> + </span> + Icon Color + </div> + <div v-for="color in getReducedColors()" + class="color-button" + :style="{ backgroundColor:color }" + v-on:click="chooseIconColor(color)" + > </div> </div> - - <div class="row"> - <div class="sixteen wide column"> - <p>Icon Color</p> - <div v-for="color in getReducedColors()" - class="color-button" - :style="{ backgroundColor:color }" - v-on:click="chooseIconColor(color)" - > - </div> - </div> - </div> - </div> + </div> </div> @@ -147,20 +152,20 @@ } </script> <style type="text/css" scoped> - .icon-button { + .icon-button, .color-button { height: 40px; width: calc(10% - 7px); display: inline-block; cursor: pointer; font-size: 1.3em; - } - .color-button { - display: inline-block; - width: calc(10% - 7px); - height: 30px; - border-radius: 30px; + border: 1px solid grey; + text-align: center; + padding: 5px 0 0; + border-radius: 4px; box-shadow: 0px 1px 3px 0px #3e3e3e; margin: 7px 7px 0 0; - cursor: pointer; + } + .rounded { + border-radius: 5px; } </style> \ No newline at end of file diff --git a/client/src/components/NoteInputPanel.vue b/client/src/components/NoteInputPanel.vue index f748149..563e243 100644 --- a/client/src/components/NoteInputPanel.vue +++ b/client/src/components/NoteInputPanel.vue @@ -1,9 +1,9 @@ <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()" --> <div id="InputNotes" - class="master-note-edit" - @keyup.esc="closeButtonAction()" + class="master-note-edit" > <!-- Giant Edit Note Menu --> @@ -89,9 +89,9 @@ <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']}"> +<!-- <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> --> <!-- <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> --> @@ -107,7 +107,9 @@ <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"> + <i class="ellipsis horizontal icon"></i> + </div> <div class="edit-divide"></div> @@ -128,6 +130,7 @@ <div class="edit-button ui" 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> @@ -139,7 +142,7 @@ :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}"> <!-- Squire box grows --> - <div id="text-box-container" class="note-wrapper"> + <div id="text-box-container" class="note-wrapper" :style="{ 'background-color':styleObject['noteBackground']}"> <!-- Loading indicator --> <transition name="fade"> @@ -161,19 +164,22 @@ v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input glint"> </textarea> - <div class="large-close-button glint" v-on:click="closeButtonAction()"> + <!-- close button giant --> + <div v-if="!$store.getters.getIsUserOnMobile" class="large-close-button" v-on:click="closeButtonAction()"> <i class="fitted green close icon"></i> </div> - <!-- little tags on the side, only show on desktop --> - <div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)"> - <span class="add-mini-tag" v-if="noteTags.length == 0"> + <!-- tags on the side, only show on desktop --> + <div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }" + v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)" + :style="{ 'background-color':styleObject['noteBackground'] }"> + <span class="add-mini-tag" v-if="allTags.length == 0"> <i class="tags icon"></i>Add Tags </span> - <span v-for="tag in allTags" class="active-mini-tag" v-if="isTagOnNote(tag.id)"> - #{{ tag.text }} + <span v-for="tag in allTags" class="active-mini-tag"> + #{{ tag }} </span> - <span class="active-mini-tag" v-if="noteTags.length > 0"> + <span class="active-mini-tag" v-if="allTags.length > 0"> + </span> @@ -201,21 +207,23 @@ /> <!-- Side slide menus for colors, tags, images and other options --> - <side-slide-menu v-if="colors" v-on:close="colors = false" name="colors"> +<!-- <side-slide-menu v-if="colors" v-on:close="colors = false" name="colors"> <color-picker @changeColor="onChangeColor" @close="colors = false; $router.go(-1)" :style-object="styleObject" /> - </side-slide-menu> + </side-slide-menu> --> - <side-slide-menu v-if="tags" v-on:close="tags = false; fetchNoteTags()" name="tags" :style-object="styleObject"> + <!-- tag edit menu --> + <side-slide-menu v-if="tags" v-on:close="tags = false; fetchNoteTags()" name="tags"> <div class="ui basic segment"> <note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/> </div> </side-slide-menu> - <side-slide-menu v-if="images" v-on:close="images = false" name="images" :style-object="styleObject"> + <!-- images menu --> + <side-slide-menu v-if="images" v-on:close="images = false" name="images"> <div class="ui basic segment"> <simple-attachment-note :note-id="noteid" @@ -224,67 +232,81 @@ </div> </side-slide-menu> - <side-slide-menu v-if="options" v-on:close="options = false" name="note-options" :style-object="styleObject"> + <side-slide-menu v-if="options" v-on:close="options = false" name="note-options"> <div class="ui basic padded segment"> <div class="ui grid"> - <div class="sixteen wide column"> - <h3>Note Options</h3> - </div> + <div class="eight wide column"> + <div class="ui dividing header"> + Note Options + </div> <div class="ui labeled icon fluid basic button" v-on:click="onToggleArchived()"> <i class="archive icon" :class="{'green':(archived == 1)}"></i> <span v-if="archived == 1">Un-Archive Note</span> <span v-if="archived != 1">Archive Note</span> </div> - </div> - <div class="eight wide column"> <div class="ui labeled icon fluid basic button" v-on:click="onTogglePinned"> <i class="pin icon" :class="{'green':(pinned == 1)}"></i> <span v-if="pinned == 1">Un-Pin Note</span> <span v-if="pinned != 1">Pin Note</span> </div> </div> - <div class="sixteen wide column"> - <h3>List Options</h3> - </div> - <div class="sixteen wide column"> + + <div class="eight wide column"> + <div class="ui dividing header"> + List Options + </div> <div class="ui labeled icon fluid basic button" v-on:click="sortList"> <i class="sort amount up icon"></i> - Sort List + Sort List (Complete to bottom) + </div> + <div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems"> + <i class="list ul icon"></i> + Uncheck All </div> - </div> - <div class="eight wide column"> <div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems"> <i class="trash icon"></i> Delete Checked </div> </div> + <div class="eight wide column"> - <div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems"> - <i class="list ul icon"></i> - Uncheck All + <div class="ui dividing header"> + Calculate Line </div> - </div> - <div class="sixteen wide column"> - <h3>Misc Options</h3> - </div> - <div class="eight wide column"> - <div class="ui labeled icon fluid basic button" v-on:click="calculateMath" data-tooltip="Calculates algebra before '='"> + <p> + Calculates algebra before '=' + </p> + <div class="ui labeled icon fluid basic button" v-on:click="calculateMath"> <i class="calculator icon"></i> - Simple Math - </div> - </div> - <div class="eight wide column"> - <!-- data-tooltip="Files on note" --> - <div v-on:click="openEditAttachment" class="ui labeled icon fluid basic button"> - <i class="folder icon"></i> - Note Files - {{ attachmentCount }} + Calculate Simple Math </div> </div> + <div class="eight wide column"> + <!-- data-tooltip="Files on note" --> + <div class="ui dividing header"> + Note Attachments & Links + </div> + <p> + Attachment & Link Count {{ attachmentCount }} + </p> + <div v-on:click="openEditAttachment" class="ui labeled icon fluid basic button"> + <i class="folder icon"></i> + View all Attachments & Links + </div> + </div> + + <color-picker + @changeColor="onChangeColor" + @close="colors = false; $router.go(-1)" + :style-object="styleObject" + /> + <div class="sixteen wide column" v-if="rawTextId > 0"> - <h3>Share Note</h3> + <div class="ui dividing header"> + Share Note + </div> <share-note-component :note-id="noteid" :raw-text-id="rawTextId" @@ -296,7 +318,7 @@ </side-slide-menu> <!-- create table option --> - <side-slide-menu v-if="table" v-on:close="table = false; fetchNoteTags()" name="table" :style-object="styleObject"> + <side-slide-menu v-if="table" v-on:close="table = false;" name="table" :style-object="styleObject"> <div class="ui basic segment"> <h2>Insert Table</h2> <div class="table-tic-table"> @@ -521,77 +543,15 @@ }, totalTime + 40) }, - removeTag(tagId){ - - this.allTags = [] - let entryId = 0 - - //Find fucking note tag for removal - this.noteTags.forEach(noteTag => { - if(noteTag['tagId'] == tagId){ - entryId = noteTag['entryId'] - } - }) - - let postData = { - 'tagId':entryId, - 'noteId':this.noteid - } - - axios.post('/api/tag/removefromnote', postData) - .then(response => { - this.fetchNoteTags() - }) - .catch(error => { this.$bus.$emit('notification', 'Failed to Remove Tag') }) - }, - addTag(tagText){ - - this.allTags = [] - - let postData = { - 'tagText':tagText, - 'noteId':this.noteid - } - - axios.post('/api/tag/addtonote', postData) - .then(response => { - this.fetchNoteTags() - }) - .catch(error => { this.$bus.$emit('notification', 'Failed to Add Tag') }) - }, fetchNoteTags(){ - axios.post('/api/tag/get', {'noteId': this.noteid}) + axios.post('/api/tag/fornote', {'noteId': this.noteid}) .then(({data}) => { - this.allTags = data.allTags - this.noteTags = data.noteTagIds - //Stick used tags at top. - if(this.noteTags.length > 0){ - - let frontTags = [] - - for (var i = this.allTags.length - 1; i >= 0; i--) { - this.noteTags.forEach(noteTag => { - if(this.allTags[i]['id'] == noteTag['tagId']){ - frontTags.push(this.allTags[i]) - this.allTags.splice(i,1) - } - }) - } - - this.allTags.unshift(...frontTags) - } + //Setup note tags from string + this.allTags = data.tags ? data.tags.split(',') : [] + }) }, - isTagOnNote(id){ - for (let i = 0; i < this.noteTags.length; i++) { - const current = this.noteTags[i] - if(current && current['tagId'] == id){ - return true - } - } - return false - }, initSquire(){ //Set up squire and load note text @@ -604,10 +564,6 @@ } - //Load tags on mobile - this.fetchNoteTags() - - //Set up websockets after squire is set up setTimeout(() => { this.setupWebSockets() @@ -783,6 +739,9 @@ this.lastNoteHash = this.hashString( response.data.text ) // this.diffNoteText = response.data.text + //Setup note tags + this.allTags = response.data.tags ? response.data.tags.split(','):[] + //Set up note colors if(response.data.color){ this.styleObject = JSON.parse(response.data.color) diff --git a/client/src/components/NoteTitleDisplayCard.vue b/client/src/components/NoteTitleDisplayCard.vue index a2fae45..cf95566 100644 --- a/client/src/components/NoteTitleDisplayCard.vue +++ b/client/src/components/NoteTitleDisplayCard.vue @@ -21,27 +21,9 @@ class="big-text"><p>{{ note.title }}</p></span> <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> - <br> - </span> - - <!-- Sub text display --> - <span v-if="note.subtext.length > 0" - class="small-text" - v-html="note.subtext"></span> - - - <!-- Not indexed warning --> -<!-- <span v-if="note.indexed != 1"> - <span class="green label">Not Indexed</span> - </span> --> - - - <div class="ui fluid basic button" v-if="note.encrypted == 1"> - <i class="green lock icon"></i> - Locked - </div> - + <span v-for="tag in (note.tags.split(','))" class="little-tag" v-on:click="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span> + <br> + </span> <!-- Shared Details --> <span class="subtext" v-if="note.shared == 2"> @@ -62,23 +44,71 @@ </span> </span> - </div> + <!-- Sub text display --> + <span v-if="note.subtext.length > 0" + class="small-text" + v-html="note.subtext"></span> + + + <!-- Not indexed warning --> +<!-- <span v-if="note.indexed != 1"> + <span class="green label">Not Indexed</span> + </span> --> + - <div v-if="titleView" class="single-line-text" @click="cardClicked"> - <span class="title-line" v-if="note.title.length > 0">{{ note.title }}<br></span> - <span class="sub-line" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span> - <span v-if="note.title.length == 0 && note.title.length == 0">Empty Note</span> +<!-- <div class="ui fluid basic button" v-if="note.encrypted == 1"> + <i class="green lock icon"></i> + Locked + </div> --> + + </div> + + <!-- slim card view --> + <div v-if="titleView" class="thin-container" @click="cardClicked"> + + <!-- icon --> + <span v-if="noteIcon" class="thin-icon"> + <i :class="`${noteIcon} icon`" :style="{ 'color':iconColor }"></i> + </span> + + <!-- title --> + <span class="thin-title" v-if="note.title.length > 0">{{ note.title }}</span> + + <!-- snippet --> + <span class="thin-sub" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span> + <span v-if="note.title.length == 0 && removeHtml(note.subtext).length == 0">Empty Note</span> + + <!-- tags --> + <span v-if="note.tags" class="thin-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> + + <!-- edited --> + <span class="thin-right"> + {{$helpers.timeAgo( note.updated )}} + + <i class="green link ellipsis vertical icon"></i> + </span> + </div> <!-- Toolbar on the bottom --> <div class="tool-bar" @click.self="cardClicked" v-if="!titleView"> - <div class="icon-bar"> - <span class="time-ago-display" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> + <div v-if="getThumbs.length > 0"> + <div class="tiny-thumb-box" v-on:click="openEditAttachment"> + <img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`"> + </div> + </div> + + <div class="icon-bar" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> + + <span class="time-ago-display"> {{$helpers.timeAgo( note.updated )}} </span> - <span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> + <span class="teeny-buttons"> <span v-if="!note.trashed"> @@ -115,19 +145,13 @@ </i> <delete-button class="teeny-button" :note-id="note.id" /> </span> - - - </span> </div> - <div v-if="getThumbs.length > 0"> - <div class="tiny-thumb-box" v-on:click="openEditAttachment"> - <img v-for="thumb in getThumbs" class="tiny-thumb" :src="`/api/static/thumb_${thumb}`"> - </div> - </div> + </div> + <!-- tag edit menu --> <side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true"> <div class="ui basic segment"> <note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/> @@ -333,13 +357,11 @@ .teeny-buttons { float: right; - width: 65%; text-align: right; } .time-ago-display { - width: 35%; - float: left; - text-align: center; + font-size: 11px; + font-weight: bold; } .tags { width: 100%; @@ -364,9 +386,7 @@ /*Strict font sizes for card display*/ .small-text { - max-height: 267px; width: 100%; - overflow: hidden; display: inline-block; } .small-text, .small-text > p, .small-text > h1, .small-text > h2 { @@ -426,7 +446,7 @@ /*width: calc(33.333% - 10px);*/ width: calc(25% - 10px); /*min-width: 190px;*/ - min-height: 130px; + /*min-height: 130px;*/ /*transition: box-shadow 0.3s;*/ box-sizing: border-box; cursor: pointer; @@ -435,7 +455,11 @@ letter-spacing: 0.05rem; display: flex; flex-direction: column; + align-items: stretch; text-align: left; + + min-height: 100px; + max-height: 450px; } .note-title-display-card:hover { /*box-shadow: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);*/ @@ -446,21 +470,49 @@ width: 100%; min-height: 20px; max-width: none; + padding: 10px; + margin: 0; + overflow: hidden; + border-radius: 0; + border: none; /*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/ } - - .single-line-text { + .title-view + .title-view { + border-top: 1px solid var(--border_color); + } + + .thin-container.single-line-text { width: calc(100% - 25px); - margin: 5px 10px; + /*margin: 5px 10px;*/ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; } - .title-line { + + .thin-container .thin-title { font-weight: bold; font-size: 1.2em; - padding: 0 20px 0 0; + } + .thin-container .thin-sub { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + opacity: 0.85; + } + .thin-container .thin-tags { + float: left; + margin-top: 3px; + } + .thin-container .thin-right { + float: right; + color: var(--dark_border_color); + } + .thin-container .thin-icon { + float: right; } .icon-bar { @@ -468,6 +520,7 @@ padding: 5px 10px 0; opacity: 1; width: 100%; + background-color: rgba(200, 200, 200, 0.2); } .hover-hide { opacity: 0.0; diff --git a/client/src/pages/HomePage.vue b/client/src/pages/HomePage.vue index a5ab63a..b2b420e 100644 --- a/client/src/pages/HomePage.vue +++ b/client/src/pages/HomePage.vue @@ -191,11 +191,24 @@ </div> </div> + <!-- Overview --> + <div class="middle aligned centered row"> + <div class="six wide column"> + <h2 class="ui dividing header">Powerful text editing and privacy</h2> + <h3>Easily edit, share and organize thousands of notes.</h3> + <h3>Feel safe knowing no one can read your notes but you.</h3> + <!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> --> + </div> + <div class="four wide column"> + <img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas"> + </div> + </div> + <!-- theme selector --> <div class="ui white row"> <div class="sixteen wide middle aligned column"> <div class="ui container"> - <h2> + <h2 style="color: var(--main-accent);"> Pick your theme </h2> <h3 v-if="$parent.loggedIn">Go to settings to change theme</h3> @@ -211,19 +224,6 @@ </div> </div> - <!-- Overview --> - <div class="middle aligned centered row"> - <div class="six wide column"> - <h2 class="ui dividing header">Powerful text editing and privacy</h2> - <h3>Easily edit, share and organize thousands of notes.</h3> - <h3>Feel safe knowing no one can read your notes but you.</h3> - <!-- <h3>Tools to organize and collaborate on thousands of notes while maintaining security and respecting your privacy.</h3> --> - </div> - <div class="four wide column"> - <img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas"> - </div> - </div> - <!-- features list --> <div class="top aligned centered row"> @@ -355,7 +355,7 @@ <i class="grey lock icon"></i> <i class="bottom left corner yellow key icon"></i> </i> - All Note Text is Encrypted + Secure Notes <div class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div> </div> </h2> @@ -365,7 +365,7 @@ <i class="grey search icon"></i> <i class="bottom left corner orange font icon"></i> </i> - Note Search is Encrypted + Private Search <div class="sub header">Search the contents of all your notes without compromising security.</div> </div> </h2> @@ -375,7 +375,7 @@ <i class="grey share alternate icon"></i> <i class="bottom left corner share icon"></i> </i> - Encrypted Note Sharing + Encrypted Sharing <div class="sub header">Shared notes are still encrypted, only readable by you and the shared users.</div> </div> </h2> diff --git a/client/src/pages/NotesPage.vue b/client/src/pages/NotesPage.vue index a2d4d64..508011f 100644 --- a/client/src/pages/NotesPage.vue +++ b/client/src/pages/NotesPage.vue @@ -27,9 +27,15 @@ 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 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> @@ -223,6 +229,9 @@ this.$parent.loginGateway() + //If user is on title view, + this.titleView = this.$store.getters.getIsUserOnMobile + this.$io.on('new_note_created', noteId => { //Do not update note if its open diff --git a/dontSync.txt b/dontSync.txt index 6db8a5f..6007d6b 100644 --- a/dontSync.txt +++ b/dontSync.txt @@ -11,4 +11,5 @@ common.js bundle.* client/dist* server/public/* -client/dist* \ No newline at end of file +client/dist* +*_scrape* \ No newline at end of file diff --git a/server/helpers/Auth.js b/server/helpers/Auth.js index 7367c48..d49ada3 100644 --- a/server/helpers/Auth.js +++ b/server/helpers/Auth.js @@ -6,6 +6,7 @@ const speakeasy = require('speakeasy') let Auth = {} const tokenSecretKey = process.env.JSON_KEY +const sessionTokenUses = 300 //Defines number of uses each session token has before being refreshed //Creates session token Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { @@ -26,7 +27,7 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => return db.promise().query( 'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)', - [salt, encryptedMasterPass, created, 40, userHash, sessionId]) + [salt, encryptedMasterPass, created, sessionTokenUses, userHash, sessionId]) }) .then((r,f) => { diff --git a/server/helpers/SiteScrape.js b/server/helpers/SiteScrape.js index 39472a4..295036b 100644 --- a/server/helpers/SiteScrape.js +++ b/server/helpers/SiteScrape.js @@ -54,7 +54,7 @@ SiteScrape.getCleanUrls = (textBlock) => { SiteScrape.getHostName = (url) => { var hostname = 'https://'+(new URL(url)).hostname; - console.log('hostname', hostname) + // console.log('hostname', hostname) return hostname } @@ -63,36 +63,95 @@ SiteScrape.getDisplayImage = ($, url) => { const hostname = SiteScrape.getHostName(url) - let metaImg = $('meta[property="og:image"]') - let shortcutIcon = $('link[rel="shortcut icon"]') - let favicon = $('link[rel="icon"]') + let metaImg = $('[property="og:image"]') + let shortcutIcon = $('[rel="shortcut icon"]') + let favicon = $('[rel="icon"]') let randomImg = $('img') - console.log('----') + //Set of images we may want gathered from various places in source + let imagesWeWant = [] + let thumbnail = '' //Scrape metadata for page image - //Grab the first random image we find - if(randomImg && randomImg[0] && randomImg[0].attribs){ - thumbnail = hostname + randomImg[0].attribs.src - console.log('random img '+thumbnail) + if(randomImg && randomImg.length > 0){ + + let imgSrcs = [] + for (let i = 0; i < randomImg.length; i++) { + imgSrcs.push( randomImg[i].attribs.src ) + } + + const half = Math.ceil(imgSrcs.length / 2) + imagesWeWant = [...imgSrcs.slice(-half), ...imgSrcs.slice(0,half) ] + } - //Grab the favicon of the site + //Grab the shortcut icon if(favicon && favicon[0] && favicon[0].attribs){ - thumbnail = hostname + favicon[0].attribs.href - console.log('favicon '+thumbnail) + imagesWeWant.push(favicon[0].attribs.href) } //Grab the shortcut icon if(shortcutIcon && shortcutIcon[0] && shortcutIcon[0].attribs){ - thumbnail = hostname + shortcutIcon[0].attribs.href - console.log('shortcut '+thumbnail) + imagesWeWant.push(shortcutIcon[0].attribs.href) } //Grab the presentation image for the site if(metaImg && metaImg[0] && metaImg[0].attribs){ - thumbnail = metaImg[0].attribs.content - console.log('ogImg '+thumbnail) + imagesWeWant.unshift(metaImg[0].attribs.content) + } + + // console.log(imagesWeWant) + + //Remove everything that isn't an accepted file format + for (let i = imagesWeWant.length - 1; i >= 0; i--) { + + let img = String(imagesWeWant[i]) + + if( + !img.includes('.jpg') && + !img.includes('.jpeg') && + !img.includes('.png') && + !img.includes('.gif') + ){ + imagesWeWant.splice(i,1) + } + } + + //Find if we have absolute thumbnails or not + let foundAbsolute = false + for (let i = imagesWeWant.length - 1; i >= 0; i--) { + + let img = imagesWeWant[i] + + //Add host name if its not included + if(String(img).includes('//') || String(img).includes('http')){ + foundAbsolute = true + break + } + } + + //Go through all found images. Grab the one closest to the top. Closer is better + for (let i = imagesWeWant.length - 1; i >= 0; i--) { + + let img = imagesWeWant[i] + + if(!String(img).includes('//') && foundAbsolute){ + continue; + } + + //Only add host to images if no absolute images were found + if(!String(img).includes('//') ){ + if(img.indexOf('/') != 0){ + img = '/' + img + } + img = hostname + img + } + + if(img.indexOf('//') == 0){ + img = 'https:' + img //Scrape breaks without protocol + } + + thumbnail = img + } - console.log('-----') return thumbnail } diff --git a/server/index.js b/server/index.js index 677fa1a..b8c64ea 100644 --- a/server/index.js +++ b/server/index.js @@ -257,7 +257,6 @@ const printResults = true let UserTest = require('@models/User') let NoteTest = require('@models/Note') let AuthTest = require('@helpers/Auth') - Auth.test() UserTest.keyPairTest('genMan30', '1', printResults) .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults)) @@ -266,7 +265,6 @@ UserTest.keyPairTest('genMan30', '1', printResults) Auth.testTwoFactor() }) - //Test app.get('/api', (req, res) => res.send('Solidscribe API is up and running')) diff --git a/server/models/Attachment.js b/server/models/Attachment.js index ded35d5..e7b1134 100644 --- a/server/models/Attachment.js +++ b/server/models/Attachment.js @@ -325,14 +325,14 @@ Attachment.downloadFileFromUrl = (url) => { return new Promise((resolve, reject) => { - if(url == null){ + if(url == null || url == undefined || url == ''){ resolve(null) } const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) - const extension = '.'+url.split('.').pop() //This is throwing an error - let fileName = random+'_scrape'+extension - const thumbPath = 'thumb_'+fileName + let extension = '' + let fileName = random+'_scrape' + let thumbPath = 'thumb_'+fileName console.log('Scraping image url') console.log(url) @@ -347,6 +347,8 @@ Attachment.downloadFileFromUrl = (url) => { .on('response', res => { console.log(res.statusCode) console.log(res.headers['content-type']) + //Get mime type from header content type + // extension = '.'+String(res.headers['content-type']).split('/').pop() }) .pipe(fs.createWriteStream(filePath+thumbPath)) .on('close', () => { @@ -354,14 +356,17 @@ Attachment.downloadFileFromUrl = (url) => { //resize image if its real big gm(filePath+thumbPath) .resize(550) //Resize to width of 550 px - .quality(75) //compression level 0 - 100 (best) + .quality(85) //compression level 0 - 100 (best) .write(filePath+thumbPath, function (err) { - if(err){ console.log(err) } + if(err){ + console.log(err) + return resolve(null) + } + + console.log('Saved Image') + return resolve(fileName) }) - - console.log('Saved Image') - resolve(fileName) }) }) } @@ -396,7 +401,7 @@ Attachment.processUrl = (userId, noteId, url) => { .query(`INSERT INTO attachment (note_id, user_id, attachment_type, text, url, last_indexed, file_location) VALUES (?, ?, ?, ?, ?, ?, ?)`, - [noteId, userId, 1, 'Processing...', url, created, null]) + [noteId, userId, 1, url, url, created, null]) .then((rows, fields) => { //Set two bigger variables then return request for processing request = rp(options) diff --git a/server/models/Note.js b/server/models/Note.js index 9367b3d..8c5a422 100644 --- a/server/models/Note.js +++ b/server/models/Note.js @@ -681,6 +681,7 @@ Note.get = (userId, noteId, masterKey) => { note_raw_text.text, note_raw_text.salt, note_raw_text.updated as updated, + GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags, note.id, note.user_id, note.created, @@ -697,7 +698,9 @@ Note.get = (userId, noteId, masterKey) => { JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id) LEFT JOIN attachment ON (note.id = attachment.note_id) LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.id) - WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, noteId]) + LEFT JOIN note_tag ON (note.id = note_tag.note_id AND note_tag.user_id = ?) + LEFT JOIN tag ON (note_tag.tag_id = tag.id) + WHERE note.user_id = ? AND note.id = ? LIMIT 1`, [userId, userId, noteId]) }) .then((rows, fields) => { diff --git a/server/models/Tag.js b/server/models/Tag.js index b502db4..deedc51 100644 --- a/server/models/Tag.js +++ b/server/models/Tag.js @@ -138,6 +138,33 @@ Tag.get = (userId, noteId) => { }) } +// +// Get just tag string for note +// +Tag.fornote = (userId, noteId) => { + return new Promise((resolve, reject) => { + + + db.promise() + .query(`SELECT GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags + FROM note_tag + LEFT JOIN tag ON (note_tag.tag_id = tag.id) + WHERE note_tag.note_id = ? + AND user_id = ?; + `, [noteId,userId]) + .then((rows, fields) => { + + //pull IDs out of returned results + // let ids = rows[0].map( item => {}) + + resolve( rows[0][0] ) //Return all tags found by query + }) + .catch(console.log) + + + }) +} + // // Get all tags for a note and concatinate into a string 'all, tags, like, this' // diff --git a/server/models/User.js b/server/models/User.js index f82edd0..17d254f 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -9,7 +9,7 @@ const speakeasy = require('speakeasy') let User = module.exports = {} -const version = '3.3.1' +const version = '3.3.3' //Login a user, if that user does not exist create them //Issues login token diff --git a/server/routes/tagController.js b/server/routes/tagController.js index 5e444d1..7157221 100644 --- a/server/routes/tagController.js +++ b/server/routes/tagController.js @@ -50,6 +50,12 @@ router.post('/get', function (req, res) { .then( data => res.send(data) ) }) +//Get the latest notes the user has created +router.post('/fornote', function (req, res) { + Tags.fornote(userId, req.body.noteId) + .then( data => res.send(data) ) +}) + //Get all the tags for this user in order of usage router.post('/usertags', function (req, res) { Tags.userTags(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters)