Add prettier
This commit is contained in:
parent
0cbbc1b8ff
commit
b176b79144
|
@ -1,12 +1,15 @@
|
|||
{
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"modules": false,
|
||||
"loose": true,
|
||||
"targets": {
|
||||
"browsers": ["ie 11"]
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false,
|
||||
"loose": true,
|
||||
"targets": {
|
||||
"browsers": ["ie 11"]
|
||||
}
|
||||
}
|
||||
}],
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-stage-0"
|
||||
],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "airbnb",
|
||||
"extends": ["airbnb", "plugin:prettier/recommended"],
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"browser": true
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"singleQuote": true
|
||||
}
|
|
@ -20,7 +20,10 @@ function brotli(opts) {
|
|||
}
|
||||
|
||||
if (file.isStream()) {
|
||||
this.emit('error', new gutil.PluginError('brotli', 'Streams not supported'));
|
||||
this.emit(
|
||||
'error',
|
||||
new gutil.PluginError('brotli', 'Streams not supported')
|
||||
);
|
||||
} else if (file.isBuffer()) {
|
||||
file.path += '.br';
|
||||
file.contents = new Buffer(br.compress(file.contents, opts).buffer);
|
||||
|
@ -30,11 +33,14 @@ function brotli(opts) {
|
|||
}
|
||||
|
||||
gulp.task('css', function() {
|
||||
return gulp.src(['src/css/fonts.css', 'src/css/fontello.css', 'src/css/style.css'])
|
||||
return gulp
|
||||
.src(['src/css/fonts.css', 'src/css/fontello.css', 'src/css/style.css'])
|
||||
.pipe(concat('bundle.css'))
|
||||
.pipe(autoprefixer({
|
||||
browsers: ['ie 11']
|
||||
}))
|
||||
.pipe(
|
||||
autoprefixer({
|
||||
browsers: ['ie 11']
|
||||
})
|
||||
)
|
||||
.pipe(nano())
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
@ -48,9 +54,12 @@ gulp.task('js', function(cb) {
|
|||
compiler.run(function(err, stats) {
|
||||
if (err) throw new gutil.PluginError('webpack', err);
|
||||
|
||||
gutil.log('[webpack]', stats.toString({
|
||||
colors: true
|
||||
}));
|
||||
gutil.log(
|
||||
'[webpack]',
|
||||
stats.toString({
|
||||
colors: true
|
||||
})
|
||||
);
|
||||
|
||||
if (stats.hasErrors()) process.exit(1);
|
||||
|
||||
|
@ -59,22 +68,20 @@ gulp.task('js', function(cb) {
|
|||
});
|
||||
|
||||
gulp.task('fonts', function() {
|
||||
return gulp.src('src/font/*')
|
||||
.pipe(gulp.dest('dist/font'));
|
||||
return gulp.src('src/font/*').pipe(gulp.dest('dist/font'));
|
||||
});
|
||||
|
||||
gulp.task('fonts:woff', function() {
|
||||
return gulp.src('src/font/*(*.woff|*.woff2)')
|
||||
.pipe(gulp.dest('dist/br/font'));
|
||||
return gulp.src('src/font/*(*.woff|*.woff2)').pipe(gulp.dest('dist/br/font'));
|
||||
});
|
||||
|
||||
gulp.task('config', function() {
|
||||
return gulp.src('../config.default.toml')
|
||||
.pipe(gulp.dest('dist/br'));
|
||||
return gulp.src('../config.default.toml').pipe(gulp.dest('dist/br'));
|
||||
});
|
||||
|
||||
function compress() {
|
||||
return gulp.src(['dist/**/!(*.br|*.woff|*.woff2)', '!dist/{br,br/**}'])
|
||||
return gulp
|
||||
.src(['dist/**/!(*.br|*.woff|*.woff2)', '!dist/{br,br/**}'])
|
||||
.pipe(brotli({ quality: 11 }))
|
||||
.pipe(gulp.dest('dist/br'));
|
||||
}
|
||||
|
@ -83,48 +90,71 @@ gulp.task('compress', ['css', 'js', 'fonts'], compress);
|
|||
gulp.task('compress:dev', ['css', 'fonts'], compress);
|
||||
|
||||
gulp.task('bindata', ['compress', 'config'], function(cb) {
|
||||
exec('go-bindata -nomemcopy -nocompress -pkg assets -o ../assets/bindata.go -prefix "dist/br" dist/br/...', cb);
|
||||
exec(
|
||||
'go-bindata -nomemcopy -nocompress -pkg assets -o ../assets/bindata.go -prefix "dist/br" dist/br/...',
|
||||
cb
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task('bindata:dev', ['compress:dev', 'config'], function(cb) {
|
||||
exec('go-bindata -debug -pkg assets -o ../assets/bindata.go -prefix "dist/br" dist/br/...', cb);
|
||||
exec(
|
||||
'go-bindata -debug -pkg assets -o ../assets/bindata.go -prefix "dist/br" dist/br/...',
|
||||
cb
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task('dev', ['css', 'fonts', 'fonts:woff', 'config', 'compress:dev', 'bindata:dev'], function() {
|
||||
gulp.watch('src/css/*.css', ['css']);
|
||||
gulp.task(
|
||||
'dev',
|
||||
['css', 'fonts', 'fonts:woff', 'config', 'compress:dev', 'bindata:dev'],
|
||||
function() {
|
||||
gulp.watch('src/css/*.css', ['css']);
|
||||
|
||||
var config = require('./webpack.config.dev.js');
|
||||
var compiler = webpack(config);
|
||||
var app = express();
|
||||
var config = require('./webpack.config.dev.js');
|
||||
var compiler = webpack(config);
|
||||
var app = express();
|
||||
|
||||
app.use(require('webpack-dev-middleware')(compiler, {
|
||||
noInfo: true,
|
||||
publicPath: config.output.publicPath,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
require('webpack-dev-middleware')(compiler, {
|
||||
noInfo: true,
|
||||
publicPath: config.output.publicPath,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
app.use(require('webpack-hot-middleware')(compiler));
|
||||
app.use(require('webpack-hot-middleware')(compiler));
|
||||
|
||||
app.use('/', express.static('dist'));
|
||||
app.use('/', express.static('dist'));
|
||||
|
||||
app.use('*', proxy('localhost:1337', {
|
||||
proxyReqPathResolver: function(req) {
|
||||
return req.originalUrl;
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
'*',
|
||||
proxy('localhost:1337', {
|
||||
proxyReqPathResolver: function(req) {
|
||||
return req.originalUrl;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
app.listen(3000, function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
app.listen(3000, function(err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Listening at http://localhost:3000');
|
||||
});
|
||||
});
|
||||
console.log('Listening at http://localhost:3000');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
gulp.task('build', ['css', 'js', 'fonts', 'fonts:woff', 'config', 'compress', 'bindata']);
|
||||
gulp.task('build', [
|
||||
'css',
|
||||
'js',
|
||||
'fonts',
|
||||
'fonts:woff',
|
||||
'config',
|
||||
'compress',
|
||||
'bindata'
|
||||
]);
|
||||
|
||||
gulp.task('default', ['dev']);
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
"css-loader": "^0.28.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb": "^16.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-import-resolver-webpack": "^0.9.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"express": "^4.14.1",
|
||||
"express-http-proxy": "^1.0.1",
|
||||
|
@ -32,6 +34,7 @@
|
|||
"gulp-cssnano": "^2.1.2",
|
||||
"gulp-util": "^3.0.8",
|
||||
"jest": "^22.4.3",
|
||||
"prettier": "1.11.1",
|
||||
"style-loader": "^0.20.3",
|
||||
"through2": "^2.0.3",
|
||||
"webpack": "^4.1.1",
|
||||
|
@ -58,6 +61,8 @@
|
|||
"url-pattern": "^1.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prettier": "prettier --write {.*,*.js,src/css/*.css}",
|
||||
"prettier:all": "prettier --write {.*,*.js,src/**/*.js,src/css/*.css}",
|
||||
"test": "jest",
|
||||
"test:verbose": "jest --verbose",
|
||||
"test:watch": "jest --watch"
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../font/fontello.woff2?48901973') format('woff2'),
|
||||
url('../font/fontello.woff?48901973') format('woff'),
|
||||
url('../font/fontello.ttf?48901973') format('truetype');
|
||||
url('../font/fontello.woff?48901973') format('woff'),
|
||||
url('../font/fontello.ttf?48901973') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
||||
font-family: "fontello";
|
||||
[class^='icon-']:before,
|
||||
[class*=' icon-']:before {
|
||||
font-family: 'fontello';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
|
@ -16,7 +17,7 @@
|
|||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: .2em;
|
||||
margin-right: 0.2em;
|
||||
text-align: center;
|
||||
/* opacity: .8; */
|
||||
|
||||
|
@ -29,7 +30,7 @@
|
|||
|
||||
/* Animation center compensation - margins should be symmetric */
|
||||
/* remove if not needed */
|
||||
margin-left: .2em;
|
||||
margin-left: 0.2em;
|
||||
|
||||
/* you can be more comfortable with increased icons size */
|
||||
/* font-size: 120%; */
|
||||
|
@ -42,9 +43,21 @@
|
|||
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
||||
}
|
||||
|
||||
.icon-cancel:before { content: '\e800'; } /* '' */
|
||||
.icon-menu:before { content: '\e801'; } /* '' */
|
||||
.icon-cog:before { content: '\e802'; } /* '' */
|
||||
.icon-search:before { content: '\e803'; } /* '' */
|
||||
.icon-user:before { content: '\f061'; } /* '' */
|
||||
.icon-ellipsis:before { content: '\f141'; } /* '' */
|
||||
.icon-cancel:before {
|
||||
content: '\e800';
|
||||
} /* '' */
|
||||
.icon-menu:before {
|
||||
content: '\e801';
|
||||
} /* '' */
|
||||
.icon-cog:before {
|
||||
content: '\e802';
|
||||
} /* '' */
|
||||
.icon-search:before {
|
||||
content: '\e803';
|
||||
} /* '' */
|
||||
.icon-user:before {
|
||||
content: '\f061';
|
||||
} /* '' */
|
||||
.icon-ellipsis:before {
|
||||
content: '\f141';
|
||||
} /* '' */
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Montserrat-Regular'),
|
||||
url(/font/Montserrat-Regular.woff2) format('woff2'),
|
||||
url(/font/Montserrat-Regular.woff) format('woff'),
|
||||
url(/font/Montserrat-Regular.ttf) format('truetype');
|
||||
url(/font/Montserrat-Regular.woff2) format('woff2'),
|
||||
url(/font/Montserrat-Regular.woff) format('woff'),
|
||||
url(/font/Montserrat-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -13,29 +13,27 @@
|
|||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Montserrat-Bold'),
|
||||
url(/font/Montserrat-Bold.woff2) format('woff2'),
|
||||
url(/font/Montserrat-Bold.woff) format('woff'),
|
||||
url(/font/Montserrat-Bold.ttf) format('truetype');
|
||||
url(/font/Montserrat-Bold.woff2) format('woff2'),
|
||||
url(/font/Montserrat-Bold.woff) format('woff'),
|
||||
url(/font/Montserrat-Bold.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto Mono'),
|
||||
local('RobotoMono-Regular'),
|
||||
url(/font/RobotoMono-Regular.woff2) format('woff2'),
|
||||
url(/font/RobotoMono-Regular.woff) format('woff'),
|
||||
url(/font/RobotoMono-Regular.ttf) format('truetype');
|
||||
src: local('Roboto Mono'), local('RobotoMono-Regular'),
|
||||
url(/font/RobotoMono-Regular.woff2) format('woff2'),
|
||||
url(/font/RobotoMono-Regular.woff) format('woff'),
|
||||
url(/font/RobotoMono-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Mono Bold'),
|
||||
local('RobotoMono-Bold'),
|
||||
url(/font/RobotoMono-Bold.woff2) format('woff2'),
|
||||
url(/font/RobotoMono-Bold.woff) format('woff'),
|
||||
url(/font/RobotoMono-Bold.ttf) format('truetype');
|
||||
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
|
||||
url(/font/RobotoMono-Bold.woff2) format('woff2'),
|
||||
url(/font/RobotoMono-Bold.woff) format('woff'),
|
||||
url(/font/RobotoMono-Bold.ttf) format('truetype');
|
||||
}
|
||||
|
|
|
@ -9,7 +9,12 @@ body {
|
|||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
@ -35,16 +40,17 @@ p {
|
|||
line-height: 1.5;
|
||||
}
|
||||
|
||||
i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
||||
i[class^='icon-']:before,
|
||||
i[class*=' icon-']:before {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #6BB758 !important;
|
||||
color: #6bb758 !important;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #F6546A !important;
|
||||
color: #f6546a !important;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
|
@ -60,8 +66,8 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
.app-info {
|
||||
width: 100%;
|
||||
font-family: Montserrat, sans-serif;
|
||||
background: #F6546A;
|
||||
color: #FFF;
|
||||
background: #f6546a;
|
||||
color: #fff;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
|
@ -79,9 +85,9 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
bottom: 0;
|
||||
width: 200px;
|
||||
background: #222;
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
font-family: Montserrat, sans-serif;
|
||||
transition: transform .2s;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
|
@ -109,7 +115,7 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
|
||||
.tablist p.selected {
|
||||
padding-left: 10px;
|
||||
border-left: 5px solid #6BB758;
|
||||
border-left: 5px solid #6bb758;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
|
@ -141,16 +147,16 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
.button-connect {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: #6BB758;
|
||||
color: #FFF;
|
||||
background: #6bb758;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button-connect:hover {
|
||||
background: #7BBF6A;
|
||||
background: #7bbf6a;
|
||||
}
|
||||
|
||||
.button-connect:active {
|
||||
background: #6BB758;
|
||||
background: #6bb758;
|
||||
}
|
||||
|
||||
.side-buttons {
|
||||
|
@ -168,16 +174,16 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
border-top: 1px solid #1D1D1D;
|
||||
border-top: 1px solid #1d1d1d;
|
||||
}
|
||||
|
||||
.side-buttons i:not(:first-child) {
|
||||
border-left: 1px solid #1D1D1D;
|
||||
border-left: 1px solid #1d1d1d;
|
||||
}
|
||||
|
||||
.side-buttons i:hover {
|
||||
color: #CCC;
|
||||
background: #1D1D1D;
|
||||
color: #ccc;
|
||||
background: #1d1d1d;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
|
@ -186,7 +192,7 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transition: left .2s, transform .2s;
|
||||
transition: left 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.connect {
|
||||
|
@ -199,7 +205,8 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
.connect .navicon, .settings .navicon {
|
||||
.connect .navicon,
|
||||
.settings .navicon {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -223,30 +230,30 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
border: none;
|
||||
}
|
||||
|
||||
.connect-form input[type="submit"],
|
||||
.connect-form input[type="text"],
|
||||
.connect-form input[type="password"] {
|
||||
.connect-form input[type='submit'],
|
||||
.connect-form input[type='text'],
|
||||
.connect-form input[type='password'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.connect-form input[type="submit"] {
|
||||
.connect-form input[type='submit'] {
|
||||
height: 50px;
|
||||
margin-bottom: 20px;
|
||||
font-family: Montserrat, sans-serif;
|
||||
background: #6BB758;
|
||||
color: #FFF;
|
||||
background: #6bb758;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.connect-form input[type="submit"]:hover {
|
||||
background: #7BBF6A;
|
||||
.connect-form input[type='submit']:hover {
|
||||
background: #7bbf6a;
|
||||
}
|
||||
|
||||
.connect-form input[type="submit"]:active {
|
||||
background: #6BB758;
|
||||
.connect-form input[type='submit']:active {
|
||||
background: #6bb758;
|
||||
}
|
||||
|
||||
.connect-form input[type="checkbox"] {
|
||||
.connect-form input[type='checkbox'] {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
|
@ -278,7 +285,7 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
right: 0;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
border-bottom: 1px solid #DDD;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
@ -304,28 +311,31 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.chat-server .userlist, .chat-private .userlist {
|
||||
.chat-server .userlist,
|
||||
.chat-private .userlist {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-server .userlist-bar, .chat-private .userlist-bar {
|
||||
.chat-server .userlist-bar,
|
||||
.chat-private .userlist-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-leave {
|
||||
border-left: 1px solid #DDD;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.button-leave:hover {
|
||||
background: #DDD;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.button-userlist {
|
||||
display: none;
|
||||
border-left: 1px solid #DDD;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.chat-server .button-userlist, .chat-private .button-userlist {
|
||||
.chat-server .button-userlist,
|
||||
.chat-private .button-userlist {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -369,8 +379,8 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
right: 0;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
border-left: 1px solid #DDD;
|
||||
border-bottom: 1px solid #DDD;
|
||||
border-left: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
padding: 0 15px;
|
||||
|
@ -403,13 +413,13 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
.search-input-wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: #FFF;
|
||||
border-bottom: 1px solid #DDD;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.search i {
|
||||
padding: 15px;
|
||||
color: #DDD;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
|
@ -465,17 +475,17 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
}
|
||||
|
||||
.message-error {
|
||||
color: #F6546A;
|
||||
color: #f6546a;
|
||||
}
|
||||
|
||||
.message-prompt {
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
color: #6BB758;
|
||||
color: #6bb758;
|
||||
}
|
||||
|
||||
.message-action {
|
||||
color: #FF6698;
|
||||
color: #ff6698;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
|
@ -486,13 +496,13 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
|
||||
.message-sender {
|
||||
font-weight: 700;
|
||||
color: #6BB758;
|
||||
color: #6bb758;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.message a {
|
||||
text-decoration: none;
|
||||
color: #0066FF;
|
||||
color: #0066ff;
|
||||
}
|
||||
|
||||
.message a:hover {
|
||||
|
@ -507,8 +517,8 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
height: 50px;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
border-top: 1px solid #DDD;
|
||||
background: #FFF;
|
||||
border-top: 1px solid #ddd;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.message-input-nick {
|
||||
|
@ -517,8 +527,8 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
line-height: 30px;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
background: #6BB758 !important;
|
||||
color: #FFF;
|
||||
background: #6bb758 !important;
|
||||
color: #fff;
|
||||
font-family: Montserrat, sans-serif !important;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
@ -536,10 +546,10 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
bottom: 50px;
|
||||
right: 0;
|
||||
width: 200px;
|
||||
border-left: 1px solid #DDD;
|
||||
border-left: 1px solid #ddd;
|
||||
background: #f0f0f0;
|
||||
z-index: 2;
|
||||
transition: transform .2s;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.userlist p {
|
||||
|
@ -548,7 +558,7 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
}
|
||||
|
||||
.userlist p:hover {
|
||||
background: #DDD;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.settings {
|
||||
|
@ -569,18 +579,18 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
|
||||
.settings button {
|
||||
margin: 5px;
|
||||
color: #FFF;
|
||||
background: #6BB758;
|
||||
color: #fff;
|
||||
background: #6bb758;
|
||||
padding: 10px 20px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.settings button:hover {
|
||||
background: #7BBF6A;
|
||||
background: #7bbf6a;
|
||||
}
|
||||
|
||||
.settings button:active {
|
||||
background: #6BB758;
|
||||
background: #6bb758;
|
||||
}
|
||||
|
||||
.settings div {
|
||||
|
@ -589,11 +599,11 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
|
||||
.settings .error {
|
||||
margin: 10px;
|
||||
color: #F6546A;
|
||||
color: #f6546a;
|
||||
}
|
||||
|
||||
.input-file {
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
background: #222 !important;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
|
@ -664,7 +674,8 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.chat-channel .chat-title-bar, .chat-channel .messagebox {
|
||||
.chat-channel .chat-title-bar,
|
||||
.chat-channel .messagebox {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,10 @@ import { sendMessage, raw } from 'state/messages';
|
|||
import { setNick, disconnect, whois, away } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { find } from 'utils';
|
||||
import createCommandMiddleware, { beforeHandler, notFoundHandler } from './middleware/command';
|
||||
import createCommandMiddleware, {
|
||||
beforeHandler,
|
||||
notFoundHandler
|
||||
} from './middleware/command';
|
||||
|
||||
const help = [
|
||||
'/join <channel> - Join a channel',
|
||||
|
@ -26,7 +29,8 @@ const help = [
|
|||
const text = content => ({ content });
|
||||
const error = content => ({ content, type: 'error' });
|
||||
const prompt = content => ({ content, type: 'prompt' });
|
||||
const findHelp = cmd => find(help, line => line.slice(1, line.indexOf(' ')) === cmd);
|
||||
const findHelp = cmd =>
|
||||
find(help, line => line.slice(1, line.indexOf(' ')) === cmd);
|
||||
|
||||
export default createCommandMiddleware(COMMAND, {
|
||||
join({ dispatch, server }, channel) {
|
||||
|
@ -160,10 +164,7 @@ export default createCommandMiddleware(COMMAND, {
|
|||
dispatch(raw(cmd, server));
|
||||
return prompt(`=> ${cmd}`);
|
||||
}
|
||||
return [
|
||||
prompt('=> /raw'),
|
||||
error('Missing message')
|
||||
];
|
||||
return [prompt('=> /raw'), error('Missing message')];
|
||||
},
|
||||
|
||||
help(_, ...commands) {
|
||||
|
|
|
@ -14,14 +14,26 @@ export default class App extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { connected, tab, channels, servers,
|
||||
privateChats, showTabList, select, push } = this.props;
|
||||
const mainClass = showTabList ? 'main-container off-canvas' : 'main-container';
|
||||
const {
|
||||
connected,
|
||||
tab,
|
||||
channels,
|
||||
servers,
|
||||
privateChats,
|
||||
showTabList,
|
||||
select,
|
||||
push
|
||||
} = this.props;
|
||||
const mainClass = showTabList
|
||||
? 'main-container off-canvas'
|
||||
: 'main-container';
|
||||
return (
|
||||
<div className="wrap">
|
||||
{!connected &&
|
||||
<div className="app-info">Connection lost, attempting to reconnect...</div>
|
||||
}
|
||||
{!connected && (
|
||||
<div className="app-info">
|
||||
Connection lost, attempting to reconnect...
|
||||
</div>
|
||||
)}
|
||||
<div className="app-container" onClick={this.handleClick}>
|
||||
<TabList
|
||||
tab={tab}
|
||||
|
@ -33,9 +45,15 @@ export default class App extends Component {
|
|||
push={push}
|
||||
/>
|
||||
<div className={mainClass}>
|
||||
<Route name="chat"><Chat /></Route>
|
||||
<Route name="connect"><Connect /></Route>
|
||||
<Route name="settings"><Settings /></Route>
|
||||
<Route name="chat">
|
||||
<Chat />
|
||||
</Route>
|
||||
<Route name="connect">
|
||||
<Connect />
|
||||
</Route>
|
||||
<Route name="settings">
|
||||
<Settings />
|
||||
</Route>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,36 +24,48 @@ export default class TabList extends PureComponent {
|
|||
/>
|
||||
);
|
||||
|
||||
server.forEach((channel, name) => tabs.push(
|
||||
<TabListItem
|
||||
key={address + name}
|
||||
server={address}
|
||||
target={name}
|
||||
content={name}
|
||||
selected={tab.server === address && tab.name === name}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
));
|
||||
|
||||
if (privateChats.has(address) && privateChats.get(address).size > 0) {
|
||||
tabs.push(<div key={`${address}-pm}`} className="tab-label">Private messages</div>);
|
||||
|
||||
privateChats.get(address).forEach(nick => tabs.push(
|
||||
server.forEach((channel, name) =>
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + nick}
|
||||
key={address + name}
|
||||
server={address}
|
||||
target={nick}
|
||||
content={nick}
|
||||
selected={tab.server === address && tab.name === nick}
|
||||
target={name}
|
||||
content={name}
|
||||
selected={tab.server === address && tab.name === name}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
if (privateChats.has(address) && privateChats.get(address).size > 0) {
|
||||
tabs.push(
|
||||
<div key={`${address}-pm}`} className="tab-label">
|
||||
Private messages
|
||||
</div>
|
||||
);
|
||||
|
||||
privateChats
|
||||
.get(address)
|
||||
.forEach(nick =>
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + nick}
|
||||
server={address}
|
||||
target={nick}
|
||||
content={nick}
|
||||
selected={tab.server === address && tab.name === nick}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<button className="button-connect" onClick={this.handleConnectClick}>Connect</button>
|
||||
<button className="button-connect" onClick={this.handleConnectClick}>
|
||||
Connect
|
||||
</button>
|
||||
<div className="tab-container">{tabs}</div>
|
||||
<div className="side-buttons">
|
||||
<i className="icon-user" />
|
||||
|
|
|
@ -90,10 +90,7 @@ export default class Chat extends Component {
|
|||
onToggleSearch={toggleSearch}
|
||||
onToggleUserList={toggleUserList}
|
||||
/>
|
||||
<Search
|
||||
search={search}
|
||||
onSearch={this.handleSearch}
|
||||
/>
|
||||
<Search search={search} onSearch={this.handleSearch} />
|
||||
<MessageBox
|
||||
hasMoreMessages={hasMoreMessages}
|
||||
messages={messages}
|
||||
|
|
|
@ -7,8 +7,16 @@ import { linkify } from 'utils';
|
|||
|
||||
export default class ChatTitle extends PureComponent {
|
||||
render() {
|
||||
const { status, title, tab, channel, onTitleChange,
|
||||
onToggleSearch, onToggleUserList, onCloseClick } = this.props;
|
||||
const {
|
||||
status,
|
||||
title,
|
||||
tab,
|
||||
channel,
|
||||
onTitleChange,
|
||||
onToggleSearch,
|
||||
onToggleUserList,
|
||||
onCloseClick
|
||||
} = this.props;
|
||||
|
||||
let closeTitle;
|
||||
if (tab.isChannel()) {
|
||||
|
@ -21,7 +29,9 @@ export default class ChatTitle extends PureComponent {
|
|||
|
||||
let serverError = null;
|
||||
if (!tab.name && status.error) {
|
||||
serverError = <span className="chat-topic error">Error: {status.error}</span>;
|
||||
serverError = (
|
||||
<span className="chat-topic error">Error: {status.error}</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -38,7 +48,9 @@ export default class ChatTitle extends PureComponent {
|
|||
<span className="chat-title">{title}</span>
|
||||
</Editable>
|
||||
<div className="chat-topic-wrap">
|
||||
<span className="chat-topic">{linkify(channel.get('topic')) || null}</span>
|
||||
<span className="chat-topic">
|
||||
{linkify(channel.get('topic')) || null}
|
||||
</span>
|
||||
{serverError}
|
||||
</div>
|
||||
<i className="icon-search" title="Search" onClick={onToggleSearch} />
|
||||
|
@ -51,7 +63,9 @@ export default class ChatTitle extends PureComponent {
|
|||
</div>
|
||||
<div className="userlist-bar">
|
||||
<i className="icon-user" />
|
||||
<span className="chat-usercount">{channel.get('users', List()).size}</span>
|
||||
<span className="chat-usercount">
|
||||
{channel.get('users', List()).size}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,9 @@ export default class Message extends PureComponent {
|
|||
|
||||
render() {
|
||||
const { message } = this.props;
|
||||
const className = message.type ? `message message-${message.type}` : 'message';
|
||||
const className = message.type
|
||||
? `message message-${message.type}`
|
||||
: 'message';
|
||||
const style = {
|
||||
paddingLeft: `${window.messageIndent + 15}px`,
|
||||
textIndent: `-${window.messageIndent}px`,
|
||||
|
@ -15,11 +17,13 @@ export default class Message extends PureComponent {
|
|||
return (
|
||||
<p className={className} style={style}>
|
||||
<span className="message-time">{message.time}</span>
|
||||
{message.from &&
|
||||
{message.from && (
|
||||
<span className="message-sender" onClick={this.handleNickClick}>
|
||||
{' '}{message.from}
|
||||
{' '}
|
||||
{message.from}
|
||||
</span>
|
||||
}{' '}{message.content}
|
||||
)}{' '}
|
||||
{message.content}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ export default class MessageBox extends PureComponent {
|
|||
|
||||
if (nextProps.messages.get(0) !== this.props.messages.get(0)) {
|
||||
if (nextProps.tab === this.props.tab) {
|
||||
const addedMessages = nextProps.messages.size - this.props.messages.size;
|
||||
const addedMessages =
|
||||
nextProps.messages.size - this.props.messages.size;
|
||||
let addedHeight = 0;
|
||||
for (let i = 0; i < addedMessages; i++) {
|
||||
addedHeight += nextProps.messages.get(i).height;
|
||||
|
@ -126,10 +127,12 @@ export default class MessageBox extends PureComponent {
|
|||
|
||||
handleScroll = ({ scrollTop, clientHeight, scrollHeight }) => {
|
||||
if (this.mounted) {
|
||||
if (!this.loading &&
|
||||
if (
|
||||
!this.loading &&
|
||||
this.props.hasMoreMessages &&
|
||||
scrollTop <= fetchThreshold &&
|
||||
scrollTop < this.prevScrollTop) {
|
||||
scrollTop < this.prevScrollTop
|
||||
) {
|
||||
this.fetchMore();
|
||||
}
|
||||
|
||||
|
@ -165,11 +168,7 @@ export default class MessageBox extends PureComponent {
|
|||
if (index === 0) {
|
||||
if (this.props.hasMoreMessages) {
|
||||
return (
|
||||
<div
|
||||
key="top"
|
||||
className="messagebox-top-indicator"
|
||||
style={style}
|
||||
>
|
||||
<div key="top" className="messagebox-top-indicator" style={style}>
|
||||
Loading messages...
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -7,8 +7,16 @@ export default class MessageInput extends PureComponent {
|
|||
};
|
||||
|
||||
handleKey = e => {
|
||||
const { tab, onCommand, onMessage,
|
||||
add, reset, increment, decrement, currentHistoryEntry } = this.props;
|
||||
const {
|
||||
tab,
|
||||
onCommand,
|
||||
onMessage,
|
||||
add,
|
||||
reset,
|
||||
increment,
|
||||
decrement,
|
||||
currentHistoryEntry
|
||||
} = this.props;
|
||||
|
||||
if (e.key === 'Enter' && e.target.value) {
|
||||
if (e.target.value[0] === '/') {
|
||||
|
@ -36,7 +44,12 @@ export default class MessageInput extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { nick, currentHistoryEntry, onNickChange, onNickEditDone } = this.props;
|
||||
const {
|
||||
nick,
|
||||
currentHistoryEntry,
|
||||
onNickChange,
|
||||
onNickEditDone
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="message-input-wrap">
|
||||
<Editable
|
||||
|
|
|
@ -8,7 +8,9 @@ export default class Search extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
inputRef = el => { this.input = el; };
|
||||
inputRef = el => {
|
||||
this.input = el;
|
||||
};
|
||||
|
||||
handleSearch = e => this.props.onSearch(e.target.value);
|
||||
|
||||
|
|
|
@ -11,12 +11,14 @@ export default class SearchResult extends PureComponent {
|
|||
|
||||
return (
|
||||
<p className="search-result" style={style}>
|
||||
<span className="message-time">{timestamp(new Date(result.time * 1000))}</span>
|
||||
<span className="message-time">
|
||||
{timestamp(new Date(result.time * 1000))}
|
||||
</span>
|
||||
<span>
|
||||
{' '}
|
||||
<span className="message-sender">{result.from}</span>
|
||||
</span>
|
||||
<span>{' '}{linkify(result.content)}</span>
|
||||
<span> {linkify(result.content)}</span>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ export default class UserList extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
listRef = el => { this.list = el; };
|
||||
listRef = el => {
|
||||
this.list = el;
|
||||
};
|
||||
|
||||
renderUser = ({ index, style, key }) => {
|
||||
const { users, onNickClick } = this.props;
|
||||
|
|
|
@ -14,7 +14,10 @@ export default class Connect extends Component {
|
|||
|
||||
let address = e.target.address.value.trim();
|
||||
const nick = e.target.nick.value.trim();
|
||||
const channels = e.target.channels.value.split(',').map(s => s.trim()).filter(s => s);
|
||||
const channels = e.target.channels.value
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s);
|
||||
const opts = {
|
||||
name: e.target.name.value.trim(),
|
||||
tls: e.target.ssl.checked
|
||||
|
@ -78,18 +81,32 @@ export default class Connect extends Component {
|
|||
<Navicon />
|
||||
<form className="connect-form" onSubmit={this.handleSubmit}>
|
||||
<h1>Connect</h1>
|
||||
<input name="name" type="text" placeholder="Name" defaultValue={defaults.name} />
|
||||
<input name="address" type="text" placeholder="Address" defaultValue={defaults.address} />
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
defaultValue={defaults.name}
|
||||
/>
|
||||
<input
|
||||
name="address"
|
||||
type="text"
|
||||
placeholder="Address"
|
||||
defaultValue={defaults.address}
|
||||
/>
|
||||
<input name="nick" type="text" placeholder="Nick" />
|
||||
<input
|
||||
name="channels"
|
||||
type="text"
|
||||
placeholder="Channels"
|
||||
defaultValue={defaults.channels ? defaults.channels.join(',') : null}
|
||||
defaultValue={
|
||||
defaults.channels ? defaults.channels.join(',') : null
|
||||
}
|
||||
/>
|
||||
{optionals}
|
||||
<p>
|
||||
<label htmlFor="ssl"><input name="ssl" type="checkbox" defaultChecked={defaults.ssl} />SSL</label>
|
||||
<label htmlFor="ssl">
|
||||
<input name="ssl" type="checkbox" defaultChecked={defaults.ssl} />SSL
|
||||
</label>
|
||||
<i className="icon-ellipsis" onClick={this.handleShowClick} />
|
||||
</p>
|
||||
<input type="submit" value="Connect" />
|
||||
|
|
|
@ -26,7 +26,7 @@ const Settings = ({ settings, onCertChange, onKeyChange, uploadCert }) => {
|
|||
/>
|
||||
</div>
|
||||
<button onClick={uploadCert}>{status}</button>
|
||||
{ error ? <p className="error">{error}</p> : null }
|
||||
{error ? <p className="error">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -30,7 +30,8 @@ export default class Editable extends PureComponent {
|
|||
getInputWidth(value) {
|
||||
if (this.input) {
|
||||
const style = window.getComputedStyle(this.input);
|
||||
const padding = parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10);
|
||||
const padding =
|
||||
parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10);
|
||||
// Make sure the width is atleast 1px so the caret always shows
|
||||
const width = stringWidth(value, style.font) || 1;
|
||||
return padding + width;
|
||||
|
@ -68,7 +69,9 @@ export default class Editable extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
inputRef = el => { this.input = el; }
|
||||
inputRef = el => {
|
||||
this.input = el;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, className, value } = this.props;
|
||||
|
@ -77,21 +80,21 @@ export default class Editable extends PureComponent {
|
|||
width: this.state.width
|
||||
};
|
||||
|
||||
return (
|
||||
this.state.editing ?
|
||||
<input
|
||||
autoFocus
|
||||
ref={this.inputRef}
|
||||
className={className}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKey}
|
||||
style={style}
|
||||
spellCheck={false}
|
||||
/> :
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
return this.state.editing ? (
|
||||
<input
|
||||
autoFocus
|
||||
ref={this.inputRef}
|
||||
className={className}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKey}
|
||||
style={style}
|
||||
spellCheck={false}
|
||||
/>
|
||||
) : (
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,9 @@ export default class FileInput extends PureComponent {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<button className="input-file" onClick={this.handleClick}>{this.props.name}</button>
|
||||
<button className="input-file" onClick={this.handleClick}>
|
||||
{this.props.name}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,35 @@ import { connect } from 'react-redux';
|
|||
import { createStructuredSelector } from 'reselect';
|
||||
import Chat from 'components/pages/Chat';
|
||||
import { getSelectedTabTitle } from 'state';
|
||||
import { getSelectedChannel, getSelectedChannelUsers, part } from 'state/channels';
|
||||
import { getCurrentInputHistoryEntry, addInputHistory, resetInputHistory,
|
||||
incrementInputHistory, decrementInputHistory } from 'state/input';
|
||||
import { getSelectedMessages, getHasMoreMessages,
|
||||
runCommand, sendMessage, fetchMessages, addFetchedMessages } from 'state/messages';
|
||||
import {
|
||||
getSelectedChannel,
|
||||
getSelectedChannelUsers,
|
||||
part
|
||||
} from 'state/channels';
|
||||
import {
|
||||
getCurrentInputHistoryEntry,
|
||||
addInputHistory,
|
||||
resetInputHistory,
|
||||
incrementInputHistory,
|
||||
decrementInputHistory
|
||||
} from 'state/input';
|
||||
import {
|
||||
getSelectedMessages,
|
||||
getHasMoreMessages,
|
||||
runCommand,
|
||||
sendMessage,
|
||||
fetchMessages,
|
||||
addFetchedMessages
|
||||
} from 'state/messages';
|
||||
import { openPrivateChat, closePrivateChat } from 'state/privateChats';
|
||||
import { getSearch, searchMessages, toggleSearch } from 'state/search';
|
||||
import { getCurrentNick, getCurrentServerStatus, disconnect, setNick, setServerName } from 'state/servers';
|
||||
import {
|
||||
getCurrentNick,
|
||||
getCurrentServerStatus,
|
||||
disconnect,
|
||||
setNick,
|
||||
setServerName
|
||||
} from 'state/servers';
|
||||
import { getSelectedTab, select } from 'state/tab';
|
||||
import { getShowUserList, toggleUserList } from 'state/ui';
|
||||
|
||||
|
@ -29,29 +50,35 @@ const mapState = createStructuredSelector({
|
|||
});
|
||||
|
||||
const mapDispatch = dispatch => ({
|
||||
...bindActionCreators({
|
||||
addFetchedMessages,
|
||||
closePrivateChat,
|
||||
disconnect,
|
||||
fetchMessages,
|
||||
openPrivateChat,
|
||||
part,
|
||||
runCommand,
|
||||
searchMessages,
|
||||
select,
|
||||
sendMessage,
|
||||
setNick,
|
||||
setServerName,
|
||||
toggleSearch,
|
||||
toggleUserList
|
||||
}, dispatch),
|
||||
...bindActionCreators(
|
||||
{
|
||||
addFetchedMessages,
|
||||
closePrivateChat,
|
||||
disconnect,
|
||||
fetchMessages,
|
||||
openPrivateChat,
|
||||
part,
|
||||
runCommand,
|
||||
searchMessages,
|
||||
select,
|
||||
sendMessage,
|
||||
setNick,
|
||||
setServerName,
|
||||
toggleSearch,
|
||||
toggleUserList
|
||||
},
|
||||
dispatch
|
||||
),
|
||||
|
||||
inputActions: bindActionCreators({
|
||||
add: addInputHistory,
|
||||
reset: resetInputHistory,
|
||||
increment: incrementInputHistory,
|
||||
decrement: decrementInputHistory
|
||||
}, dispatch)
|
||||
inputActions: bindActionCreators(
|
||||
{
|
||||
add: addInputHistory,
|
||||
reset: resetInputHistory,
|
||||
increment: incrementInputHistory,
|
||||
decrement: decrementInputHistory
|
||||
},
|
||||
dispatch
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(mapState, mapDispatch)(Chat);
|
||||
|
|
|
@ -10,7 +10,9 @@ import routes from './routes';
|
|||
import runModules from './modules';
|
||||
|
||||
const production = process.env.NODE_ENV === 'production';
|
||||
const host = production ? window.location.host : `${window.location.hostname}:1337`;
|
||||
const host = production
|
||||
? window.location.host
|
||||
: `${window.location.hostname}:1337`;
|
||||
const socket = new Socket(host);
|
||||
const store = configureStore(socket);
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { socketAction } from 'state/actions';
|
||||
import { setConnected } from 'state/app';
|
||||
import { broadcast, inform, print, addMessage, addMessages } from 'state/messages';
|
||||
import {
|
||||
broadcast,
|
||||
inform,
|
||||
print,
|
||||
addMessage,
|
||||
addMessages
|
||||
} from 'state/messages';
|
||||
import { reconnect } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { normalizeChannel } from 'utils';
|
||||
|
@ -21,7 +27,10 @@ function findChannels(state, server, user) {
|
|||
return channels;
|
||||
}
|
||||
|
||||
export default function handleSocket({ socket, store: { dispatch, getState } }) {
|
||||
export default function handleSocket({
|
||||
socket,
|
||||
store: { dispatch, getState }
|
||||
}) {
|
||||
const handlers = {
|
||||
message(message) {
|
||||
dispatch(addMessage(message, message.server, message.to));
|
||||
|
@ -41,10 +50,12 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
const [joinedChannel] = channels;
|
||||
if (tab.server && tab.name) {
|
||||
const { nick } = state.servers.get(tab.server);
|
||||
if (tab.server === server &&
|
||||
if (
|
||||
tab.server === server &&
|
||||
nick === user &&
|
||||
tab.name !== joinedChannel &&
|
||||
normalizeChannel(tab.name) === normalizeChannel(joinedChannel)) {
|
||||
normalizeChannel(tab.name) === normalizeChannel(joinedChannel)
|
||||
) {
|
||||
dispatch(select(server, joinedChannel));
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +64,9 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
},
|
||||
|
||||
part({ user, server, channel, reason }) {
|
||||
dispatch(inform(withReason(`${user} left the channel`, reason), server, channel));
|
||||
dispatch(
|
||||
inform(withReason(`${user} left the channel`, reason), server, channel)
|
||||
);
|
||||
},
|
||||
|
||||
quit({ user, server, reason }) {
|
||||
|
@ -63,7 +76,9 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
|
||||
nick({ server, oldNick, newNick }) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(broadcast(`${oldNick} changed nick to ${newNick}`, server, channels));
|
||||
dispatch(
|
||||
broadcast(`${oldNick} changed nick to ${newNick}`, server, channels)
|
||||
);
|
||||
},
|
||||
|
||||
topic({ server, channel, topic, nick }) {
|
||||
|
@ -84,14 +99,20 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
whois(data) {
|
||||
const tab = getState().tab.selected;
|
||||
|
||||
dispatch(print([
|
||||
`Nick: ${data.nick}`,
|
||||
`Username: ${data.username}`,
|
||||
`Realname: ${data.realname}`,
|
||||
`Host: ${data.host}`,
|
||||
`Server: ${data.server}`,
|
||||
`Channels: ${data.channels}`
|
||||
], tab.server, tab.name));
|
||||
dispatch(
|
||||
print(
|
||||
[
|
||||
`Nick: ${data.nick}`,
|
||||
`Username: ${data.username}`,
|
||||
`Realname: ${data.realname}`,
|
||||
`Host: ${data.host}`,
|
||||
`Server: ${data.server}`,
|
||||
`Channels: ${data.channels}`
|
||||
],
|
||||
tab.server,
|
||||
tab.name
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
print(message) {
|
||||
|
@ -100,11 +121,17 @@ export default function handleSocket({ socket, store: { dispatch, getState } })
|
|||
},
|
||||
|
||||
connection_update({ server, errorType }) {
|
||||
if (errorType === 'verify' &&
|
||||
confirm('The server is using a self-signed certificate, continue anyway?')) {
|
||||
dispatch(reconnect(server, {
|
||||
skipVerify: true
|
||||
}));
|
||||
if (
|
||||
errorType === 'verify' &&
|
||||
confirm(
|
||||
'The server is using a self-signed certificate, continue anyway?'
|
||||
)
|
||||
) {
|
||||
dispatch(
|
||||
reconnect(server, {
|
||||
skipVerify: true
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { connect, setServerName } from '../servers';
|
||||
import { connect, setServerName } from '../servers';
|
||||
|
||||
describe('connect()', () => {
|
||||
it('sets host and port correctly', () => {
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import Immutable from 'immutable';
|
||||
import reducer, { compareUsers } from '../channels';
|
||||
import { connect } from '../servers';
|
||||
import { connect } from '../servers';
|
||||
import * as actions from '../actions';
|
||||
|
||||
describe('channel reducer', () => {
|
||||
it('removes channels on PART', () => {
|
||||
let state = Immutable.fromJS({
|
||||
srv1: {
|
||||
chan1: {}, chan2: {}, chan3: {}
|
||||
chan1: {},
|
||||
chan2: {},
|
||||
chan3: {}
|
||||
},
|
||||
srv2: {
|
||||
chan1: {}
|
||||
|
@ -45,14 +47,10 @@ describe('channel reducer', () => {
|
|||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' },
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -64,9 +62,7 @@ describe('channel reducer', () => {
|
|||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -86,9 +82,7 @@ describe('channel reducer', () => {
|
|||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||
},
|
||||
chan2: {
|
||||
users: []
|
||||
|
@ -118,9 +112,7 @@ describe('channel reducer', () => {
|
|||
]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -131,13 +123,7 @@ describe('channel reducer', () => {
|
|||
type: actions.socket.USERS,
|
||||
server: 'srv',
|
||||
channel: 'chan1',
|
||||
users: [
|
||||
'user3',
|
||||
'user2',
|
||||
'@user4',
|
||||
'user1',
|
||||
'+user5'
|
||||
]
|
||||
users: ['user3', 'user2', '@user4', 'user1', '+user5']
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
|
@ -152,7 +138,7 @@ describe('channel reducer', () => {
|
|||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('handles SOCKET_TOPIC', () => {
|
||||
|
@ -188,14 +174,12 @@ describe('channel reducer', () => {
|
|||
]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick1' ,'v', 'o'));
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick1', 'v', 'o'));
|
||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick2', 'o', ''));
|
||||
state = reducer(state, socket_mode('srv', 'chan2', 'not_there', 'x', ''));
|
||||
|
||||
|
@ -208,9 +192,7 @@ describe('channel reducer', () => {
|
|||
]
|
||||
},
|
||||
chan2: {
|
||||
users: [
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -240,10 +222,7 @@ describe('channel reducer', () => {
|
|||
it('handles SOCKET_SERVERS', () => {
|
||||
const state = reducer(undefined, {
|
||||
type: actions.socket.SERVERS,
|
||||
data: [
|
||||
{ host: '127.0.0.1' },
|
||||
{ host: 'thehost' }
|
||||
]
|
||||
data: [{ host: '127.0.0.1' }, { host: 'thehost' }]
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
|
@ -280,7 +259,8 @@ describe('channel reducer', () => {
|
|||
function socket_join(server, channel, user) {
|
||||
return {
|
||||
type: actions.socket.JOIN,
|
||||
server, user,
|
||||
server,
|
||||
user,
|
||||
channels: [channel]
|
||||
};
|
||||
}
|
||||
|
@ -288,29 +268,35 @@ function socket_join(server, channel, user) {
|
|||
function socket_mode(server, channel, user, add, remove) {
|
||||
return {
|
||||
type: actions.socket.MODE,
|
||||
server, channel, user, add, remove
|
||||
server,
|
||||
channel,
|
||||
user,
|
||||
add,
|
||||
remove
|
||||
};
|
||||
}
|
||||
|
||||
describe('compareUsers()', () => {
|
||||
it('compares users correctly', () => {
|
||||
expect([
|
||||
{ renderName: 'user5' },
|
||||
{ renderName: '@user2' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user2' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: '~bob' },
|
||||
{ renderName: '%apples' },
|
||||
{ renderName: '&cake' }
|
||||
].sort(compareUsers)).toEqual([
|
||||
expect(
|
||||
[
|
||||
{ renderName: 'user5' },
|
||||
{ renderName: '@user2' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user2' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: '~bob' },
|
||||
{ renderName: '%apples' },
|
||||
{ renderName: '&cake' }
|
||||
].sort(compareUsers)
|
||||
).toEqual([
|
||||
{ renderName: '~bob' },
|
||||
{ renderName: '&cake' },
|
||||
{ renderName: '@user2' },
|
||||
{ renderName: '%apples' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: '+user1' },
|
||||
{ renderName: 'user2' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user3' },
|
||||
{ renderName: 'user5' }
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -17,10 +17,12 @@ describe('message reducer', () => {
|
|||
|
||||
expect(state.toJS()).toMatchObject({
|
||||
srv: {
|
||||
'#chan1': [{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}]
|
||||
'#chan1': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -34,10 +36,12 @@ describe('message reducer', () => {
|
|||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
from: 'bar',
|
||||
content: 'msg'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
tab: '#chan2',
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
|
@ -51,15 +55,18 @@ describe('message reducer', () => {
|
|||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
from: 'bar',
|
||||
content: 'msg'
|
||||
}
|
||||
],
|
||||
'#chan2': [{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}]
|
||||
'#chan2': [
|
||||
{
|
||||
from: 'foo',
|
||||
content: 'msg'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -92,10 +99,9 @@ describe('message reducer', () => {
|
|||
};
|
||||
|
||||
const thunk = broadcast('test', 'srv', ['#chan1', '#chan3']);
|
||||
thunk(
|
||||
action => { state.messages = reducer(undefined, action); },
|
||||
() => state
|
||||
);
|
||||
thunk(action => {
|
||||
state.messages = reducer(undefined, action);
|
||||
}, () => state);
|
||||
|
||||
const messages = state.messages.toJS();
|
||||
|
||||
|
@ -109,18 +115,11 @@ describe('message reducer', () => {
|
|||
it('deletes all messages related to server when disconnecting', () => {
|
||||
let state = fromJS({
|
||||
srv: {
|
||||
'#chan1': [
|
||||
{ content: 'msg1' },
|
||||
{ content: 'msg2' }
|
||||
],
|
||||
'#chan2': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||
'#chan2': [{ content: 'msg' }]
|
||||
},
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -131,9 +130,7 @@ describe('message reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -141,18 +138,11 @@ describe('message reducer', () => {
|
|||
it('deletes all messages related to channel when parting', () => {
|
||||
let state = fromJS({
|
||||
srv: {
|
||||
'#chan1': [
|
||||
{ content: 'msg1' },
|
||||
{ content: 'msg2' }
|
||||
],
|
||||
'#chan2': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||
'#chan2': [{ content: 'msg' }]
|
||||
},
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -164,14 +154,10 @@ describe('message reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
srv: {
|
||||
'#chan2': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan2': [{ content: 'msg' }]
|
||||
},
|
||||
srv2: {
|
||||
'#chan1': [
|
||||
{ content: 'msg' }
|
||||
]
|
||||
'#chan1': [{ content: 'msg' }]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,9 +32,12 @@ describe('server reducer', () => {
|
|||
}
|
||||
});
|
||||
|
||||
state = reducer(state, connect('127.0.0.2:1337', 'nick', {
|
||||
name: 'srv'
|
||||
}));
|
||||
state = reducer(
|
||||
state,
|
||||
connect('127.0.0.2:1337', 'nick', {
|
||||
name: 'srv'
|
||||
})
|
||||
);
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
'127.0.0.1': {
|
||||
|
@ -190,7 +193,7 @@ describe('server reducer', () => {
|
|||
status: {
|
||||
connected: false
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
@ -8,9 +8,7 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: 'srv', name: '#chan' },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' }
|
||||
]
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
});
|
||||
|
||||
state = reducer(state, setSelectedTab('srv', 'user1'));
|
||||
|
@ -62,7 +60,7 @@ describe('tab reducer', () => {
|
|||
history: [
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: '#chan3' }
|
||||
{ server: 'srv', name: '#chan3' }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
@ -75,14 +73,12 @@ describe('tab reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.DISCONNECT,
|
||||
server: 'srv',
|
||||
server: 'srv'
|
||||
});
|
||||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: 'srv', name: '#chan3' },
|
||||
history: [
|
||||
{ server: 'srv1', name: 'bob' },
|
||||
]
|
||||
history: [{ server: 'srv1', name: 'bob' }]
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -93,14 +89,13 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: null, name: null },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' }
|
||||
]
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
});
|
||||
});
|
||||
|
||||
it('selects the tab and adds it to history when navigating to a tab', () => {
|
||||
const state = reducer(undefined,
|
||||
const state = reducer(
|
||||
undefined,
|
||||
locationChanged('chat', {
|
||||
server: 'srv',
|
||||
name: '#chan'
|
||||
|
@ -109,9 +104,7 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state.toJS()).toEqual({
|
||||
selected: { server: 'srv', name: '#chan' },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' }
|
||||
]
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,11 +29,13 @@ function updateRenderName(user) {
|
|||
}
|
||||
|
||||
function createUser(nick, mode) {
|
||||
return updateRenderName(new User({
|
||||
nick,
|
||||
renderName: nick,
|
||||
mode: mode || ''
|
||||
}));
|
||||
return updateRenderName(
|
||||
new User({
|
||||
nick,
|
||||
renderName: nick,
|
||||
mode: mode || ''
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function loadUser(nick) {
|
||||
|
@ -80,13 +82,14 @@ export const getChannels = state => state.channels;
|
|||
|
||||
const key = (v, k) => k.toLowerCase();
|
||||
|
||||
export const getSortedChannels = createSelector(
|
||||
getChannels,
|
||||
channels => channels.withMutations(c =>
|
||||
c.forEach((server, address) =>
|
||||
c.update(address, chans => chans.sortBy(key))
|
||||
export const getSortedChannels = createSelector(getChannels, channels =>
|
||||
channels
|
||||
.withMutations(c =>
|
||||
c.forEach((server, address) =>
|
||||
c.update(address, chans => chans.sortBy(key))
|
||||
)
|
||||
)
|
||||
).sortBy(key)
|
||||
.sortBy(key)
|
||||
);
|
||||
|
||||
export const getSelectedChannel = createSelector(
|
||||
|
@ -125,7 +128,9 @@ export default createReducer(Map(), {
|
|||
[actions.socket.QUIT](state, { server, user }) {
|
||||
return state.withMutations(s => {
|
||||
s.get(server).forEach((v, channel) => {
|
||||
s.updateIn([server, channel, 'users'], users => users.filter(u => u.nick !== user));
|
||||
s.updateIn([server, channel, 'users'], users =>
|
||||
users.filter(u => u.nick !== user)
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -139,8 +144,8 @@ export default createReducer(Map(), {
|
|||
return users;
|
||||
}
|
||||
|
||||
return users.update(i,
|
||||
user => updateRenderName(user.set('nick', newNick))
|
||||
return users.update(i, user =>
|
||||
updateRenderName(user.set('nick', newNick))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -148,7 +153,8 @@ export default createReducer(Map(), {
|
|||
},
|
||||
|
||||
[actions.socket.USERS](state, { server, channel, users }) {
|
||||
return state.setIn([server, channel, 'users'],
|
||||
return state.setIn(
|
||||
[server, channel, 'users'],
|
||||
List(users.map(user => loadUser(user)))
|
||||
);
|
||||
},
|
||||
|
@ -183,10 +189,13 @@ export default createReducer(Map(), {
|
|||
|
||||
return state.withMutations(s => {
|
||||
data.forEach(channel => {
|
||||
s.setIn([channel.server, channel.name], Map({
|
||||
users: List(),
|
||||
topic: channel.topic
|
||||
}));
|
||||
s.setIn(
|
||||
[channel.server, channel.name],
|
||||
Map({
|
||||
users: List(),
|
||||
topic: channel.topic
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { List, Map, Record } from 'immutable';
|
||||
import { createSelector } from 'reselect';
|
||||
import { findBreakpoints, messageHeight, linkify, timestamp, isChannel } from 'utils';
|
||||
import {
|
||||
findBreakpoints,
|
||||
messageHeight,
|
||||
linkify,
|
||||
timestamp,
|
||||
isChannel
|
||||
} from 'utils';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getApp } from './app';
|
||||
import { getSelectedTab } from './tab';
|
||||
|
@ -24,7 +30,8 @@ export const getMessages = state => state.messages;
|
|||
export const getSelectedMessages = createSelector(
|
||||
getSelectedTab,
|
||||
getMessages,
|
||||
(tab, messages) => messages.getIn([tab.server, tab.name || tab.server], List())
|
||||
(tab, messages) =>
|
||||
messages.getIn([tab.server, tab.name || tab.server], List())
|
||||
);
|
||||
|
||||
export const getHasMoreMessages = createSelector(
|
||||
|
@ -37,18 +44,24 @@ export const getHasMoreMessages = createSelector(
|
|||
|
||||
export default createReducer(Map(), {
|
||||
[actions.ADD_MESSAGE](state, { server, tab, message }) {
|
||||
return state.updateIn([server, tab], List(), list => list.push(new Message(message)));
|
||||
return state.updateIn([server, tab], List(), list =>
|
||||
list.push(new Message(message))
|
||||
);
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGES](state, { server, tab, messages, prepend }) {
|
||||
return state.withMutations(s => {
|
||||
if (prepend) {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
s.updateIn([server, tab], List(), list => list.unshift(new Message(messages[i])));
|
||||
s.updateIn([server, tab], List(), list =>
|
||||
list.unshift(new Message(messages[i]))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
messages.forEach(message =>
|
||||
s.updateIn([server, message.tab || tab], List(), list => list.push(new Message(message)))
|
||||
s.updateIn([server, message.tab || tab], List(), list =>
|
||||
list.push(new Message(message))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -60,18 +73,28 @@ export default createReducer(Map(), {
|
|||
|
||||
[actions.PART](state, { server, channels }) {
|
||||
return state.withMutations(s =>
|
||||
channels.forEach(channel =>
|
||||
s.deleteIn([server, channel])
|
||||
)
|
||||
channels.forEach(channel => s.deleteIn([server, channel]))
|
||||
);
|
||||
},
|
||||
|
||||
[actions.UPDATE_MESSAGE_HEIGHT](state, { wrapWidth, charWidth, windowWidth }) {
|
||||
[actions.UPDATE_MESSAGE_HEIGHT](
|
||||
state,
|
||||
{ wrapWidth, charWidth, windowWidth }
|
||||
) {
|
||||
return state.withMutations(s =>
|
||||
s.forEach((server, serverKey) =>
|
||||
server.forEach((target, targetKey) =>
|
||||
target.forEach((message, index) => s.setIn([serverKey, targetKey, index, 'height'],
|
||||
messageHeight(message, wrapWidth, charWidth, 6 * charWidth, windowWidth))
|
||||
target.forEach((message, index) =>
|
||||
s.setIn(
|
||||
[serverKey, targetKey, index, 'height'],
|
||||
messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
6 * charWidth,
|
||||
windowWidth
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -111,7 +134,13 @@ function initMessage(message, tab, state) {
|
|||
|
||||
message.length = message.content.length;
|
||||
message.breakpoints = findBreakpoints(message.content);
|
||||
message.height = messageHeight(message, wrapWidth, charWidth, 6 * charWidth, windowWidth);
|
||||
message.height = messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
6 * charWidth,
|
||||
windowWidth
|
||||
);
|
||||
message.content = linkify(message.content);
|
||||
|
||||
return message;
|
||||
|
@ -175,10 +204,14 @@ export function sendMessage(content, to, server) {
|
|||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
tab: to,
|
||||
message: initMessage({
|
||||
from: state.servers.getIn([server, 'nick']),
|
||||
content
|
||||
}, to, state),
|
||||
message: initMessage(
|
||||
{
|
||||
from: state.servers.getIn([server, 'nick']),
|
||||
content
|
||||
},
|
||||
to,
|
||||
state
|
||||
),
|
||||
socket: {
|
||||
type: 'message',
|
||||
data: { content, to, server }
|
||||
|
@ -190,12 +223,13 @@ export function sendMessage(content, to, server) {
|
|||
export function addMessage(message, server, to) {
|
||||
const tab = getMessageTab(server, to);
|
||||
|
||||
return (dispatch, getState) => dispatch({
|
||||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
tab,
|
||||
message: initMessage(message, tab, getState())
|
||||
});
|
||||
return (dispatch, getState) =>
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
tab,
|
||||
message: initMessage(message, tab, getState())
|
||||
});
|
||||
}
|
||||
|
||||
export function addMessages(messages, server, to, prepend, next) {
|
||||
|
@ -209,7 +243,9 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||
messages[0].next = true;
|
||||
}
|
||||
|
||||
messages.forEach(message => initMessage(message, message.tab || tab, state));
|
||||
messages.forEach(message =>
|
||||
initMessage(message, message.tab || tab, state)
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGES,
|
||||
|
@ -222,25 +258,36 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||
}
|
||||
|
||||
export function broadcast(message, server, channels) {
|
||||
return addMessages(channels.map(channel => ({
|
||||
tab: channel,
|
||||
content: message,
|
||||
type: 'info'
|
||||
})), server);
|
||||
return addMessages(
|
||||
channels.map(channel => ({
|
||||
tab: channel,
|
||||
content: message,
|
||||
type: 'info'
|
||||
})),
|
||||
server
|
||||
);
|
||||
}
|
||||
|
||||
export function print(message, server, channel, type) {
|
||||
if (Array.isArray(message)) {
|
||||
return addMessages(message.map(line => ({
|
||||
content: line,
|
||||
type
|
||||
})), server, channel);
|
||||
return addMessages(
|
||||
message.map(line => ({
|
||||
content: line,
|
||||
type
|
||||
})),
|
||||
server,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
return addMessage({
|
||||
content: message,
|
||||
type
|
||||
}, server, channel);
|
||||
return addMessage(
|
||||
{
|
||||
content: message,
|
||||
type
|
||||
},
|
||||
server,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
export function inform(message, server, channel) {
|
||||
|
|
|
@ -10,11 +10,12 @@ const lowerCaseValue = v => v.toLowerCase();
|
|||
|
||||
export const getSortedPrivateChats = createSelector(
|
||||
getPrivateChats,
|
||||
privateChats => privateChats.withMutations(p =>
|
||||
p.forEach((server, address) =>
|
||||
p.update(address, chats => chats.sortBy(lowerCaseValue))
|
||||
privateChats =>
|
||||
privateChats.withMutations(p =>
|
||||
p.forEach((server, address) =>
|
||||
p.update(address, chats => chats.sortBy(lowerCaseValue))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
function open(state, server, nick) {
|
||||
|
|
|
@ -45,10 +45,13 @@ export const getCurrentServerStatus = createSelector(
|
|||
export default createReducer(Map(), {
|
||||
[actions.CONNECT](state, { host, nick, options }) {
|
||||
if (!state.has(host)) {
|
||||
return state.set(host, new Server({
|
||||
nick,
|
||||
name: options.name || host
|
||||
}));
|
||||
return state.set(
|
||||
host,
|
||||
new Server({
|
||||
nick,
|
||||
name: options.name || host
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return state;
|
||||
|
@ -73,9 +76,8 @@ export default createReducer(Map(), {
|
|||
|
||||
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
||||
if (!oldNick || oldNick === state.get(server).nick) {
|
||||
return state.update(server, s => s
|
||||
.set('nick', newNick)
|
||||
.set('editedNick', null)
|
||||
return state.update(server, s =>
|
||||
s.set('nick', newNick).set('editedNick', null)
|
||||
);
|
||||
}
|
||||
return state;
|
||||
|
|
|
@ -40,19 +40,29 @@ export default createReducer(new State(), {
|
|||
[actions.SELECT_TAB]: selectTab,
|
||||
|
||||
[actions.PART](state, action) {
|
||||
return state.set('history', state.history.filter(tab =>
|
||||
!(tab.server === action.server && tab.name === action.channels[0])
|
||||
));
|
||||
return state.set(
|
||||
'history',
|
||||
state.history.filter(
|
||||
tab =>
|
||||
!(tab.server === action.server && tab.name === action.channels[0])
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
||||
return state.set('history', state.history.filter(tab =>
|
||||
!(tab.server === action.server && tab.name === action.nick)
|
||||
));
|
||||
return state.set(
|
||||
'history',
|
||||
state.history.filter(
|
||||
tab => !(tab.server === action.server && tab.name === action.nick)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.set('history', state.history.filter(tab => tab.server !== action.server));
|
||||
return state.set(
|
||||
'history',
|
||||
state.history.filter(tab => tab.server !== action.server)
|
||||
);
|
||||
},
|
||||
|
||||
[LOCATION_CHANGED](state, action) {
|
||||
|
|
|
@ -7,20 +7,24 @@ import createSocketMiddleware from './middleware/socket';
|
|||
import commands from './commands';
|
||||
|
||||
export default function configureStore(socket) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const composeEnhancers =
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const reducer = createReducer(routeReducer);
|
||||
|
||||
const store = createStore(reducer, composeEnhancers(
|
||||
applyMiddleware(
|
||||
thunk,
|
||||
routeMiddleware,
|
||||
createSocketMiddleware(socket),
|
||||
message,
|
||||
commands
|
||||
const store = createStore(
|
||||
reducer,
|
||||
composeEnhancers(
|
||||
applyMiddleware(
|
||||
thunk,
|
||||
routeMiddleware,
|
||||
createSocketMiddleware(socket),
|
||||
message,
|
||||
commands
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export default class Socket {
|
|||
this.retry();
|
||||
};
|
||||
|
||||
this.ws.onmessage = (e) => {
|
||||
this.ws.onmessage = e => {
|
||||
this.setTimeoutPing();
|
||||
|
||||
const msg = JSON.parse(e.data);
|
||||
|
|
|
@ -2,55 +2,40 @@ import React from 'react';
|
|||
import linkify from '../linkify';
|
||||
|
||||
describe('linkify()', () => {
|
||||
const proto = href => href.indexOf('http') !== 0 ? `http://${href}` : href;
|
||||
const linkTo = href => <a href={proto(href)} rel="noopener noreferrer" target="_blank">{href}</a>;
|
||||
const proto = href => (href.indexOf('http') !== 0 ? `http://${href}` : href);
|
||||
const linkTo = href => (
|
||||
<a href={proto(href)} rel="noopener noreferrer" target="_blank">
|
||||
{href}
|
||||
</a>
|
||||
);
|
||||
|
||||
it('returns the arg when no matches are found', () => [
|
||||
null,
|
||||
undefined,
|
||||
10,
|
||||
false,
|
||||
true,
|
||||
'just some text',
|
||||
''
|
||||
].forEach(input => expect(linkify(input)).toBe(input)));
|
||||
it('returns the arg when no matches are found', () =>
|
||||
[null, undefined, 10, false, true, 'just some text', ''].forEach(input =>
|
||||
expect(linkify(input)).toBe(input)
|
||||
));
|
||||
|
||||
it('linkifies text', () => Object.entries({
|
||||
'google.com': linkTo('google.com'),
|
||||
'google.com stuff': [
|
||||
linkTo('google.com'),
|
||||
' stuff'
|
||||
],
|
||||
'cake google.com stuff': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff'
|
||||
],
|
||||
'cake google.com stuff https://google.com': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff ',
|
||||
linkTo('https://google.com')
|
||||
],
|
||||
'cake google.com stuff pie https://google.com ': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff pie ',
|
||||
linkTo('https://google.com'),
|
||||
' '
|
||||
],
|
||||
' google.com': [
|
||||
' ',
|
||||
linkTo('google.com')
|
||||
],
|
||||
'google.com ': [
|
||||
linkTo('google.com'),
|
||||
' '
|
||||
],
|
||||
'/google.com?': [
|
||||
'/',
|
||||
linkTo('google.com'),
|
||||
'?'
|
||||
]
|
||||
}).forEach(([ input, expected ]) => expect(linkify(input)).toEqual(expected)));
|
||||
it('linkifies text', () =>
|
||||
Object.entries({
|
||||
'google.com': linkTo('google.com'),
|
||||
'google.com stuff': [linkTo('google.com'), ' stuff'],
|
||||
'cake google.com stuff': ['cake ', linkTo('google.com'), ' stuff'],
|
||||
'cake google.com stuff https://google.com': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff ',
|
||||
linkTo('https://google.com')
|
||||
],
|
||||
'cake google.com stuff pie https://google.com ': [
|
||||
'cake ',
|
||||
linkTo('google.com'),
|
||||
' stuff pie ',
|
||||
linkTo('https://google.com'),
|
||||
' '
|
||||
],
|
||||
' google.com': [' ', linkTo('google.com')],
|
||||
'google.com ': [linkTo('google.com'), ' '],
|
||||
'/google.com?': ['/', linkTo('google.com'), '?']
|
||||
}).forEach(([input, expected]) =>
|
||||
expect(linkify(input)).toEqual(expected)
|
||||
));
|
||||
});
|
||||
|
|
|
@ -8,7 +8,10 @@ export function normalizeChannel(channel) {
|
|||
return channel;
|
||||
}
|
||||
|
||||
return channel.split('#').join('').toLowerCase();
|
||||
return channel
|
||||
.split('#')
|
||||
.join('')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function isChannel(name) {
|
||||
|
|
|
@ -30,12 +30,19 @@ export default function linkify(text) {
|
|||
}
|
||||
|
||||
result.push(
|
||||
<a target="_blank" rel="noopener noreferrer" href={match.getAnchorHref()}>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={match.getAnchorHref()}
|
||||
>
|
||||
{match.matchedText}
|
||||
</a>
|
||||
);
|
||||
} else if (typeof result[result.length - 1] === 'string') {
|
||||
result[result.length - 1] += text.slice(pos, match.offset + match.matchedText.length);
|
||||
result[result.length - 1] += text.slice(
|
||||
pos,
|
||||
match.offset + match.matchedText.length
|
||||
);
|
||||
} else {
|
||||
result.push(text.slice(pos, match.offset + match.matchedText.length));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,13 @@ export function findBreakpoints(text) {
|
|||
return breakpoints;
|
||||
}
|
||||
|
||||
export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowWidth) {
|
||||
export function messageHeight(
|
||||
message,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
indent = 0,
|
||||
windowWidth
|
||||
) {
|
||||
let pad = (6 + (message.from ? message.from.length + 1 : 0)) * charWidth;
|
||||
let height = lineHeight + 8;
|
||||
|
||||
|
@ -26,7 +32,7 @@ export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowW
|
|||
wrapWidth -= userListWidth;
|
||||
}
|
||||
|
||||
if (pad + (message.length * charWidth) < wrapWidth) {
|
||||
if (pad + message.length * charWidth < wrapWidth) {
|
||||
return height;
|
||||
}
|
||||
|
||||
|
@ -35,7 +41,7 @@ export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowW
|
|||
let prevPos = 0;
|
||||
|
||||
for (let i = 0; i < breaks.length; i++) {
|
||||
if (pad + ((breaks[i].end - prevBreak) * charWidth) >= wrapWidth) {
|
||||
if (pad + (breaks[i].end - prevBreak) * charWidth >= wrapWidth) {
|
||||
prevBreak = prevPos;
|
||||
pad = indent;
|
||||
height += lineHeight;
|
||||
|
@ -44,7 +50,7 @@ export function messageHeight(message, wrapWidth, charWidth, indent = 0, windowW
|
|||
prevPos = breaks[i].next;
|
||||
}
|
||||
|
||||
if (pad + ((message.length - prevBreak) * charWidth) >= wrapWidth) {
|
||||
if (pad + (message.length - prevBreak) * charWidth >= wrapWidth) {
|
||||
height += lineHeight;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,10 @@ export default function initRouter(routes, store) {
|
|||
|
||||
history.listen(location => {
|
||||
const nextMatch = match(patterns, location);
|
||||
if (nextMatch && nextMatch.location.pathname !== matched.location.pathname) {
|
||||
if (
|
||||
nextMatch &&
|
||||
nextMatch.location.pathname !== matched.location.pathname
|
||||
) {
|
||||
matched = nextMatch;
|
||||
store.dispatch(matched);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,7 @@ var webpack = require('webpack');
|
|||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: [
|
||||
'webpack-hot-middleware/client',
|
||||
'./src/js/index'
|
||||
],
|
||||
entry: ['webpack-hot-middleware/client', './src/js/index'],
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/'
|
||||
|
@ -21,12 +18,18 @@ module.exports = {
|
|||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.js$/, loader: 'eslint-loader', exclude: /node_modules/, enforce: 'pre' },
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'eslint-loader',
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
options: {
|
||||
fix: true
|
||||
}
|
||||
},
|
||||
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
|
||||
{ test: /\.css$/, loader: 'style-loader!css-loader' }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
]
|
||||
plugins: [new webpack.HotModuleReplacementPlugin()]
|
||||
};
|
||||
|
|
|
@ -3,9 +3,7 @@ var webpack = require('webpack');
|
|||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: [
|
||||
'./src/js/index'
|
||||
],
|
||||
entry: ['./src/js/index'],
|
||||
output: {
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
|
@ -19,7 +17,15 @@ module.exports = {
|
|||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ test: /\.js$/, loader: 'eslint-loader', exclude: /node_modules/, enforce: 'pre' },
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'eslint-loader',
|
||||
exclude: /node_modules/,
|
||||
enforce: 'pre',
|
||||
options: {
|
||||
fix: true
|
||||
}
|
||||
},
|
||||
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
|
||||
{ test: /\.css$/, loader: 'style-loader!css-loader' }
|
||||
]
|
||||
|
|
|
@ -2581,6 +2581,12 @@ eslint-config-airbnb@^16.1.0:
|
|||
dependencies:
|
||||
eslint-config-airbnb-base "^12.1.0"
|
||||
|
||||
eslint-config-prettier@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz#5ecd65174d486c22dff389fe036febf502d468a3"
|
||||
dependencies:
|
||||
get-stdin "^5.0.1"
|
||||
|
||||
eslint-import-resolver-node@^0.3.1:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
|
||||
|
@ -2648,6 +2654,13 @@ eslint-plugin-jsx-a11y@^6.0.3:
|
|||
emoji-regex "^6.1.0"
|
||||
jsx-ast-utils "^2.0.0"
|
||||
|
||||
eslint-plugin-prettier@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz#33e4e228bdb06142d03c560ce04ec23f6c767dd7"
|
||||
dependencies:
|
||||
fast-diff "^1.1.1"
|
||||
jest-docblock "^21.0.0"
|
||||
|
||||
eslint-plugin-react@^7.7.0:
|
||||
version "7.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz#f606c719dbd8a1a2b3d25c16299813878cca0160"
|
||||
|
@ -2939,6 +2952,10 @@ fast-deep-equal@^1.0.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
||||
|
||||
fast-diff@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||
|
@ -3241,6 +3258,10 @@ get-caller-file@^1.0.1:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
||||
|
||||
get-stdin@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
|
||||
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
|
@ -4267,6 +4288,10 @@ jest-diff@^22.4.3:
|
|||
jest-get-type "^22.4.3"
|
||||
pretty-format "^22.4.3"
|
||||
|
||||
jest-docblock@^21.0.0:
|
||||
version "21.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414"
|
||||
|
||||
jest-docblock@^22.4.3:
|
||||
version "22.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19"
|
||||
|
@ -5885,6 +5910,10 @@ preserve@^0.2.0:
|
|||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
|
||||
|
||||
prettier@1.11.1:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
|
||||
|
||||
pretty-format@^22.4.3:
|
||||
version "22.4.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f"
|
||||
|
|
Loading…
Reference in New Issue