I need to get back into using git. The hell is wrong with me!?
This commit is contained in:
83
client/src/pages/AttachmentsPage.vue
Normal file
83
client/src/pages/AttachmentsPage.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="ui basic segment no-fluf-segment">
|
||||
<div class="ui grid">
|
||||
|
||||
<div class="ui sixteen wide column">
|
||||
<h2 class="ui header">
|
||||
<i class="folder icon"></i>
|
||||
<div class="content">
|
||||
Attachments
|
||||
<div class="sub header">Files and scraped web pages from notes</div>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="ui segment" v-if="searchParams.noteId">
|
||||
Showing Attachments for note. <div class="ui button" v-on:click="clearNote">Clear</div>
|
||||
</div>
|
||||
|
||||
<div class="ui basic segment">
|
||||
<attachment-display
|
||||
v-for="item in attachments"
|
||||
:item="item"
|
||||
:key="item.id"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'attachment-display': require('@/components/AttachmentDisplayCard').default,
|
||||
},
|
||||
data: function(){
|
||||
return {
|
||||
attachments: [],
|
||||
searchParams: {}
|
||||
}
|
||||
},
|
||||
beforeCreate: function(){
|
||||
//
|
||||
// Perform Login check
|
||||
//
|
||||
this.$parent.loginGateway()
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
//Mount notes on load if note ID is set
|
||||
if(this.$route.params && this.$route.params.id){
|
||||
const inputNoteId = this.$route.params.id
|
||||
this.searchParams['noteId'] = inputNoteId
|
||||
}
|
||||
|
||||
this.searchAttachments()
|
||||
},
|
||||
methods: {
|
||||
clearNote(){
|
||||
this.$router.push('/attachments/')
|
||||
delete this.searchParams.noteId
|
||||
this.searchAttachments()
|
||||
},
|
||||
searchAttachments (){
|
||||
|
||||
axios.post('/api/attachment/search', this.searchParams)
|
||||
.then( results => {
|
||||
this.attachments = results.data
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css" scoped>
|
||||
.attachment-display-area {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 5%;
|
||||
}
|
||||
</style>
|
@@ -1,13 +1,310 @@
|
||||
<style type="text/css" scoped>
|
||||
|
||||
.hero {
|
||||
background-size: 50%;
|
||||
background-color: #0a2f13;
|
||||
background: linear-gradient(270deg, #21ba45, #3710a4);
|
||||
background-size: 400% 400%;
|
||||
overflow: hidden;
|
||||
|
||||
-webkit-animation: fadeorama 16s ease infinite;
|
||||
-moz-animation: fadeorama 16s ease infinite;
|
||||
animation: fadeorama 16s ease infinite;
|
||||
}
|
||||
.lightly-padded {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.massive-text {
|
||||
color: white;
|
||||
font-size: 4rem;
|
||||
}
|
||||
.blinking {
|
||||
animation:blinkingText 1.5s linear infinite;
|
||||
}
|
||||
@keyframes blinkingText{
|
||||
0%{ opacity: 0.9; }
|
||||
50%{ opacity: 0; }
|
||||
100%{ opacity: 0.9; }
|
||||
}
|
||||
.subtext {
|
||||
border-bottom: 1px solid white;
|
||||
border-right: 1px solid white;
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
.stand-out {
|
||||
color: white;
|
||||
text-shadow:
|
||||
2px 2px 1px black,
|
||||
-2px -2px 1px black,
|
||||
-2px 2px 1px black,
|
||||
2px -2px 1px black;
|
||||
}
|
||||
h2, h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes fadeorama {
|
||||
0%{background-position:0% 50%}
|
||||
50%{background-position:100% 50%}
|
||||
100%{background-position:0% 50%}
|
||||
}
|
||||
@-moz-keyframes fadeorama {
|
||||
0%{background-position:0% 50%}
|
||||
50%{background-position:100% 50%}
|
||||
100%{background-position:0% 50%}
|
||||
}
|
||||
@keyframes fadeorama {
|
||||
0%{background-position:0% 50%}
|
||||
50%{background-position:100% 50%}
|
||||
100%{background-position:0% 50%}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui container">
|
||||
<h1>Welcome</h1>
|
||||
<div class="lightly-padded">
|
||||
<div class="ui centered vertically divided stackable grid">
|
||||
|
||||
<div class="row hero fadeBg" :style="{ 'height':(height+'px') }">
|
||||
|
||||
<!-- All marketing images if you need to review -->
|
||||
<div v-if="false" class="sixteen wide column">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/add.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/gardening.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/growth.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/icecream.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/investing.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/onboarding.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/robot.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/solution.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/watching.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/cloud.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/grandma.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/hamburger.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/idea.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/notebook.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/plan.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/secure.svg" alt="">
|
||||
<img loading="lazy" width="10%" src="/api/static/assets/marketing/void.svg" alt="">
|
||||
</div>
|
||||
|
||||
<div class="one wide large screen only column"></div>
|
||||
|
||||
<!-- desktop column - large screen only -->
|
||||
<div class="seven wide middle aligned left aligned column">
|
||||
<h2 class="massive-text">Take Notes, <br>Like Never Before</h2>
|
||||
<h3 class="subtext">
|
||||
Using an online note application <i class="i cursor icon blinking"></i>
|
||||
</h3>
|
||||
<br>
|
||||
<i class="huge inverted chevron circle down icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="eight wide middle aligned left aligned column">
|
||||
<img loading="lazy" width="90%" src="/api/static/assets/marketing/notebook.svg" alt="The Venus fly laptop about to capture another victim">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<h2>Everyone has knowledge that need to be expressed</h2>
|
||||
<h3>Utilize action potential to create notes by encoding raw brainwaves converted to written language</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/idea.svg" alt="Explosion of New Ideas">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/gardening.svg" alt="Pruning the mind garden">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Dream it, then do it</h2>
|
||||
<h3>Easily record your unlimited imagination. Ideas, stories, notes, plays, poems anything, that can reasonably be put into text</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered green row">
|
||||
<div class="six wide column">
|
||||
<h2>Unbridled Input</h2>
|
||||
<h3>Revolutionary technology allows the use of any keyboard with up to 395 keys</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/add.svg" alt="A shpere of newness">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/solution.svg" alt="Hypercube of Solutions">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Solutions with the Internet</h2>
|
||||
<h3>With the power to save any combination of letters, you can easily inscribe thoughts</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<h2>Search your data</h2>
|
||||
<h3>Type in a word and find that same word but somewhere else</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/cloud.svg" alt="Girl falling into the spiral of digital chaos">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/plan.svg" alt="Scheme for planetary destruction">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Embrace the Void</h2>
|
||||
<h3>Remove unnecessary clutter for your brain and save it to the cloud, allowing you to easily embrace the gaping abyss</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<h2>Space for Growth</h2>
|
||||
<h3>Groom a clear path for new expressions and innovations. Elevate your being and lower your cholesterol</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/growth.svg" alt="Endless progress at the cost of sanity and health">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/onboarding.svg" alt="Shrunken man near giant tablet">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Become your Data</h2>
|
||||
<h3>We exist as electrical impulses, no different from data on a computer</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- set -->
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<h2>Ice Cream</h2>
|
||||
<h3>Get excited without all the screaming</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/icecream.svg" alt="Emergence of a 4th dimensional being perceived as a large ice cream ">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/secure.svg" alt="marketing mumbo jumbo">
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<h2>Data Backups</h2>
|
||||
<h3>Nothing you do will be forgotten.<br>You can never take back what you have done</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle aligned centered row">
|
||||
<div class="six wide column">
|
||||
<h2>Freedom to unleash yourself</h2>
|
||||
<h3>Imagine an awakening of what could be</h3>
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/grandma.svg" alt="Drinking the blood of the elderly">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- final slide -->
|
||||
<div class="middle aligned centered green row">
|
||||
<div class="twelve wide center aligned column">
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<h2>What are you waiting for?<br>Sign up now.</h2>
|
||||
<br>
|
||||
<router-link class="ui huge white labeled icon button" to="/login">
|
||||
<i class="plug icon"></i>Sign Me Up!
|
||||
</router-link>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
OR
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<span class="ui button" v-on:click="showRealInformation">View real information about this site</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="realInformation" class="middle aligned centered row" ref="real">
|
||||
<div class="six wide column">
|
||||
<h2 class="ui center aligned">
|
||||
What is this really?
|
||||
</h2>
|
||||
<h3>Its just a little web app for taking notes. This page is mocking the "over the top" marketing sites use to sell their products.</h3>
|
||||
<p>
|
||||
This App exists because I was tired of all my data being owned by big companies, having it farmed out for marketing, and leaving the contents of my life exposed to corporations.
|
||||
</p>
|
||||
<p>
|
||||
If you want to give it a shot, feel free to make an account. There are no ads. None of this data is shared or public. I don't make any money.
|
||||
</p>
|
||||
<p>
|
||||
If you see anything broken or want to see a feature implemented, I'm open to suggestions. <i class="thumbs up icon"></i>
|
||||
</p>
|
||||
<p>Hero Slide Photo Credit - <a target="_blank" href="https://unsplash.com/@tkaslik14">https://unsplash.com/@tkaslik14</a></p>
|
||||
<p>Generic Marketing Images - <a target="_blank" href="https://undraw.co/">https://unDraw.co/</a></p>
|
||||
</div>
|
||||
<div class="four wide column">
|
||||
<img loading="lazy" width="100%" src="/api/static/assets/marketing/watching.svg" alt="Drinking the blood of the elderly">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WelcomePage'
|
||||
name: 'WelcomePage',
|
||||
data(){
|
||||
return {
|
||||
height: null,
|
||||
realInformation: false,
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
//Don't change hero banner on mobile
|
||||
if(!this.$store.getters.getIsUserOnMobile){
|
||||
let windowHeight = window.innerHeight
|
||||
this.height = windowHeight - (windowHeight * 0.10)
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
showRealInformation(){
|
||||
|
||||
|
||||
this.realInformation = !this.realInformation
|
||||
if(this.realInformation){
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.real.scrollIntoView({'behavior':'smooth'})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,41 +1,41 @@
|
||||
<template>
|
||||
<div class="ui container">
|
||||
<h3>Login</h3>
|
||||
<p>Begin the login process by typing your username or email.</p>
|
||||
<p>To create an account, type in the username you want to use followed by the password.</p>
|
||||
<p>You will remain logged in on this browser, until you log out.</p>
|
||||
|
||||
<div class="ui segment" v-on:keyup.enter="submit">
|
||||
<div class="ui large form">
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" v-if="username.length > 0">
|
||||
<div class="ui input">
|
||||
<input v-model="password" type="password" name="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui massive compact fluid green submit button">Login</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui basic segment no-fluf-segment">
|
||||
<div class="ui grid">
|
||||
<div class="ui sixteen wide column">
|
||||
|
||||
|
||||
<p><b>Create an account:</b> type in the username you want to use followed by the password.</p>
|
||||
|
||||
<div class="ui segment" v-on:keyup.enter="submit">
|
||||
<div class="ui large form">
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="username" type="text" name="email" placeholder="Username or E-mail address" autofocus>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui input">
|
||||
<input v-model="password" type="password" name="password" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{ 'disabled':(username.length == 0 || password.length == 0)}" v-on:click="submit" class="ui massive compact fluid green submit button">Login</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>You will remain logged in on this browser, until you log out.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
//ajax calls
|
||||
import axios from 'axios';
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data () {
|
||||
return {
|
||||
message: 'Login stuff',
|
||||
enabled: false,
|
||||
username: '',
|
||||
password: ''
|
||||
@@ -61,26 +61,23 @@
|
||||
|
||||
axios.post('/api/user/login', data)
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
if(response.data.success){
|
||||
|
||||
const token = response.data.token
|
||||
const username = response.data.username
|
||||
|
||||
vm.$store.commit('setLoginToken', {token, username})
|
||||
|
||||
//Redirect user to notes section after login
|
||||
this.$router.push('/notes')
|
||||
vm.$router.push('/notes')
|
||||
} else {
|
||||
vm.$store.commit('destroyLoginToken')
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('There was an error with log in request')
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'getRudeMessage'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,120 +1,132 @@
|
||||
<template>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui basic segment no-fluf-segment">
|
||||
|
||||
<div class="ui equal width grid">
|
||||
<div class="ui grid" :class="{ 'mush-it-up':showOneColumn() }" ref="content">
|
||||
|
||||
<!-- <div class="ui row">{{ $store.getters.getIsUserOnMobile ? 'Mobile Device':'Normal Browser' }}</div> -->
|
||||
|
||||
<!-- mobile search menu -->
|
||||
<div class="ui mobile only row">
|
||||
<!-- Small screen new note button -->
|
||||
<div class="ui four wide column">
|
||||
<div @click="createNote" class="ui fluid green icon button">
|
||||
<i class="plus icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui twelve wide column">
|
||||
<div class="ui form">
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- search menu -->
|
||||
<div class="ui large screen only row">
|
||||
|
||||
<div class="ui two wide column">
|
||||
<div @click="createNote" class="ui fluid green button">
|
||||
<i class="plus icon"></i>
|
||||
New Note
|
||||
</div>
|
||||
</div>
|
||||
<!-- Note filter options -->
|
||||
<div class="row">
|
||||
|
||||
<div class="ui five wide column">
|
||||
<div
|
||||
:class="{ 'sixteen wide column':showOneColumn(), 'eight wide column':!showOneColumn() }"
|
||||
>
|
||||
<div class="ui form">
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui nine wide column">
|
||||
|
||||
<router-link class="ui basic button" to="/help">Help</router-link>
|
||||
|
||||
<div v-on:click="toggleNightMode" class="ui basic icon button">
|
||||
<i class="eye icon"></i> Dark Theme:
|
||||
<span v-if="$store.getters.getIsNightMode">On</span>
|
||||
<span v-else>Off</span>
|
||||
</div>
|
||||
|
||||
<div v-on:click="toggleArchivedVisible" class="ui basic icon button">
|
||||
<i class="archive icon"></i> Archived:
|
||||
<span v-if="showArchived == 1">Visible</span>
|
||||
<span v-else>Hidden</span>
|
||||
</div>
|
||||
|
||||
<div class="ui right floated basic button"
|
||||
data-tooltip="Log Out" data-position="left center"
|
||||
v-on:click="destroyLoginToken"><i class="user icon"></i> {{username}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui row">
|
||||
|
||||
<!-- tags display -->
|
||||
<div class="ui two wide large screen only column" v-if="activeNoteId1 == null && activeNoteId2 == null">
|
||||
<div class="ui small basic fluid button" @click="reset">
|
||||
<i class="undo icon"></i>Reset Filters
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui section list">
|
||||
<div class="item" v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
|
||||
<div class="ui clickable basic fluid large label" :class="{ 'green':(searchTags.includes(tag.id)) }">
|
||||
{{ucWords(tag.text)}} <div class="detail">{{tag.usages}}</div>
|
||||
<div class="fields">
|
||||
<div class="ten wide field">
|
||||
<input v-model="searchTerm" @keyup="searchKeyUp" @:keyup.enter="search" placeholder="Search Notes" />
|
||||
</div>
|
||||
<div class="six wide field">
|
||||
<span class="ui fluid green button"
|
||||
v-if="showClear"
|
||||
@click="reset">
|
||||
<i class="undo icon"></i>Reset Filters
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Note title cards -->
|
||||
<div class="ui fourteen wide computer sixteen wide mobile column">
|
||||
<h2>
|
||||
({{notes.length}}) <fast-filters />
|
||||
<div
|
||||
:class="{ 'sixteen wide column':showOneColumn(), 'eight wide column':!showOneColumn() }"
|
||||
>
|
||||
<h2 class="ui right floated">
|
||||
<fast-filters />
|
||||
</h2>
|
||||
|
||||
<h3 v-if="searchTerm.length > 0 && notes.length == 0">No notes found. Check your spelling, try completing the word or using a different phrase.</h3>
|
||||
|
||||
<h3 v-if="searchTerm.length == 0 && notes.length == 0">Create your first note. Click the "New Note" button.</h3>
|
||||
|
||||
<div v-if="working"><div class="ui active inline loader"></div> Working...</div>
|
||||
|
||||
<div v-if="notes !== null && !working"
|
||||
class="note-card-display-area"
|
||||
:class="{'one-column':(activeNoteId1 != null || activeNoteId2 != null )}
|
||||
">
|
||||
<note-title-display-card
|
||||
v-for="note in notes"
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="commonTags.length > 0" class="sixteen wide column">
|
||||
<span v-for="tag in commonTags" @click="toggleTagFilter(tag.id)">
|
||||
<span class="ui clickable basic label" :class="{ 'green':(searchTags.includes(tag.id)) }">
|
||||
{{ucWords(tag.text)}} <span class="detail">{{tag.usages}}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 v-if="fastFilters['withLinks'] == 1">Only showing notes containing Links</h2>
|
||||
<h2 v-if="fastFilters['withTags'] == 1">Only showing notse with Tags</h2>
|
||||
<h2 v-if="fastFilters['onlyArchived'] == 1">Only showing Archived notes.</h2>
|
||||
|
||||
<!-- Note title card display -->
|
||||
<div class="sixteen wide column">
|
||||
<h3 v-if="searchTerm.length > 0 && notes.length == 0">No notes found. Check your spelling, try completing the word or using a different phrase.</h3>
|
||||
|
||||
<h3 v-if="searchTerm.length == 0 && notes.length == 0">Create your first note. Click the "New Note" button.</h3>
|
||||
|
||||
<div v-if="working">
|
||||
<div class="ui active inline loader"></div> Working...
|
||||
</div>
|
||||
|
||||
<!-- Go to one wide column, do not do this on mobile interface -->
|
||||
<div v-if="notes !== null && notes.length > 0"
|
||||
:class="{'one-column':(
|
||||
(activeNoteId1 != null || activeNoteId2 != null) &&
|
||||
!$store.getters.getIsUserOnMobile
|
||||
)}">
|
||||
|
||||
<!-- pinned notes -->
|
||||
<div v-if="containsPinnednotes > 0" class="note-card-section">
|
||||
<h4><i class="green pin icon"></i>Pinned <span v-if="!working">({{containsPinnednotes}})</span></h4>
|
||||
<div class="note-card-display-area">
|
||||
<note-title-display-card
|
||||
v-for="note in notes"
|
||||
v-if="note.pinned"
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- normal notes -->
|
||||
<div v-if="containsNormalNotes > 0" class="note-card-section">
|
||||
<h4>Notes <span v-if="!working">({{containsNormalNotes}})</span></h4>
|
||||
<div class="note-card-display-area">
|
||||
<note-title-display-card
|
||||
v-for="note in notes"
|
||||
v-if="note.note_highlights && !note.pinned"
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- found in text -->
|
||||
<div v-if="containsTextResults" class="note-card-section">
|
||||
<h4><i class="green paragraph icon"></i> Found in Text ({{containsTextResults}})</h4>
|
||||
<div class="note-card-display-area">
|
||||
<note-title-display-card
|
||||
v-for="note in notes"
|
||||
v-if="note.note_highlights && note.note_highlights.length"
|
||||
:textResults="true"
|
||||
:onClick="openNote"
|
||||
:data="note"
|
||||
:currently-open="(activeNoteId1 == note.id || activeNoteId2 == note.id)"
|
||||
:key="note.id + note.color + note.note_highlights.length + note.attachment_highlights.length + ' -' + note.tag_highlights.length + '-' +note.title.length + '-' +note.subtext.length"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input-notes v-if="activeNoteId1 != null" :noteid="activeNoteId1" :position="activeNote1Position" />
|
||||
<input-notes v-if="activeNoteId2 != null" :noteid="activeNoteId2" :position="activeNote2Position" />
|
||||
|
||||
<div v-if="openAttachmentEdit">
|
||||
<edit-attachment :note-id="editAttchmentId" :key="editAttchmentId" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios';
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'SearchBar',
|
||||
@@ -122,32 +134,62 @@
|
||||
'input-notes': require('@/components/NoteInputPanel.vue').default,
|
||||
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
|
||||
'fast-filters': require('@/components/FastFilters.vue').default,
|
||||
'edit-attachment': require('@/components/AttachmentEditor.vue').default,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
username:'',
|
||||
initComponent: true,
|
||||
commonTags: [],
|
||||
searchTerm: '',
|
||||
searchTags: [],
|
||||
notes: [],
|
||||
highlights: [],
|
||||
searchDebounce: null,
|
||||
fastFilters: {},
|
||||
showArchived: 0,
|
||||
working: false,
|
||||
//Load up notes in batches
|
||||
firstLoadBatchSize: 30, //First set of rapidly loaded notes
|
||||
batchSize: 100, //Size of batch loaded when user scrolls through current batch
|
||||
batchOffset: 0, //Tracks the current batch that has been loaded
|
||||
loadingBatchTimeout: null, //Limit how quickly batches can be loaded
|
||||
loadingInProgress: false,
|
||||
|
||||
//Clear button is not visible
|
||||
showClear: false,
|
||||
initialPostData: null,
|
||||
currentPostData: null,
|
||||
|
||||
containsNormalNotes: 0,
|
||||
containsPinnednotes: 0,
|
||||
containsTextResults: 0,
|
||||
containsTagResults: 0,
|
||||
containsAttachmentResults: 0,
|
||||
|
||||
//Currently open notes in app
|
||||
activeNoteId1: null,
|
||||
activeNoteId2: null,
|
||||
|
||||
//Position determines how note is Positioned
|
||||
activeNote1Position: 0,
|
||||
activeNote2Position: 0,
|
||||
|
||||
//Attchment to edit. Only 1 for now
|
||||
openAttachmentEdit: false,
|
||||
editAttchmentId: null,
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
|
||||
let username = this.$store.getters.getUsername
|
||||
this.username = this.ucWords(username)
|
||||
this.$parent.loginGateway()
|
||||
|
||||
this.$bus.$on('open_edit_attachment', noteId => {
|
||||
this.openAttachmentEdit = true
|
||||
this.editAttchmentId = noteId
|
||||
})
|
||||
this.$bus.$on('close_edit_attachment', noteId => {
|
||||
this.openAttachmentEdit = false
|
||||
this.editAttchmentId = null
|
||||
})
|
||||
|
||||
this.$bus.$on('close_active_note', position => {
|
||||
this.closeNote(position)
|
||||
@@ -157,24 +199,51 @@
|
||||
})
|
||||
this.$bus.$on('update_fast_filters', newFilter => {
|
||||
this.fastFilters = newFilter
|
||||
this.search()
|
||||
this.search(true, this.batchSize, false)
|
||||
})
|
||||
|
||||
//New note button pushes open note event
|
||||
this.$bus.$on('open_note', noteId => {
|
||||
this.openNote(noteId)
|
||||
})
|
||||
|
||||
//Mount notes on load if note ID is set
|
||||
if(this.$route.params && this.$route.params.id){
|
||||
const id = this.$route.params.id
|
||||
console.log('About to load note ', id)
|
||||
this.openNote(id)
|
||||
}
|
||||
window.addEventListener('scroll', this.onScroll)
|
||||
|
||||
|
||||
//Load tinymce into the page only do it once
|
||||
if(document.querySelectorAll('[data-mceload]').length == 0){
|
||||
let tinyMceIncluder = document.createElement('script')
|
||||
tinyMceIncluder.setAttribute('src', '/api/static/assets/tinymce/tinymce.min.js')
|
||||
tinyMceIncluder.setAttribute('data-mceload','loaded')
|
||||
document.head.appendChild(tinyMceIncluder)
|
||||
}
|
||||
|
||||
},
|
||||
beforeDestroy(){
|
||||
window.removeEventListener('scroll', this.onScroll)
|
||||
},
|
||||
mounted() {
|
||||
|
||||
this.search()
|
||||
//Load a super fast batch
|
||||
this.search(true, this.firstLoadBatchSize, 0)
|
||||
.then( () => {
|
||||
//Load a larger batch once first batch has loaded
|
||||
this.search(false, this.batchSize, true).then( () => {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
methods: {
|
||||
showOneColumn(){
|
||||
//If note 1 or 2 is open, show one column. Or if the user is on mobile
|
||||
return (this.activeNoteId1 != null || this.activeNoteId2 != null) &&
|
||||
!this.$store.getters.getIsUserOnMobile
|
||||
},
|
||||
openNote(id){
|
||||
|
||||
//Do not open same note twice
|
||||
@@ -196,7 +265,6 @@
|
||||
this.activeNote2Position = 2 //Left side of page
|
||||
return
|
||||
}
|
||||
|
||||
//2 notes open
|
||||
if(this.activeNoteId2 != null && this.activeNoteId1 == null){
|
||||
this.activeNoteId1 = id
|
||||
@@ -236,33 +304,116 @@
|
||||
|
||||
this.search()
|
||||
},
|
||||
search(showLoading = true){
|
||||
onScroll(e){
|
||||
|
||||
//Add archived to fast filters
|
||||
this.fastFilters['archived'] = 0
|
||||
if(this.showArchived == 1){
|
||||
this.fastFilters['archived'] = 1
|
||||
}
|
||||
clearTimeout(this.loadingBatchTimeout)
|
||||
this.loadingBatchTimeout = setTimeout(() => {
|
||||
|
||||
let postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
searchTags: this.searchTags,
|
||||
fastFilters: this.fastFilters,
|
||||
}
|
||||
//Distance to bottom of page
|
||||
const bottomOfWindow =
|
||||
Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop)
|
||||
+ window.innerHeight
|
||||
|
||||
//height of page
|
||||
const offsetHeight = this.$refs.content.clientHeight
|
||||
|
||||
//Determine percentage down the page
|
||||
const percentageDown = Math.round( (bottomOfWindow/offsetHeight)*100 )
|
||||
|
||||
// console.log( percentageDown + '%' )
|
||||
|
||||
//If greater than 80 of the way down the page, load the next batch
|
||||
if(percentageDown >= 80){
|
||||
|
||||
console.log('loading batch')
|
||||
this.search(false, this.batchSize, true)
|
||||
}
|
||||
|
||||
}, 50)
|
||||
|
||||
if(showLoading){
|
||||
this.working = true
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
search(showLoading = true, notesInNextLoad = null, mergeExisting = false){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
//Perform search
|
||||
let vm = this
|
||||
axios.post('/api/note/search', postData).
|
||||
then(response => {
|
||||
vm.commonTags = response.data.tags
|
||||
vm.notes = response.data.notes
|
||||
vm.highlights = response.data.highlights
|
||||
this.working = false
|
||||
if(this.loadingInProgress){
|
||||
return resolve()
|
||||
}
|
||||
|
||||
//Remove all filter limits
|
||||
delete this.fastFilters.limitSize
|
||||
delete this.fastFilters.limitOffset
|
||||
|
||||
let postData = {
|
||||
searchQuery: this.searchTerm,
|
||||
searchTags: this.searchTags,
|
||||
fastFilters: this.fastFilters,
|
||||
}
|
||||
|
||||
if(showLoading){
|
||||
this.working = true
|
||||
}
|
||||
|
||||
//Save initial post data on first load
|
||||
if(this.initialPostData == null){
|
||||
this.initialPostData = JSON.stringify(postData)
|
||||
}
|
||||
//If post data is not the same as initial, show clear button
|
||||
if(JSON.stringify(postData) != this.initialPostData){
|
||||
this.showClear = true
|
||||
}
|
||||
|
||||
if(notesInNextLoad && notesInNextLoad > 0){
|
||||
//Create limit based off of the number of notes already loaded
|
||||
postData.fastFilters.limitSize = notesInNextLoad
|
||||
postData.fastFilters.limitOffset = this.batchOffset
|
||||
}
|
||||
|
||||
//Perform search - or die
|
||||
this.loadingInProgress = true
|
||||
axios.post('/api/note/search', postData).
|
||||
then(response => {
|
||||
|
||||
if(!mergeExisting){
|
||||
this.containsNormalNotes = 0
|
||||
this.containsPinnednotes = 0
|
||||
this.containsTextResults = 0
|
||||
this.batchOffset = 0 // Reset batch offset if we are not merging note batches
|
||||
|
||||
this.commonTags = []
|
||||
this.notes = []
|
||||
}
|
||||
|
||||
//Save the number of notes just loaded
|
||||
this.batchOffset += response.data.notes.length
|
||||
|
||||
//Mush the two new sets of data together
|
||||
this.commonTags = this.commonTags.concat(response.data.tags)
|
||||
this.notes = this.notes.concat(response.data.notes)
|
||||
|
||||
|
||||
//Go through each note and see which section to display
|
||||
response.data.notes.forEach(note => {
|
||||
|
||||
if(note.note_highlights.length > 0){
|
||||
this.containsTextResults++
|
||||
return
|
||||
}
|
||||
|
||||
if(note.pinned == 1){
|
||||
this.containsPinnednotes++
|
||||
return
|
||||
}
|
||||
|
||||
this.containsNormalNotes++
|
||||
})
|
||||
|
||||
this.working = false
|
||||
this.loadingInProgress = false
|
||||
|
||||
return resolve(true)
|
||||
})
|
||||
})
|
||||
},
|
||||
searchKeyUp(){
|
||||
@@ -270,19 +421,7 @@
|
||||
clearTimeout(vm.searchDebounce)
|
||||
vm.searchDebounce = setTimeout(() => {
|
||||
vm.search()
|
||||
}, 300)
|
||||
},
|
||||
createNote(event){
|
||||
const title = ''
|
||||
let vm = this
|
||||
|
||||
axios.post('/api/note/create', {title})
|
||||
.then(response => {
|
||||
|
||||
if(response.data && response.data.id){
|
||||
vm.openNote(response.data.id)
|
||||
}
|
||||
})
|
||||
}, 500)
|
||||
},
|
||||
ucWords(str){
|
||||
return (str + '')
|
||||
@@ -291,31 +430,20 @@
|
||||
})
|
||||
},
|
||||
reset(){
|
||||
this.showClear = false
|
||||
this.searchTerm = ''
|
||||
this.searchTags = []
|
||||
this.fastFilters = {}
|
||||
this.$bus.$emit('reset_fast_filters')
|
||||
this.search()
|
||||
},
|
||||
destroyLoginToken() {
|
||||
this.$store.commit('destroyLoginToken')
|
||||
this.$router.push('/')
|
||||
},
|
||||
toggleNightMode(){
|
||||
this.$store.commit('toggleNightMode')
|
||||
},
|
||||
toggleArchivedVisible(){
|
||||
if(this.showArchived == 0){
|
||||
this.showArchived = 1
|
||||
} else {
|
||||
this.showArchived = 0
|
||||
}
|
||||
this.search()
|
||||
this.search(true, this.firstLoadBatchSize, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css" scoped>
|
||||
.mush-it-up {
|
||||
width: calc(50% - 130px);
|
||||
}
|
||||
.detail {
|
||||
float: right;
|
||||
}
|
||||
@@ -323,4 +451,14 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.display-area-title {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
.note-card-section {
|
||||
/*padding-bottom: 15px;*/
|
||||
}
|
||||
.note-card-section + .note-card-section {
|
||||
padding: 15px 0 0;
|
||||
}
|
||||
</style>
|
120
client/src/pages/QuickPage.vue
Normal file
120
client/src/pages/QuickPage.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="ui basic segment no-fluf-segment">
|
||||
<div class="ui grid">
|
||||
|
||||
<div class="ui sixteen wide column">
|
||||
<h2 class="ui header">
|
||||
<i class="coffee icon"></i>
|
||||
<div class="content">
|
||||
Quick
|
||||
<div class="sub header">Add new information with great speed</div>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="ui sixteen wide column">
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<textarea
|
||||
class="quick-note-input"
|
||||
rows="1"
|
||||
ref="fastInput"
|
||||
v-model="newText"
|
||||
v-on:keydown="checkKeyup"
|
||||
placeholder="Push to the top of the quick note."
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div v-on:click="appendQuickNote" class="ui green button">Save (CRTL + Enter)</div>
|
||||
<div v-if="quickNoteId" v-on:click="openNoteEdit" class="ui right floated basic button">Edit</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fun" v-html="savedQuickNoteText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style type="text/css" scoped>
|
||||
.quick-note-input {
|
||||
box-sizing: border-box !important;
|
||||
resize: none !important;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
data: function(){
|
||||
return {
|
||||
newText: '',
|
||||
savedQuickNoteText: '',
|
||||
quickNoteId: null
|
||||
}
|
||||
},
|
||||
beforeCreate: function(){
|
||||
//
|
||||
// Perform Login check
|
||||
//
|
||||
this.$parent.loginGateway()
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
if(this.$refs.fastInput){
|
||||
//Load up note text
|
||||
this.getQuickNote()
|
||||
|
||||
//Set focus to input pane
|
||||
this.$nextTick(() => {
|
||||
this.$refs.fastInput.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loggedIn () {
|
||||
return this.$store.getters.getLoggedIn
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openNoteEdit(){
|
||||
this.$router.push({'path':'/notes/open/'+this.quickNoteId})
|
||||
},
|
||||
checkKeyup(event){
|
||||
|
||||
let element = event.target
|
||||
let padding = 22
|
||||
|
||||
element.style.height = 'auto';
|
||||
element.style.height = (element.scrollHeight + padding) +'px';
|
||||
|
||||
//If command+enter or control+enter is pressed, submit
|
||||
if((event.metaKey || event.ctrlKey) && [13].includes(event.keyCode)){
|
||||
this.appendQuickNote()
|
||||
}
|
||||
},
|
||||
appendQuickNote(){
|
||||
|
||||
//Don't submit empty note
|
||||
if(this.newText.trim() == ''){ return }
|
||||
|
||||
axios.post('/api/quick-note/update', { 'pushText':this.newText } )
|
||||
.then( results => {
|
||||
|
||||
this.newText = '' //Clear text area
|
||||
this.$refs.fastInput.style.height = 'auto' //Back to normal size
|
||||
|
||||
this.savedQuickNoteText = results.data.text
|
||||
this.quickNoteId = results.data.id
|
||||
})
|
||||
},
|
||||
getQuickNote (){
|
||||
axios.post('/api/quick-note/get')
|
||||
.then( results => {
|
||||
this.savedQuickNoteText = results.data.text
|
||||
this.quickNoteId = results.data.id
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
49
client/src/pages/SharePage.vue
Normal file
49
client/src/pages/SharePage.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui container">
|
||||
<div class="fun" :style="{'color':color}" v-if="noteText" v-html="noteText"></div>
|
||||
</div>
|
||||
<div class="ui basic segment"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'SharePage',
|
||||
data(){
|
||||
return {
|
||||
noteText: null,
|
||||
color: '#000'
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
//Mount notes on load if note ID is set
|
||||
if(this.$route.params && this.$route.params.id){
|
||||
const id = this.$route.params.id
|
||||
this.openNote(id)
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
openNote(noteId){
|
||||
axios.post('/api/public/note', {'noteId': noteId})
|
||||
.then( response => {
|
||||
|
||||
let colors = JSON.parse(response.data.color)
|
||||
|
||||
if(colors && colors.noteBackground){
|
||||
document.body.style.background = colors.noteBackground
|
||||
}
|
||||
if(colors && colors.noteText){
|
||||
this.color = colors.noteText
|
||||
}
|
||||
|
||||
this.noteText = response.data.text
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
Reference in New Issue
Block a user