refactor with bootstrap 4

This commit is contained in:
Synox 2016-09-14 08:47:24 +02:00
parent b486aff415
commit 14d0253ccb
39 changed files with 660 additions and 870 deletions

View File

@ -1,3 +1,6 @@
{
"presets": ["es2015"]
"presets": [
"es2015",
"stage-0"
]
}

25
.eslintrc Normal file
View File

@ -0,0 +1,25 @@
{
"ecmaFeatures": {
"jsx": true,
"modules": true
},
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"rules": {
// "quotes": [2, "single"],
"jsx-quotes": 2,
"strict": [
2,
"never"
],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2
},
"plugins": [
"react"
]
}

View File

@ -1,52 +0,0 @@
{
"esnext": true,
"camelcase": true,
"devel": false,
"asi": false,
"boss": false,
"eqnull": false,
"es5": true,
"moz": false,
"evil": false,
"expr": true,
"funcscope": true,
"iterator": false,
"lastsemic": false,
"laxbreak": false,
"laxcomma": false,
"loopfunc": false,
"multistr": true,
"noyield": false,
"notypeof": false,
"proto": false,
"scripturl": false,
"shadow": false,
"sub": false,
"supernew": false,
"validthis": false,
"plusplus": false,
"jquery": true,
"mocha": false,
"node": true,
"latedef": true,
"quotmark": "single",
"maxparams": 6,
"maxdepth": 5,
"maxerr": 50,
"globalstrict": false,
"globals": {
"inject": true,
"angular": true,
"require": true,
"process": true,
"module": true,
"describe": true,
"beforeEach": true,
"afterEach": true,
"it": true,
"expect": true,
"__dirname": true,
"exports": true,
"spyOn": true
}
}

View File

@ -1,6 +0,0 @@
language: node_js
node_js:
- "node"
before_script:
- npm install -g gulp-cli
script: gulp build

View File

@ -7,7 +7,6 @@ composer install
cp -rv src/{backend.php,config.sample.php} dist/
# build Javascript frontend
npm install
gulp build
npm run build
echo "done"

57
dist/bundle_65d8f46de4091cada75c.js vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/index.html vendored
View File

@ -1 +1 @@
<!DOCTYPE html> <html lang=de> <head> <meta charset=utf-8> <title>Mailbox</title> <meta name=viewport content="width=device-width,initial-scale=1"> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=black> <meta name=description content=Mailbox> <link rel=icon href="data:;base64,iVBORw0KGgo="> </head> <body ng-app=app ng-cloak> <div ui-view></div> <footer> <p>Powered by <a href=https://github.com/synox/disposable-mailbox><strong>synox/disposable-mailbox</strong></a> | <a href=https://github.com/synox/disposable-mailbox><span class="octicon octicon-mark-github"></span> Fork me on github</a></p> </footer> <script type="text/javascript" src="mailbox_1ad13ef2ffe2df84a4e6.js"></script></body> </html>
<!DOCTYPE html> <html lang=de> <head> <meta charset=utf-8> <title>Mailbox</title> <meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=black> <meta name=description content=Mailbox> <link rel=icon href="data:;base64,iVBORw0KGgo="> </head> <body ng-app=app ng-cloak> <app></app> <footer> <p>Powered by <a href=https://github.com/synox/disposable-mailbox><strong>synox/disposable-mailbox</strong></a> | <a href=https://github.com/synox/disposable-mailbox><span class="octicon octicon-mark-github"></span> Fork me on github</a></p> </footer> <script type="text/javascript" src="bundle_65d8f46de4091cada75c.js"></script></body> </html>

File diff suppressed because one or more lines are too long

View File

@ -1,73 +0,0 @@
import gulp from 'gulp';
import path from 'path';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import karma from 'karma';
import ip from 'ip';
import webpackConfig from './webpack.config';
import del from 'del';
gulp.task('clean', function () {
return del([
'dist/*.js',
'target/build']
);
});
/**
* Gulp-Task: Fuehrt webpack aus und startet den Development-Server
*/
gulp.task('dev-server', () => {
return new WebpackDevServer(webpack(webpackConfig.development), {
hot: true,
contentBase: './dist/',
watchOptions: {
aggregateTimeout: 100, poll: 300
}, stats: {
colors: true
}
}).listen(3000, 'localhost', function (err) {
if (err) {
console.error(err);
}
});
});
/**
* Gulp-Task: Fuehrt die Karma-Tests auf dem PhantomJS Browser aus
*/
gulp.task('test-phantomjs', (done) => {
let hostname = process.env.host || ip.address();
let externalport = process.env.externalport || 7777;
return new karma.Server({
configFile: __dirname + '/karma.conf.js',
hostname: hostname,
port: externalport,
browsers: ['PhantomJS']
}, done).start();
});
gulp.task('webpack-prod', [], (done) => {
return webpack(webpackConfig.production, done);
});
gulp.task('build', ['test-phantomjs', 'clean', 'webpack-prod'], (done) => {
return gulp
.src(path.join('target', 'build', '*'))
.pipe(gulp.dest('dist'));
});
gulp.task('build-skipTests', ['clean', 'webpack-prod'], (done) => {
return gulp
.src(path.join('target', 'build', '*'))
.pipe(gulp.dest('dist'));
});
gulp.task('default', ['dev-server']);

