Welcome back, mio!

This commit is contained in:
flash 2018-03-22 02:56:41 +00:00
parent 19d19adec3
commit d058036da1
38 changed files with 1210 additions and 235 deletions

View file

@ -0,0 +1,14 @@
.button {
border-radius: 4px;
border: 0;
padding: 7px 10px;
background: #462e55;
color: #fff;
text-align: left;
cursor: pointer;
line-height: 1.4em;
display: inline-block;
text-decoration: none;
font-family: sans-serif;
font-size: 12px;
}

View file

@ -0,0 +1,5 @@
.container {
box-shadow: 0 2px 5px fade(#111, 80%);
background: #222;
margin: 2px;
}

View file

@ -0,0 +1,93 @@
.form {
height: @auth-title-height;
overflow: hidden;
transition: height .3s ease-in-out;
&__wrapper {
&:not(:last-child) {
border-bottom: 1px solid #333;
}
}
&__toggle {
display: none;
&:checked ~ .form {
height: @auth-form-height;
}
&:not(:checked) ~ .form .form__title:hover {
cursor: pointer;
background: #333;
z-index: 100;
box-shadow: 0 2px 5px fade(#111, 80%);
}
}
&__title {
font-size: 1.4em;
display: block;
padding: 10px;
height: @auth-title-height;
transition: background .2s, box-shadow .2s;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
&__content {
display: flex;
flex-direction: column;
margin: 0 5px;
height: @auth-form-height - @auth-title-height;
overflow: hidden;
transition: height .5s ease-in-out;
align-items: center;
flex-grow: 1;
justify-content: center;
&--no-registration {
max-width: 400px;
}
}
&__link {
color: #88c;
text-decoration: none;
&:hover {
text-decoration: underline;
}
&:active {
color: #c88;
}
}
&__row {
margin-bottom: 5px;
width: 100%;
&--columns {
display: flex;
justify-content: space-between;
align-items: center;
}
}
&__column {
&--message {
padding-left: 10px;
color: #aaa;
}
&--error {
color: #a33;
}
}
}

View file

@ -0,0 +1,20 @@
.logout {
max-width: 400px;
padding: 5px;
&__message {
font-size: 1.2em;
}
&__paragraph {
&:not(:last-child) {
margin-bottom: 2px;
}
}
&__buttons {
margin-top: 5px;
display: flex;
justify-content: flex-end;
}
}

View file

@ -0,0 +1,17 @@
.text {
display: block;
width: 100%;
border: 0;
background: #1a1a1a;
border-radius: 4px;
color: #fff;
padding: 5px;
@media (max-width: @auth-mobile) {
font-size: 1.2em;
}
&__label {
display: block;
}
}

View file

@ -0,0 +1,44 @@
@auth-mobile: 500px;
@auth-title-height: 40px;
@auth-form-height: 200px;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
position: relative;
}
html,
body {
height: 100%;
width: 100%;
}
body {
background: #5e3e71 url('https://static.flash.moe/images/grid.png');
font: 12px/20px sans-serif;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.auth {
min-width: 400px;
min-height: @auth-form-height;
display: flex;
flex-direction: column;
justify-content: center;
@media (max-width: @auth-mobile) {
min-width: 0;
width: 100%;
}
}
@import "classes/button";
@import "classes/container";
@import "classes/form";
@import "classes/logout";
@import "classes/text";

View file

@ -0,0 +1,11 @@
.mio__avatar {
flex-shrink: 0;
background-color: #fbeeff;
background-size: contain;
background-repeat: no-repeat;
background-position: 50%;
display: block;
border: 1px solid #9475b2;
max-height: 200px;
max-width: 200px;
}

View file

@ -0,0 +1,19 @@
.mio__container {
border: 1px solid #9475b2;
background-color: #fbeeff;
margin: 2px 0;
&__title {
background-color: #9475b2;
color: #306;
font-size: 1.17em;
font-weight: 700;
padding: 3px;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
&__content {
margin: 2px 5px;
}
}

View file

@ -0,0 +1,25 @@
.mio__footer {
text-align: center;
font-size: .9em;
line-height: 1.5em;
margin-bottom: 1em;
&__copyright__link {
color: inherit;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
&__links__link {
color: inherit;
text-decoration: none;
margin: 0 2px;
&:hover {
text-decoration: underline;
}
}
}

View file

@ -0,0 +1,81 @@
@mio-forum-listing-mobile: 500px;
.mio__forum__listing {
&__forums {
margin: 0;
}
&__entry {
display: flex;
padding: 2px 0;
align-items: center;
&:not(:last-child) {
border-bottom: 1px solid #9475b2;
}
&__icon {
width: 50px;
line-height: 50px;
font-size: 2.5em;
text-align: center;
flex-grow: 0;
flex-shrink: 0;
}
&__info {
flex-grow: 1;
flex-shrink: 1;
}
&__title {
font-size: 1.2em;
line-height: 1.5em;
color: #306;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
&__description {
font-size: .8em;
line-height: 1.3em;
}
&__stats {
text-align: right;
flex-grow: 0;
flex-shrink: 0;
font-style: italic;
font-family: Verdana, Arial, Helvetica, sans-serif;
@media (max-width: @mio-forum-listing-mobile) {
display: none;
}
}
&__topics {
font-size: 1.5em;
color: #222;
}
&__posts {
font-size: .9em;
color: #555;
}
&__activity {
flex-grow: 0;
flex-shrink: 0;
text-align: right;
margin: 0 10px;
width: 220px;
@media (max-width: @mio-forum-listing-mobile) {
display: none;
}
}
}
}

View file

@ -0,0 +1,90 @@
@mio-header-mobile: 700px;
.mio__header {
display: flex;
margin: 4px 0;
@media (max-width: @mio-header-mobile) {
flex-direction: column;
align-items: center;
}
&__logo {
flex-grow: 0;
flex-shrink: 0;
&__link {
color: inherit;
text-decoration: none;
cursor: pointer;
display: block;
}
&__image {
vertical-align: bottom;
@media (max-width: @mio-header-mobile) {
max-width: 200px;
max-height: 100px;
}
}
}
&__menu {
flex-grow: 1;
flex-shrink: 1;
display: flex;
align-items: flex-start;
justify-content: flex-end;
@media (max-width: @mio-header-mobile) {
width: 100%;
}
}
&__user {
border: 1px solid #9475b2;
display: inline-block;
&__content {
margin: 2px;
display: flex;
}
&__avatar {
width: 60px;
height: 60px;
}
&__links {
text-align: right;
list-style: none;
margin: 0 2px;
line-height: 1.5em;
&__container {
flex-grow: 1;
}
@media (max-width: @mio-header-mobile) {
font-size: 1.5em;
}
}
&__link {
color: inherit;
text-decoration: none;
display: block;
min-width: 70px;
&:hover {
text-decoration: underline;
}
}
@media (max-width: @mio-header-mobile) {
width: 100%;
margin: 0;
}
}
}

View file

@ -0,0 +1,5 @@
.mio__heading {
&--1 {
line-height: 1.2em;
}
}

View file

@ -0,0 +1,29 @@
.mio__input__button {
font-family: 'visitor1';
font-size: 20px;
line-height: 1.25em;
color: #fff;
background: #9475b2;
border: 1px solid #306;
box-shadow: inset 0 0 0 1px #643b8c;
border-radius: 2px;
padding: 1px 10px;
transition: background .2s, border-color .2s;
cursor: pointer;
text-align: left;
display: inline-block;
text-decoration: none;
&:focus {
border-color: #407;
}
&:hover {
background: #A586c3;
}
&:active {
border-color: #306;
background: #8364a1;
}
}

View file

@ -0,0 +1,15 @@
.mio__input__text {
border: 1px solid #aaa;
padding: 1px;
background: #fff;
color: #111;
&:focus {
border-color: #9475b2;
}
}
.mio--dark .mio__input__text {
background: #111;
color: #fff;
}

View file

@ -0,0 +1,16 @@
.mio__input__textarea {
border: 1px solid #aaa;
padding: 1px;
vertical-align: bottom;
background: #fff;
color: #111;
&:focus {
border-color: #9475b2;
}
}
.mio--dark .mio__input__textarea {
background: #111;
color: #fff;
}

View file

@ -0,0 +1,111 @@
@mio-navigation-mobile: 1000px;
.mio__navigation {
margin: 5px 0;
width: 100%;
display: flex;
border: 0 solid #000;
border-top-width: 1px;
align-items: flex-start;
justify-content: center;
@media (max-width: @mio-navigation-mobile) {
border: none;
align-items: center;
flex-direction: column;
}
&--top {
border-top-width: 0;
border-bottom-width: 1px;
align-items: flex-end;
.mio__navigation__option {
border-top-width: 1px;
border-bottom-width: 0;
&--selected {
top: 1px;
}
}
}
&__option {
list-style: none;
background-color: #c9bbcc;
border: 1px solid #000;
border-top-width: 0;
flex-grow: 0;
@media (max-width: @mio-navigation-mobile) {
background-color: #9475b2;
width: 100%;
border: none;
flex-grow: 1;
margin-bottom: 1px;
}
&:not(:first-child) {
border-left-width: 0;
}
&--selected {
background-color: #fbeeff;
top: -1px;
@media (max-width: @mio-navigation-mobile) {
background-color: #A586c3;
top: 0;
}
&:not(:first-child) {
margin-left: -1px;
border-left-width: 1px;
}
.mio__navigation__link {
padding: 3px 1em;
}
}
}
&__link {
display: block;
padding: 2px 1em;
color: #000;
text-decoration: none;
@media (max-width: @mio-navigation-mobile) {
padding: 10px 15px;
color: #306;
font-size: 1.5em;
}
&:hover {
color: #609;
}
}
}
.mio--dark {
.mio__navigation,
.mio__navigation__option {
border-color: #9475b2;
}
.mio__navigation__option {
background-color: #462e55;
}
.mio__navigation__option--selected {
background-color: #23172a;
}
.mio__navigation__link {
color: #9475b2;
&:hover {
color: #a586c3;
}
}
}

View file

@ -0,0 +1,120 @@
@mio-profile-mobile: 700px;
.mio__profile {
&__avatar {
width: 200px;
height: 200px;
@media (max-width: @mio-profile-mobile) {
width: 100px;
height: 100px;
margin: 20px 0;
}
}
&__icon {
vertical-align: middle;
}
&__header {
height: 300px;
background-color: #fbeeff;
background-size: cover;
background-repeat: no-repeat;
background-position: 50%;
@media (max-width: @mio-profile-mobile) {
height: auto;
background-size: 700px auto;
background-position: center top;
}
&__content {
margin: 2px;
display: flex;
align-items: flex-start;
justify-content: space-between;
@media (max-width: @mio-profile-mobile) {
flex-direction: column-reverse;
align-items: center;
margin: 0;
}
}
}
&__info {
display: flex;
@media (max-width: @mio-profile-mobile) {
flex-direction: column;
background-color: #9475b2;
width: 100%;
padding-top: 2px;
}
&__section {
margin-right: 2px;
@media (max-width: @mio-profile-mobile) {
margin: 0 1px;
}
}
&__block {
border: 1px solid #9475b2;
padding: 2px 4px;
margin-bottom: 2px;
background-color: rgba(251, 238, 255, .9);
@media (max-width: @mio-profile-mobile) {
background-color: #fbeeff;
}
}
&__row {
display: flex;
}
&__column {
min-width: 80px;
@media (max-width: @mio-profile-mobile) {
min-width: auto;
flex-grow: 1;
text-align: right;
}
&--heading {
font-weight: bold;
@media (max-width: @mio-profile-mobile) {
text-align: left;
}
}
&--numeric {
text-align: right;
}
&--user-title {
text-align: center;
width: 100%;
font-style: italic;
}
&--icons {
text-align: center;
min-width: 0;
}
&--country {
font-size: .8em;
line-height: 1.4em;
width: 110px;
text-align: center;
align-self: flex-end;
}
}
}
}

52
assets/less/mio/main.less Normal file
View file

@ -0,0 +1,52 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
position: relative;
}
html,
body {
width: 100%;
height: 100%;
}
.mio {
background-color: #fbeeff;
background-image: linear-gradient(180deg, #c2affe, transparent);
background-repeat: repeat-x;
background-size: 100% 150px;
font: 12px/20px Tahoma, Verdana, Arial, Helvetica, sans-serif;
color: #000;
&__wrapper {
max-width: 1200px;
margin: 0 auto;
padding: 1px;
}
&--dark {
background-color: #23172a;
background-image: linear-gradient(180deg, #462e55, transparent);
color: #fff;
}
}
// Input elements
@import "classes/input/button";
@import "classes/input/text";
@import "classes/input/textarea";
// Base styles
@import "classes/avatar";
@import "classes/container";
@import "classes/heading";
// Specific styles
@import "classes/footer";
@import "classes/header";
@import "classes/navigation";
@import "classes/profile";
// Forums
@import "classes/forum/listing";

View file

@ -40,24 +40,24 @@ lessc --verbose $STYLE_DIR/$LESS_ENTRY_FILE $PUBLIC_CSS/$STYLE_NAME.css
echo echo
done done
# scripts # scripts (don't need yet, build script gets stuck here bc no files exists)
echo # echo
echo "=> TypeScript" # echo "=> TypeScript"
for SCRIPT_DIR in $ASSETS_TS/*/; do # for SCRIPT_DIR in $ASSETS_TS/*/; do
SCRIPT_NAME=`basename $SCRIPT_DIR` # SCRIPT_NAME=`basename $SCRIPT_DIR`
SCRIPT_NAME_LOWER=`echo $SCRIPT_NAME | tr '[A-Z]' '[a-z]'` # SCRIPT_NAME_LOWER=`echo $SCRIPT_NAME | tr '[A-Z]' '[a-z]'`
echo "==> $SCRIPT_NAME" # echo "==> $SCRIPT_NAME"
find $SCRIPT_DIR -name "*.ts" | xargs tsc \ # find $SCRIPT_DIR -name "*.ts" | xargs tsc \
-d \ # -d \
-t es5 \ # -t es5 \
--listFiles \ # --listFiles \
--listEmittedFiles \ # --listEmittedFiles \
--noImplicitAny \ # --noImplicitAny \
--removeComments \ # --removeComments \
--outFile $PUBLIC_JS/$SCRIPT_NAME_LOWER.js # --outFile $PUBLIC_JS/$SCRIPT_NAME_LOWER.js
mv -v $PUBLIC_JS/$SCRIPT_NAME_LOWER.d.ts $ASSETS_TS/$SCRIPT_NAME.d.ts # mv -v $PUBLIC_JS/$SCRIPT_NAME_LOWER.d.ts $ASSETS_TS/$SCRIPT_NAME.d.ts
echo # echo
done # done
# node imports # node imports
echo echo

View file

@ -19,137 +19,160 @@ $username_validation_errors = [
]; ];
$mode = $_GET['m'] ?? 'login'; $mode = $_GET['m'] ?? 'login';
$prevent_registration = $app->config->get('Auth', 'prevent_registration', 'bool', false);
$app->templating->var('auth_mode', $mode); $app->templating->var('auth_mode', $mode);
$app->templating->addPath('auth', __DIR__ . '/../views/auth');
$app->templating->var('prevent_registration', $prevent_registration);
if (!empty($_REQUEST['username'])) {
$app->templating->var('auth_username', $_REQUEST['username']);
}
if (!empty($_REQUEST['email'])) {
$app->templating->var('auth_email', $_REQUEST['email']);
}
switch ($mode) { switch ($mode) {
case 'logout': case 'logout':
if ($app->getSession() === null) { if ($app->getSession() === null) {
echo "You aren't logged in."; header('Location: /');
} else { return;
echo "You've been logged out."; }
// this is temporary, don't scream at me for using md5
if (isset($_GET['s']) && md5($app->getSession()->session_key) === $_GET['s']) {
set_cookie_m('uid', '', -3600); set_cookie_m('uid', '', -3600);
set_cookie_m('sid', '', -3600); set_cookie_m('sid', '', -3600);
$app->getSession()->delete(); $app->getSession()->delete();
$app->setSession(null); $app->setSession(null);
header('Location: /');
return;
} }
echo '<meta http-equiv="refresh" content="1; url=/">'; echo $app->templating->render('logout');
break; break;
case 'login': case 'login':
if ($app->getSession() !== null) { if ($app->getSession() !== null) {
echo '<meta http-equiv="refresh" content="0; url=/">'; header('Location: /');
break; break;
} }
if ($_SERVER['REQUEST_METHOD'] === 'GET') { $auth_login_error = '';
echo $app->templating->render('auth.login');
break; while ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['username'], $_POST['password'])) {
$auth_login_error = "You didn't fill all the forms!";
break;
}
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
try {
$user = User::where('username', $username)->orWhere('email', $username)->firstOrFail();
} catch (ModelNotFoundException $e) {
$auth_login_error = 'Invalid username or password!';
break;
}
if (!$user->validatePassword($password)) {
$auth_login_error = 'Invalid username or password!';
break;
}
$session = Session::createSession($user, 'Misuzu T2');
$app->setSession($session);
set_cookie_m('uid', $session->user_id, 604800);
set_cookie_m('sid', $session->session_key, 604800);
// Temporary key generation for chat login.
// Should eventually be replaced with a callback login system.
// Also uses different cookies since $httponly is required to be false for these.
$user->last_ip = IPAddress::remote();
$user->user_chat_key = bin2hex(random_bytes(16));
$user->save();
setcookie('msz_tmp_id', $user->user_id, time() + 604800, '/', '.flashii.net');
setcookie('msz_tmp_key', $user->user_chat_key, time() + 604800, '/', '.flashii.net');
header('Location: /');
return;
} }
if (!isset($_POST['username'], $_POST['password'])) { if (!empty($auth_login_error)) {
echo json_encode_m(['error' => "You didn't fill all the forms!"]); $app->templating->var('auth_login_error', $auth_login_error);
break;
} }
$username = $_POST['username'] ?? ''; echo $app->templating->render('auth');
$password = $_POST['password'] ?? '';
try {
$user = User::where('username', $username)->orWhere('email', $username)->firstOrFail();
} catch (ModelNotFoundException $e) {
echo json_encode_m(['error' => 'Invalid username or password!']);
break;
}
if (!$user->validatePassword($password)) {
echo json_encode_m(['error' => 'Invalid username or password!']);
break;
}
$session = Session::createSession($user, 'Misuzu T2');
$app->setSession($session);
set_cookie_m('uid', $session->user_id, 604800);
set_cookie_m('sid', $session->session_key, 604800);
// Temporary key generation for chat login.
// Should eventually be replaced with a callback login system.
// Also uses different cookies since $httponly is required to be false for these.
$user->last_ip = IPAddress::remote();
$user->user_chat_key = bin2hex(random_bytes(16));
$user->save();
setcookie('msz_tmp_id', $user->user_id, time() + 604800, '/', '.flashii.net');
setcookie('msz_tmp_key', $user->user_chat_key, time() + 604800, '/', '.flashii.net');
echo json_encode_m(['error' => 'You are now logged in!', 'next' => '/']);
break; break;
case 'register': case 'register':
if ($app->getSession() !== null) { if ($app->getSession() !== null) {
return '<meta http-equiv="refresh" content="0; url=/">'; header('Location: /');
} }
$prevent_registration = $app->config->get('Auth', 'prevent_registration', 'bool', false); $auth_register_error = '';
if ($_SERVER['REQUEST_METHOD'] === 'GET') { while ($_SERVER['REQUEST_METHOD'] === 'POST') {
$app->templating->var('prevent_registration', $prevent_registration); if ($prevent_registration) {
echo $app->templating->render('auth.register'); $auth_register_error = 'Registration is not allowed on this instance.';
break;
}
if ($prevent_registration) {
echo json_encode_m(['error' => 'Registration is not allowed on this instance.']);
break;
}
if (!isset($_POST['username'], $_POST['password'], $_POST['email'])) {
echo json_encode_m(['error' => "You didn't fill all the forms!"]);
break;
}
$username = $_POST['username'] ?? '';
$username_validate = User::validateUsername($username);
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
if ($username_validate !== '') {
echo json_encode_m(['error' => $username_validation_errors[$username_validate]]);
break;
}
try {
$existing = User::where('username', $username)->firstOrFail();
if ($existing->user_id > 0) {
echo json_encode_m(['error' => 'This username is already taken!']);
break; break;
} }
} catch (ModelNotFoundException $e) {
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || !check_mx_record($email)) { if (!isset($_POST['username'], $_POST['password'], $_POST['email'])) {
echo json_encode_m(['error' => 'The e-mail address you entered is invalid!']); $auth_register_error = "You didn't fill all the forms!";
break;
}
try {
$existing = User::where('email', $email)->firstOrFail();
if ($existing->user_id > 0) {
echo json_encode_m(['error' => 'This e-mail address has already been used!']);
break; break;
} }
} catch (ModelNotFoundException $e) {
}
if (password_entropy($password) < 32) { $username = $_POST['username'] ?? '';
echo json_encode_m(['error' => 'Your password is too weak!']); $username_validate = User::validateUsername($username);
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
if ($username_validate !== '') {
$auth_register_error = $username_validation_errors[$username_validate];
break;
}
try {
$existing = User::where('username', $username)->firstOrFail();
if ($existing->user_id > 0) {
$auth_register_error = 'This username is already taken!';
break;
}
} catch (ModelNotFoundException $e) {
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || !check_mx_record($email)) {
$auth_register_error = 'The e-mail address you entered is invalid!';
break;
}
try {
$existing = User::where('email', $email)->firstOrFail();
if ($existing->user_id > 0) {
$auth_register_error = 'This e-mail address has already been used!';
break;
}
} catch (ModelNotFoundException $e) {
}
if (password_entropy($password) < 32) {
$auth_register_error = 'Your password is too weak!';
break;
}
User::createUser($username, $password, $email);
$app->templating->var('auth_register_message', 'Welcome to Flashii! You may now log in.');
break; break;
} }
User::createUser($username, $password, $email); if (!empty($auth_register_error)) {
$app->templating->var('auth_register_error', $auth_register_error);
}
echo json_encode_m(['error' => 'Welcome to Flashii! You may now log in.', 'next' => '/auth.php?m=login']); echo $app->templating->render('auth');
break; break;
} }

View file

@ -119,6 +119,8 @@ class Application extends ApplicationBase
$twig->addFilter('json_decode'); $twig->addFilter('json_decode');
$twig->addFilter('byte_symbol'); $twig->addFilter('byte_symbol');
$twig->addFilter('country_name', 'get_country_name');
$twig->addFilter('md5'); // using this for logout CSRF for now, remove this when proper CSRF is in place
$twig->addFunction('byte_symbol'); $twig->addFunction('byte_symbol');
$twig->addFunction('session_id'); $twig->addFunction('session_id');
@ -128,6 +130,6 @@ class Application extends ApplicationBase
$twig->var('app', $this); $twig->var('app', $this);
$twig->addPath('nova', __DIR__ . '/../views/nova'); $twig->addPath('mio', __DIR__ . '/../views/mio');
} }
} }

