refactor frontend code and build pipeline

This commit is contained in:
BuckarooBanzay 2020-04-21 13:28:57 +02:00
parent 800f071d6c
commit 00aa1d606a
21 changed files with 427 additions and 385 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -1,4 +1,4 @@
name: jshint name: jshint_backend
on: [push, pull_request] on: [push, pull_request]
@ -9,12 +9,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: apt - name: apt
run: sudo apt-get install -y nodejs npm run: sudo apt-get install -y nodejs npm
- name: install
- name: npm install
run: npm i run: npm i
- name: jshint
- name: npm test run: npm run jshint_backend
run: npm test

17
.github/workflows/jshint_frontend.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: jshint_frontend
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: apt
run: sudo apt-get install -y nodejs npm
- name: install
run: npm i
- name: jshint
run: npm run jshint_frontend

View File

@ -1,27 +1,30 @@
# Stage 1 testing # Stage 1 testing
FROM node:13.13.0-alpine FROM node:13.13.0-alpine as builder
COPY package.json /data/ COPY . /data
COPY package-lock.json /data/
COPY src /data/src
COPY public /data/public
COPY .git/refs/heads/master /data/public/version.txt
RUN cd /data && npm i && npm test # build
RUN cd /data &&\
npm ci &&\
npm test &&\
npm run jshint_backend &&\
npm run jshint_frontend &&\
npm run bundle
# Stage 2 package # Stage 2 package
FROM node:13.13.0-alpine FROM node:13.13.0-alpine
COPY package.json /data/ COPY . /data
COPY package-lock.json /data/ RUN apk update && apk add curl
COPY src /data/src
COPY public /data/public
COPY .git/refs/heads/master /data/public/version.txt
RUN cd /data && npm i --only=production RUN cd /data && npm ci --only=production
COPY --from=builder /data/public /data/public
WORKDIR /data WORKDIR /data
EXPOSE 8080 EXPOSE 8080
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost:8080/ || exit 1
CMD ["npm", "start"] CMD ["npm", "start"]

16
package-lock.json generated
View File

@ -397,6 +397,13 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true "dev": true
}, },
"fsevents": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
"integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
"dev": true,
"optional": true
},
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -697,6 +704,15 @@
"string_decoder": "~0.10.x" "string_decoder": "~0.10.x"
} }
}, },
"rollup": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.6.1.tgz",
"integrity": "sha512-1RhFDRJeg027YjBO6+JxmVWkEZY0ASztHhoEUEWxOwkh4mjO58TFD6Uo7T7Y3FbmDpRTfKhM5NVxJyimCn0Elg==",
"dev": true,
"requires": {
"fsevents": "~2.1.2"
}
},
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",

View File

@ -4,8 +4,11 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "cd src && jshint . && cd ../public/js/ && jshint .", "test": "echo ok",
"start": "node src/index.js" "jshint_backend": "cd src && jshint .",
"jshint_frontend": "cd public/js && jshint .",
"start": "node src/index.js",
"bundle": "cd public/js && rollup -c rollup.config.js"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
@ -15,6 +18,7 @@
"jsonwebtoken": "^8.4.0" "jsonwebtoken": "^8.4.0"
}, },
"devDependencies": { "devDependencies": {
"jshint": "^2.10.3" "jshint": "^2.10.3",
"rollup": "^2.6.1"
} }
} }

View File

@ -14,14 +14,6 @@
<script src="js/lib/mithril.min.js"></script> <script src="js/lib/mithril.min.js"></script>
<script src="js/lib/moment.js"></script> <script src="js/lib/moment.js"></script>
<script src="js/state.js"></script> <script src="js/bootstrap.js"></script>
<script src="js/api.js"></script>
<script src="js/service.js"></script>
<script src="js/nav.js"></script>
<script src="js/login.js"></script>
<script src="js/messages.js"></script>
<script src="js/message_detail.js"></script>
<script src="js/compose.js"></script>
<script src="js/main.js"></script>
</body> </body>
</html> </html>

2
public/js/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bundle.js
bundle.js.map

View File

@ -4,7 +4,6 @@
"esversion": 6, "esversion": 6,
"browser": true, "browser": true,
"globals": { "globals": {
"webmail": true,
"moment": true, "moment": true,
"m": true "m": true
} }

View File

