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:
parent
b666bfc197
commit
148b822d49
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ html {
|
||||
height:100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: none;
|
||||
background: var(--body_bg_color);
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
|
@ -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 ) );
|
||||
}( document ) );
|
||||
|
@ -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>
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -11,4 +11,5 @@ common.js
|
||||
bundle.*
|
||||
client/dist*
|
||||
server/public/*
|
||||
client/dist*
|
||||
client/dist*
|
||||
*_scrape*
|
@ -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) => {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) => {
|
||||
|
@ -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'
|
||||
//
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user