switch front to typescript

This commit is contained in:
grimhilt 2023-04-02 16:44:54 +02:00
parent 8258581435
commit 4b5bd9da67
36 changed files with 10000 additions and 578 deletions

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

7335
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,27 +5,45 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@vueuse/components": "^9.13.0",
"@vueuse/core": "^9.13.0",
"axios": "^1.3.4",
"core-js": "^3.8.3",
"dompurify": "^3.0.1",
"vue": "^3.2.13",
"vue-router": "^4.1.6",
"vuex": "^4.0.2"
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@babel/preset-typescript": "^7.21.4",
"@types/jest": "^27.0.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-plugin-unit-jest": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"@vue/test-utils": "^2.0.0-0",
"@vue/vue3-jest": "^27.0.0-alpha.1",
"@vueuse/components": "^9.13.0",
"@vueuse/core": "^9.13.0",
"axios": "^1.3.4",
"babel-jest": "^27.0.6",
"core-js": "^3.8.3",
"dompurify": "^3.0.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"jest": "^27.0.5",
"prettier": "^2.4.1",
"ts-jest": "^27.0.4",
"typescript": "~4.5.5"
},
"eslintConfig": {
"root": true,
@ -34,14 +52,27 @@
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
"eslint:recommended",
"@vue/typescript/recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
"ecmaVersion": 2020
},
"rules": {
"vue/multi-word-component-names": "off"
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"jest": true
}
}
]
},
"browserslist": [
"> 1%",

View File

@ -10,14 +10,14 @@ import { RouterView } from "vue-router";
</template>
<script>
import Sidebar from './views/sidebar/Sidebar'
import Sidebar from "./views/sidebar/Sidebar";
export default {
name: 'App',
name: "App",
components: {
Sidebar,
},
}
};
</script>
<style>
@ -26,5 +26,4 @@ export default {
height: 100%;
width: 100%;
}
</style>

View File

@ -1,8 +1,6 @@
<template>
<div class="wrapper">
<slot class="badge" name="body">
0
</slot>
<slot class="badge" name="body"> 0 </slot>
</div>
</template>
@ -24,5 +22,4 @@
font-size: 1.1rem;
line-height: 1.4rem;
}
</style>

View File

@ -1,11 +0,0 @@
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import store from './store/store'
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');

10
front/src/main.ts Normal file
View File

@ -0,0 +1,10 @@
import { createApp } from "vue";
import router from "./router";
import App from "./App.vue";
import store from "./store/store";
const app = createApp(App);
app.use(router);
app.use(store);
app.mount("#app");

View File

