mirror of
https://github.com/kevin-DL/build-gmail-clone-with-vue-3.git
synced 2026-01-11 18:54:31 +00:00
Delete everything except the starter stuff
This commit is contained in:
57
src/App.vue
57
src/App.vue
@@ -1,23 +1,52 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<MailScreen />
|
||||
</template>
|
||||
<template #fallback>
|
||||
Loading...
|
||||
</template>
|
||||
</Suspense>
|
||||
</div>
|
||||
<h1>VMail Inbox</h1>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import MailScreen from '@/components/MailScreen.vue';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
MailScreen
|
||||
data(){
|
||||
return {
|
||||
"emails": [
|
||||
{
|
||||
"id": 1,
|
||||
"from": "team@vuemastery.com",
|
||||
"subject": "What's up with Vue 3.0? Here's how to find out from Evan You",
|
||||
"body": "The opening keynote of VueConf US this year was Evan You (the creator of Vue), giving his State of the Vuenion address. He walked us through the journey of getting Vue 3 from a prototype to a reality the past year. He also dove into Vue's overall growth in the community.",
|
||||
"sentAt": "2020-03-27T18:25:43.511Z",
|
||||
"archived": false,
|
||||
"read": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"from": "jeffrey@vuetraining.net",
|
||||
"subject": "Learn by doing - Vue 3 Zero to Intermediate in 8 weeks",
|
||||
"body": "Building projects is one of the most effective ways to learn - and _the_ most effective way _remember_ what you've learned - but it can be frustrating.\n\nThis 8-week course takes the pain out of 'learning by doing'.\n\nEach week we give you\n\n* a project that will grow your skills without overwhelming you\n* links to hand-picked resources, such as Vue Mastery videos, that share the knowledge you'll need for the project (no more useless rabbit holes)\n* answers to any and all questions you have while working\n* feedback on your completed code (so you're only learning good habits)\n\nOur instructors are standing by to answer your questions.\n\nReady to learn?",
|
||||
"sentAt": "2020-05-20T18:25:43.511Z",
|
||||
"archived": false,
|
||||
"read": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"from": "damian@dulisz.com",
|
||||
"subject": "#177: Updated Vue.js Roadmap; Vuex v4.0.0-alpha.1 has been released; Kia King Ishii join the core team; Nuxt v2.12 released; Videos from Vue.js Amsterdam 2020 are here!",
|
||||
"body": "First of all, lets congratulate Kia King Ishii on joining the Vue.js core team! 🎉 He has been doing an incredible job building vuex-orm and will now focus on working on the next versions of Vuex.\n\nSpeaking of which – Vuex v4.0.0-alpha.1 has just been released! This is the version of Vuex that will work with Vue 3.0 but keep the familiar API you know from the current version.",
|
||||
"sentAt": "2020-03-18T18:25:43.511Z",
|
||||
"archived": false,
|
||||
"read": true
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"from": "anthony@vuejsdevelopers.com",
|
||||
"subject": "'Vue 3 Release Roadmap' + 6 more must-read articles from this week",
|
||||
"body": "Newsletter Issue #161",
|
||||
"sentAt": "2020-03-24T18:25:43.511Z",
|
||||
"archived": true,
|
||||
"read": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div class="bulk-action-bar">
|
||||
<span class="checkbox">
|
||||
<input type="checkbox"
|
||||
:checked="allAreSelected"
|
||||
:class="[partialSelection ? 'partial-check' : '']"
|
||||
@click="bulkSelect">
|
||||
</span>
|
||||
|
||||
<span class="buttons">
|
||||
<button @click="emailSelection.markRead()"
|
||||
:disabled="Array.from(emailSelection.emails).every(e => e.read)">
|
||||
Mark Read
|
||||
</button>
|
||||
<button @click="emailSelection.markUnread()"
|
||||
:disabled="Array.from(emailSelection.emails).every(e => !e.read)">
|
||||
Mark Unread
|
||||
</button>
|
||||
<button v-if="selectedScreen == 'inbox'"
|
||||
@click="emailSelection.archive()"
|
||||
:disabled="numberSelected == 0">
|
||||
Archive
|
||||
</button>
|
||||
<button v-else
|
||||
@click="emailSelection.moveToInbox()"
|
||||
:disabled="numberSelected == 0">
|
||||
Move to Inbox
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useEmailSelection } from '../composition/useEmailSelection';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
setup(props){
|
||||
let emailSelection = useEmailSelection();
|
||||
|
||||
let numberSelected = computed(() => {
|
||||
return emailSelection.emails.size;
|
||||
})
|
||||
let allAreSelected = computed(() => {
|
||||
return props.emails.length == numberSelected.value && numberSelected.value !== 0;
|
||||
})
|
||||
let partialSelection = computed(() => {
|
||||
return numberSelected.value > 0 && !allAreSelected.value;
|
||||
})
|
||||
|
||||
let bulkSelect = function(){
|
||||
if(allAreSelected.value) {
|
||||
emailSelection.clear();
|
||||
} else {
|
||||
emailSelection.addMultiple(props.emails)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
partialSelection,
|
||||
allAreSelected,
|
||||
bulkSelect,
|
||||
emailSelection,
|
||||
numberSelected
|
||||
}
|
||||
},
|
||||
props: {
|
||||
emails: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
selectedScreen: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<button @click="selectScreen('inbox');"
|
||||
:class="[selectedScreen == 'inbox' ? 'selected' : '']">
|
||||
Inbox View
|
||||
</button>
|
||||
<button @click="selectScreen('archive')"
|
||||
:class="[selectedScreen == 'archive' ? 'selected' : '']">
|
||||
Archived View
|
||||
</button>
|
||||
|
||||
<h1>VMail {{capitalize(selectedScreen)}}</h1>
|
||||
|
||||
<BulkActionBar :emails="filteredEmails"
|
||||
:selectedScreen="selectedScreen" />
|
||||
|
||||
<MailTable :emails="filteredEmails" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
import MailTable from '@/components/MailTable.vue';
|
||||
import BulkActionBar from '@/components/BulkActionBar.vue';
|
||||
import { useEmailSelection } from '../composition/useEmailSelection';
|
||||
|
||||
export default {
|
||||
async setup(){
|
||||
let response = await axios.get('http://localhost:3000/emails');
|
||||
let emails = response.data;
|
||||
let selectedScreen = 'inbox';
|
||||
|
||||
return {
|
||||
emails,
|
||||
selectedScreen,
|
||||
emailSelection: useEmailSelection()
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BulkActionBar,
|
||||
MailTable
|
||||
},
|
||||
methods: {
|
||||
selectScreen(newScreen) {
|
||||
this.selectedScreen = newScreen;
|
||||
this.emailSelection.clear();
|
||||
},
|
||||
capitalize(word) {
|
||||
if(!word || !word.length){ return; }
|
||||
|
||||
return word[0].toUpperCase() + word.slice(1)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedEmails(){
|
||||
return this.emails.sort((e1, e2) => {
|
||||
return e1.sentAt < e2.sentAt ? 1 : -1
|
||||
})
|
||||
},
|
||||
unarchivedEmails(){
|
||||
return this.sortedEmails.filter(e => !e.archived)
|
||||
},
|
||||
archivedEmails(){
|
||||
return this.sortedEmails.filter(e => e.archived)
|
||||
},
|
||||
filteredEmails(){
|
||||
let filters = {
|
||||
inbox: this.unarchivedEmails,
|
||||
archive: this.archivedEmails
|
||||
}
|
||||
return filters[this.selectedScreen]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<table class="mail-table">
|
||||
<tbody>
|
||||
<tr v-for="email in emails"
|
||||
:key="email.id"
|
||||
:class="[email.read ? 'read': '', 'clickable']"
|
||||
@click="openEmail(email)">
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
:checked="emailSelection.emails.has(email)"
|
||||
@click="emailSelection.toggle(email)" />
|
||||
</td>
|
||||
<td>{{email.from}}</td>
|
||||
<td>
|
||||
<p><strong>{{email.subject}}</strong> - {{email.body}}</p>
|
||||
</td>
|
||||
<td class="date">{{format(new Date(email.sentAt), 'MMM do yyyy')}}</td>
|
||||
<td><button @click="archiveEmail(email)">Archive</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ModalView v-if="openedEmail" :closeModal="() => { openedEmail = null; }">
|
||||
<MailView :email="openedEmail"
|
||||
:changeEmail="(args) => changeEmail(openedEmail, args)" />
|
||||
</ModalView>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format } from 'date-fns';
|
||||
import MailView from '@/components/MailView.vue';
|
||||
import ModalView from '@/components/ModalView.vue';
|
||||
import { useEmailSelection } from '../composition/useEmailSelection';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
async setup(){
|
||||
return {
|
||||
format,
|
||||
openedEmail: null,
|
||||
emailSelection: useEmailSelection()
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MailView,
|
||||
ModalView,
|
||||
},
|
||||
methods: {
|
||||
openEmail(email){
|
||||
this.openedEmail = email;
|
||||
|
||||
if(email) {
|
||||
email.read = true
|
||||
axios.put(`http://localhost:3000/emails/${email.id}`, email)
|
||||
}
|
||||
},
|
||||
archiveEmail(email){
|
||||
email.archived = true;
|
||||
axios.put(`http://localhost:3000/emails/${email.id}`, email)
|
||||
},
|
||||
changeEmail(email, {indexChange, toggleArchive, toggleRead, save, closeModal}) {
|
||||
if(toggleArchive) { email.archived = !email.archived }
|
||||
if(toggleRead) { email.read = !email.read }
|
||||
if(save) { axios.put(`http://localhost:3000/emails/${email.id}`, email) }
|
||||
if(closeModal) { this.openedEmail = null; return null; }
|
||||
|
||||
if(indexChange) {
|
||||
let index = this.emails.findIndex(e => e == email);
|
||||
this.openEmail(this.emails[index + indexChange])
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
emails: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<div class="email-display">
|
||||
<div class="toolbar">
|
||||
<button @click="toggleArchive">{{email.archived ? 'Move to Inbox (e)' : 'Archive (e)'}}</button>
|
||||
<button @click="toggleRead">{{email.read ? 'Mark Unread (r)' : 'Mark Read (r)'}}</button>
|
||||
<button @click="goNewer">Newer (k)</button>
|
||||
<button @click="goOlder">Older (j)</button>
|
||||
</div>
|
||||
|
||||
<h2 class="mb-0">Subject: <strong>{{email.subject}}</strong></h2>
|
||||
<div><em>From {{email.from}} on {{format(new Date(email.sentAt), 'MMM do yyyy')}}</em></div>
|
||||
<div v-html="marked(email.body)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format } from 'date-fns';
|
||||
import marked from 'marked';
|
||||
import { useKeydown } from '../composition/useKeydown';
|
||||
|
||||
export default {
|
||||
setup({changeEmail}){
|
||||
let toggleArchive = () => changeEmail({toggleArchive: true, save: true, closeModal: true})
|
||||
let toggleRead = () => changeEmail({toggleRead: true, save: true})
|
||||
let goNewer = () => changeEmail({indexChange: -1})
|
||||
let goOlder = () => changeEmail({indexChange: 1})
|
||||
let goNewerAndArchive = () => changeEmail({indexChange: -1, toggleArchive: true})
|
||||
let goOlderAndArchive = () => changeEmail({indexChange: 1, toggleArchive: true})
|
||||
|
||||
useKeydown([
|
||||
{key: 'e', fn: toggleArchive},
|
||||
{key: 'r', fn: toggleRead},
|
||||
{key: 'k', fn: goNewer},
|
||||
{key: 'j', fn: goOlder},
|
||||
{key: '[', fn: goNewerAndArchive},
|
||||
{key: ']', fn: goOlderAndArchive}
|
||||
])
|
||||
|
||||
return {
|
||||
format,
|
||||
marked,
|
||||
goOlder,
|
||||
goNewer,
|
||||
toggleRead,
|
||||
toggleArchive
|
||||
}
|
||||
},
|
||||
props: {
|
||||
email: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
changeEmail: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<div class="modal">
|
||||
<div class="overlay" @click="closeModal"></div>
|
||||
<div class="modal-card">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useKeydown } from '../composition/useKeydown';
|
||||
|
||||
export default {
|
||||
setup({closeModal}){
|
||||
useKeydown([{key: 'Escape', fn: closeModal}])
|
||||
},
|
||||
props: {
|
||||
closeModal: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,47 +0,0 @@
|
||||
import { reactive } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
let emailSet = new Set()
|
||||
|
||||
export const useEmailSelection = function(){
|
||||
const emails = reactive(emailSet)
|
||||
|
||||
const forSelected = (fn) => {
|
||||
emails.forEach(email => {
|
||||
fn(email)
|
||||
axios.put(`http://localhost:3000/emails/${email.id}`, email)
|
||||
})
|
||||
}
|
||||
const clear = () => {
|
||||
emails.clear();
|
||||
}
|
||||
const toggle = (id) => {
|
||||
if(emails.has(id)) {
|
||||
emails.delete(id)
|
||||
} else {
|
||||
emails.add(id);
|
||||
}
|
||||
}
|
||||
const addMultiple = (newEmails) => {
|
||||
newEmails.forEach(email => {
|
||||
emails.add(email)
|
||||
})
|
||||
}
|
||||
const markRead = () => { forSelected(e => e.read = true )}
|
||||
const markUnread = () => { forSelected(e => e.read = false )}
|
||||
const archive = () => { forSelected(e => e.archived = true); clear();}
|
||||
const moveToInbox = () => { forSelected(e => e.archived = false); clear();}
|
||||
|
||||
return {
|
||||
emails,
|
||||
clear,
|
||||
toggle,
|
||||
addMultiple,
|
||||
markRead,
|
||||
markUnread,
|
||||
archive,
|
||||
moveToInbox
|
||||
}
|
||||
}
|
||||
|
||||
export default useEmailSelection;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { onBeforeUnmount } from 'vue';
|
||||
|
||||
export const useKeydown = function(keyCombos) {
|
||||
let onkey = function(event) {
|
||||
let kc = keyCombos.find(({key, fn}) => key == event.key )
|
||||
if(kc) {
|
||||
kc.fn()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onkey);
|
||||
|
||||
onBeforeUnmount(()=> {
|
||||
window.removeEventListener('keydown', onkey);
|
||||
})
|
||||
}
|
||||
|
||||
export default useKeydown;
|
||||
Reference in New Issue
Block a user