@ -1,32 +1,30 @@
(function(){ import state from './state.js';
var api = {}; export const fetchMails = function(){
api.fetchMails = function(){
return m.request({ return m.request({
url: "api/inbox", url: "api/inbox",
headers: { "authorization": webmail.token } headers: { "authorization": state.token }
}); });
}; };
api.deleteMail = function(index){ export const deleteMail = function(index){
return m.request({ return m.request({
method: "DELETE", method: "DELETE",
url: "api/inbox/" + index, url: "api/inbox/" + index,
headers: { "authorization": webmail.token } headers: { "authorization": state.token }
}); });
}; };
api.markRead = function(index){ export const markRead = function(index){
return m.request({ return m.request({
method: "POST", method: "POST",
url: "api/markread", url: "api/markread",
data: { index: index }, data: { index: index },
headers: { "authorization": webmail.token } headers: { "authorization": state.token }
}); });
}; };
api.sendMail = function(recipient, subject, text){ export const sendMail = function(recipient, subject, text){
return m.request({ return m.request({
method: "POST", method: "POST",
url: "api/send", url: "api/send",
@ -35,28 +33,21 @@ api.sendMail = function(recipient, subject, text){
subject: subject, subject: subject,
text: text text: text
}, },
headers: { "authorization": webmail.token } headers: { "authorization": state.token }
}); });
}; };
api.verifyToken = function(){ export const verifyToken = function(){
return m.request({ return m.request({
url: "api/verify", url: "api/verify",
headers: { "authorization": webmail.token } headers: { "authorization": state.token }
}); });
}; };
api.login = function(username, password){ export const login = function(username, password){
return m.request({ return m.request({
method: "POST", method: "POST",
url: "api/login", url: "api/login",
data: { username: username, password: password } data: { username: username, password: password }
}); });
}; };
//publish
window.webmail.api = api;
})();

16
public/js/bootstrap.js vendored Normal file
View File

@ -0,0 +1,16 @@
(function(){
var s = document.createElement("script");
if (location.host === "127.0.0.1:8080") {
//dev
s.setAttribute("src", "js/main.js");
s.setAttribute("type", "module");
} else {
//prod
s.setAttribute("src", "js/bundle.js");
}
document.body.appendChild(s);
})();

View File