View File

@ -1,112 +0,0 @@
var ip = require('ip');
var webjsConfig = require('./shared.build.config');
module.exports = function (config) {
var seleniumWebgrid = {
hostname: 'webtestgrid.myhost.com',
port: 4444
};
config.set({
hostname: ip.address(),
basePath: __dirname,
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
/* list of files/patterns to load in the browser
Fuer den Phantomjs wird fuer die Methode 'bind' ein Polyfill geladen
da der Browser die Methode nicht kennt und diese von den Frameworks verwendet wird */
files: [
'./node_modules/phantomjs-polyfill/bind-polyfill.js', {
pattern: 'spec.bundle.js', watched: false
}
],
// files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'spec.bundle.js': ['webpack', 'sourcemap', 'coverage']
},
webpack: {
debug: true, devtool: 'inline-source-map', module: {
loaders: webjsConfig.webpackLoaders
}
},
webpackServer: {
// prevent console spamming when running in Karma!
noInfo: true
},
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'junit', 'coverage'],
// enable colors in the output
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// toggle whether to watch files and rerun tests upon incurring changes
autoWatch: false,
// Browser-Konfiguration auf dem Selenium Grid
customLaunchers: {
'SeleniumCH': {
base: 'WebDriver',
config: seleniumWebgrid,
browserName: 'chrome'
},
'SeleniumFF': {
base: 'WebDriver',
config: seleniumWebgrid,
browserName: 'firefox'
},
'SeleniumIE': {
base: 'WebDriver',
config: seleniumWebgrid,
browserName: 'internet explorer',
'x-ua-compatible': 'IE=edge'
}
},
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['PhantomJS'], // Test auf dem PhantomJS
// browsers: ['SeleniumFF', 'SeleniumCH', 'SeleniumIE'], // Test auf dem Selenium-Webgrid
// if true, Karma runs tests once and exits
singleRun: true,
plugins: [
'karma-junit-reporter',
'karma-jasmine',
'karma-coverage',
'karma-phantomjs-launcher',
'karma-webpack',
'karma-sourcemap-loader',
'karma-webdriver-launcher'
],
// Coverage & JUnit Report fuer SonarQube
junitReporter: {
outputDir: 'target/surefire', suite: 'unit'
}, coverageReporter: {
reporters: [
{
type: 'lcov', dir: 'target/surefire', subdir: '.'
}
]
}
});
};

View File

