chore: initial

This commit is contained in:
cha0s 2020-12-07 03:24:45 -06:00
commit a434670e5e
63 changed files with 19077 additions and 0 deletions

116
.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

18
app/.eslint.defaults.js Normal file
View File

@ -0,0 +1,18 @@
const config = {
globals: {
__non_webpack_require__: true,
process: true,
},
rules: {
'babel/object-curly-spacing': 'off',
'brace-style': ['error', 'stroustrup'],
'no-bitwise': ['error', {int32Hint: true}],
'no-plusplus': 'off',
'no-shadow': 'off',
'no-underscore-dangle': 'off',
'padded-blocks': ['error', {classes: 'always'}],
yoda: 'off',
},
};
module.exports = config;

3
app/.eslintrc.js Normal file
View File

@ -0,0 +1,3 @@
const neutrino = require('neutrino');
module.exports = neutrino().eslintrc();

119
app/.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
/build
/latus.yml

5
app/.mocharc.js Normal file
View File

@ -0,0 +1,5 @@
const neutrino = require('neutrino');
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
module.exports = neutrino().mocha();

59
app/.neutrinorc.js Normal file
View File

@ -0,0 +1,59 @@
require('dotenv/config');
const airbnbBase = require('@neutrinojs/airbnb-base');
const clean = require('@neutrinojs/clean');
const mocha = require('@neutrinojs/mocha');
const node = require('@neutrinojs/node');
const {EnvironmentPlugin} = require('webpack');
const nodeExternals = require('webpack-node-externals');
module.exports = {
options: {
root: __dirname,
},
use: [
airbnbBase({
eslint: {
cache: false,
baseConfig: require('./.eslint.defaults'),
},
}),
clean({
cleanOnceBeforeBuildPatterns: ['**/*.hot-update.*'],
}),
mocha(),
node(),
(neutrino) => {
neutrino.config
.plugin('environment')
.use(EnvironmentPlugin, [{
SIDE: 'server',
}]);
neutrino.config
.entry('index')
.prepend('@latus/core/start');
if ('production' !== neutrino.config.get('mode')) {
neutrino.config
.entry('index')
.prepend('dotenv/config');
neutrino.config
.plugin('start-server')
.tap((args) => {
const options = args[0];
const inspectArg = process.argv.find((arg) => -1 !== arg.indexOf('--inspect'));
if (inspectArg) {
options.nodeArgs.push(inspectArg);
}
const profArg = process.argv.find((arg) => -1 !== arg.indexOf('--prof'));
if (profArg) {
options.nodeArgs.push(profArg);
}
return args;
});
}
neutrino.config
.externals(nodeExternals({
}));
},
],
};

17
app/docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
version: '2'
services:
redis:
image: redis:6
ports:
- 6380:6379
mysql:
image: mysql:8
command:
- '--default-authentication-plugin=mysql_native_password'
environment:
- MYSQL_DATABASE=db
- MYSQL_ROOT_PASSWORD=UNSAFE_DEV_PASSWORD
ports:
- 32342:3306

36
app/latus.default.yml Normal file
View File

@ -0,0 +1,36 @@
'@latus/core': {
up: [
'@latus/db',
'@latus/redis',
'@latus/http',
'@latus/repl',
],
}
'@latus/db': {
docker: 'cached',
}
'@latus/http': {
plugins: [
'@latus/core',
'@latus/socket',
'@latus/react',
],
request: [
'@latus/user/session',
'@latus/user/passport',
],
}
'@latus/react': {}
'@latus/redis': {
docker: 'cached',
}
'@latus/redis/session': {}
'@latus/repl': {}
'@latus/socket': {
middleware: [
'@latus/user/session',
'@latus/user/passport',
],
}
'@latus/user/passport': {}
'@latus/user/session': {}