@ -1,61 +1,55 @@
(function(){ import state from './state.js';
import { sendMail } from './service.js';
var state = webmail.compose; const Compose = {
view: function(){
return [
m("div", {class:"row"}, [
m("input[type=text]", {
class:"form-control",
placeholder:"Recipient",
value: state.compose.recipient,
oninput: function(e){ state.compose.recipient = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("input[type=text]", {
class:"form-control",
placeholder:"Subject",
value: state.compose.subject,
oninput: function(e){ state.compose.subject = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("textarea", {
class:"form-control",
placeholder:"Text",
style: "height: 300px;",
value: state.compose.body,
oninput: function(e){ state.compose.body = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("button[type=submit]", {
class:"btn btn-sm btn-block btn-primary",
onclick: sendMail,
disabled: !state.compose.body || !state.compose.subject || !state.compose.recipient
}, "Submit")
])
];
}
};
var Compose = { export default {
view: function(){ view: function(){
return [ if (state.loginState.loggedIn)
m("div", {class:"row"}, [ return m("div", {class:"row"}, [
m("input[type=text]", { m("div", {class:"col-md-2"}),
class:"form-control", m("form", {class:"col-md-8"}, m(Compose)),
placeholder:"Recipient", m("div", {class:"col-md-2"})
value: state.recipient, ]);
oninput: function(e){ state.recipient = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("input[type=text]", {
class:"form-control",
placeholder:"Subject",
value: state.subject,
oninput: function(e){ state.subject = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("textarea", {
class:"form-control",
placeholder:"Text",
style: "height: 300px;",
value: state.body,
oninput: function(e){ state.body = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("button[type=submit]", {
class:"btn btn-sm btn-block btn-primary",
onclick: webmail.service.sendMail,
disabled: !state.body || !state.subject || !state.recipient
}, "Submit")
])
];
}
};
else
webmail.routes["/compose"] = { return null;
view: function(){ }
if (webmail.loginState.loggedIn) };
return m("div", {class:"row"}, [
m("div", {class:"col-md-2"}),
m("form", {class:"col-md-8"}, m(Compose)),
m("div", {class:"col-md-2"})
]);
else
return null;
}
};
})();

View File

@ -1,67 +1,64 @@
(function(){ import state from './state.js';
import { login, logout } from './service.js';
var state = webmail.loginState; var LoginButton = function(){
var LoginButton = function(){ var infoText = m("span", {class:"badge badge-light"}, state.loginState.errorMsg ? state.loginState.errorMsg : "");
var spinner = state.loginState.busy ? m("i", {class:"fas fa-spinner fa-spin"}) : null;
var infoText = m("span", {class:"badge badge-light"}, state.errorMsg ? state.errorMsg : ""); return m("button", {
var spinner = state.busy ? m("i", {class:"fas fa-spinner fa-spin"}) : null; class:"btn btn-sm btn-block btn-primary",
disabled: !state.loginState.username || !state.loginState.password,
onclick: function(){ login(state.loginState.username, state.loginState.password); }
}, [spinner, " Login ", infoText]);
};
return m("button", { var LogoutButton = function(){
class:"btn btn-sm btn-block btn-primary", return m("button[type=submit]", {
disabled: !state.username || !state.password, class:"btn btn-sm btn-block btn-secondary",
onclick: function(){ webmail.service.login(state.username, state.password); } onclick: logout
}, [spinner, " Login ", infoText]); }, "Logout");
}; };
var LogoutButton = function(){ var LoginForm = {
return m("button[type=submit]", { view: function(){
class:"btn btn-sm btn-block btn-secondary", return [
onclick: webmail.service.logout m("div", {class:"row"}, [
}, "Logout"); m("h3", "Webmail login")
}; ]),
m("div", {class:"row"}, [
m("input[type=text]", {
class:"form-control",
placeholder:"Playername",
disabled: state.loginState.loggedIn,
value: state.loginState.username,
oninput: function(e){ state.loginState.username = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("input[type=password]", {
class:"form-control",
placeholder:"Password",
disabled: state.loginState.loggedIn,
value: state.loginState.password,
oninput: function(e){ state.loginState.password = e.target.value; }
})
]),
m("div", {class:"row"}, [
state.loginState.loggedIn ? LogoutButton() : LoginButton()
])
];
}
};
var LoginForm = { export default {
view: function(){ view: function(){
return [ return [
m("div", {class:"row"}, [ m("div", {class:"row"}, [
m("h3", "Webmail login") m("div", {class:"col-md-4"}),
]), m("form", {class:"col-md-4"}, m(LoginForm)),
m("div", {class:"row"}, [ m("div", {class:"col-md-4"})
m("input[type=text]", { ])
class:"form-control", ];
placeholder:"Playername", }
disabled: state.loggedIn, };
value: state.username,
oninput: function(e){ state.username = e.target.value; }
})
]),
m("div", {class:"row"}, [
m("input[type=password]", {
class:"form-control",
placeholder:"Password",
disabled: state.loggedIn,
value: state.password,
oninput: function(e){ state.password = e.target.value; }
})
]),
m("div", {class:"row"}, [
state.loggedIn ? LogoutButton() : LoginButton()
])
];
}
};
webmail.routes["/login"] = {
view: function(){
return [
m("div", {class:"row"}, [
m("div", {class:"col-md-4"}),
m("form", {class:"col-md-4"}, m(LoginForm)),
m("div", {class:"col-md-4"})
])
];
}
};
})();

View File

@ -1,5 +1,5 @@
(function(){ import routes from './routes.js';
import Nav from './nav.js';
m.route(document.getElementById("app"), "/login", webmail.routes); m.route(document.getElementById("app"), "/login", routes);
m.mount(document.getElementById("nav"), Nav);
})();

View File

@ -1,41 +1,40 @@
(function(){ import state from './state.js';
import { readMail, reply } from './service.js';
webmail.routes["/message/:id"] = { export default {
view: function(){ view: function(){
if (!webmail.mails) if (!state.mails)
return m("div", "Loading..."); return m("div", "Loading...");
var id = m.route.param("id"); var id = m.route.param("id");
var mail = webmail.service.readMail(id); var mail = readMail(id);
var timeStr = ""; var timeStr = "";
if (mail.time){ if (mail.time){
var time_m = moment(mail.time * 1000); var time_m = moment(mail.time * 1000);
var durationStr = moment.duration(time_m - moment()).humanize(true); var durationStr = moment.duration(time_m - moment()).humanize(true);
timeStr = time_m.format("YYYY-MM-DD HH:mm:ss") + " (" + durationStr + ")"; timeStr = time_m.format("YYYY-MM-DD HH:mm:ss") + " (" + durationStr + ")";
}
var body = [];
mail.body.split("\n").forEach(function(line){
body.push(line);
body.push( m("br") );
});
var replyBtn = m("button[type=button]", {
onclick: function(){ webmail.service.reply(id); },
class: "btn btn-sm btn-primary"
}, "Reply");
return [
m("h2", mail.subject),
m("h5", [ "From: ", m("b", mail.sender), replyBtn ]),
m("h5", [ "Sent: ", m("b", timeStr) ]),
m("div", body)
];
} }
};
})(); var body = [];
mail.body.split("\n").forEach(function(line){
body.push(line);
body.push( m("br") );
});
var replyBtn = m("button[type=button]", {
onclick: function(){ reply(id); },
class: "btn btn-sm btn-primary"
}, "Reply");
return [
m("h2", mail.subject),
m("h5", [ "From: ", m("b", mail.sender), replyBtn ]),
m("h5", [ "Sent: ", m("b", timeStr) ]),
m("div", body)
];
}
};

View File

@ -1,79 +1,75 @@
(function(){ import state from './state.js';
import { deleteMail } from './service.js';
var InboxRow = {
view: function(vnode){
function openMail(){
m.route.set("/message/:id", { id: vnode.attrs.row.index });
}
var InboxRow = { var timeStr = "";
view: function(vnode){
function openMail(){
m.route.set("/message/:id", { id: vnode.attrs.row.index });
}
function deleteMail(){ if (vnode.attrs.row.time){
webmail.service.deleteMail(vnode.attrs.row.index); var time_m = moment(vnode.attrs.row.time * 1000);
} var durationStr = moment.duration(time_m - moment()).humanize(true);
var timeStr = ""; timeStr = time_m.format("YYYY-MM-DD HH:mm:ss") + " (" + durationStr + ")";
}
if (vnode.attrs.row.time){ var rowClass = vnode.attrs.row.unread ? "table-primary" : "";
var time_m = moment(vnode.attrs.row.time * 1000);
var durationStr = moment.duration(time_m - moment()).humanize(true);
timeStr = time_m.format("YYYY-MM-DD HH:mm:ss") + " (" + durationStr + ")"; return m("tr", {class: rowClass}, [
} m("td", vnode.attrs.row.sender),
m("td", vnode.attrs.row.subject),
var rowClass = vnode.attrs.row.unread ? "table-primary" : ""; m("td", timeStr),
m("td", [
return m("tr", {class: rowClass}, [ m("div", { class: "btn-group" }, [
m("td", vnode.attrs.row.sender), m("button[type=button]", { class: "btn btn-primary", onclick: openMail },[
m("td", vnode.attrs.row.subject), m("i", {class:"fa fa-envelope-open"}),
m("td", timeStr), "Open"
m("td", [ ]),
m("div", { class: "btn-group" }, [ m("button[type=button]", {
m("button[type=button]", { class: "btn btn-primary", onclick: openMail },[ class: "btn btn-danger",
m("i", {class:"fa fa-envelope-open"}), onclick: () => deleteMail(vnode.attrs.row.index)
"Open" },[
]), m("i", {class:"fa fa-trash"}),
m("button[type=button]", { class: "btn btn-danger", onclick: deleteMail },[ "Remove"
m("i", {class:"fa fa-trash"}),
"Remove"
])
]) ])
]) ])
]); ])
]);
}
};
var InboxTable = {
view: function(){
if (!state.mails){
return m("div", "Loading...");
} }
};
var InboxTable = { var head = m("thead", m("tr", [
view: function(){ m("th", "Sender"),
if (!webmail.mails){ m("th", "Subject"),
return m("div", "Loading..."); m("th", "Sent"),
} m("th", "Action")
]));
var head = m("thead", m("tr", [ var body = m("tbody", state.mails.map(function(row){
m("th", "Sender"), return m(InboxRow, {row: row});
m("th", "Subject"), }));
m("th", "Sent"),
m("th", "Action")
]));
var body = m("tbody", webmail.mails.map(function(row){ return m("table",
return m(InboxRow, {row: row}); {class:"table table-condensed table-striped table-sm"},
})); [head, body]
);
}
};
return m("table", export default {
{class:"table table-condensed table-striped table-sm"}, view: function(){
[head, body] if (state.loginState.loggedIn)
); return m(InboxTable);
} else
}; return null;
}
};
webmail.routes["/messages"] = {
view: function(){
if (webmail.loginState.loggedIn)
return m(InboxTable);
else
return null;
}
};
})();

View File

@ -1,37 +1,34 @@
import state from './state.js';
import { countUnread } from './service.js';
(function(){ function NavLinks(){
function NavLinks(){ var links = [];
var links = []; links.push( m("a", {class:"nav-link", href:"#!/login"}, "Login") );
links.push( m("a", {class:"nav-link", href:"#!/login"}, "Login") ); if (state.loginState.loggedIn){
links.push( m("a", {class:"nav-link", href:"#!/messages"}, [
if (webmail.loginState.loggedIn){ "Messages",
links.push( m("a", {class:"nav-link", href:"#!/messages"}, [ m("span", {class: "badge badge-light"}, countUnread())
"Messages", ])
m("span", {class: "badge badge-light"}, webmail.service.countUnread()) );
]) links.push( m("a", {class:"nav-link", href:"#!/compose"}, "Compose") );
);
links.push( m("a", {class:"nav-link", href:"#!/compose"}, "Compose") );
}
return m("ul", {class:"navbar-nav"}, links);
} }
function NavBarContent(){ return m("ul", {class:"navbar-nav"}, links);
return m("div", {class:"container"}, [ }
m("i", {class:"fa fa-envelope"}),
m("a", {class:"navbar-brand", href:"#"}, "Minetest webmail"), function NavBarContent(){
m("div", {class:"navbar-collapse"}, NavLinks()) return m("div", {class:"container"}, [
]); m("i", {class:"fa fa-envelope"}),
m("a", {class:"navbar-brand", href:"#"}, "Minetest webmail"),
m("div", {class:"navbar-collapse"}, NavLinks())
]);
}
export default {
view: function(){
return m("nav", {class:"navbar navbar-dark bg-dark fixed-top navbar-expand-lg"}, NavBarContent());
} }
};
m.mount(document.getElementById("nav"), {
view: function(){
return m("nav", {class:"navbar navbar-dark bg-dark fixed-top navbar-expand-lg"}, NavBarContent());
}
});
})();

View File

@ -0,0 +1,10 @@
export default [{
input: 'main.js',
output: {
file :'bundle.js',
format: 'umd',
sourcemap: true,
compact: true
}
}];

12
public/js/routes.js Normal file
View File

@ -0,0 +1,12 @@
import Login from './login.js';
import Compose from './compose.js';
import Messages from './messages.js';
import MessageDetail from './message_detail.js';
export default {
"/login": Login,
"/compose": Compose,
"/messages": Messages,
"/message/:id": MessageDetail
};

View File

@ -1,73 +1,79 @@
(function(state){ import state from './state.js';
import {
var service = {}; markRead,
verifyToken,
login as api_login,
fetchMails as api_fetchMails,
sendMail as api_sendMail,
deleteMail as api_deleteMail
} from './api.js';
//verify token if available //verify token if available
if (webmail.token){ if (state.token){
webmail.api.verifyToken() verifyToken()
.then(function(result){ .then(function(result){
if (result.username){ if (result.username){
state.username = result.username; state.loginState.username = result.loginState.username;
state.loggedIn = true; state.loginState.loggedIn = true;
//fetch messages after token alright //fetch messages after token alright
service.fetchMails(); fetchMails();
} }
}); });
} }
service.login = function(username, password){ export const login = function(username, password){
if (!username || !password) if (!username || !password)
return; return;
state.errorMsg = ""; state.loginState.errorMsg = "";
state.busy = true; state.loginState.busy = true;
webmail.api.login(username, password) api_login(username, password)
.then(function(result){ .then(function(result){
state.busy = false; state.loginState.busy = false;
if (result.success){ if (result.success){
state.loggedIn = true; state.loginState.loggedIn = true;
state.errorMsg = ""; state.loginState.errorMsg = "";
//save token //save token
webmail.token = result.token; state.token = result.token;
localStorage["webmail-token"] = result.token; localStorage["webmail-token"] = result.token;
//fetch mails after login //fetch mails after login
service.fetchMails(); fetchMails();
} else { } else {
state.errorMsg = "Login failed: " + result.message; state.loginState.errorMsg = "Login failed: " + result.message;
} }
}) })
.catch(function(){ .catch(function(){
state.errorMsg = "System error!"; state.loginState.errorMsg = "System error!";
state.busy = false; state.loginState.busy = false;
}); });
}; };
service.logout = function(){ export const logout = function(){
state.loggedIn = false; state.loginState.loggedIn = false;
webmail.mails = []; state.mails = [];
//clear token //clear token
webmail.token = null; state.token = null;
delete localStorage["webmail-token"]; delete localStorage["webmail-token"];
}; };
service.fetchMails = function(){ export const fetchMails = function(){
if (!webmail.mails || !webmail.mails.length){ if (!state.mails || !state.mails.length){
webmail.api.fetchMails() api_fetchMails()
.then(function(result){ .then(function(result){
webmail.mails = result; state.mails = result;
}); });
} }
}; };
service.countUnread = function(){ export const countUnread = function(){
var count = 0; var count = 0;
if (webmail.mails && webmail.mails.length){ if (state.mails && state.mails.length){
webmail.mails.forEach(function(mail){ state.mails.forEach(function(mail){
if (mail.unread) if (mail.unread)
count++; count++;
}); });
@ -76,29 +82,23 @@ service.countUnread = function(){
return count; return count;
}; };
service.sendMail = function(){ export const sendMail = function(){
webmail.api.sendMail(webmail.compose.recipient, webmail.compose.subject, webmail.compose.body); api_sendMail(state.compose.recipient, state.compose.subject, state.compose.body);
webmail.compose.recipient = ""; state.compose.recipient = "";
webmail.compose.subject = ""; state.compose.subject = "";
webmail.compose.body = ""; state.compose.body = "";
}; };
service.reply = function(index){
var mail = service.readMail(index);
webmail.compose.recipient = mail.sender;
webmail.compose.subject = "Re: " + mail.subject;
webmail.compose.body = "\n---- Original message ----\n" + mail.body;
m.route.set("/compose");
};
service.readMail = function(index){
if (webmail.mails && webmail.mails.length){
var mail = webmail.mails[index-1]; export const readMail = function(index){
if (state.mails && state.mails.length){
var mail = state.mails[index-1];
//mark as read with api //mark as read with api
if (mail.unread){ if (mail.unread){
webmail.api.markRead(index); markRead(index);
//mark read locally //mark read locally
mail.unread = false; mail.unread = false;
@ -108,11 +108,20 @@ service.readMail = function(index){
} }
}; };
service.deleteMail = function(index){ export const reply = function(index){
return webmail.api.deleteMail(index) var mail = readMail(index);
state.compose.recipient = mail.sender;
state.compose.subject = "Re: " + mail.subject;
state.compose.body = "\n---- Original message ----\n" + mail.body;
m.route.set("/compose");
};
export const deleteMail = function(index){
return api_deleteMail(index)
.then(function(){ .then(function(){
var new_index = 1; var new_index = 1;
webmail.mails = webmail.mails state.mails = state.mails
.filter(function(mail){ return mail.index != index; }) .filter(function(mail){ return mail.index != index; })
.map(function(mail){ .map(function(mail){
mail.index = new_index++; mail.index = new_index++;
@ -120,9 +129,3 @@ service.deleteMail = function(index){
}); });
}); });
}; };
webmail.service = service;
})(webmail.loginState);

View File

@ -1,24 +1,20 @@
(function(){
var webmail = { const state = {
routes: {}, routes: {},
token: localStorage["webmail-token"], token: localStorage["webmail-token"],
loginState: { loginState: {
username: "", username: "",
password: "", password: "",
loggedIn: false, loggedIn: false,
errorMsg: "", errorMsg: "",
busy: false busy: false
}, },
compose: { compose: {
recipient: "", recipient: "",
subject: "", subject: "",
body: "" body: ""
}, },
mails: null mails: null
}; };
//publish export default state;
window.webmail = webmail;
})();