View file

@ -37,7 +37,7 @@ class TemplateEngine
private $vars = []; private $vars = [];
/** /**
* Creates the twig environment and registers the utility filters and functions. * Creates the twig environment.
*/ */
public function __construct( public function __construct(
?string $cache = null, ?string $cache = null,

View file

@ -113,6 +113,23 @@ function get_country_code(string $ipAddr, string $fallback = 'XX'): string
return $fallback; return $fallback;
} }
function get_country_name(string $code): string
{
switch (strtolower($code)) {
case 'xx':
return 'Unknown';
case 'a1':
return 'Anonymous Proxy';
case 'a2':
return 'Satellite Provider';
default:
return locale_get_display_region("-{$code}", 'en');
}
}
function is_int_ex($value, int $boundary_low, int $boundary_high): bool function is_int_ex($value, int $boundary_low, int $boundary_high): bool
{ {
return is_int($value) && $value >= $boundary_low && $value <= $boundary_high; return is_int($value) && $value >= $boundary_low && $value <= $boundary_high;

80
views/auth/auth.twig Normal file
View file

@ -0,0 +1,80 @@
{% extends '@auth/master.twig' %}
{% block content %}
<div class="container">
<div class="form__wrapper">
<input class="form__toggle" id="_authmode_login" type="radio" name="_authmode"{% if auth_mode == 'login' %} checked{% endif %}>
<div class="form form--login">
<label class="form__title" for="_authmode_login">Login</label>
<form class="form__content" method="post" action="?m=login">
<label class="form__row">
<input class="text text--username" type="text" name="username" placeholder="username" value="{{ auth_username|default('') }}" required>
</label>
<label class="form__row">
<input class="text text--password" type="password" name="password" placeholder="password" required>
</label>
<div class="form__row form__row--columns">
<div class="form__column form__column--message{% if auth_login_error is defined %} form__column--error{% endif %}">
{{ auth_login_message|default(auth_login_error|default('')) }}
</div>
<div class="form__column">
<button class="button button--login">Login</button>
</div>
</div>
</form>
</div>
</div>
<div class="form__wrapper">
<input class="form__toggle" id="_authmode_register" type="radio" name="_authmode"{% if auth_mode == 'register' %} checked{% endif %}>
<div class="form form-register">
<label class="form__title" for="_authmode_register">Create an account</label>
{% if prevent_registration|default(false) %}
<div class="form__content form__content--no-registration">
<p>You're currently using the site via the public testing website, if you want to create an account please do so from the <a class="form__link" href="https://flashii.net/auth.php?m=register" rel="noopener noreferrer">main website</a>.</p>
</div>
{% else %}
<form class="form__content" method="post" action="?m=register">
<label class="form__row">
<input class="text text--username" type="text" name="username" placeholder="username" value="{{ auth_username|default('') }}" required>
</label>
<label class="form__row">
<input class="text text--password" type="password" name="password" placeholder="password" required>
</label>
<label class="form__row">
<input class="text text--email" type="text" name="email" placeholder="e-mail" value="{{ auth_email|default('') }}" required>
</label>
<div class="form__row form__row--columns">
<div class="form__column form__column--message{% if auth_register_error is defined %} form__column--error{% endif %}">
{{ auth_register_message|default(auth_register_error|default('')) }}
</div>
<div class="form__column">
<button class="button button--register">Sign up</button>
</div>
</div>
</form>
{% endif %}
</div>
</div>
{# <div class="form__wrapper">
<input class="form__toggle" id="_authmode_restore" type="radio" name="_authmode"{% if auth_mode == 'restore' %} checked{% endif %}>
<div class="form form--restore">
<label class="form__title" for="_authmode_restore">Forgot login details</label>
<form class="form__content" method="post" action="?m=restore">
<label class="form__row">
<input class="text text--email" type="text" name="email" placeholder="e-mail" value="{{ auth_email|default('') }}" required>
</label>
<div class="form__row form__row--columns">
<div class="form__column form__column--message{% if auth_restore_error is defined %} form__column--error{% endif %}">
{{ auth_restore_message|default(auth_restore_error|default('')) }}
</div>
<div class="form__column">
<button class="button button--restore">Send reminder</button>
</div>
</div>
</form>
</div>
</div> #}
</div>
{% endblock %}

13
views/auth/logout.twig Normal file
View file

@ -0,0 +1,13 @@
{% extends '@auth/master.twig' %}
{% block content %}
<div class="container logout">
<div class="logout__message">
<p class="logout__paragraph">We couldn't verify that you were actually the person attempting to log out.</p>
<p class="logout__paragraph">Press the button below to verify the logout request, otherwise click back in your browser or close this tab.</p>
</div>
<div class="logout__buttons">
<a href="?m=logout&amp;s={{ app.session.session_key|md5 }}" class="button button--logout">Logout</a>
</div>
</div>
{% endblock %}

14
views/auth/master.twig Normal file
View file

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Authentication</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/auth.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="auth">
{{ block('content') is defined ? block('content') : '' }}
</div>
</body>
</html>

View file

@ -0,0 +1,21 @@
{% set title = title|default('Flashii') %}
{% set description = description|default("Where the floor doesn't fall far from the carrot.") %}
<title>{{ title }}</title>
<meta name="twitter:title" content="{{ title }}">
<meta property="og:title" content="{{ title }}">
<meta name="description" content="{{ description }}">
<meta name="twitter:description" content="{{ description }}">
<meta property="og:description" content="{{ description }}">
<meta name="twitter:card" content="summary">
<meta property="og:type" content="object">
<meta property="og:site_name" content="Flashii">
{% if icon is defined %}
<meta name="twitter:image:src" content="{{ icon }}">
<meta property="og:image" content="{{ icon }}">
{% endif %}

View file

@ -0,0 +1,25 @@
{% extends '@mio/home/master.twig' %}
{% from '@mio/macros.twig' import navigation, link %}
{% block content %}
<div class="mio__container">
<div class="mio__container__title">Welcome!</div>
<div class="mio__container__content">
<p>Welcome to Flashii, the site is still heavily in development. You can follow us on {{ link('https://twitter.com/flashiinet', 'Twitter') }} where we'll post updates every so often, we'll also announce when the site is ready for public use there!</p>
{% if app.session == null %}
<p>You can <a href="/auth.php?m=register">create an account</a> already though, if you'd like!</p>
{% else %}
<p>If you want a custom avatar, let me (flash) know and I'll set it for you. The settings page doesn't exist yet..</p>
{% endif %}
</div>
<div class="mio__container__title">What happened to the previous design?</div>
<div class="mio__container__content">
<p>I decided to shelve it for a bit. I'm working on this all by myself and I'm not a designer, so coming up with good designs that flow nicely together isn't easy for me.</p>
<p>This layout is a LOT simpler to work with than the other one, so this'll be the main design until later.</p>
<p>A dark mode will also be available at a later date and for those that grow attached this to javascript light style, you will eventually be able to switch to this design whenever you like from your settings!</p>
</div>
</div>
{{ navigation(mio_navigation, '/') }}
{% endblock %}

View file

@ -0,0 +1 @@
{% extends '@mio/master.twig' %}

16
views/mio/macros.twig Normal file
View file

@ -0,0 +1,16 @@
{% macro link(url, content, class) %}
{% spaceless %}
<a href="{{ url }}" {% if '://' in url %} target="_blank" rel="noreferrer noopener"{% endif %} {% if class is defined %}class="{{ class }}"{% endif %}>{{ content|raw }}</a>
{% endspaceless %}
{% endmacro %}
{% macro navigation(links, current, top) %}
{% set top = top|default(false) == true %}
{% set current = current|default(null) %}
<ul class="mio__navigation{% if top %} mio__navigation--top{% endif %}">
{% for name, url in links %}
<li class="mio__navigation__option{% if url == current or name == current %} mio__navigation__option--selected{% endif %}"><a href="{{ url }}" class="mio__navigation__link">{{ name }}</a></li>
{% endfor %}
</ul>
{% endmacro %}

73
views/mio/master.twig Normal file
View file

@ -0,0 +1,73 @@
{% from '@mio/macros.twig' import link, navigation %}
{% set mio_navigation = {
'Home':'/',
'Chat':'https://chat.flashii.net',
} %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% include '@mio/_layout/meta.twig' %}
<link href="https://static.flash.moe/fonts/visitor/visitor1.css" rel="stylesheet">
<link href="https://use.fontawesome.com/releases/v5.0.8/css/all.css" rel="stylesheet">
<link href="/css/mio.css" rel="stylesheet" type="text/css">
</head>
<body class="mio">
<div class="mio__wrapper">
<div class="mio__header">
<div class="mio__header__logo">
<a href="/" class="mio__header__logo__link">
<img class="mio__header__logo__image" src="https://static.flash.moe/logos/logo-exo.png" alt="Flashii">
</a>
</div>
<div class="mio__header__menu">
{% if app.session is not null %}
<div class="mio__container mio__header__user">
<div class="mio__container__title">Hey, {{ app.session.user.username }}!</div>
<div class="mio__container__content mio__header__user__content">
<div class="mio__avatar mio__header__user__avatar" style="background-image:url('https://secret.flashii.net/avatar-serve.php?id={{ app.session.user.user_id }}');"></div>
<div class="mio__header__user__links__container">
<ul class="mio__header__user__links">
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/profile.php?u={{ app.session.user.user_id }}">Profile</a></li>
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/auth.php?m=logout&amp;s={{ app.session.session_key|md5 }}">Logout</a></li>
</ul>
</div>
</div>
</div>
{% else %}
<a href="/auth.php" class="mio__input__button">Login/Register</a>
{% endif %}
</div>
</div>
{% block content %}
<div class="mio__container">
<div class="mio__container__title">Hello!</div>
<div class="mio__container__content">
This page has no content!
</div>
</div>
{{ navigation(mio_navigation) }}
{% endblock %}
<div class="mio__footer">
<div class="mio__footer__copyright">
{{ link('https://flash.moe', 'flash.moe', 'mio__footer__copyright__link') }} 2013-{{
''|date('Y') }} /
{{ link('https://github.com/flashwave/misuzu/tree/' ~ git_branch(), git_branch(), 'mio__footer__copyright__link') }} # {{ link('https://github.com/flashwave/misuzu/commit/' ~ git_hash(true), git_hash(), 'mio__footer__copyright__link') }}
</div>
<div class="mio__footer__links">
{{ link('#', 'Terms of Service', 'mio__footer__links__link') }}
{{ link('#', 'Rules', 'mio__footer__links__link') }}
{{ link('#', 'Contact', 'mio__footer__links__link') }}
{{ link('#', 'Status', 'mio__footer__links__link') }}
{{ link('https://twitter.com/flashiinet', 'Twitter', 'mio__footer__links__link') }}
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
{% extends '@mio/master.twig' %}

42
views/mio/user/view.twig Normal file
View file

@ -0,0 +1,42 @@
{% extends '@mio/user/master.twig' %}
{% from '@mio/macros.twig' import navigation %}
{% set title = 'Profile of ' ~ profile.username %}
{% block content %}
{{ navigation(mio_navigation, false, true) }}
<div class="mio__profile">
<div class="mio__container mio__profile__header">
<div class="mio__container__title">Profile of {{ profile.username }}</div>
<div class="mio__container__content mio__profile__header__content">
<div class="mio__profile__info">
<div class="mio__profile__info__section">
<div class="mio__profile__info__block">
<div class="mio__profile__info__row">
<div class="mio__profile__info__column mio__profile__info__column--icons">
<img class="mio__profile__icon" src="https://static.flash.moe/flags/fff/{{ profile.user_country|lower }}.png" alt="{{ profile.user_country }}">
</div>
<div class="mio__profile__info__column mio__profile__info__column--country">
{{ profile.user_country|country_name }}
</div>
</div>
</div>
<div class="mio__profile__info__block">
<div class="mio__profile__info__row" title="{{ profile.created_at.format('r') }}">
<div class="mio__profile__info__column mio__profile__info__column--heading">
Joined
</div>
<div class="mio__profile__info__column">
{{ profile.created_at.diffForHumans }}
</div>
</div>
</div>
</div>
</div>
<div class="mio__avatar mio__profile__avatar" style="background-image:url('https://secret.flashii.net/avatar-serve.php?id={{ profile.user_id }}');"></div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,25 +0,0 @@
{% extends '@nova/auth/master.twig' %}
{% set banner_classes = 'banner--large landing__banner' %}
{% block banner_content %}
<h1 style="align-self: center; text-align: left; flex-grow: 1; padding-left: 2em">Welcome back!</h1>
{% endblock %}
{% block content %}
<div class="platform form" id="auth-form">
<div>
<a href="/auth.php?m=register"><button class="button" type="button">Click here if you don't have an account yet</button></a>
</div>
<div>
<input class="form__text" type="text" name="username" placeholder="Username">
</div>
<div>
<input class="form__text" type="password" name="password" placeholder="Password">
</div>
<div>
<button class="button">Login!</button>
</div>
</div>
{{ parent() }}
{% endblock %}

View file

@ -1,58 +0,0 @@
{% extends '@nova/master.twig' %}
{% block content %}
<script>
var authHttp;
window.addEventListener('load', function () {
var authf = document.getElementById('auth-form');
if (!authf)
return;
authHttp = new XMLHttpRequest();
authHttp.addEventListener('readystatechange', function () {
if (authHttp.readyState === 4 && authHttp.status === 200)
authfHandle(JSON.parse(authHttp.responseText));
});
authf.addEventListener('keydown', function (e) {
if (e.keyCode === 13)
authfSubmit();
});
var buttons = document.getElementsByTagName('button');
buttons[buttons.length - 1].addEventListener('click', function () { authfSubmit(); });
});
function authfHandle(obj)
{
if (obj.error)
alert(obj.error);
if (obj.next)
location.assign(obj.next);
}
function authfSubmit()
{
authHttp.open('POST', location.pathname + '?m={{ auth_mode }}', true);
authHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
authHttp.send(authfForms());
}
function authfForms()
{
var elems = document.getElementsByTagName('input');
var str = "";
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
str += encodeURIComponent(elem.name) + "=" + encodeURIComponent(elem.value) + "&";
}
return str.slice(0, -1);
}
</script>
{% endblock %}

View file

@ -1,37 +0,0 @@
{% extends '@nova/auth/master.twig' %}
{% set banner_classes = 'banner--large landing__banner' %}
{% block banner_content %}
<h1 style="align-self: center; text-align: left; flex-grow: 1; padding-left: 2em">
{% if not prevent_registration %}
Welcome, thanks for dropping by!
{% else %}
Stop!
{% endif %}
</h1>
{% endblock %}
{% block content %}
{% if not prevent_registration %}
<div class="platform form" id="auth-form">
<div>
<input class="form__text" type="text" name="username" placeholder="Username">
</div>
<div>
<input class="form__text" type="password" name="password" placeholder="Password">
</div>
<div>
<input class="form__text" type="text" name="email" placeholder="E-mail">
</div>
<div>
<button class="button">Create your account!</button>
</div>
</div>
{{ parent() }}
{% else %}
<div class="platform">
<p>You're currently using the site via the public testing end, if you want to create an account please do so from the main website.</p>
</div>
{% endif %}
{% endblock %}

View file

@ -4,7 +4,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1">
{% include '@nova/_layout/meta.twig' %} {% include '@nova/_layout/meta.twig' %}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"> <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Exo+2:200,200italic,300,300italic,400,400italic" rel="stylesheet" type="text/css"> <link href="https://fonts.googleapis.com/css?family=Exo+2:200,200italic,300,300italic,400,400italic" rel="stylesheet" type="text/css">

View file

@ -55,7 +55,7 @@
<div class="profile__content"> <div class="profile__content">
{% spaceless %} {% spaceless %}
<div class="profile__container profile__container--left"> <div class="profile__container profile__container--left">
<div class="profile__avatar" style="background-image: url('https://static.flash.moe/images/discord-logo.png');"></div> <div class="profile__avatar" style="background-image: url('https://secret.flashii.net/avatar-serve.php?id={{ profile.user_id }}');"></div>
<div class="platform profile__platform profile__hierarchies"> <div class="platform profile__platform profile__hierarchies">
{% for id, data in hierarchies %} {% for id, data in hierarchies %}
{% if data.display %} {% if data.display %}