Tons of littele interface changes and cleanups

Massive update to image scraper with much better image getter
Lots of little ui updates for mobile
This commit is contained in:
Max G 2022-01-27 04:48:19 +00:00
parent b666bfc197
commit 148b822d49
17 changed files with 400 additions and 273 deletions

View File

@ -53,7 +53,7 @@ helpers.timeAgo = (time) => {
if (typeof format[2] == 'string') { if (typeof format[2] == 'string') {
return format[list_choice] return format[list_choice]
} else { } else {
return Math.floor(seconds / format[2]) + ' ' + format[1]// + ' ' + token return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token
} }
} }
} }

View File

@ -43,7 +43,7 @@ html {
height:100%; height:100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
background: none; background: var(--body_bg_color);
} }
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;

View File

@ -2117,7 +2117,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) {
break; break;
} }
} }
data = data.replace( /^[ \t\r\n]+/g, sibling ? ' ' : '' ); data = data.replace( /^[ \r\n]+/g, sibling ? ' ' : '' );
} }
if ( endsWithWS ) { if ( endsWithWS ) {
walker.currentNode = child; walker.currentNode = child;
@ -2132,7 +2132,7 @@ var cleanTree = function cleanTree ( node, config, preserveWS ) {
break; break;
} }
} }
data = data.replace( /[ \t\r\n]+$/g, sibling ? ' ' : '' ); data = data.replace( /[ \r\n]+$/g, sibling ? ' ' : '' );
} }
if ( data ) { if ( data ) {
child.data = data; child.data = data;
@ -2693,7 +2693,8 @@ var sanitizeToDOMFragment = function ( html, isPaste, self ) {
ALLOW_UNKNOWN_PROTOCOLS: true, ALLOW_UNKNOWN_PROTOCOLS: true,
WHOLE_DOCUMENT: false, WHOLE_DOCUMENT: false,
RETURN_DOM: true, RETURN_DOM: true,
RETURN_DOM_FRAGMENT: true RETURN_DOM_FRAGMENT: true,
FORCE_BODY: false
}) : null; }) : null;
return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment(); return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment();
}; };
@ -5011,4 +5012,4 @@ if ( typeof exports === 'object' ) {
} }
} }
}( document ) ); }( document ) );

View File

@ -1,54 +1,59 @@
<template> <template>
<div :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}"> <div>
<div class="ui basic segment">
<div class="ui grid"> <div class="ui grid">
<div class="ui sixteen wide center aligned column"> <div class="ui sixteen wide column">
<div class="ui fluid button" v-on:click="clearStyles"> <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> <i class="refresh icon"></i>
Clear All Styles Reset
</div> </div>
</div> </div>
<div class="row"> <div class="sixteen wide column rounded" :style="{ 'background-color':allStyles['noteBackground'], 'color':allStyles['noteText']}">
<div class="sixteen wide column"> <div class="ui dividing header" :style="{ 'color':allStyles['noteText']}">
<br> <i class="fill drip icon"></i>
<p>Note Color</p> Background Color
<div v-for="color in colors" </div>
class="color-button" <div v-for="color in colors"
:style="{ backgroundColor:color }" class="color-button"
v-on:click="chosenColor(color)" :style="{ backgroundColor:color }"
></div> 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> </div>
<div class="row"> <div class="sixteen wide column">
<div class="sixteen wide column"> <div class="ui dividing header">
<p>Note Icon <span v-if="allStyles.noteIcon" >
<span v-if="allStyles.noteIcon" > <i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i>
<i :class="`large ${allStyles.noteIcon} icon`" :style="{ 'color':allStyles.iconColor }"></i> </span>
</span> Icon Color
</p> </div>
<div v-for="icon in icons" class="icon-button" v-on:click="chosenIcon(icon)" > <div v-for="color in getReducedColors()"
<i :class="`large ${icon} icon`" :style="{ 'color':allStyles.iconColor }"></i> class="color-button"
</div> :style="{ backgroundColor:color }"
v-on:click="chooseIconColor(color)"
>
</div> </div>
</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>
</div> </div>
@ -147,20 +152,20 @@
} }
</script> </script>
<style type="text/css" scoped> <style type="text/css" scoped>
.icon-button { .icon-button, .color-button {
height: 40px; height: 40px;
width: calc(10% - 7px); width: calc(10% - 7px);
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
} border: 1px solid grey;
.color-button { text-align: center;
display: inline-block; padding: 5px 0 0;
width: calc(10% - 7px); border-radius: 4px;
height: 30px;
border-radius: 30px;
box-shadow: 0px 1px 3px 0px #3e3e3e; box-shadow: 0px 1px 3px 0px #3e3e3e;
margin: 7px 7px 0 0; margin: 7px 7px 0 0;
cursor: pointer; }
.rounded {
border-radius: 5px;
} }
</style> </style>