@ -2,65 +2,52 @@
"name": "disposable-mailbox",
"version": "0.0.1",
"description": "disposable-mailbox",
"homepage": "https://github.com/synox/disposable-mailbox",
"dependencies": {
"angular": "^1.5.2",
"angular-sanitize": "^1.5.6",
"angular-stickyfill": "^0.1.0",
"angular-ui-bootstrap": "^1.3.3",
"angular-ui-router": "^1.0.0-beta.1",
"autolinker": "^0.27.0",
"babel-polyfill": "^6.9.1",
"bootstrap": "^3.3.6",
"phonetic": "^0.1.1"
},
"devDependencies": {
"angular-mocks": "^1.5.2",
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"browser-sync": "^2.10.0",
"browser-sync-webpack-plugin": "^1.0.0",
"css-loader": "^0.23.1",
"del": "^2.2.1",
"file-loader": "^0.9.0",
"gulp": "^3.9.0",
"gulp-esdoc": "^0.2.0",
"gulp-file-inline": "^1.3.6",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.15.0",
"ip": "^1.0.2",
"isparta-loader": "^2.0.0",
"jasmine-core": "^2.3.4",
"json-loader": "^0.5.3",
"karma": "^1.1.0",
"karma-coverage": "^1.0.0",
"karma-jasmine": "~1.0.2",
"karma-junit-reporter": "^1.1.0",
"karma-phantomjs-launcher": "^1.0.1",
"karma-sourcemap-loader": "^0.3.4",
"karma-webdriver-launcher": "^1.0.4",
"karma-webpack": "^1.5.1",
"ng-annotate-webpack-plugin": "^0.1.2",
"node-libs-browser": "^1.0.0",
"node-sass": "^3.8.0",
"node.extend": "^1.1.5",
"phantomjs": "^2.1.7",
"phantomjs-polyfill": "0.0.2",
"proxy-middleware": "^0.15.0",
"raw-loader": "^0.5.1",
"sass-loader": "^4.0.0",
"style-loader": "^0.13.0",
"url-loader": "^0.5.6",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.12.1"
"scripts": {
"start": "node server.js",
"build": "NODE_ENV=production webpack -p",
"size": "NODE_ENV=production webpack --json | webpack-bundle-size-analyzer ",
"lint": "./node_modules/eslint/bin/eslint.js src"
},
"repository": {
"type": "git",
"url": "https://github.com/synox/disposable-mailbox.git"
},
"license": "CC-BY-NC-SA-4.0",
"scripts": {
"test": "gulp test-phantomjs"
}
"homepage": "https://github.com/synox/disposable-mailbox",
"dependencies": {
"angular": "^1.5.2",
"angular-sanitize": "^1.5.6",
"angular-stickyfill": "^0.1.0",
"hasher": "^1.2.0",
"autolinker": "^0.27.0",
"babel-polyfill": "^6.9.1",
"bootstrap": "^4.0.0-alpha.3",
"phonetic": "^0.1.1"
},
"devDependencies": {
"babel-core": "^6.0.20",
"babel-eslint": "^4.1.3",
"babel-loader": "^6.0.1",
"babel-preset-es2015": "^6.0.15",
"babel-preset-react": "^6.0.15",
"babel-preset-stage-0": "^6.0.15",
"css-loader": "^0.23.1",
"eslint": "^1.10.3",
"eslint-plugin-react": "^3.6.2",
"file-loader": "^0.9.0",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.15.0",
"json-loader": "^0.5.4",
"node-sass": "^3.8.0",
"react-hot-loader": "^1.3.0",
"sass-loader": "^4.0.0",
"style-loader": "^0.13.0",
"uglify-loader": "^1.3.0",
"url-loader": "^0.5.6",
"webpack": "^1.12.2",
"webpack-bundle-size-analyzer": "^2.0.2",
"webpack-dev-server": "^1.12.1",
"webpack-merge": "^0.14.1",
"webpack-validator": "^2.2.3"
},
"license": "CC-BY-NC-SA-4.0"
}

14
server.js Normal file
View File

@ -0,0 +1,14 @@
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
new WebpackDevServer(webpack(config), {
hot: false,
historyApiFallback: true
}).listen(3000, 'localhost', function (err, result) {
if (err) {
return console.log(err);
}
console.log('Listening at http://localhost:3000/');
});

View File

@ -1,33 +0,0 @@
/*
* @author u215942 (Stefan Zeller)
* @version: 1.0.1
* @since 04.04.2016
*/
exports.webpackLoaders = [
{
test: /\.js$/, exclude: [/node_modules/],
loader: 'babel',
query: {
// https://github.com/babel/babel-loader#options
cacheDirectory: true,
presets: ['es2015']
}
}, {
test: /\.json$/, loader: 'json'
}, {
test: /\.html$/, loader: 'html'
}, {
test: /\.css$/, loader: 'style!css'
}, {
test: /\.scss$/, loader: 'style!css!sass'
}, {
test: /\.(jpe?g|png|gif|svg)$/i, loader: 'url'
}, {
test: /\.(woff|woff2)$/, loader: 'url?mimetype=application/font-woff'
}, {
test: /\.ttf$/, loader: 'url'
}, {
test: /\.eot$/, loader: 'url'
}
];

View File

@ -1,5 +0,0 @@
import 'angular';
import 'angular-mocks';
let context = require.context('./src/mailbox', true, /\.spec\.js/);
context.keys().forEach(context);

View File

@ -1,20 +1,123 @@
// Vendor-Imports
import angular from "angular";
import uiRouter from "angular-ui-router";
import uiBootstrap from "angular-ui-bootstrap";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap/scss/bootstrap.scss";
import "babel-polyfill";
import angularStickyfill from "angular-stickyfill";
import "angular-stickyfill/dist/angular-stickyfill.css";
import Mailbox from "./mailbox/mailbox";
import {cleanUsername, generateRandomUsername} from "./util";
import hasher from "hasher";
import Header from "./components/header/header";
import List from "./components/list/list";
import Mail from "./components/mail/mail";
class AppController {
/*@ngInject*/
constructor($http, $log, config, $interval) {
this.$interval = $interval;
this.$http = $http;
this.config = config;
this.$log = $log;
this.$log.log('start controller');
this.address = null;
this.username = null;
this.mails = [];
this.state = {isUpdating: false};
}
$onInit() {
this.$log.debug("init");
hasher.changed.add(this.onHashChange.bind(this));
hasher.initialized.add(this.onHashChange.bind(this)); //add initialized listener (to grab initial value in case it is already set)
hasher.init(); //initialize hasher (start listening for history changes)
this.intervalPromise = this.$interval(() => this.updateMails(), this.config.reload_interval_ms);
}
$onDestroy() {
this.$interval.cancel(this.intervalPromise);
}
onHashChange(hash) {
this.updateUsername(hash);
}
onChangeUsername({username}) {
this.updateUsername(username);
}
onGotoRandom() {
let username = generateRandomUsername();
this.updateUsername(username);
}
loadEmails(username) {
return this.$http.get(this.config.backend_url, {params: {username: username, action: "get"}})
.then(response=> {
return response.data;
}
);
}
loadEmailsAsync(username) {
this.$log.debug("updating mails for ", username);
this.state.isUpdating = true;
this.loadEmails(this.username).then(data=> {
this.mails = data.mails;
this.address = data.address;
this.username = data.username;
this.state.isUpdating = false;
this.$log.debug("received mails for ", username);
});
}
updateMails() {
if (this.username) {
this.loadEmailsAsync(this.username);
}
}
updateUsername(username) {
this.username = cleanUsername(username);
if (this.username.length > 0) {
hasher.setHash(this.username);
this.address = this.username; // use username until real address has been loaded
this.updateMails();
} else {
this.address = null;
this.mails = [];
}
}
}
// Interne Modul-Imports
angular.module('app', [
uiRouter, uiBootstrap, Mailbox.name, angularStickyfill
List, Mail, angularStickyfill, Header
])
.component('app', {
template: `
<header
username="$ctrl.username"
address="$ctrl.address"
mailcount="$ctrl.mails.length"
on-change-username="$ctrl.onChangeUsername($event)"
on-goto-random="$ctrl.onGotoRandom($event)">
</header>
<inbox
mails="$ctrl.mails"
username="$ctrl.username"
address="$ctrl.address"
state="$ctrl.state">
</inbox>
`,
controller: AppController
})
.constant('config', {
'backend_url': 'http://dubgo.com/m2/backend.php',
'backend_url': './backend.php',
'reload_interval_ms': 10000
})

View File

@ -0,0 +1,82 @@
import angular from "angular";
import "./header.scss";
class HeaderController {
/*@ngInject*/
constructor($log) {
this.$log = $log;
this.inputFieldUsername = "";
}
$onInit() {
this.inputFieldUsername = this.address;
}
$onChanges(changes) {
if (changes.address) {
this.inputFieldUsername = this.username;
this.address = this.address;
}
}
gotoMailbox(username) {
this.onChangeUsername({
$event: {
username: username
}
});
}
randomize() {
this.onGotoRandom();
}
}
const HeaderComponent = {
bindings: {
address: '<',
username: '<',
mailcount: '<',
onChangeUsername: '&',
onGotoRandom: '&'
},
controller: HeaderController,
template: `
<div class="nav-container">
<div class="container">
<nav class="navbar navbar-light">
<a class="navbar-brand"><span class="octicon-inbox"></span>
&nbsp;
{{$ctrl.address}}
&nbsp;
<span ng-if="$ctrl.mailcount" class="tag tag-pill tag-default">{{$ctrl.mailcount}}</span>
</a>
<ul class="nav navbar-nav">
<button type="button nav-link" class="btn btn-outline-primary pull-xs-right"
ng-click="$ctrl.randomize()">
<span class="glyphicon glyphicon-random"></span>&nbsp;
randomize
</button>
<form class="form-inline pull-xs-right" ng-submit="$ctrl.gotoMailbox($ctrl.inputFieldUsername)">
<input ng-model="$ctrl.inputFieldUsername"
placeholder="username"
type="text" class="form-control"/>
<button type="submit" class="btn btn-outline-success">login</button>
</form>
</ul>
</nav>
</div>
</div>
`
};
export default angular
.module('header', [])
.component('header', HeaderComponent)
.name;

View File

@ -0,0 +1,11 @@
.nav-container {
background-color: #D9E2E9;
//heigh: 300px;
}
.octicon-inbox {
display: inline-block;
width: 26px;
height: 23px;
background: url('octicon-inbox.gif') no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

View File

@ -0,0 +1,25 @@
<main>
<div class="container min-height">
<p ng-if="!$ctrl.username">
Use the buttons above to create a new inbox, or open a specific mailbox.
</p>
<div ng-if="$ctrl.username && $ctrl.mails.length == 0">
<div class="waiting-screen">
<h1>{{$ctrl.address}}</h1>
<p class="lead">Inbox is empty.</p>
<p><br/>
<img src="spinner.gif">
<br/></p>
<p class="lead">Emails to {{address}} will be automatically displayed on this page. </p>
</div>
</div>
<div ng-if="$ctrl.username" ng-repeat="mail in $ctrl.mails | orderBy:'-date' track by $index"
class="email-table">
<mail mail="mail"></mail>
</div>
</div>
</main>

View File

@ -0,0 +1,29 @@
import angular from "angular";
import template from "./list.html";
import "./list.scss";
class ListController {
/*@ngInject*/
constructor($log) {
this.$log = $log;
// @Input:
// this.mails = [];
// this.username = null;
// this.address = null;
}
}
export default angular.module('mailbox.inbox', [])
.component('inbox', {
template, controller: ListController,
bindings: {
address: '<',
username: '<',
mails: '<',
state: '<'
}
})
.name;

View File

@ -0,0 +1,18 @@
body {
background: #eeeeee;
}
footer p {
margin-top: 50px;
text-align: center;
}
.email-table {
margin-top: 20px;
}
div.min-height {
min-height: 400px;
}

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,38 @@
<section class="email">
<div class="row sticky-header" ec-stickyfill>
<div class="col-sm-12 email-summary">{{$ctrl.mail.subject}}</div>
</div>
<div class="row">
<div class="col-sm-12 email-headers">
<dl>
<dt>To:</dt>
<dd>{{$ctrl.mail.toString}}</dd>
<div ng-if="$ctrl.mail.cc" ng-repeat="(address,name) in $ctrl.mail.cc">
<dt>CC:</dt>
<dd>{{address}}</dd>
</div>
<dt>From:</dt>
<dd>{{$ctrl.mail.fromName}} &lt;{{$ctrl.mail.fromAddress}}&gt;</dd>
<dt>Date:</dt>
<dd>{{$ctrl.mail.date}}</dd>
</dl>
</div>
</div>
<div class="row mail-content" ng-init="htmlTabActive=false">
<button ng-show="htmlTabActive" class="btn btn-outline-info btn-sm" ng-click="htmlTabActive=false">show text
</button>
<button ng-show="$ctrl.mail.textHtml && !htmlTabActive" class="btn btn-outline-info btn-sm"
ng-click="htmlTabActive=true">show html
</button>
<div ng-if="!htmlTabActive" ng-bind-html="$ctrl.mail.textPlain | nl2br | autolink "></div>
<div ng-if="htmlTabActive" class="mail-conent" ng-bind-html="$ctrl.mail.textHtml"></div>
</div>
</section>

View File

@ -1,18 +1,23 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import ngSanitize from 'angular-sanitize';
import Autolinker from 'autolinker';
import template from './mail.html';
import controller from './mail.controller';
import './mail.scss';
import angular from "angular";
import ngSanitize from "angular-sanitize";
import Autolinker from "autolinker";
import template from "./mail.html";
import "./mail.scss";
export default angular.module('mailbox.inbox.mail', [uiRouter, ngSanitize])
class MailController {
/*@ngInject*/
constructor() {
}
}
export default angular.module('mailbox.inbox.mail', [ngSanitize])
.component('mail', {
template, controller,
template,
controller: MailController,
bindings: {
mail: '='
mail: '<'
}
})
// http://stackoverflow.com/a/20033625/79461
@ -29,4 +34,4 @@ export default angular.module('mailbox.inbox.mail', [uiRouter, ngSanitize])
return Autolinker.link(data, {truncate: {length: 50, location: 'middle', newWindow: true}});
}
}
);
).name;

View File

@ -0,0 +1,55 @@
$email-border-color: #7C96AB;
$email-summary-background: white;
$email-header-background: white;
.sticky-header {
top: 0;
z-index: 1000;
}
.email {
.email-summary {
font-weight: bold;
border-top: 5px solid $email-border-color;
border-bottom: 1px solid $email-border-color;
padding: 10px;
background-color: $email-summary-background;
}
.email-headers {
background-color: $email-header-background;
dl {
padding: 0 0 4px;
color: black;
overflow: hidden;
dt {
float: left;
color: black;
margin: 0 3px 0 0;
}
dd {
display: block;
overflow: hidden;
margin-bottom: .3rem;
}
}
}
.mail-content {
background-color: white;
padding-left: 20px;
overflow: hidden;
}
}
.waiting-screen {
padding: 40px 15px;
text-align: center;
}
div.min-height {
min-height: 400px;
}

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>Mailbox</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
@ -13,12 +13,14 @@
</head>
<body ng-app="app" ng-cloak>
<div ui-view></div>
<app></app>
<!--<div ui-view></div>-->
<footer>
<p>Powered by <a href="https://github.com/synox/disposable-mailbox"><strong>synox/disposable-mailbox</strong></a>
| <a href="https://github.com/synox/disposable-mailbox"><span class="octicon octicon-mark-github"></span> Fork me on github</a></p>
| <a href="https://github.com/synox/disposable-mailbox"><span class="octicon octicon-mark-github"></span> Fork
me on github</a></p>
</footer>

View File

@ -1,84 +0,0 @@
import phonetic from 'phonetic';
class MailboxController {
/*@ngInject*/
constructor($log, $interval, config, mailboxService, $rootScope, $stateParams, $state) {
this.$rootScope = $rootScope;
this.$log = $log;
this.$interval = $interval;
this.config = config;
this.mailboxService = mailboxService;
this.mails = [];
this.$stateParams = $stateParams;
this.$state = $state;
this.address = null; // is set when mails are loaded
this.state = 'home';
}
$onInit() {
if (this.getCurrentUsername()) {
this.state = 'loading';
this.address = this.getCurrentUsername(); // use username until real address has been loaded
this.intervalPromise = this.$interval(() => this.loadMails(), this.config.reload_interval_ms);
this.loadMails();
}
}
static cleanUsername(username) {
return username.replace(/[@].*$/, '');
}
gotoMailbox(username) {
username = MailboxController.cleanUsername(username);
this.address = username; // use username until real address has been loaded
this.$state.go('inbox', {username: username});
}
gotoRandomAddress() {
let username = this.generateRandomUsername();
this.gotoMailbox(username);
}
generateRandomUsername() {
let username = phonetic.generate({syllables: 3, phoneticSimplicity: 1});
if (Math.random() >= 0.5) {
username += this.getRandomInt(30, 99);
}
return username.toLowerCase();
}
getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
$onDestroy() {
this.$log.debug("destroying controller");
this.$interval.cancel(this.intervalPromise);
}
loadMails() {
this.mailboxService.loadEmails(this.getCurrentUsername())
.then(data => {
this.mails = data.mails;
if (this.mails.length !== 0) {
this.state = 'list';
} else {
this.state = 'empty';
}
this.address = data.address;
this.loadingData = false;
});
}
getCurrentUsername() {
return this.$stateParams.username;
}
}
export default MailboxController;

View File

@ -1,77 +0,0 @@
<div class="navbar" role="navigation">
<div class="container">
<div class="hidden-xs">
<div class="navbar-header">
<a class="navbar-brand"><span class="octicon-inbox"></span> Mailbox</a>
</div>
<form class="navbar-form navbar-left">
<a class="btn btn-default" ng-click="$ctrl.gotoRandomAddress()" role="button">
<span class="glyphicon glyphicon-random" aria-hidden="true"></span>
create random
</a>
</form>
<form class="navbar-form navbar-left" role="search" ng-submit="$ctrl.gotoMailbox($ctrl.address)">
<input ng-model="$ctrl.address" type='text' class="form-control"/>
<button type="submit" class="btn btn-default">open</button>
</form>
</div>
<form ng-submit="$ctrl.gotoMailbox($ctrl.address)">
<div class="form-inline visible-xs-block">
<div class="form-group col-xs-4">
<span class="octicon-inbox"></span></a>
<a class="btn btn-default" ng-click="$ctrl.gotoRandomAddress()" role="button">
<span class="glyphicon glyphicon-random" aria-hidden="true"></span>
</a>
</div>
<div class="form-group col-xs-6">
<input ng-model="$ctrl.address" type='text' class="form-control"/>
</div>
<div class="form-group col-xs-2">
<button type="submit" class="btn btn-default">open</button>
</div>
</div>
</form>
</div>
</div>
<main>
<div class="container min-height" ng-switch="$ctrl.state">
<p ng-switch-when="home">
Use the buttons above to create a new inbox, or open a specific mailbox.
</p>
<div ng-switch-when="list" ng-repeat="mail in $ctrl.mails | orderBy:'-date' track by $index"
class="email-table">
<mail mail="mail"></mail>
</div>
<div ng-switch-when="loading" class="waiting-screen">
<h1>&nbsp;</h1>
<p class="lead">Loading Mails</p>
<p><br/>
<img src="spinner.gif">
<br/>
</p>
</div>
<div ng-switch-when="empty">
<div class="waiting-screen">
<h1>{{$ctrl.address}}</h1>
<p class="lead">Inbox is empty.</p>
<p><br/>
<img src="spinner.gif">
<br/></p>
<p class="lead">Emails to {{address}} will be automatically displayed on this page. </p>
</div>
</div>
</div>
</main>

View File

@ -1,14 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import template from './inbox.html';
import controller from './inbox.controller';
import './inbox.scss';
export default angular.module('mailbox.inbox', [uiRouter])
.component('inbox', {
template, controller,
bindings: {
data: '<'
}
})

View File