44
app/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "reddichat",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development",
"forcelatus": "pkgs=$(find node_modules/@latus -maxdepth 1 -mindepth 1 -printf '@latus/%f '); yarn upgrade $pkgs",
"lint": "eslint --cache --format codeframe --ext mjs,jsx,js src",
"repl": "rlwrap -C qmp socat STDIO UNIX:$(ls /tmp/latus-*.sock | tail -n 1)",
"start": "NODE_ENV=production node build/index.js",
"test": "mocha --watch src",
"watch": "webpack --watch --mode development"
},
"dependencies": {
"@latus/core": "^1.0.0",
"@latus/db": "^1.0.0",
"@latus/governor": "^1.0.0",
"@latus/http": "^1.0.0",
"@latus/react": "^1.0.0",
"@latus/redis": "^1.0.0",
"@latus/repl": "^1.0.0",
"@latus/socket": "^1.0.0",
"@latus/user": "^1.0.0",
"classnames": "^2.2.6",
"dotenv": "8.2.0",
"react-hot-loader": "4.13.0",
"react-router-dom": "^5.2.0"
},
"devDependencies": {
"@neutrinojs/airbnb-base": "^9.1.0",
"@neutrinojs/clean": "^9.1.0",
"@neutrinojs/mocha": "^9.1.0",
"@neutrinojs/node": "^9.1.0",
"babel-plugin-webpack-alias": "^2.1.2",
"eslint": "^6",
"eslint-import-resolver-webpack": "^0.12.1",
"js-yaml": "3.14.0",
"neutrino": "^9.1.0",
"source-map-support": "0.5.19",
"webpack": "^4",
"webpack-cli": "^3"
}
}

3
app/postcss.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
plugins: {},
};

1
app/src/index.js Normal file
View File

@ -0,0 +1 @@
process.stdout.write('Your application is starting...\n');

View File

@ -0,0 +1,119 @@
import './index.scss';
import React from 'react';
import {Link} from 'react-router-dom';
const About = () => (
<div className="about">
<h1 className="about__title">
<span className="about__titleMessage">
<span class="about__nowrap">Hi!</span> <span class="about__nowrap about__smile">^_^</span>
{' '}
<img
alt="cha0s's icon"
title="This is a photo-realistic portrait. I have telekinetic powers."
className="about__avatar"
src="http://i.imgur.com/Xf7KBRR.png"
/>
</span>
</h1>
<section className="about__explains">
<h2>reddichat is a chat site for redditors</h2>
<p>
Chatting with people on reddit.com is
{' '}
<em>okay</em>
, but it&apos;s kinda lame. I built this site for fun and because I thought it would be
{' '}
an interesting project. I recently brought it back from the dead after about 5 years.
Spooooky.
</p>
<h2>
Don&apos;t expect permanent logs
{' '}
<small>
...that means you too, 3-letter bois
</small>
</h2>
<p>
I&apos;m big on privacy and all that, so I&apos;m not keeping any permanent logs of this
chat. This may or may not be a good idea. I could go either way. I am inclined to suggest
that only through the transient nature of existence can we truly appreciate its permanence.
Or something.
</p>
<p>
Chat and private messages last for
{' '}
<strong style={{color: 'cyan'}}>10</strong>
{' '}
minutes. Messages in /r/anonymous last for
{' '}
<strong style={{color: 'orange'}}>1</strong>
{' '}
minute.
</p>
<h2>Be excellent to each other</h2>
<p>
This service has one simple rule, which you agree to respect when using it:
{' '}
<a
className="about__dont-be-a-dick"
href="http://meta.wikimedia.org/wiki/Don%27t_be_a_dick#Fundamentals"
rel="noreferrer"
target="_blank"
>
Don&apos;t be a dick
</a>
</p>
<p> Please and thank you! </p>
<p className="about__caveat">
Small caveat: I do like the idea of having an unmoderated space.
{' '}
<Link to="/chat/r/anonymous">/r/anonymous</Link>
{' '}
is that space. If you think that anonymous people on the Internet saying
stupid/offensive/ignorant/inflammatory things sucks, then I urge you to stay out. There be
dragons. You have been warned. You can go yell at them in there about it if you
like.<br /><strong>That's the point!</strong>
</p>
<br />
<p className="about__caveat">
Also keep in mind that <strong>I can always see your IP address</strong>,
and <strong>if you do anything actually illegal, I won't cover for you.</strong>
</p>
<h2>Still here?</h2>
<p>Let me tell you some facts about this site.</p>
<p>
It&apos;s written in JavaScript on top of a handcrafted application framework I am
calling <strong>latus</strong> for now. It's pretty neat, if you're a nerd.
</p>
<p>
The frontend uses React/Redux and the backend uses Node.js/Express/Passport/Redis/MySQL.
Real-time communication is accomplished with my own packet netcode built on top of
socket.io.
</p>
<p>
Also notable is that this site was created with
{' '}
<strong>love</strong>
, as are all my creations. I hope that you have a great time using it. :)
</p>
<h2>Feeling charitable?</h2>
<div className="about__crypto">
<span className="coin-type btc" title="Bitcoin" />
<span>
If you&apos;d like to contribute, send some bitcoin to
{' '}
<a
href="bitcoin:3CrCpsrzxaFyXUuLuokjatAXxTq3skxCzh"
>
3CrCpsrzxaFyXUuLuokjatAXxTq3skxCzh
</a>
.
</span>
</div>
</section>
</div>
);
export default About;

View File

@ -0,0 +1,110 @@
@import 'scss/breakpoints.scss';
@import 'scss/colors.scss';
.about {
height: 100%;
overflow: auto;
padding: 2em;
font-family: var(--thick-title-font-family);
text-align: center;
word-break: break-word;
strong {
text-decoration: underline;
}
}
.about h2 {
color: #aaaaaa;
margin: 1.5em 0 0.5em;
small {
display: block;
margin-top: 0.5em;
}
}
.about small {
font-size: 0.5em;
}
.about a {
color: lighten($color-active, 20%);
}
.about p {
margin: auto;
&:not(:first-child) {
margin-top: 0.5em;
}
@include breakpoint(tablet) {
width: 50%;
}
}
.about__nowrap {
white-space: nowrap;
}
.about__explains {
margin: auto;
padding-bottom: 2em;
@include breakpoint(desktop) {
max-width: calc(100% - 10em);
}
}
.about__inner {
padding: 1em;
width: 100%;
}
.about__avatar {
// margin-left: 0.5em;
max-height: 1em;
}
.about__title {
background-color: #171717;
display: flex;
font-family: var(--title-font-family);
font-size: 2em;
justify-content: space-around;
padding: 0.5em;
text-align: center;
@include breakpoint(tiny) {
font-size: 2.5em;
padding: 1em;
}
@include breakpoint(tablet) {
font-size: 4em;
padding: 1em;
}
@include breakpoint(desktop) {
margin: auto;
max-width: 50%;
}
}
.about__smile {
display: inline-block;
margin: 0.5em 0;
}
.about__titleMessage {
// display: flex;
align-items: center;
}
.about__dont-be-a-dick {
display: flow-root;
margin: 1em 0;
text-decoration: underline;
}
.about__crypto {
margin: 1em 0;
}
.about__caveat {
font-size: 0.7em;
}

View File

@ -0,0 +1,40 @@
import './index.scss';
import React from 'react';
import {hot} from 'react-hot-loader';
import {Route, Router, Switch} from 'react-router-dom';
import {createBrowserHistory} from 'history';
import About from 'components/about';
import Chat from 'components/chat';
import Home from 'components/home';
import Left from 'components/left';
import Right from 'components/right';
const history = createBrowserHistory();
const App = () => (
<div className="app">
<Left />
<Router history={history}>
<Switch>
<Route
component={Home}
exact
path="/"
/>
<Route
component={About}
path="/about"
/>
<Route
component={Chat}
path="/chat"
/>
</Switch>
</Router>
<Right />
</div>
);
export default hot(module)(App);

View File

@ -0,0 +1,35 @@
@import 'scss/colors.scss';
.app {
background-image: url('~images/transpaper.png');
display: flex;
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
}
.panes {
position: relative;
width: 100%;
height: 100%;
&.horizontal > .pane {
float: left;
height: 100%;
}
&.vertical > .pane {
width: 100%;
}
}
.unread {
background-color: $color-unread;
border-radius: 10px;
display: inline-block;
font-family: var(--message-font-family);
font-size: 0.6em;
font-weight: bold;
margin-right: 0.6em;
padding: 0.4em;
text-align: center;
}

View File

@ -0,0 +1 @@
export default () => null;

View File

@ -0,0 +1,41 @@
import './index.scss';
import React, {useEffect} from 'react';
import {Link} from 'react-router-dom';
export default function Home() {
useEffect(() => {
window.document.title = 'reddichat';
});
return (
<div className="home">
<div className="home__inner">
<h1 className="home__title">Reddi? Chat!</h1>
<div className="home__bar">
<div className="home__login-wrapper">
<a className="home__login button" href="/auth/reddit">Touch to start</a>
</div>
<p className="home__instructions">
You will be sent to reddit to log in.
reddit will simply confirm your reddichat login and send you back here.
Then you can chat!
</p>
<p className="home__anonymous">
If you are exceptionally brave (or particularly toxic), you may choose to
{' '}
<Link to="/chat/r/anonymous">chat anonymously</Link>
.
</p>
<p className="home__about">
If you&apos;d like to read a bit about the site, see the
{' '}
<Link to="/about">about page</Link>
.
</p>
</div>
</div>
</div>
);
}
Home.propTypes = {};

View File

@ -0,0 +1,87 @@
@import 'scss/breakpoints.scss';
@import 'scss/colors.scss';
.home {
margin: auto;
max-width: map-get($breakpoints, desktop);
}
.home__inner {
padding: 1em;
@include breakpoint(desktop) {
padding: 3em 1em 1em;
}
width: 100%;
}
.home__title {
background-color: #171717;
font-family: var(--title-font-family);
font-size: 2em;
padding: 1em;
text-align: center;
@include breakpoint(tiny) {
font-size: 2.5em;
padding: 2em;
}
@include breakpoint(tablet) {
font-size: 4em;
padding: 2em;
}
}
.home__bar {
padding: 2em 0;
}
.home__login-wrapper {
padding-top: 1em;
text-align: center;
width: 100%;
@include breakpoint(desktop) {
padding-top: 2em;
}
}
.home__login {
background-color: $color-active;
display: inline-block;
font-family: var(--thick-title-font-family);
font-size: 2em;
padding: 1em;
text-decoration: none;
transition: background-color 0.2s;
&:hover {
background-color: lighten($color-active, 10%);
}
}
.home__instructions {
font-family: var(--message-font-family);
font-size: 1.5em;
line-height: 1.5em;
padding: 2em 0;
text-align: center;
width: 100%;
@include breakpoint(desktop) {
font-size: 2em;
}
}
.home__anonymous {
line-height: 1.5em;
text-align: center;
a {
color: lighten($color-active, 20%);
}
}
.home__about {
font-size: 0.8em;
line-height: 1em;
margin-top: 2em;
text-align: center;
a {
color: lighten($color-active, 20%);
}
}

View File

@ -0,0 +1,65 @@
import './index.scss';
import classnames from 'classnames';
import React from 'react';
// import {useDispatch, useSelector} from 'react-redux';
// import {
// leftActiveIndexSelector,
// leftIsOpenSelector,
// setLeftActiveIndex,
// } from '~/common/state/app';
// import {
// idSelector,
// pendingFriendshipSelector,
// unreadChannelSelector,
// unreadUserSelector,
// } from '~/common/state/user';
// import Bar from './bar';
// import Branding from './branding';
// import ChatLeftFriends from './chat--leftFriends';
// import ChatLeftRooms from './chat--leftRooms';
export default function ChatLeft() {
// const dispatch = useDispatch();
// const leftActiveIndex = useSelector(leftActiveIndexSelector);
// const active = useSelector(leftActiveIndexSelector);
// const isOpen = useSelector(leftIsOpenSelector);
// const id = useSelector(idSelector);
// const pendingFriendship = useSelector(pendingFriendshipSelector)
// .filter(({addeeId}) => addeeId === id).length;
// const unreadChannel = useSelector(unreadChannelSelector);
// const unreadUser = useSelector(unreadUserSelector);
const showsAsOpen = true;
// const leftButtons = [
// {
// count: unreadChannel,
// icon: '💬',
// label: 'Chat',
// },
// {
// count: unreadUser + pendingFriendship,
// icon: '😁',
// label: 'Friends',
// },
// ];
return (
<div
className={classnames('left', 'flexed', showsAsOpen ? 'open' : 'closed')}
>
{/* <Branding />
<Bar
active={leftActiveIndex}
buttons={leftButtons}
className="bar--left"
isHorizontal
onActive={(active) => dispatch(setLeftActiveIndex(active))}
/>
{0 === active && <ChatLeftRooms />}
{1 === active && <ChatLeftFriends />} */}
</div>
);
}
ChatLeft.propTypes = {};

View File

@ -0,0 +1,22 @@
@import 'scss/breakpoints.scss';
@import 'scss/colors.scss';
.left {
background-color: #373737;
flex-shrink: 0;
height: 100%;
transform: translateX(-100%);
transition: width 0.2s;
width: 25em;
@include breakpoint(desktop) {
transform: none;
}
}
.left .bar__buttonItem:last-child {
padding-right: 0.5em;
}
.bar--left {
display: inline-block;
}

View File

@ -0,0 +1,57 @@
import './index.scss';
import classnames from 'classnames';
import React from 'react';
// import {useDispatch, useSelector} from 'react-redux';
import {Link} from 'react-router-dom';
// import {
// rightActiveIndexSelector,
// rightIsOpenSelector,
// setRightActiveIndex,
// } from '~/common/state/app';
// import {channelUsersSelector} from '~/common/state/chat';
// import {
// blockedSelector,
// } from '~/common/state/user';
// import useChannel from '~/client/hooks/useChannel';
// import Bar from './bar';
// import ChatRightBlocked from './chat--rightBlocked';
// import ChatRightUsers from './chat--rightUsers';
export default function ChatRight() {
// const dispatch = useDispatch();
// const activeIndex = useSelector(rightActiveIndexSelector);
// const channel = useChannel();
// const blockedIds = useSelector(blockedSelector);
// const channelUsers = useSelector((state) => channelUsersSelector(state, channel));
// const isOpen = useSelector(rightIsOpenSelector);
const showsAsOpen = true;
// const rightButtons = []
// .concat(channelUsers.length > 0 ? [{icon: '🙃', label: 'Present'}] : [])
// .concat(blockedIds.length > 0 ? [{icon: '', label: 'Blocked'}] : []);
return (
<div
className={classnames('right', 'flexed', showsAsOpen ? 'open' : 'closed')}
>
{/* <Bar
active={activeIndex}
buttons={rightButtons}
className="bar--right"
isHorizontal
onActive={(activeIndex) => dispatch(setRightActiveIndex(activeIndex))}
/>
<div className="right__links">
<Link className="right__link" to="/about">About</Link>
<span className="right__linksBullet"></span>
<Link className="right__link" to="/logout">Log out</Link>
</div>
{0 === activeIndex && channelUsers.length > 0 && <ChatRightUsers />}
{1 === activeIndex && blockedIds.length > 0 && <ChatRightBlocked ids={blockedIds} />} */}
</div>
);
}
ChatRight.propTypes = {};

View File

@ -0,0 +1,81 @@
@import 'scss/breakpoints.scss';
@import 'scss/colors.scss';
.right {
background-color: #373737;
flex-shrink: 0;
right: 0;
transform: translateX(100%);
transition: 0.2s width;
@include breakpoint(desktop) {
transform: none;
}
}
.right.open {
width: 25em;
}
.right__buttons {
display: flex;
flex-direction: row-reverse;
.right--closed & {
flex-direction: column;
}
}
.right__buttonItem {
padding: 0.5em;
.right.closed & {
padding-bottom: 0;
}
.right.open & {
padding-left: 0;
}
}
.right__button {
background-color: #2a2a2a;
border: none;
height: 3.5em;
transition: 0.1s background-color;
width: 3.5em;
&:hover {
background-color: #272727;
}
&.active {
background-color: #555555;
}
}
.right__buttonIcon {
display: flow-root;
}
.right__buttonText {
font-size: 0.7em;
}
.right__links {
background-color: #272727;
display: flex;
justify-content: flex-end;
padding: 0.5em;
}
.right__linksBullet {
color: #888;
font-size: 0.5em;
line-height: 2em;
margin: 0 1em;
}
.right__link {
color: lighten($color-active, 20%);
font-family: var(--thick-title-font-family);
text-decoration: none;
transition: color 0.2s;
&:hover {
color: lighten($color-active, 40%);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

9
app/src/react/index.jsx Normal file
View File

@ -0,0 +1,9 @@
import './index.scss';
import App from 'components/app';
export default {
hooks: {
'@latus/react/components': () => App,
},
};

278
app/src/react/index.scss Normal file
View File

@ -0,0 +1,278 @@
@import 'scss/colors.scss';
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
em {
font-style: italic;
}
strong {
font-weight: bold;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.6em;
}
h3 {
font-size: 1.4em;
}
h4 {
font-size: 1.2em;
}
h5 {
font-size: 1.1em;
}
h6 {
font-size: 1.05em;
}
* {
box-sizing: border-box;
}
html {
background-color: #212121;
color: #FFFFFF;
--message-font-family: verdana, arial, helvetica, sans-serif;
--title-font-family: LatoLight, Ubuntu, "Droid Sans", sans-serif;
--thick-title-font-family: LatoRegular, Ubuntu, "Droid Sans", sans-serif;
}
body {
scrollbar-width: thin;
scrollbar-color: #777 #333;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: #333;
}
::-webkit-scrollbar-thumb {
background-color: #777;
border-radius: 20px;
border: 3px solid #333;
}
code {
font-family: monospace;
}
label {
background-color: rgba(255, 255, 255, 0.025);
color: #ffffff;
display: flex;
flex-direction: column;
flex-wrap: wrap;
font-family: var(--thick-title-font-family);
font-size: 1em;
min-height: 3em;
padding: 0.5em 0.5em 0.5em 1em;
user-select: none;
@media(min-width: 20em) {
align-items: center;
flex-direction: row;
justify-content: space-between;
}
}
label:nth-of-type(2n+1) {
background-color: rgba(0, 0, 0, 0.025);
}
[contenteditable] {
cursor: text;
}
input {
background: #333;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 0.75em;
padding: 0.5em;
}
fieldset {
background-color: #151515;
border: 1px solid rgba(255, 255, 255, 0.1);
display: inline-block;
margin: 0 0 1em 0;
padding: 0.5em;
position: relative;
top: -0.5em;
width: 100%;
}
button, .button {
background: #222222;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 100%;
}
button, .button, input[type="checkbox"], input[type="checkbox"] + label {
cursor: pointer;
}
select {
background: #222222;
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ffffff;
cursor: pointer;
font-size: 0.75em;
-moz-appearance: none;
-webkit-appearance: none;
padding: 0.5em;
}
*:focus {
box-shadow: 0 0 2px 0 $color-active;
outline: none;
z-index: 1;
}
.react-tabs {
width: 100%;
height: 100%;
}
.react-tabs__tab-list {
background-color: #272727;
font-family: var(--title-font-family);
font-size: 0.9em;
overflow-x: hidden;
scrollbar-width: thin;
white-space: nowrap;
width: 100%;
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: #2e1d1d;
}
&::-webkit-scrollbar-thumb {
background-color: #777;
border-radius: 0;
border-width: 1px;
}
}
.react-tabs__tab {
background-color: #2d2d2d;
color: #aaaaaa;
cursor: pointer;
display: inline-block;
height: 3em;
&:not(:last-of-type) {
border-right: 1px solid #282828;
}
&:hover {
color: #ddd;
}
.wrapper {
align-content: space-between;
align-items: center;
display: flex;
height: 100%;
padding: 0 0.5em;
justify-content: space-evenly;
.text {
height: 1.25em;
padding: 0 0.5em;
}
}
.icon {
padding: 0 0.25em 0 0.5em;
}
.close {
background-color: transparent;
border: none;
color: #999999;
padding: 0.25em;
visibility: hidden;
&:hover {
color: #ffffff;
}
}
&:hover .close {
visibility: visible;
}
}
.react-tabs__tab--selected[class] {
background-color: #1e1e1e;
color: #ffffff;
}
.react-tabs__tab-panel {
display: none;
overflow-y: auto;
height: calc(100% - 2.7em);
width: 100%;
}
.react-tabs__tab-panel--selected {
display: block;
}
.muted {
color: $color-muted;
}
@font-face {
font-family: 'LatoLight';
src: url('./fonts/Lato-Light.eot');
src: url('./fonts/Lato-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('./fonts/Lato-Light.woff') format('woff'), /* Modern Browsers */ url('./fonts/Lato-Light.ttf') format('truetype');
font-weight: normal;
text-rendering: optimizeLegibility;
}
@font-face {
font-family: 'LatoRegular';
src: url('./fonts/Lato-Regular.eot');
src: url('./fonts/Lato-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('./fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */ url('./fonts/Lato-Regular.ttf') format('truetype');
font-weight: normal;
text-rendering: optimizeLegibility;
}

View File

@ -0,0 +1,31 @@
$breakpoints: (
tiny: 320px,
tablet: 720px,
desktop: 1280px,
hd: 1920px,
uhd: 3840px,
);
@mixin breakpoint($breakpoint, $min-max: 'min-width') {
$output: false;
@if (type-of($breakpoint) == number) {
$output: $breakpoint;
}
@else {
@if $min-max == 'max-width' {
$output: #{map-get($breakpoints, $breakpoint) - 1px };
}
@else {
$output: #{map-get($breakpoints, $breakpoint)};
}
}
@if $breakpoint == default {
@content;
}
@else {
@media ($min-max: #{$output}) {
@content;
}
}
}

View File

@ -0,0 +1,4 @@
$color-active: rgb(0, 99, 112);
$color-muted: #bbbbbb;
$color-owner: #d65130;
$color-unread: rgb(180, 0, 0);

35
app/webpack.config.js Normal file
View File

@ -0,0 +1,35 @@
// Whilst the configuration object can be modified here, the recommended way of making
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
// Neutrino's inspect feature can be used to view/export the generated configuration.
const {join} = require('path');
const {readConfig, Latus} = require('@latus/core');
const neutrino = require('neutrino');
module.exports = new Promise(async (resolve, reject) => {
try {
const config = readConfig();
const paths = Object.entries(config).map(([plugin]) => {
try {
require.resolve(plugin);
return plugin;
}
catch (error) {
return join(process.cwd(), plugin);
}
});
const latus = new Latus({
config,
modules: paths.map((path) => require(path)),
});
const configs = {
app: require('./.neutrinorc'),
};
latus.invokeFlat('@latus/core/build', configs);
const webpackConfigs = Object.values(configs).map((config) => neutrino(config).webpack());
resolve(webpackConfigs);
}
catch (error) {
reject(error);
}
});

8843
app/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
const config = {
globals: {
process: true,
window: true,
},
ignorePatterns: [
'/*',
'!/src',
],
rules: {
'babel/object-curly-spacing': 'off',
'brace-style': ['error', 'stroustrup'],
'no-bitwise': ['error', {int32Hint: true}],
'no-plusplus': 'off',
'no-shadow': 'off',
'no-underscore-dangle': 'off',
'padded-blocks': ['error', {classes: 'always'}],
yoda: 'off',
},
};
module.exports = config;

3
config/.eslintrc.js Normal file
View File

@ -0,0 +1,3 @@
const neutrino = require('neutrino');
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)()).eslintrc();

5
config/.mocharc.js Normal file
View File

@ -0,0 +1,5 @@
const neutrino = require('neutrino');
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)()).mocha();

34
config/.neutrinorc.js Normal file
View File

@ -0,0 +1,34 @@
const airbnbBase = require('@neutrinojs/airbnb-base');
const library = require('@neutrinojs/library');
const mocha = require('@neutrinojs/mocha');
module.exports = () => ({
options: {},
use: [
(neutrino) => {
neutrino.options.output = '.';
},
airbnbBase({
eslint: {
cache: false,
baseConfig: require(`${__dirname}/.eslint.defaults`),
},
}),
(neutrino) => {
const {name} = neutrino.options.packageJson;
library({
clean: false,
name,
target: 'node',
})(neutrino);
},
(neutrino) => {
const options = neutrino.config.module
.rule('compile')
.use('babel')
.get('options');
options.presets[0][1].targets = {esmodules: true};
},
mocha(),
],
});

3
config/package/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*.js
/*.js.map
!/webpack.config.js

View File

@ -0,0 +1,36 @@
{
"name": "package",
"version": "1.0.0",
"main": "index.js",
"author": "cha0s",
"license": "MIT",
"scripts": {
"build": "NODE_PATH=./node_modules webpack --mode production",
"clean": "rm -f yarn.lock && yarn",
"dev": "NODE_PATH=./node_modules webpack --mode development",
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
},
"files": [
"index.js",
"index.js.map"
],
"dependencies": {
"debug": "4.3.1"
},
"devDependencies": {
"@neutrinojs/airbnb-base": "^9.4.0",
"@neutrinojs/copy": "9.4.0",
"@neutrinojs/library": "^9.4.0",
"@neutrinojs/mocha": "^9.4.0",
"chai": "4.2.0",
"eslint": "^7",
"eslint-import-resolver-webpack": "0.13.0",
"mocha": "^8",
"neutrino": "^9.4.0",
"webpack": "^4",
"webpack-cli": "^3"
}
}

View File

View File

@ -0,0 +1,6 @@
// Whilst the configuration object can be modified here, the recommended way of making
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
// Neutrino's inspect feature can be used to view/export the generated configuration.
const neutrino = require('neutrino');
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();

39
config/split-config.js Normal file
View File

@ -0,0 +1,39 @@
const library = require('@neutrinojs/library');
const config = require('./.neutrinorc');
module.exports = ({name, files}, clientMains) => {
const mains = files
.filter((file) => file.match(/\.js$/))
.map((file) => file.slice(0, -3))
.reduce((r, file) => ({...r, [file]: file}), {});
if (clientMains.length > 0) {
const serverMains = Object
.entries(mains)
.filter(([key]) => -1 === clientMains.indexOf(key))
.reduce((r, [k, v]) => ({...r, [k]: v}), {});
const serverConfig = config();
serverConfig.options.mains = serverMains;
const clientConfig = config();
clientConfig.options.mains = clientMains.reduce((r, file) => ({...r, [file]: file}), {});
clientConfig.use[2] = (neutrino) => {
library({
clean: false,
name,
target: 'web',
})(neutrino);
};
clientConfig.use.push((neutrino) => {
neutrino.config.node.set('Buffer', true);
});
return [serverConfig, clientConfig];
}
else {
const serverConfig = config();
serverConfig.options = {
mains,
};
return [serverConfig];
}
};

8
config/webpack.config.js Normal file
View File

@ -0,0 +1,8 @@
// Whilst the configuration object can be modified here, the recommended way of making
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
// Neutrino's inspect feature can be used to view/export the generated configuration.
const neutrino = require('neutrino');
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
module.exports = configs.map((config) => neutrino(config).webpack());

6
lerna.json Normal file
View File

@ -0,0 +1,6 @@
{
"packages": [
"packages/*"
],
"version": "1.0.0"
}

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "@reddichat/monorepo",
"private": true,
"scripts": {
"build": "lerna run build",
"clean": "lerna run clean",
"forcedev": "lerna run dev && lerna run forcepub",
"lint": "lerna run lint",
"test": "lerna run test",
"watch": "lerna run watch --parallel"
},
"devDependencies": {
"@neutrinojs/airbnb-base": "^9.4.0",
"@neutrinojs/library": "^9.4.0",
"@neutrinojs/mocha": "^9.4.0",
"chai": "4.2.0",
"eslint": "^7",
"eslint-import-resolver-webpack": "0.13.0",
"lerna": "^3.22.1",
"mocha": "^8",
"neutrino": "^9.4.0",
"webpack": "^4",
"webpack-cli": "^3"
}
}

3
packages/chat/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*.js
/*.js.map
!/webpack.config.js

View File

@ -0,0 +1,38 @@
{
"name": "@reddichat/chat",
"version": "1.0.0",
"main": "index.js",
"author": "cha0s",
"license": "MIT",
"scripts": {
"build": "NODE_PATH=./node_modules webpack --mode production",
"clean": "rm -f yarn.lock && yarn",
"dev": "NODE_PATH=./node_modules webpack --mode development",
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
},
"files": [
"client.js",
"client.js.map",
"index.js",
"index.js.map"
],
"dependencies": {
"debug": "4.3.1"
},
"devDependencies": {
"@neutrinojs/airbnb-base": "^9.4.0",
"@neutrinojs/copy": "9.4.0",
"@neutrinojs/library": "^9.4.0",
"@neutrinojs/mocha": "^9.4.0",
"chai": "4.2.0",
"eslint": "^7",
"eslint-import-resolver-webpack": "0.13.0",
"mocha": "^8",
"neutrino": "^9.4.0",
"webpack": "^4",
"webpack-cli": "^3"
}
}

View File

View File

View File

@ -0,0 +1,6 @@
// Whilst the configuration object can be modified here, the recommended way of making
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
// Neutrino's inspect feature can be used to view/export the generated configuration.
const neutrino = require('neutrino');
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();

3
packages/fun/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*.js
/*.js.map
!/webpack.config.js

38
packages/fun/package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "@reddichat/fun",
"version": "1.0.0",
"main": "index.js",
"author": "cha0s",
"license": "MIT",
"scripts": {
"build": "NODE_PATH=./node_modules webpack --mode production",
"clean": "rm -f yarn.lock && yarn",
"dev": "NODE_PATH=./node_modules webpack --mode development",
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
},
"files": [
"client.js",
"client.js.map",
"index.js",
"index.js.map"
],
"dependencies": {
"debug": "4.3.1"
},
"devDependencies": {
"@neutrinojs/airbnb-base": "^9.4.0",
"@neutrinojs/copy": "9.4.0",
"@neutrinojs/library": "^9.4.0",
"@neutrinojs/mocha": "^9.4.0",
"chai": "4.2.0",
"eslint": "^7",
"eslint-import-resolver-webpack": "0.13.0",
"mocha": "^8",
"neutrino": "^9.4.0",
"webpack": "^4",
"webpack-cli": "^3"
}
}

View File

View File

View File

@ -0,0 +1,6 @@
// Whilst the configuration object can be modified here, the recommended way of making
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
// Neutrino's inspect feature can be used to view/export the generated configuration.
const neutrino = require('neutrino');
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();

3
packages/user/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*.js
/*.js.map
!/webpack.config.js

View File

@ -0,0 +1,38 @@
{
"name": "@reddichat/user",
"version": "1.0.0",
"main": "index.js",
"author": "cha0s",
"license": "MIT",
"scripts": {
"build": "NODE_PATH=./node_modules webpack --mode production",
"clean": "rm -f yarn.lock && yarn",
"dev": "NODE_PATH=./node_modules webpack --mode development",
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
},
"files": [
"client.js",
"client.js.map",
"index.js",
"index.js.map"
],
"dependencies": {
"debug": "4.3.1"
},
"devDependencies": {
"@neutrinojs/airbnb-base": "^9.4.0",
"@neutrinojs/copy": "9.4.0",
"@neutrinojs/library": "^9.4.0",
"@neutrinojs/mocha": "^9.4.0",
"chai": "4.2.0",
"eslint": "^7",
"eslint-import-resolver-webpack": "0.13.0",
"mocha": "^8",
"neutrino": "^9.4.0",
"webpack": "^4",
"webpack-cli": "^3"
}
}

View File

View File

View File

@ -0,0 +1,6 @@
// Whilst the configuration object can be modified here, the recommended way of making
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
// Neutrino's inspect feature can be used to view/export the generated configuration.
const neutrino = require('neutrino');
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();

8470
yarn.lock Normal file

File diff suppressed because it is too large Load Diff