View File

@ -1,9 +1,9 @@
<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()" -->
<div <div
id="InputNotes" id="InputNotes"
class="master-note-edit" class="master-note-edit"
@keyup.esc="closeButtonAction()"
> >
<!-- Giant Edit Note Menu --> <!-- Giant Edit Note Menu -->
@ -89,9 +89,9 @@
<div class="edit-divide"></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']}"> <!-- <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> <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"> <!-- <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> <i class="tags icon"></i>
</div> --> </div> -->
@ -107,7 +107,9 @@
<div class="edit-divide"></div> <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"> <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> <i class="ellipsis horizontal icon"></i>
&nbsp;&nbsp;
</div> </div>
<div class="edit-divide"></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"> <div class="edit-button ui" v-on:click="closeButtonAction()" :data-tooltip="`Close\n(ESC)`" data-position="bottom center">
<i class="green close icon"></i> <i class="green close icon"></i>
<span class="ui green text">Done</span>
</div> </div>
</div> </div>
@ -139,7 +142,7 @@
:class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}"> :class="{ 'side-menu-open':sideMenuOpen, 'size-down':(sizeDown == true),}">
<!-- Squire box grows --> <!-- 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 --> <!-- Loading indicator -->
<transition name="fade"> <transition name="fade">
@ -161,19 +164,22 @@
v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input glint"> v-on:blur="save" type="text" v-model="noteTitle" placeholder="Title" class="stealth-input glint">
</textarea> </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> <i class="fitted green close icon"></i>
</div> </div>
<!-- little 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 }" v-on:click="$router.push(`/notes/open/${noteid}/menu/tags`)"> <div class="note-mini-tag-area" :class="{ 'size-down':sizeDown }"
<span class="add-mini-tag" v-if="noteTags.length == 0"> 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 <i class="tags icon"></i>Add Tags
</span> </span>
<span v-for="tag in allTags" class="active-mini-tag" v-if="isTagOnNote(tag.id)"> <span v-for="tag in allTags" class="active-mini-tag">
#{{ tag.text }} #{{ tag }}
</span> </span>
<span class="active-mini-tag" v-if="noteTags.length > 0"> <span class="active-mini-tag" v-if="allTags.length > 0">
+ +
</span> </span>
@ -201,21 +207,23 @@
/> />
<!-- Side slide menus for colors, tags, images and other options --> <!-- 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 <color-picker
@changeColor="onChangeColor" @changeColor="onChangeColor"
@close="colors = false; $router.go(-1)" @close="colors = false; $router.go(-1)"
:style-object="styleObject" :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"> <div class="ui basic segment">
<note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/> <note-tag-edit :noteId="noteid" :key="'tags-for-note-'+noteid"/>
</div> </div>
</side-slide-menu> </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"> <div class="ui basic segment">
<simple-attachment-note <simple-attachment-note
:note-id="noteid" :note-id="noteid"
@ -224,67 +232,81 @@
</div> </div>
</side-slide-menu> </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 basic padded segment">
<div class="ui grid"> <div class="ui grid">
<div class="sixteen wide column">
<h3>Note Options</h3>
</div>
<div class="eight wide column"> <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()"> <div class="ui labeled icon fluid basic button" v-on:click="onToggleArchived()">
<i class="archive icon" :class="{'green':(archived == 1)}"></i> <i class="archive icon" :class="{'green':(archived == 1)}"></i>
<span v-if="archived == 1">Un-Archive Note</span> <span v-if="archived == 1">Un-Archive Note</span>
<span v-if="archived != 1">Archive Note</span> <span v-if="archived != 1">Archive Note</span>
</div> </div>
</div>
<div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="onTogglePinned"> <div class="ui labeled icon fluid basic button" v-on:click="onTogglePinned">
<i class="pin icon" :class="{'green':(pinned == 1)}"></i> <i class="pin icon" :class="{'green':(pinned == 1)}"></i>
<span v-if="pinned == 1">Un-Pin Note</span> <span v-if="pinned == 1">Un-Pin Note</span>
<span v-if="pinned != 1">Pin Note</span> <span v-if="pinned != 1">Pin Note</span>
</div> </div>
</div> </div>
<div class="sixteen wide column">
<h3>List Options</h3> <div class="eight wide column">
</div> <div class="ui dividing header">
<div class="sixteen wide column"> List Options
</div>
<div class="ui labeled icon fluid basic button" v-on:click="sortList"> <div class="ui labeled icon fluid basic button" v-on:click="sortList">
<i class="sort amount up icon"></i> <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>
<div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems"> <div class="ui labeled icon fluid basic button" v-on:click="deleteCompletedListItems">
<i class="trash icon"></i> <i class="trash icon"></i>
Delete Checked Delete Checked
</div> </div>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="uncheckAllListItems"> <div class="ui dividing header">
<i class="list ul icon"></i> Calculate Line
Uncheck All
</div> </div>
</div> <p>
<div class="sixteen wide column"> Calculates algebra before '='
<h3>Misc Options</h3> </p>
</div> <div class="ui labeled icon fluid basic button" v-on:click="calculateMath">
<div class="eight wide column">
<div class="ui labeled icon fluid basic button" v-on:click="calculateMath" data-tooltip="Calculates algebra before '='">
<i class="calculator icon"></i> <i class="calculator icon"></i>
Simple Math Calculate 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 }}
</div> </div>
</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"> <div class="sixteen wide column" v-if="rawTextId > 0">
<h3>Share Note</h3> <div class="ui dividing header">
Share Note
</div>
<share-note-component <share-note-component
:note-id="noteid" :note-id="noteid"
:raw-text-id="rawTextId" :raw-text-id="rawTextId"
@ -296,7 +318,7 @@
</side-slide-menu> </side-slide-menu>
<!-- create table option --> <!-- 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"> <div class="ui basic segment">
<h2>Insert Table</h2> <h2>Insert Table</h2>
<div class="table-tic-table"> <div class="table-tic-table">
@ -521,77 +543,15 @@
}, totalTime + 40) }, 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(){ fetchNoteTags(){
axios.post('/api/tag/get', {'noteId': this.noteid}) axios.post('/api/tag/fornote', {'noteId': this.noteid})
.then(({data}) => { .then(({data}) => {
this.allTags = data.allTags
this.noteTags = data.noteTagIds
//Stick used tags at top. //Setup note tags from string
if(this.noteTags.length > 0){ this.allTags = data.tags ? data.tags.split(',') : []
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)
}
}) })
}, },
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(){ initSquire(){
//Set up squire and load note text //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 //Set up websockets after squire is set up
setTimeout(() => { setTimeout(() => {
this.setupWebSockets() this.setupWebSockets()
@ -783,6 +739,9 @@
this.lastNoteHash = this.hashString( response.data.text ) this.lastNoteHash = this.hashString( response.data.text )
// this.diffNoteText = response.data.text // this.diffNoteText = response.data.text
//Setup note tags
this.allTags = response.data.tags ? response.data.tags.split(','):[]
//Set up note colors //Set up note colors
if(response.data.color){ if(response.data.color){
this.styleObject = JSON.parse(response.data.color) this.styleObject = JSON.parse(response.data.color)

View File

@ -21,27 +21,9 @@
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="$emit('tagClick', tag.split(':')[1] )">#{{ tag.split(':')[0] }}</span>
<br> <br>
</span> </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>
<!-- Shared Details --> <!-- Shared Details -->
<span class="subtext" v-if="note.shared == 2"> <span class="subtext" v-if="note.shared == 2">
@ -62,23 +44,71 @@
</span> </span>
</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"> <!-- <div class="ui fluid basic button" v-if="note.encrypted == 1">
<span class="title-line" v-if="note.title.length > 0">{{ note.title }}<br></span> <i class="green lock icon"></i>
<span class="sub-line" v-if="note.subtext.length > 0">{{ removeHtml(note.subtext) }}</span> Locked
<span v-if="note.title.length == 0 && note.title.length == 0">Empty Note</span> </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> </div>
<!-- Toolbar on the bottom --> <!-- Toolbar on the bottom -->
<div class="tool-bar" @click.self="cardClicked" v-if="!titleView"> <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 )}} {{$helpers.timeAgo( note.updated )}}
</span> </span>
<span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> <span class="teeny-buttons">
<span v-if="!note.trashed"> <span v-if="!note.trashed">
@ -115,19 +145,13 @@
</i> </i>
<delete-button class="teeny-button" :note-id="note.id" /> <delete-button class="teeny-button" :note-id="note.id" />
</span> </span>
</span> </span>
</div> </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> </div>
<!-- tag edit menu -->
<side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true"> <side-slide-menu v-if="showTagSlideMenu" v-on:close="toggleTags(false)" :full-shadow="true" :skip-history="true">
<div class="ui basic segment"> <div class="ui basic segment">
<note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/> <note-tag-edit :noteId="note.id" :key="'display-tags-for-note-'+note.id"/>
@ -333,13 +357,11 @@
.teeny-buttons { .teeny-buttons {
float: right; float: right;
width: 65%;
text-align: right; text-align: right;
} }
.time-ago-display { .time-ago-display {
width: 35%; font-size: 11px;
float: left; font-weight: bold;
text-align: center;
} }
.tags { .tags {
width: 100%; width: 100%;
@ -364,9 +386,7 @@
/*Strict font sizes for card display*/ /*Strict font sizes for card display*/
.small-text { .small-text {
max-height: 267px;
width: 100%; width: 100%;
overflow: hidden;
display: inline-block; display: inline-block;
} }
.small-text, .small-text > p, .small-text > h1, .small-text > h2 { .small-text, .small-text > p, .small-text > h1, .small-text > h2 {
@ -426,7 +446,7 @@
/*width: calc(33.333% - 10px);*/ /*width: calc(33.333% - 10px);*/
width: calc(25% - 10px); width: calc(25% - 10px);
/*min-width: 190px;*/ /*min-width: 190px;*/
min-height: 130px; /*min-height: 130px;*/
/*transition: box-shadow 0.3s;*/ /*transition: box-shadow 0.3s;*/
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
@ -435,7 +455,11 @@
letter-spacing: 0.05rem; letter-spacing: 0.05rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch;
text-align: left; text-align: left;
min-height: 100px;
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: 0px 2px 2px 1px rgba(210, 211, 211, 0.8);*/
@ -446,21 +470,49 @@
width: 100%; width: 100%;
min-height: 20px; min-height: 20px;
max-width: none; 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);*/ /*box-shadow: 0px 0px 1px 1px rgba(210, 211, 211, 0.46);*/
} }
.title-view + .title-view {
.single-line-text { border-top: 1px solid var(--border_color);
}
.thin-container.single-line-text {
width: calc(100% - 25px); width: calc(100% - 25px);
margin: 5px 10px; /*margin: 5px 10px;*/
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
box-sizing: border-box; box-sizing: border-box;
} }
.title-line {
.thin-container .thin-title {
font-weight: bold; font-weight: bold;
font-size: 1.2em; 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 { .icon-bar {
@ -468,6 +520,7 @@
padding: 5px 10px 0; padding: 5px 10px 0;
opacity: 1; opacity: 1;
width: 100%; width: 100%;
background-color: rgba(200, 200, 200, 0.2);
} }
.hover-hide { .hover-hide {
opacity: 0.0; opacity: 0.0;

View File

@ -191,11 +191,24 @@
</div> </div>
</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 --> <!-- theme selector -->
<div class="ui white row"> <div class="ui white row">
<div class="sixteen wide middle aligned column"> <div class="sixteen wide middle aligned column">
<div class="ui container"> <div class="ui container">
<h2> <h2 style="color: var(--main-accent);">
Pick your theme Pick your theme
</h2> </h2>
<h3 v-if="$parent.loggedIn">Go to settings to change theme</h3> <h3 v-if="$parent.loggedIn">Go to settings to change theme</h3>
@ -211,19 +224,6 @@
</div> </div>
</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 --> <!-- features list -->
<div class="top aligned centered row"> <div class="top aligned centered row">
@ -355,7 +355,7 @@
<i class="grey lock icon"></i> <i class="grey lock icon"></i>
<i class="bottom left corner yellow key icon"></i> <i class="bottom left corner yellow key icon"></i>
</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 class="sub header">All note text is encrypted. No one can read your notes. None of your data is shared.</div>
</div> </div>
</h2> </h2>
@ -365,7 +365,7 @@
<i class="grey search icon"></i> <i class="grey search icon"></i>
<i class="bottom left corner orange font icon"></i> <i class="bottom left corner orange font icon"></i>
</i> </i>
Note Search is Encrypted Private Search
<div class="sub header">Search the contents of all your notes without compromising security.</div> <div class="sub header">Search the contents of all your notes without compromising security.</div>
</div> </div>
</h2> </h2>
@ -375,7 +375,7 @@
<i class="grey share alternate icon"></i> <i class="grey share alternate icon"></i>
<i class="bottom left corner share icon"></i> <i class="bottom left corner share icon"></i>
</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 class="sub header">Shared notes are still encrypted, only readable by you and the shared users.</div>
</div> </div>
</h2> </h2>

View File

@ -27,9 +27,15 @@
v-on:tagClick="tagId => toggleTagFilter(tagId)" 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"> <div class="ui right floated 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> <span v-if="titleView">
<i v-if="!titleView" class="bars icon"></i> <i class="th icon"></i> Tiles
</span>
<span v-if="!titleView">
<i class="list icon"></i> List
</span>
</div> </div>
</div> </div>
@ -223,6 +229,9 @@
this.$parent.loginGateway() this.$parent.loginGateway()
//If user is on title view,
this.titleView = this.$store.getters.getIsUserOnMobile
this.$io.on('new_note_created', noteId => { this.$io.on('new_note_created', noteId => {
//Do not update note if its open //Do not update note if its open

View File

@ -11,4 +11,5 @@ common.js
bundle.* bundle.*
client/dist* client/dist*
server/public/* server/public/*
client/dist* client/dist*
*_scrape*

View File

@ -6,6 +6,7 @@ const speakeasy = require('speakeasy')
let Auth = {} let Auth = {}
const tokenSecretKey = process.env.JSON_KEY const tokenSecretKey = process.env.JSON_KEY
const sessionTokenUses = 300 //Defines number of uses each session token has before being refreshed
//Creates session token //Creates session token
Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => { Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) => {
@ -26,7 +27,7 @@ Auth.createToken = (userId, masterKey, pastId = null, pastCreatedDate = null) =>
return db.promise().query( return db.promise().query(
'INSERT INTO user_active_session (salt, encrypted_master_password, created, uses, user_hash, session_id) VALUES (?,?,?,?,?,?)', '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) => { .then((r,f) => {

View File

@ -54,7 +54,7 @@ SiteScrape.getCleanUrls = (textBlock) => {
SiteScrape.getHostName = (url) => { SiteScrape.getHostName = (url) => {
var hostname = 'https://'+(new URL(url)).hostname; var hostname = 'https://'+(new URL(url)).hostname;
console.log('hostname', hostname) // console.log('hostname', hostname)
return hostname return hostname
} }
@ -63,36 +63,95 @@ SiteScrape.getDisplayImage = ($, url) => {
const hostname = SiteScrape.getHostName(url) const hostname = SiteScrape.getHostName(url)
let metaImg = $('meta[property="og:image"]') let metaImg = $('[property="og:image"]')
let shortcutIcon = $('link[rel="shortcut icon"]') let shortcutIcon = $('[rel="shortcut icon"]')
let favicon = $('link[rel="icon"]') let favicon = $('[rel="icon"]')
let randomImg = $('img') 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 //Scrape metadata for page image
//Grab the first random image we find if(randomImg && randomImg.length > 0){
if(randomImg && randomImg[0] && randomImg[0].attribs){
thumbnail = hostname + randomImg[0].attribs.src let imgSrcs = []
console.log('random img '+thumbnail) 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){ if(favicon && favicon[0] && favicon[0].attribs){
thumbnail = hostname + favicon[0].attribs.href imagesWeWant.push(favicon[0].attribs.href)
console.log('favicon '+thumbnail)
} }
//Grab the shortcut icon //Grab the shortcut icon
if(shortcutIcon && shortcutIcon[0] && shortcutIcon[0].attribs){ if(shortcutIcon && shortcutIcon[0] && shortcutIcon[0].attribs){
thumbnail = hostname + shortcutIcon[0].attribs.href imagesWeWant.push(shortcutIcon[0].attribs.href)
console.log('shortcut '+thumbnail)
} }
//Grab the presentation image for the site //Grab the presentation image for the site
if(metaImg && metaImg[0] && metaImg[0].attribs){ if(metaImg && metaImg[0] && metaImg[0].attribs){
thumbnail = metaImg[0].attribs.content imagesWeWant.unshift(metaImg[0].attribs.content)
console.log('ogImg '+thumbnail) }
// 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 return thumbnail
} }

View File

@ -257,7 +257,6 @@ const printResults = true
let UserTest = require('@models/User') let UserTest = require('@models/User')
let NoteTest = require('@models/Note') let NoteTest = require('@models/Note')
let AuthTest = require('@helpers/Auth') let AuthTest = require('@helpers/Auth')
Auth.test() Auth.test()
UserTest.keyPairTest('genMan30', '1', printResults) UserTest.keyPairTest('genMan30', '1', printResults)
.then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults)) .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
@ -266,7 +265,6 @@ UserTest.keyPairTest('genMan30', '1', printResults)
Auth.testTwoFactor() Auth.testTwoFactor()
}) })
//Test //Test
app.get('/api', (req, res) => res.send('Solidscribe API is up and running')) app.get('/api', (req, res) => res.send('Solidscribe API is up and running'))

View File

@ -325,14 +325,14 @@ Attachment.downloadFileFromUrl = (url) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(url == null){ if(url == null || url == undefined || url == ''){
resolve(null) resolve(null)
} }
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15) const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
const extension = '.'+url.split('.').pop() //This is throwing an error let extension = ''
let fileName = random+'_scrape'+extension let fileName = random+'_scrape'
const thumbPath = 'thumb_'+fileName let thumbPath = 'thumb_'+fileName
console.log('Scraping image url') console.log('Scraping image url')
console.log(url) console.log(url)
@ -347,6 +347,8 @@ Attachment.downloadFileFromUrl = (url) => {
.on('response', res => { .on('response', res => {
console.log(res.statusCode) console.log(res.statusCode)
console.log(res.headers['content-type']) 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)) .pipe(fs.createWriteStream(filePath+thumbPath))
.on('close', () => { .on('close', () => {
@ -354,14 +356,17 @@ Attachment.downloadFileFromUrl = (url) => {
//resize image if its real big //resize image if its real big
gm(filePath+thumbPath) gm(filePath+thumbPath)
.resize(550) //Resize to width of 550 px .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) { .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 .query(`INSERT INTO attachment
(note_id, user_id, attachment_type, text, url, last_indexed, file_location) (note_id, user_id, attachment_type, text, url, last_indexed, file_location)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?)`,
[noteId, userId, 1, 'Processing...', url, created, null]) [noteId, userId, 1, url, url, created, null])
.then((rows, fields) => { .then((rows, fields) => {
//Set two bigger variables then return request for processing //Set two bigger variables then return request for processing
request = rp(options) request = rp(options)

View File

@ -681,6 +681,7 @@ Note.get = (userId, noteId, masterKey) => {
note_raw_text.text, note_raw_text.text,
note_raw_text.salt, note_raw_text.salt,
note_raw_text.updated as updated, note_raw_text.updated as updated,
GROUP_CONCAT(DISTINCT(tag.text) ORDER BY tag.text DESC) AS tags,
note.id, note.id,
note.user_id, note.user_id,
note.created, 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) 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 attachment ON (note.id = attachment.note_id)
LEFT JOIN user as shareUser ON (note.share_user_id = shareUser.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) => { .then((rows, fields) => {

View File

@ -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' // Get all tags for a note and concatinate into a string 'all, tags, like, this'
// //

View File

@ -9,7 +9,7 @@ const speakeasy = require('speakeasy')
let User = module.exports = {} 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 //Login a user, if that user does not exist create them
//Issues login token //Issues login token

View File

@ -50,6 +50,12 @@ router.post('/get', function (req, res) {
.then( data => res.send(data) ) .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 //Get all the tags for this user in order of usage
router.post('/usertags', function (req, res) { router.post('/usertags', function (req, res) {
Tags.userTags(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters) Tags.userTags(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters)