move everything

This commit is contained in:
Synox 2016-11-20 21:45:57 +01:00
parent efb14f90e1
commit cd5b93eb0c
45 changed files with 195 additions and 686 deletions

View file

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

View file

@ -1,19 +0,0 @@
{
"ecmaFeatures": {
"jsx": true,
"modules": true
},
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"rules": {
// "quotes": [2, "single"],
"jsx-quotes": 2,
"strict": [
2,
"never"
]
}
}

53
Vagrantfile vendored
View file

@ -1,53 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "avenuefactory/lamp"
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 993, host: 9993
config.vm.synced_folder "./", "/var/www/html", id: "vagrant-root",
owner: "vagrant",
group: "www-data",
mount_options: ["dmode=775,fmode=664"]
config.vm.provision "shell", inline: <<-SHELL
echo updating... && sudo apt-get -qq update
echo installing... && sudo apt-get -qq -y install php5-imap
sudo service apache2 restart
# Install and Configure Dovecot (http://blog.tedivm.com/open-source/2014/03/building-an-email-library-testing-with-vagrant-dovecot-and-travis-ci/)
#https://github.com/tedious/DovecotTesting/blob/master/resources/Scripts/Provision.sh
if which dovecot > /dev/null; then
echo 'Dovecot is already installed'
else
sudo mkdir /home/vagrant/Maildir
sudo chown -R vagrant:vagrant /home/vagrant/Maildir
sudo chmod a+rw /home/vagrant/Maildir
echo 'Installing Dovecot'
sudo apt-get -qq -y install dovecot-imapd
sudo touch /etc/dovecot/local.conf
sudo chmod go+rw /etc/dovecot/local.conf
echo 'mail_location = maildir:/home/vagrant/Maildir' >> /etc/dovecot/local.conf
echo 'disable_plaintext_auth = no' >> /etc/dovecot/local.conf
echo 'mail_max_userip_connections = 10000' >> /etc/dovecot/local.conf
sudo restart dovecot
fi
# Create user "test"
if getent passwd test > /dev/null; then
echo 'test already exists'
else
sudo useradd "test" -m -s /bin/bash
echo "test:test"|sudo chpasswd
echo 'User "test" created'
fi
SHELL
end

View file

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

View file

@ -3,6 +3,6 @@
"php-imap/php-imap": "~2.0"
},
"config": {
"vendor-dir": "dist/backend-libs"
"vendor-dir": "src/backend-libs"
}
}

114
dist/backend.php vendored
View file

@ -1,114 +0,0 @@
<?php
# set the new path of config.php (must be in a safe location outside the `public_html`)
require_once '../../config.php';
# load php dependencies:
require_once './backend-libs/autoload.php';
$imap_settings = $config['imap'];
$mailbox = new PhpImap\Mailbox($imap_settings['url'], $imap_settings['username'], $imap_settings['password']);
/**
* print error and stop program.
* @param $status http status
* @param $text error text
*/
function error($status, $text) {
@http_response_code($status);
@print("{\"error\": \"$text\"}");
die();
}
/**
* print all mails for the given $user as a json string.
* @param $username
*/
function print_inbox($username) {
global $mailbox, $config;
$name = clean_name($username);
if (strlen($name) === 0) {
error(400, 'invalid username');
}
$to = get_address($name, $config['mailHostname']);
$mail_ids = search_mails($to, $mailbox);
$emails = array();
foreach ($mail_ids as $id) {
$emails[] = $mailbox->getMail($id);
}
$address = get_address($name, $config['mailHostname']);
$data = array("mails" => $emails, 'username' => $name, 'address' => $address);
print(json_encode($data));
}
/**
* Search for mails with the recipient $to.
* @return array mail ids
*/
function search_mails($to, $mailbox) {
$filterTO = 'TO "' . $to . '"';
$filterCC = 'CC "' . $to . '"';
$mailsIdsTo = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterTO);
$mailsIdsCc = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterCC);
return array_merge($mailsIdsTo, $mailsIdsCc);
}
/**
* Remove illegal characters from username and remove everything after the @-sign. You may extend it if your server supports them.
* @param $username
* @return clean username
*/
function clean_name($username) {
$username = preg_replace('/@.*$/', "", $username); // remove part after @
$username = preg_replace('/[^A-Za-z0-9_.+-]/', "", $username); // remove special characters
return $username;
}
/**
* creates the full email address
* @param $username
* @param $domain
* @return $username@$domain
*/
function get_address($username, $domain) {
return $username . "@" . $domain;
}
/**
* deletes messages older than X days.
*/
function delete_old_messages() {
global $mailbox;
$date = date('d-M-Y', strtotime('30 days ago'));
$ids = $mailbox->searchMailbox('BEFORE ' . $date);
foreach ($ids as $id) {
$mailbox->deleteMail($id);
}
$mailbox->expungeDeletedMails();
}
header('Content-type: application/json');
// Never cache requests:
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
if (!isset($_GET['action'])) {
error(400, 'invalid parameter');
}
$action = $_GET['action'];
if ($action === "get" && isset($_GET['username'])) {
print_inbox($_GET['username']);
} else {
error(400, 'invalid action');
}
// run on every request
delete_old_messages();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,17 +0,0 @@
<?php
date_default_timezone_set('Europe/Paris');
error_reporting(0);
// configure this option if you want to allow requests from clients from other domains:
// see https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
// header("Access-Control-Allow-Origin: *");
// setup imap connection
$config['imap']['host'] = "localhost";
$config['imap']['url'] = '{' . $config['imap']['host'] . '/imap/ssl}INBOX';
$config['imap']['username'] = "test";
$config['imap']['password'] = "test";
// email domain, usually different from imap hostname:
$config['mailHostname'] = "example.com";

1
dist/index.html vendored
View file

@ -1 +0,0 @@
<!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_a99458ea657979e9cab2.js"></script></body> </html>

View file

@ -1,51 +0,0 @@
{
"name": "disposable-mailbox",
"version": "0.0.1",
"description": "disposable-mailbox",
"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"
},
"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-stage-0": "^6.0.15",
"css-loader": "^0.23.1",
"eslint": "^1.10.3",
"file-loader": "^0.9.0",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.15.0",
"json-loader": "^0.5.4",
"ng-annotate-webpack-plugin": "^0.1.3",
"node-sass": "^3.8.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"
}

View file

@ -1,14 +0,0 @@
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/');
});

5
src/chance.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,12 @@
[ec-stickyfill] {
position: -webkit-sticky;
position: sticky;
top: 0px;
}
[ec-stickyfill]:before,
[ec-stickyfill]:after {
content: '';
display: table;
}

View file

@ -0,0 +1,8 @@
/**
* An Angular directive for stickyfill (position sticky polyfill)
*
* @version v0.1.0 - 2016-08-31
* @author Corey Wilson <corey@eastcodes.com>
* @license Unlicense, http://unlicense.org/
*/
!function(e,i){"use strict";if("function"==typeof define&&define.amd)define(["angular","stickyfill"],i);else{if("undefined"==typeof module||"object"!=typeof module.exports)return i(e.angular,e.Stickyfill);module.exports=i(require("angular"),require("stickyfill"))}}(window,function(e,i){"use strict";function t(){function e(e,t,n){if("object"!=typeof i)throw new Error("stickyfill.js not loaded");i.add(t[0]),e.$on("$destroy",function(){i.remove(t[0])})}var t={link:e,restrict:"A"};return t}if("function"==typeof i)var i=i();var n="ec.stickyfill";return e.module(n,[]).directive("ecStickyfill",t),n});

View file

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

82
src/client_libs/style.css Normal file
View file

@ -0,0 +1,82 @@
body {
background: #eeeeee;
}
footer p {
margin-top: 50px;
text-align: center;
}
.email-table {
margin-top: 20px;
}
div.min-height {
min-height: 400px;
}
.nav-container {
background-color: #D9E2E9;
}
.octicon-inbox {
display: inline-block;
width: 26px;
height: 23px;
background: url('octicon-inbox.gif') no-repeat;
}
.sticky-header {
top: 0;
z-index: 1000;
}
.email {
border-bottom: 1px solid #7C96AB;
}
.email .email-summary {
font-weight: bold;
border-top: 5px solid #7C96AB;
border-bottom: 1px solid #7C96AB;
padding: 10px;
background-color: white;
}
.email .email-headers {
background-color: white;
}
.email .email-headers dl {
padding: 0 0 4px;
color: black;
overflow: hidden;
}
.email .email-headers dl dt {
float: left;
color: black;
margin: 0 3px 0 0;
}
.email .email-headers dl dd {
display: block;
overflow: hidden;
margin-bottom: .3rem;
}
.email .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

@ -9,11 +9,15 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="description" content="Mailbox">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="angular-stickyfill-0.1.0/angular-stickyfill.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"
integrity="sha384-AysaV+vQoT3kOAXZkl02PThvDr8HYKPZhNT5h/CXfBThSRXQ6jW5DO2ekP5ViFdi" crossorigin="anonymous">
</head>
<body><!-- ng-cloak-->
<body>
<main ng-controller="MailboxController as $ctrl">
<div ng-controller="MailboxController as $ctrl"> <!-- ng-cloak-->
<div class="nav-container">
<div class="container">
@ -47,15 +51,72 @@
</div>
<!--<inbox-->
<!--mails="$ctrl.mails"-->
<!--username="$ctrl.username"-->
<!--address="$ctrl.address"-->
<!--state="$ctrl.state">-->
<!--</inbox>-->
<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">
<!-- email -->
<section class="email">
<div class="row sticky-header" ec-stickyfill>
<div class="col-sm-12 email-summary">{{mail.subject}}</div>
</div>
<div class="row">
<div class="col-sm-12 email-headers">
<dl>
<dt>To:</dt>
<dd>{{mail.toString}}</dd>
<div ng-if="mail.cc" ng-repeat="(address,name) in mail.cc">
<dt>CC:</dt>
<dd>{{address}}</dd>
</div>
<dt>From:</dt>
<dd>{{mail.fromName}} &lt;{{mail.fromAddress}}&gt;</dd>
<dt>Date:</dt>
<dd>{{mail.date}}</dd>
</dl>
</div>
</div>
<div class="row mail-content" ng-init="htmlTabActive=false">
</main>
<button ng-show="htmlTabActive" class="btn btn-outline-info btn-sm"
ng-click="htmlTabActive=false">show text
</button>
<button ng-show="mail.textHtml && !htmlTabActive" class="btn btn-outline-info btn-sm"
ng-click="htmlTabActive=true">show html
</button>
<div ng-if="!htmlTabActive" ng-bind-html="mail.textPlain | nl2br | autolink "></div>
<div ng-if="htmlTabActive" class="mail-conent" ng-bind-html="mail.textHtml"></div>
</div>
</section>
</div>
</div>
</main>
</div>
<footer>
@ -65,7 +126,10 @@
</footer>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="chance.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-sanitize.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/autolinker/1.2.0/Autolinker.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chance/1.0.4/chance.min.js"></script>
<script src="angular-stickyfill-0.1.0/angular-stickyfill.js"></script>
<script src="index.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-signals/1.0.0/js-signals.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/hasher/1.2.0/hasher.js"></script>

View file

@ -21,11 +21,27 @@ function cleanUsername(username) {
}
var app = angular.module('app', []);
var app = angular.module('app', ["ngSanitize"]);
// http://stackoverflow.com/a/20033625/79461
app.filter("nl2br", function () {
return function (data) {
if (!data) return data;
return data.replace(/\r?\n/g, '<br/>');
}
}
);
// http://stackoverflow.com/a/20033625/79461
app.filter("autolink", function () {
return function (data) {
return Autolinker.link(data, {truncate: {length: 50, location: 'middle', newWindow: true}});
}
});
app.controller('MailboxController', ["$scope", "$interval", "$http", "$log", function ($scope, $interval, $http, $log) {
var self = this;
self.updateUsername = function (username) {
self.username = cleanUsername(username);
if (self.username.length > 0) {

View file

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

View file

@ -1,25 +0,0 @@
<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

@ -1,29 +0,0 @@
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

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

View file

@ -1,38 +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 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,39 +0,0 @@
import angular from "angular";
import ngSanitize from "angular-sanitize";
import Autolinker from "autolinker";
import angularStickyfill from "angular-stickyfill";
import "angular-stickyfill/dist/angular-stickyfill.css";
import template from "./mail.html";
import "./mail.scss";
class MailController {
/*@ngInject*/
constructor() {
}
}
export default angular.module('mailbox.inbox.mail', [ngSanitize, angularStickyfill])
.component('mail', {
template,
controller: MailController,
bindings: {
mail: '<'
}
})
// http://stackoverflow.com/a/20033625/79461
.filter("nl2br", function () {
return function (data) {
if (!data) return data;
return data.replace(/\r?\n/g, '<br/>');
}
}
)
// http://stackoverflow.com/a/20033625/79461
.filter("autolink", function () {
return function (data) {
return Autolinker.link(data, {truncate: {length: 50, location: 'middle', newWindow: true}});
}
}
).name;

View file

@ -1,55 +0,0 @@
$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

@ -1,114 +0,0 @@
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var validate = require('webpack-validator');
var merge = require('webpack-merge');
var ngAnnotatePlugin = require('ng-annotate-webpack-plugin');
var TARGET = process.env.npm_lifecycle_event;
// based on https://github.com/gaearon/react-hot-boilerplate
const commonConfig = {
context: path.resolve(__dirname, 'src'),
entry: [
'./index.js'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle_[hash].js'
},
plugins: [
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
}),
new ngAnnotatePlugin({add: true})
],
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
exclude: [/node_modules/],
include: path.join(__dirname, 'src'),
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'
}
]
}
};
var config;
switch (TARGET) {
case 'size':
case 'build':
config = merge(commonConfig, {
devtool: 'source-map',
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')
}
})
]
});
break;
default:
// develop
config = merge(commonConfig, {
devtool: 'eval',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
DEVELOPMENT: JSON.stringify(true)
})
]
});
// replace entry instead of merge
config.entry = [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./index.js'
];
}
if (TARGET === "size") {
// no validation with size target
module.exports = config;
} else {
module.exports = validate(config);
}