@ -1,31 +0,0 @@
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home";
import RoomView from "../views/room/RoomView";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
{
path: "/:id",
component: RoomView
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;

30
front/src/router/index.ts Normal file
View File

@ -0,0 +1,30 @@
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import RoomView from "../views/room/RoomView.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
{
path: "/:id",
component: RoomView,
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;

View File

@ -1,7 +0,0 @@
import axios from 'axios'
export default(url='/api') => {
return axios.create({
baseURL: url,
});
}

View File

@ -0,0 +1,7 @@
import axios from "axios";
export default (url = "/api") => {
return axios.create({
baseURL: url,
});
};

View File

@ -1,11 +1,11 @@
import API from './API'
import API from "./API";
export default {
registerAccount(data) {
return API().post('/mail/account', data);
return API().post("/mail/account", data);
},
getAccounts() {
return API().get('/mail/accounts');
return API().get("/mail/accounts");
},
getRooms(mailboxId) {
return API().get(`/mail/${mailboxId}/rooms`);
@ -16,4 +16,4 @@ export default {
getMembers(roomId) {
return API().get(`/mail/${roomId}/members`);
},
}
};

6
front/src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@ -1,7 +0,0 @@
export default class Account {
constructor(_id, _mail) {
this.id = _id;
this.email = _mail;
this.fetched = false;
}
}

View File

@ -1,32 +0,0 @@
export const roomType = {
ROOM: 0,
CHANNEL: 1,
GROUP: 2,
DM: 3,
THREAD: 4,
};
export default class Room {
constructor(room, thread) {
this.id = room.id;
this.user = room.user;
this.userId = room.userId;
this.roomName = room.roomName;
this.mailboxId = room.mailboxId;
this.unseen = room.unseen;
// this.type = room.type;
this.messages = [];
this.messagesFetched = false;
if (!thread) {
this.threads = [
// new Room({id:12, user: this.user, roomName: "thread 1", mailbboxId:this.mailboxId}, true),
// new Room({id:12, user: this.user, roomName: "thread 1", mailbboxId:this.mailboxId}, true),
// new Room({id:12, user: this.user, roomName: "thread 1", mailbboxId:this.mailboxId}, true),
];
} else {
this.threads = [];
}
}
}

View File

@ -1,5 +0,0 @@
export default class Thread {
constructor (roomId, name) {
}
}

View File

@ -0,0 +1,31 @@
export enum RoomType {
ROOM = 0,
CHANNEL = 1,
GROUP = 2,
DM = 3,
THREAD = 4,
};
export interface Room {
id: number;
roomName: string;
roomType: RoomType;
mailboxId: number;
user: string;
userId: number;
unseen: number;
messages: object[];
messagesFetched: boolean;
threads: object[];
}
export interface Account {
id: number;
email: string;
fetched: boolean;
}
export interface Address {
todo: boolean;
}

View File

@ -1,18 +1,40 @@
import API from "@/services/imapAPI";
import { createStore } from "vuex";
import Room from "./models/Room";
import Account from "./models/Account";
import { createStore, Store } from "vuex";
import { Room, Account, Address } from "./models/model";
const store = createStore({
state() {
function createRoom(options): Room {
return {
rooms: [new Room({ id: 12, user: "user", roomName: "room name", mailbboxId: 2, type: 1 })],
id: options.id,
roomName: options.roomName,
roomType: options.roomType,
mailboxId: options.mailboxId,
userId: options.userId,
user: options.user,
unseen: options.unseen,
messages: [],
accounts: [new Account(0, "ALL")],
messagesFetched: false,
threads: [],
};
}
export interface State {
rooms: Room[];
accounts: Account[];
addresses: Address[];
activeAccount: number;
activeRoom: number;
}
// // define injection key todo
// export const key: InjectionKey<Store<State>> = Symbol()
const store = createStore<State>({
state: {
rooms: [createRoom({ id: 12, userId: 1, user: "user", roomName: "room name", mailboxId: 2, roomType: 1 })],
accounts: [{ id: 0, email: "All", fetched: false }],
addresses: [],
activeAccount: 0,
activeRoom: 0,
};
},
mutations: {
setactiveAccount(state, payload) {
@ -27,13 +49,13 @@ const store = createStore({
},
addAccounts(state, payload) {
payload.forEach((account) => {
state.accounts.push(new Account(account.id, account.email));
state.accounts.push({ id: account.id, email: account.email, fetched: false });
});
},
addRooms(state, payload) {
// todo add if not exist
payload.rooms.forEach((room) => {
state.rooms.push(new Room(room));
state.rooms.push(createRoom(room));
});
},
addMessages(state, payload) {

View File

@ -1,15 +1,14 @@
<template>
<img :src="require('../../assets/'+ url)" >
<img :src="require('../../assets/' + url)" />
</template>
<script>
export default {
name: 'BaseAvatar',
name: "BaseAvatar",
props: {
url: String
}
}
url: String,
},
};
</script>
<style scoped>

View File

@ -1,7 +1,7 @@
<script setup>
import { ref, computed, watchEffect } from 'vue'
import Modal from './Modal'
import API from '../../services/imapAPI';
import { ref, computed, watchEffect } from "vue";
import Modal from "./Modal";
import API from "../../services/imapAPI";
const modal = ref(false);
@ -15,45 +15,48 @@ const port = ref(993);
const error = ref("");
const knownHosts = {
'outlook.com': {
'host': 'outlook.office365.com',
"outlook.com": {
host: "outlook.office365.com",
},
'hotmail.com': {
'host': 'outlook.office365.com',
"hotmail.com": {
host: "outlook.office365.com",
},
'live.com': {
'host': 'outlook.office365.com',
"live.com": {
host: "outlook.office365.com",
},
'zoho.com': {
'host': 'imap.zoho.eu',
"zoho.com": {
host: "imap.zoho.eu",
},
'yahoo.com': {
'host': 'imap.mail.yahoo.com',
"yahoo.com": {
host: "imap.mail.yahoo.com",
},
'icloud.com': {
'host': 'imap.mail.me.com',
"icloud.com": {
host: "imap.mail.me.com",
},
}
};
const refHost = computed({
set: (val) => {
host.value = val
}
host.value = val;
},
});
const err = computed({
set: (val) => { error.value = val }
set: (val) => {
error.value = val;
},
});
function validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
function checkError() {
if (!validateEmail(email.value)) {
err.value = "The email is not valid.";
} else if (pwd.value == xoauth.value == xoauth2.value == "") {
} else if (((pwd.value == xoauth.value) == xoauth2.value) == "") {
err.value = "You need at least one authentification method.";
} else if ([pwd.value, xoauth.value, xoauth2.value].filter((val) => val != "").length > 1) {
err.value = "You need only one authentification method.";
@ -74,13 +77,15 @@ function addAccountRequest() {
xoauth2: xoauth2.value,
host: host.value,
port: port.value,
tls: true
tls: true,
};
API.registerAccount(data).then((res) => {
API.registerAccount(data)
.then((res) => {
console.log(res.status);
}).catch((err) => {
console.log(err.request.status)
})
.catch((err) => {
console.log(err.request.status);
});
}
@ -91,57 +96,47 @@ watchEffect(() => {
});
function mailChange() {
if (email.value.includes('@')) {
const domain = email.value.split('@')[1];
if (email.value.includes("@")) {
const domain = email.value.split("@")[1];
if (!knownHosts[domain]) {
refHost.value = ("imap."+domain);
refHost.value = "imap." + domain;
} else {
refHost.value = (knownHosts[domain].host);
refHost.value = knownHosts[domain].host;
}
// todo check if manual
}
}
</script>
<template>
<div>
<button
@click="modal = true"
>
Open Modal!
</button>
<Modal
v-if="modal"
title="Add new account"
@close-modal="modal = false"
>
<button @click="modal = true">Open Modal!</button>
<Modal v-if="modal" title="Add new account" @close-modal="modal = false">
<template v-slot:body>
<div class="field">
<label>Email: </label>
<input @change="mailChange" v-model="email" type="email" required>
<input @change="mailChange" v-model="email" type="email" required />
</div>
<fieldset>
<legend>Authentification method</legend>
<div class="field">
<label>Plain password:</label>
<input v-model="pwd" type="password">
<input v-model="pwd" type="password" />
</div>
<div class="field">
<label>Xoauth:</label>
<input v-model="xoauth" type="text">
<input v-model="xoauth" type="text" />
</div>
<div class="field">
<label>Xoauth2:</label>
<input v-model="xoauth2" type="text">
<input v-model="xoauth2" type="text" />
</div>
</fieldset>
<div class="config">
<input v-model="host" id="host" type="text" placeholder="imap host">
<input v-model="port" id="port" type="number" placeholder="port">
<input v-model="host" id="host" type="text" placeholder="imap host" />
<input v-model="port" id="port" type="number" placeholder="port" />
</div>
<!-- tls -->
<div>
@ -154,7 +149,6 @@ function mailChange() {
</template>
<style>
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
@ -163,7 +157,7 @@ function mailChange() {
}
/* Firefox */
input[type=number] {
input[type="number"] {
appearance: textfield;
}

View File

@ -1,13 +1,13 @@
<script setup>
import { vOnClickOutside } from '@vueuse/components'
import { defineEmits, defineProps } from 'vue'
import { vOnClickOutside } from "@vueuse/components";
import { defineEmits, defineProps } from "vue";
const emit = defineEmits(['close-modal']);
const emit = defineEmits(["close-modal"]);
const props = defineProps({ title: String });
// todo close on escape
function close() {
emit('close-modal');
emit("close-modal");
}
</script>
@ -17,20 +17,15 @@ function close() {
<header class="modal-header">
<h2>{{ props.title }}</h2>
<div class="close-button" @click="close"></div>
</header>
<div class="modal-body">
<slot name="body">
This is the default body!
</slot>
<slot name="body"> This is the default body! </slot>
</div>
</div>
</div>
</template>
<style scoped>
.modal-wrapper {
display: flex;
@ -51,8 +46,8 @@ function close() {
flex-direction: column;
border-radius: 5px;
color: white;
background-color: #1D1D23;
padding: 20px
background-color: #1d1d23;
padding: 20px;
}
.modal-header {
@ -76,5 +71,4 @@ function close() {
float: right;
margin-top: 5px;
}
</style>

View File

@ -1,25 +1,18 @@
<script setup>
import { defineProps } from "vue";
const props = defineProps({ room: Object });
</script>
<template>
<div class="main">
<div>
{{ props }}
type: name / sender
</div>
<div>
action:
threads
message important
</div>
<div>action: threads message important</div>
</div>
</template>
<style scoped>
.main {
border-bottom: 1px solid #505050;

View File

@ -1,7 +1,7 @@
<script setup>
import { defineProps, onMounted, ref } from "vue";
import { decodeEmojis } from "../../utils/string";
import DOMPurify from 'dompurify';
import DOMPurify from "dompurify";
const props = defineProps({ data: Object });
const date = new Date(props.data.date);
@ -78,7 +78,7 @@ iframe {
max-height: 300px;
width: 100%;
max-width: 750px; /* template width being 600px to 640px up to 750px (experiment and test) */
background-color: rgb(234, 234, 234);;
background-color: rgb(234, 234, 234);
}
.left,

View File

@ -11,7 +11,7 @@ let { id } = route.params;
onBeforeMount(async () => {
// get data
let room = store.state.rooms.find((room) => room.id === id);
console.log(room)
console.log(room);
if (!room || room?.fetched === false) {
// todo
// await store.dispatch("fetchMessages", );
@ -68,6 +68,4 @@ onBeforeRouteUpdate(async (to, from) => {
overflow: auto;
margin-bottom: 100px;
}
</style>

View File

@ -6,16 +6,16 @@
</template>
<script>
import Accounts from './accounts/Accounts'
import Rooms from './rooms/Rooms.vue'
import Accounts from "./accounts/Accounts";
import Rooms from "./rooms/Rooms.vue";
export default {
name: 'Sidebar',
name: "Sidebar",
components: {
Accounts,
Rooms,
}
}
},
};
</script>
<style scoped>
@ -30,5 +30,4 @@ div {
}
/* todo setup max size */
</style>

View File

@ -5,22 +5,21 @@
</template>
<script>
import { mapMutations, mapState } from 'vuex'
import { mapMutations, mapState } from "vuex";
export default {
name: 'Account',
components: {
},
name: "Account",
components: {},
props: {
data: {mail: String, id: Number}
data: { mail: String, id: Number },
},
computed: {
...mapState(['activeAccount'])
...mapState(["activeAccount"]),
},
methods: {
...mapMutations(['setactiveAccount'])
}
}
...mapMutations(["setactiveAccount"]),
},
};
</script>
<style scoped>
@ -44,6 +43,4 @@ export default {
border: 2px solid white;
opacity: 0.9;
}
</style>

View File

@ -8,30 +8,28 @@
<span class="divider"></span>
<AddAccountModal />
</div>
</template>
<script>
import { mapState } from 'vuex'
import Account from './Account'
import AddAccountModal from '../../modals/AddAccountModal'
import store from '@/store/store'
import { mapState } from "vuex";
import Account from "./Account";
import AddAccountModal from "../../modals/AddAccountModal";
import store from "@/store/store";
export default {
name: 'Accounts',
name: "Accounts",
components: {
Account,
AddAccountModal
AddAccountModal,
},
computed: {
...mapState(['accounts'])
...mapState(["accounts"]),
},
created() {
store.dispatch('fetchAccounts');
}
}
store.dispatch("fetchAccounts");
},
};
</script>
<style scoped>
@ -40,7 +38,7 @@ export default {
flex-direction: column;
align-items: center;
padding: 5px;
background-color: #2A2A33;
background-color: #2a2a33;
color: white;
}
@ -48,7 +46,6 @@ export default {
width: 32px;
height: 32px;
background-color: yellow !important;
}
.divider {
border-top: 1px solid #bbb;

View File

@ -1,18 +1,12 @@
<template>
<div id="main">
jij
</div>
<div id="main">jij</div>
</template>
<script>
export default {
name: 'All',
components: {
}
}
name: "All",
components: {},
};
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -11,13 +11,10 @@
</template>
<script>
export default {
name: 'Account',
components: {
}
}
name: "Account",
components: {},
};
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -5,29 +5,26 @@
</template>
<script>
import { mapGetters } from 'vuex'
import Room from './Room'
import { mapGetters } from "vuex";
import Room from "./Room";
export default {
name: 'Rooms',
props: {
},
name: "Rooms",
props: {},
components: {
Room
Room,
},
computed: {
...mapGetters(['rooms'])
}
}
...mapGetters(["rooms"]),
},
};
</script>
<style scoped>
.test {
display: flex;
flex-direction: column;
background-color: #24242B;
background-color: #24242b;
overflow: auto;
}
</style>

View File

@ -1,11 +1,10 @@
<script setup>
import { defineProps } from 'vue'
import { defineProps } from "vue";
const props = defineProps({
thread: Object
})
console.log(props.thread)
thread: Object,
});
console.log(props.thread);
</script>
<template>
@ -25,8 +24,9 @@ console.log(props.thread)
color: #a9b2bc;
}
.room:hover, .selected {
background-color: #41474f;;
.room:hover,
.selected {
background-color: #41474f;
border-radius: 8px;
}

43
front/tsconfig.json Normal file
View File

@ -0,0 +1,43 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
// "strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"jest"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

File diff suppressed because it is too large Load Diff