@ -1,33 +0,0 @@
body {
background: #eeeeee;
}
footer p {
margin-top: 50px;
text-align: center;
}
.email-table {
margin-top: 20px;
background: white;
}
.waiting-screen {
padding: 40px 15px;
text-align: center;
}
div.min-height {
min-height: 400px;
}
.navbar {
background-color: #D9E2E9;
}
.octicon-inbox {
display: inline-block;
width: 26px;
height: 23px;
background: url('octicon-inbox.png') no-repeat;
}

View File

@ -1,4 +0,0 @@
describe('not tests', () => {
it('should run anyway', () => {
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -1,8 +0,0 @@
class MailController {
/*@ngInject*/
constructor() {
}
}
export default MailController;

View File

@ -1,45 +0,0 @@
<section class="email">
<div class="row sticky-header" ec-stickyfill>
<div class="col-sm-12 email-summary">{{$ctrl.mail.subject}}</div>
</div>
<div class="row">
<div class="col-sm-12 email-headers">
<dl>
<dt>To:</dt>
<dd>{{$ctrl.mail.toString}}</dd>
<div ng-if="$ctrl.mail.cc" ng-repeat="(address,name) in $ctrl.mail.cc">
<dt>CC:</dt>
<dd>{{address}}</dd>
</div>
<dt>From:</dt>
<dd>{{$ctrl.mail.fromName}} &lt;{{$ctrl.mail.fromAddress}}&gt;</dd>
<dt>Date:</dt>
<dd>{{$ctrl.mail.date}}</dd>
</dl>
</div>
</div>
<div class="row email-tabs">
<!-- if there is no html content, just show text: -->
<div ng-if="!$ctrl.mail.textHtml" ng-bind-html="$ctrl.mail.textPlain | nl2br | autolink" class="mail-conent">
</div>
<!-- else show tabs with plain text and html:-->
<uib-tabset ng-if="$ctrl.mail.textHtml" active="1">
<uib-tab index="1">
<uib-tab-heading><i class="glyphicon glyphicon-align-left"></i> text</uib-tab-heading>
<div class="mail-conent" ng-bind-html="$ctrl.mail.textPlain | nl2br | autolink "></div>
</uib-tab>
<uib-tab index="2" select="htmlActive=true">
<uib-tab-heading><i class="glyphicon glyphicon-picture"></i> html</uib-tab-heading>
<!-- only load htl content when tab is activated. this avoids loading images from other servers.-->
<div class="mail-conent" ng-if="htmlActive" ng-bind-html="$ctrl.mail.textHtml"></div>
</uib-tab>
</uib-tabset>
</div>
</section>

View File

@ -1,77 +0,0 @@
$email-border-color: #7C96AB;
$email-summary-background: white;
$email-header-background: white;
$tab-button-color: white;
$tab-button-color-inactive: #e0e0e0;
$tab-content-background: white;
.sticky-header {
top: 0;
z-index: 1000;
}
.email {
.email-summary {
font-weight: bold;
border-top: 5px solid $email-border-color;
border-bottom: 1px solid $email-border-color;
padding: 10px;
background-color: $email-summary-background;
}
.email-headers {
background-color: $email-header-background;
dl {
padding: 0 0 4px;
color: black;
overflow: hidden;
dt {
float: left;
color: black;
margin: 0 3px 0 0;
}
dd {
display: block;
overflow: hidden;
}
}
}
.email-tabs {
background-color: $email-header-background;
border-bottom: 1px solid $email-border-color;
.nav {
padding-left: 10px;
}
.mail-conent {
background-color: $tab-content-background;
padding: 20px;
overflow: hidden;
}
/* Tab Button (disabled)*/
.nav-tabs > li.disabled > a:hover,
.nav-tabs > li.disabled > a:focus {
background-color: $tab-button-color-inactive;
}
/* Tab Button (incative)*/
.nav-tabs > li > a {
background-color: $tab-button-color-inactive;
}
/* Tab Button (active)*/
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background-color: $tab-button-color;
}
}
}

View File

@ -1,19 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import Service from './service/mailbox.service'
import Inbox from './inbox/inbox';
import Mail from './mail/mail'
let module = angular.module('mailbox', [uiRouter, Inbox.name, Mail.name])
.config(/*@ngInject*/($stateProvider, $urlRouterProvider) => {
$urlRouterProvider.otherwise('/');
$stateProvider.state('inbox', {
url: '/:username',
component: 'inbox'
});
})
.service('mailboxService', Service);
export default module;

View File

@ -1,18 +0,0 @@
class MailboxService {
/*@ngInject*/
constructor($http, $log, $state, $stateParams, config) {
this.$http = $http;
this.config = config;
}
loadEmails(username) {
return this.$http.get(this.config.backend_url, {params: {username: username, action: "get"}})
.then(response=> {
return response.data;
}
);
}
}
export default MailboxService;

17
src/util.js Normal file
View File

@ -0,0 +1,17 @@
import phonetic from "phonetic";
export function generateRandomUsername() {
let username = phonetic.generate({syllables: 3, phoneticSimplicity: 1});
if (Math.random() >= 0.5) {
username += this.getRandomInt(30, 99);
}
return username.toLowerCase();
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
export function cleanUsername(username) {
return username.replace(/[@].*$/, '');
}

View File

@ -1,90 +1,123 @@
var webpack = require('webpack');
var extend = require('node.extend');
var path = require('path');
var browserSyncPlugin = require('browser-sync-webpack-plugin');
var ngAnnotatePlugin = require('ng-annotate-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webjsConfig = require('./shared.build.config');
var url = require('url');
var proxyMiddleware = require('proxy-middleware');
var validate = require('webpack-validator');
var merge = require('webpack-merge');
/**
* Gemeinsame Konfigurationsdatei fuer Webpack (der Teil, der fuer alle Umgebungen gleich ist)
* @type {} Webpack Konfiguration
*/
var commonConfig = {
var TARGET = process.env.npm_lifecycle_event;
// based on https://github.com/gaearon/react-hot-boilerplate
const commonConfig = {
context: path.resolve(__dirname, 'src'),
// Einstiegspunkt fuer Webpack
entry: {
mailbox: './app.js'
// angular: 'angular',
// anguboot: 'angular-ui-bootstrap',
// angurouter: 'angular-ui-router',
// bootcss: 'bootstrap/dist/css/bootstrap.css',
// angusan: 'angular-sanitize',
// autolinker: 'autolinker',
// babelpolyfill: 'babel-polyfill',
// phonetic: 'phonetic'
},
entry: [
'./app.js'
],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]bundle.js'
},
// Modulkonfiguration fuer alle Dateitypen, welcher Loader soll verwendet werden
module: {
loaders: webjsConfig.webpackLoaders
},
resolve: {
fallback: path.join(__dirname, 'node_modules')
},
resolveLoader: {fallback: path.join(__dirname, 'node_modules')}
};
/**
* Production Konfigurationsdatei fuer Webpack (der Teil, der nur fuer den produktiven Build ist)
* @type {} Webpack Konfiguration
*/
var production = extend({}, commonConfig, {
output: {
path: path.join(__dirname, 'target/build'),
filename: '[name]_[hash].js'
filename: 'bundle_[hash].js'
},
plugins: [
new ngAnnotatePlugin({add: true}),
new webpack.optimize.DedupePlugin(),
new webpack.NoErrorsPlugin(),
new webpack.optimize.UglifyJsPlugin({
minimize: true,
compress: {
warnings: true
},
sourceMap: false
}),
// add js files
new HtmlWebpackPlugin({
template: './index.html'
})
]
});
// development config
// forward requests (you may also have to change "backend_url" in app.js
var proxyOptions = url.parse('http://localhost:8080');
proxyOptions.route = '/backend.php';
var development = extend({}, commonConfig, {
plugins: [
new browserSyncPlugin({
proxy: 'localhost:3000',
middleware: proxyMiddleware(proxyOptions)
}),
new HtmlWebpackPlugin({
template: './index.html'
})
],
watch: true,
devtool: 'source-map'
});
module: {
loaders: [
{
test: /\.json$/, loader: 'json'
}, {
test: /\.html$/, loader: 'html'
}, {
test: /\.css$/, loader: 'style!css'
}, {
test: /\.scss$/, loader: 'style!css!sass'
}, {
test: /\.(jpe?g|png|gif|svg)$/i, loader: 'url'
}, {
test: /\.(woff|woff2)$/, loader: 'url?mimetype=application/font-woff'
}, {
test: /\.ttf$/, loader: 'url'
}, {
test: /\.eot$/, loader: 'url'
}
]
}
};
var config;
switch (TARGET) {
case 'size':
case 'build':
config = merge(commonConfig, {
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
minimize: true,
compress: {
warnings: false
}
}),
new webpack.DefinePlugin({
DEVELOPMENT: JSON.stringify(false)
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
})
],
// without react-hot in prod
module: {
loaders: [
{
test: /\.js$/, exclude: [/node_modules/],
loader: 'babel',
query: {
// https://github.com/babel/babel-loader#options
cacheDirectory: true,
presets: ['es2015']
}
}
]
}
});
break;
default:
// develop
config = merge(commonConfig, {
devtool: 'eval',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
DEVELOPMENT: JSON.stringify(true)
})
],
module: {
loaders: [
{
test: /\.js$/,
loaders: ['babel'],
include: path.join(__dirname, 'src')
},
]
}
});
// replace entry instead of merge
config.entry = [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./app.js'
];
}
if (TARGET === "size") {
// no validation with size target
module.exports = config;
} else {
module.exports = validate(config);
}
module.exports = {production: production, development: development};