The beginning of TypeScript
This commit is contained in:
parent
5d665bce51
commit
97593040e0
72 changed files with 1450 additions and 1285 deletions
161
app/Controllers/Forum/ForumController.php
Normal file
161
app/Controllers/Forum/ForumController.php
Normal file
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the forum pages controllers.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Controllers\Forum;
|
||||
|
||||
use Sakura\ActiveUser;
|
||||
use Sakura\Config;
|
||||
use Sakura\DB;
|
||||
use Sakura\Forum\Forum;
|
||||
use Sakura\Forum\Post;
|
||||
use Sakura\Forum\Topic;
|
||||
use Sakura\Perms\Forum as ForumPerms;
|
||||
use Sakura\User;
|
||||
|
||||
/**
|
||||
* Forum page controllers.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class ForumController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Get the most active topics
|
||||
$activeTopicsIds = DB::table('posts')
|
||||
->where('forum_id', '!=', config('forum.trash'))
|
||||
->groupBy('topic_id')
|
||||
->orderByRaw('COUNT(*) DESC')
|
||||
->limit(10)
|
||||
->get(['topic_id']);
|
||||
$activeTopics = [];
|
||||
|
||||
// make this not disgusting
|
||||
while (list($_n, $_t) = each($activeTopicsIds)) {
|
||||
$topic = new Topic($_t->topic_id);
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if we have permission to view it
|
||||
if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$fetch = DB::table('posts')
|
||||
->groupBy('topic_id')
|
||||
->orderByRaw('COUNT(*) DESC')
|
||||
->skip(11 + $_n)
|
||||
->take(1)
|
||||
->get(['topic_id']);
|
||||
|
||||
if ($fetch) {
|
||||
$activeTopicsIds[] = $fetch[0];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$activeTopics[$topic->id] = $topic;
|
||||
}
|
||||
|
||||
// Get the latest posts
|
||||
$latestPostsIds = DB::table('posts')
|
||||
->where('forum_id', '!=', config('forum.trash'))
|
||||
->orderBy('post_id', 'desc')
|
||||
->limit(10)
|
||||
->get(['post_id']);
|
||||
$latestPosts = [];
|
||||
|
||||
while (list($_n, $_p) = each($latestPostsIds)) {
|
||||
$post = new Post($_p->post_id);
|
||||
$forum = new Forum($post->forum);
|
||||
|
||||
// Check if we have permission to view it
|
||||
if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$fetch = DB::table('posts')
|
||||
->orderBy('post_id', 'desc')
|
||||
->skip(11 + $_n)
|
||||
->take(1)
|
||||
->get(['post_id']);
|
||||
|
||||
if ($fetch) {
|
||||
$latestPostsIds[] = $fetch[0];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestPosts[$post->id] = $post;
|
||||
}
|
||||
|
||||
// Get the most active poster
|
||||
$activePosterId = DB::table('posts')
|
||||
->where('forum_id', '!=', config('forum.trash'))
|
||||
->where('post_time', '>', time() - (24 * 60 * 60))
|
||||
->groupBy('poster_id')
|
||||
->orderByRaw('COUNT(*) DESC')
|
||||
->limit(1)
|
||||
->get(['poster_id']);
|
||||
$activePoster = User::construct(
|
||||
$activePosterId ? $activePosterId[0]->poster_id : 0
|
||||
);
|
||||
|
||||
$forum = new Forum;
|
||||
|
||||
return view('forum/index', compact('forum', 'activeTopics', 'latestPosts', 'activePoster'));
|
||||
}
|
||||
|
||||
public function forum($id = 0)
|
||||
{
|
||||
$forum = new Forum($id);
|
||||
|
||||
$redirect = route('forums.index');
|
||||
$message = "The forum you tried to access does not exist!";
|
||||
|
||||
// Redirect forum id 0 to the main page
|
||||
if ($forum->id === 0) {
|
||||
return header("Location: {$redirect}");
|
||||
}
|
||||
|
||||
// Check if the forum exists
|
||||
if ($forum->id < 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
// Check if the forum isn't a link
|
||||
if ($forum->type === 2) {
|
||||
$message = "The forum you tried to access is a link. You're being redirected.";
|
||||
$redirect = $forum->link;
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
return view('forum/forum', compact('forum'));
|
||||
}
|
||||
|
||||
public function markRead($id = 0)
|
||||
{
|
||||
$redirect = route('forums.index');
|
||||
|
||||
if (!session_check('s')) {
|
||||
$message = "Your session expired! Go back and try again.";
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
$forum = new Forum($id);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($forum->id < 1
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$message = "The forum you tried to access does not exist.";
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
$forum->trackUpdateAll(ActiveUser::$user->id);
|
||||
|
||||
$message = 'All topics have been marked as read!';
|
||||
$redirect = route('forums.forum', $forum->id);
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
}
|
243
app/Controllers/Forum/PostController.php
Normal file
243
app/Controllers/Forum/PostController.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the controller for posts.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Controllers\Forum;
|
||||
|
||||
use Sakura\ActiveUser;
|
||||
use Sakura\DB;
|
||||
use Sakura\Forum\Forum;
|
||||
use Sakura\Forum\Post;
|
||||
use Sakura\Forum\Topic;
|
||||
use Sakura\Perms\Forum as ForumPerms;
|
||||
|
||||
/**
|
||||
* Topic controller.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class PostController extends Controller
|
||||
{
|
||||
public function find($id = 0)
|
||||
{
|
||||
$post = new Post($id);
|
||||
$topic = new Topic($post->topic);
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($post->id === 0
|
||||
|| $topic->id === 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = route('forums.index');
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
$topicLink = route('forums.topic', $topic->id);
|
||||
|
||||
// Get all post ids from the database
|
||||
$postIds = DB::table('posts')
|
||||
->where('topic_id', $topic->id)
|
||||
->get(['post_id']);
|
||||
$postIds = array_column($postIds, 'post_id');
|
||||
|
||||
// Find in array
|
||||
$postAt = ceil(array_search($post->id, $postIds) / 10);
|
||||
|
||||
// Only append the page variable if it's more than 1
|
||||
if ($postAt > 1) {
|
||||
$topicLink .= "?page={$postAt}";
|
||||
}
|
||||
|
||||
return header("Location: {$topicLink}#p{$post->id}");
|
||||
}
|
||||
|
||||
public function raw($id = 0)
|
||||
{
|
||||
$post = new Post($id);
|
||||
$topic = new Topic($post->topic);
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($post->id === 0
|
||||
|| $topic->id === 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $post->text;
|
||||
}
|
||||
|
||||
public function edit($id = 0)
|
||||
{
|
||||
$title = $_POST['title'] ?? null;
|
||||
$text = $_POST['text'] ?? null;
|
||||
|
||||
$post = new Post($id);
|
||||
$topic = new Topic($post->topic);
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check permissions
|
||||
$noAccess = $post->id === 0
|
||||
|| $topic->id === 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id);
|
||||
|
||||
$noEdit = (
|
||||
$post->poster->id === ActiveUser::$user->id
|
||||
? !ActiveUser::$user->permission(ForumPerms::EDIT_OWN, Perms::FORUM)
|
||||
: !$forum->permission(ForumPerms::EDIT_ANY, ActiveUser::$user->id)
|
||||
) || (
|
||||
$topic->status === 1
|
||||
&& !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id)
|
||||
);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($noAccess || $noEdit) {
|
||||
if ($noDelete) {
|
||||
$message = "You aren't allowed to edit posts in this topic!";
|
||||
$redirect = route('forums.post', $post->id);
|
||||
} else {
|
||||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = route('forums.index');
|
||||
}
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
$titleLength = strlen($title);
|
||||
$textLength = strlen($text);
|
||||
$titleMin = config('forum.min_title_length');
|
||||
$titleMax = config('forum.max_title_length');
|
||||
$textMin = config('forum.min_post_length');
|
||||
$textMax = config('forum.max_post_length');
|
||||
|
||||
// Checks
|
||||
$titleTooShort = $title !== null
|
||||
&& $post->id === $topic->firstPost()->id
|
||||
&& $titleLength < $titleMin;
|
||||
$titleTooLong = $title !== null
|
||||
&& $post->id === $topic->firstPost()->id
|
||||
&& $titleLength > $titleMax;
|
||||
$textTooShort = $textLength < $textMin;
|
||||
$textTooLong = $textLength > $textMax;
|
||||
|
||||
// Check requirments
|
||||
if ($titleTooShort
|
||||
|| $titleTooLong
|
||||
|| $textTooShort
|
||||
|| $textTooLong) {
|
||||
$message = "";
|
||||
|
||||
if ($titleTooShort) {
|
||||
$message = "This title is too short!";
|
||||
} elseif ($titleTooLong) {
|
||||
$message = "This title is too long!";
|
||||
} elseif ($textTooShort) {
|
||||
$message = "Please make your post a little bit longer!";
|
||||
} elseif ($textTooLong) {
|
||||
$message = "Your post is too long, you're gonna have to cut a little!";
|
||||
}
|
||||
|
||||
$redirect = route('forums.post', $post->id);
|
||||
|
||||
if (!isset($_SESSION['replyText'])) {
|
||||
$_SESSION['replyText'] = [];
|
||||
}
|
||||
|
||||
$_SESSION['replyText']["t{$forum->id}"] = $text;
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
unset($_SESSION['replyText']["t{$forum->id}"]);
|
||||
|
||||
if ($post->id !== $topic->firstPost()->id || $title === null) {
|
||||
$title = "Re: {$topic->title}";
|
||||
} else {
|
||||
$topic->title = $title;
|
||||
$topic->update();
|
||||
}
|
||||
|
||||
// Create the post
|
||||
$post->subject = $title;
|
||||
$post->text = $text;
|
||||
$post->editTime = time();
|
||||
$post->editReason = '';
|
||||
$post->editUser = ActiveUser::$user;
|
||||
$post = $post->update();
|
||||
|
||||
$postLink = route('forums.post', $post->id);
|
||||
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
public function delete($id = 0)
|
||||
{
|
||||
$action = isset($_POST['yes']) && session_check();
|
||||
|
||||
$post = new Post($id);
|
||||
$topic = new Topic($post->topic);
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check permissions
|
||||
$noAccess = $post->id === 0
|
||||
|| $topic->id === 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id);
|
||||
|
||||
$noDelete = (
|
||||
$post->poster->id === ActiveUser::$user->id
|
||||
? !ActiveUser::$user->permission(ForumPerms::DELETE_OWN, Perms::FORUM)
|
||||
: !$forum->permission(ForumPerms::DELETE_ANY, ActiveUser::$user->id)
|
||||
) || (
|
||||
$topic->status === 1
|
||||
&& !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id)
|
||||
);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($noAccess || $noDelete) {
|
||||
if ($noDelete) {
|
||||
$message = "You aren't allowed to delete posts in this topic!";
|
||||
$redirect = Router::route('forums.post', $post->id);
|
||||
} else {
|
||||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = Router::route('forums.index');
|
||||
}
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
if ($action !== null) {
|
||||
if ($action) {
|
||||
// Set message
|
||||
$message = "Deleted the post!";
|
||||
|
||||
// Check if the topic only has 1 post
|
||||
if ($topic->replyCount() === 1) {
|
||||
// Delete the entire topic
|
||||
$topic->delete();
|
||||
|
||||
$redirect = route('forums.forum', $forum->id);
|
||||
} else {
|
||||
// Just delete the post
|
||||
$post->delete();
|
||||
|
||||
$redirect = route('forums.topic', $topic->id);
|
||||
}
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
$postLink = route('forums.post', $post->id);
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
$message = "Are you sure?";
|
||||
|
||||
return view('global/confirm', compact('message'));
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the controller for topic.
|
||||
* Holds the controller for topics.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
@ -9,9 +9,9 @@ namespace Sakura\Controllers\Forum;
|
|||
|
||||
use Sakura\ActiveUser;
|
||||
use Sakura\Forum\Forum;
|
||||
use Sakura\Forum\Post;
|
||||
use Sakura\Forum\Topic;
|
||||
use Sakura\Perms\Forum as ForumPerms;
|
||||
use Sakura\Template;
|
||||
|
||||
/**
|
||||
* Topic controller.
|
||||
|
@ -23,35 +23,22 @@ class TopicController extends Controller
|
|||
{
|
||||
public function view($id = 0)
|
||||
{
|
||||
// Attempt to get the topic
|
||||
$topic = new Topic($id);
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($topic->id === 0 || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'message' => "This topic doesn't exist or you don't have access to it!",
|
||||
'redirect' => route('forums.index'),
|
||||
]);
|
||||
if ($topic->id === 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$message = "This topic doesn't exist or you don't have access to it!";
|
||||
$redirect = route('forums.index');
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
// Update the tracking status
|
||||
$topic->trackUpdate(ActiveUser::$user->id);
|
||||
|
||||
// Update views
|
||||
$topic->viewsUpdate();
|
||||
|
||||
// Set parse variables
|
||||
Template::vars(compact('forum', 'topic'));
|
||||
|
||||
// Print page contents
|
||||
return Template::render('forum/topic');
|
||||
return view('forum/topic', compact('forum', 'topic'));
|
||||
}
|
||||
|
||||
private function modBase($id)
|
||||
|
@ -59,7 +46,9 @@ class TopicController extends Controller
|
|||
$topic = new Topic($id);
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
if ($topic->id !== 0 || $forum->permission(ForumPerms::VIEW, ActiveUser::$user->id) || session_check()) {
|
||||
if ($topic->id !== 0
|
||||
|| $forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)
|
||||
|| session_check()) {
|
||||
return compact('topic', 'forum');
|
||||
}
|
||||
|
||||
|
@ -240,9 +229,7 @@ class TopicController extends Controller
|
|||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = route('forums.index');
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
// Check if the topic exists
|
||||
|
@ -254,9 +241,7 @@ class TopicController extends Controller
|
|||
$message = "You are not allowed to post in this topic!";
|
||||
$redirect = route('forums.topic', $topic->id);
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
// Length
|
||||
|
@ -269,7 +254,7 @@ class TopicController extends Controller
|
|||
// Check requirments
|
||||
if ($tooShort
|
||||
|| $tooLong) {
|
||||
$route = Router::route('forums.topic', $topic->id);
|
||||
$route = route('forums.topic', $topic->id);
|
||||
|
||||
$message = "Your post is " . (
|
||||
$tooShort
|
||||
|
@ -278,15 +263,13 @@ class TopicController extends Controller
|
|||
);
|
||||
$redirect = "{$route}#reply";
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
if (!isset($_SESSION['replyText'])) {
|
||||
$_SESSION['replyText'] = [];
|
||||
}
|
||||
|
||||
$_SESSION['replyText']["t{$topic->id}"] = $text;
|
||||
|
||||
return Template::render('global/information');
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
unset($_SESSION['replyText']["t{$topic->id}"]);
|
||||
|
@ -306,4 +289,90 @@ class TopicController extends Controller
|
|||
// Head to the post
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
public function create($id = 0)
|
||||
{
|
||||
$title = $_POST['title'] ?? null;
|
||||
$text = $_POST['text'] ?? null;
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($id);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($forum->id === 0
|
||||
|| $forum->type !== 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)
|
||||
|| !$forum->permission(ForumPerms::REPLY, ActiveUser::$user->id)
|
||||
|| !$forum->permission(ForumPerms::CREATE_THREADS, ActiveUser::$user->id)) {
|
||||
$message = "This forum doesn't exist or you don't have access to it!";
|
||||
$redirect = route('forums.index');
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
if ($text && $title) {
|
||||
// Length
|
||||
$titleLength = strlen($title);
|
||||
$textLength = strlen($text);
|
||||
$titleMin = config('forum.min_title_length');
|
||||
$titleMax = config('forum.max_title_length');
|
||||
$textMin = config('forum.min_post_length');
|
||||
$textMax = config('forum.max_post_length');
|
||||
|
||||
// Checks
|
||||
$titleTooShort = $titleLength < $titleMin;
|
||||
$titleTooLong = $titleLength > $titleMax;
|
||||
$textTooShort = $textLength < $textMin;
|
||||
$textTooLong = $textLength > $textMax;
|
||||
|
||||
// Check requirments
|
||||
if ($titleTooShort
|
||||
|| $titleTooLong
|
||||
|| $textTooShort
|
||||
|| $textTooLong) {
|
||||
$message = "";
|
||||
|
||||
if ($titleTooShort) {
|
||||
$message = "This title is too short, it has to be longer than {$titleMin} characters!";
|
||||
} elseif ($titleTooLong) {
|
||||
$message = "This title is too long, keep it under {$titleMax} characters!";
|
||||
} elseif ($textTooShort) {
|
||||
$message = "Please make your post a little bit longer, at least {$textMin} characters!";
|
||||
} elseif ($textTooLong) {
|
||||
$message = "Your post is too long, you're gonna have to cut a little!"
|
||||
. " Can't be more than {$textMax} characters.";
|
||||
}
|
||||
|
||||
$redirect = route('forums.new', $forum->id);
|
||||
|
||||
if (!isset($_SESSION['replyText'])) {
|
||||
$_SESSION['replyText'] = [];
|
||||
}
|
||||
|
||||
$_SESSION['replyText']["f{$forum->id}"]["title"] = $title;
|
||||
$_SESSION['replyText']["f{$forum->id}"]["text"] = $text;
|
||||
|
||||
return view('global/information', compact('message', 'redirect'));
|
||||
}
|
||||
|
||||
unset($_SESSION['replyText']["f{$forum->id}"]);
|
||||
|
||||
// Create the post
|
||||
$post = Post::create(
|
||||
$title,
|
||||
$text,
|
||||
ActiveUser::$user,
|
||||
0,
|
||||
$forum->id
|
||||
);
|
||||
|
||||
// Go to the post
|
||||
$postLink = route('forums.post', $post->id);
|
||||
|
||||
// Head to the post
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
return view('forum/topic', compact('forum'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,630 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the forum pages controllers.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Controllers;
|
||||
|
||||
use Sakura\ActiveUser;
|
||||
use Sakura\Config;
|
||||
use Sakura\DB;
|
||||
use Sakura\Forum\Forum;
|
||||
use Sakura\Forum\Post;
|
||||
use Sakura\Forum\Topic;
|
||||
use Sakura\Perms;
|
||||
use Sakura\Perms\Forum as ForumPerms;
|
||||
use Sakura\Router;
|
||||
use Sakura\Template;
|
||||
use Sakura\User;
|
||||
|
||||
/**
|
||||
* Forum page controllers.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class ForumController extends Controller
|
||||
{
|
||||
/**
|
||||
* Serves the forum index.
|
||||
*
|
||||
* @return string HTML for the forum index.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Get the most active topics
|
||||
$activeTopicsIds = DB::table('posts')
|
||||
->where('forum_id', '!=', config('forum.trash'))
|
||||
->groupBy('topic_id')
|
||||
->orderByRaw('COUNT(*) DESC')
|
||||
->limit(10)
|
||||
->get(['topic_id']);
|
||||
$activeTopics = [];
|
||||
|
||||
// make this not disgusting
|
||||
while (list($_n, $_t) = each($activeTopicsIds)) {
|
||||
// Create the topic object
|
||||
$topic = new Topic($_t->topic_id);
|
||||
|
||||
// Create a forum object
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if we have permission to view it
|
||||
if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$fetch = DB::table('posts')
|
||||
->groupBy('topic_id')
|
||||
->orderByRaw('COUNT(*) DESC')
|
||||
->skip(11 + $_n)
|
||||
->take(1)
|
||||
->get(['topic_id']);
|
||||
|
||||
if ($fetch) {
|
||||
$activeTopicsIds[] = $fetch[0];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$activeTopics[$topic->id] = $topic;
|
||||
}
|
||||
|
||||
// Get the latest posts
|
||||
$latestPostsIds = DB::table('posts')
|
||||
->where('forum_id', '!=', config('forum.trash'))
|
||||
->orderBy('post_id', 'desc')
|
||||
->limit(10)
|
||||
->get(['post_id']);
|
||||
$latestPosts = [];
|
||||
|
||||
while (list($_n, $_p) = each($latestPostsIds)) {
|
||||
// Create new post object
|
||||
$post = new Post($_p->post_id);
|
||||
|
||||
// Forum id
|
||||
$forum = new Forum($post->forum);
|
||||
|
||||
// Check if we have permission to view it
|
||||
if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$fetch = DB::table('posts')
|
||||
->orderBy('post_id', 'desc')
|
||||
->skip(11 + $_n)
|
||||
->take(1)
|
||||
->get(['post_id']);
|
||||
|
||||
if ($fetch) {
|
||||
$latestPostsIds[] = $fetch[0];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$latestPosts[$post->id] = $post;
|
||||
}
|
||||
|
||||
// Get the most active poster
|
||||
$activePosterId = DB::table('posts')
|
||||
->where('forum_id', '!=', config('forum.trash'))
|
||||
->where('post_time', '>', time() - (24 * 60 * 60))
|
||||
->groupBy('poster_id')
|
||||
->orderByRaw('COUNT(*) DESC')
|
||||
->limit(1)
|
||||
->get(['poster_id']);
|
||||
$activePoster = User::construct(
|
||||
$activePosterId ? $activePosterId[0]->poster_id : 0
|
||||
);
|
||||
|
||||
// Create the forum object
|
||||
$forum = new Forum;
|
||||
|
||||
Template::vars(compact('forum', 'activeTopics', 'latestPosts', 'activePoster'));
|
||||
|
||||
// Return the compiled page
|
||||
return Template::render('forum/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a forum page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function forum($id = 0)
|
||||
{
|
||||
// Get the forum
|
||||
$forum = new Forum($id);
|
||||
|
||||
// Redirect forum id 0 to the main page
|
||||
if ($forum->id === 0) {
|
||||
return header('Location: ' . Router::route('forums.index'));
|
||||
}
|
||||
|
||||
// Check if the forum exists
|
||||
if ($forum->id < 0) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'The forum you tried to access does not exist.',
|
||||
'redirect' => Router::route('forums.index'),
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Check if the user has access to the forum
|
||||
if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'You do not have access to this forum.',
|
||||
'redirect' => Router::route('forums.index'),
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Check if the forum isn't a link
|
||||
if ($forum->type === 2) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'The forum you tried to access is a link. You\'re being redirected.',
|
||||
'redirect' => $forum->link,
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Set parse variables
|
||||
Template::vars([
|
||||
'forum' => $forum,
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('forum/forum');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a forum as read.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function markForumRead($id = 0)
|
||||
{
|
||||
// Check if the session id was supplied
|
||||
if (!isset($_GET['s']) || $_GET['s'] != session_id()) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'Your session expired! Go back and try again.',
|
||||
'redirect' => Router::route('forums.index'),
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Get the forum
|
||||
$forum = new Forum($id);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($forum->id < 1) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'The forum you tried to access does not exist.',
|
||||
'redirect' => Router::route('forums.index'),
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Check if the user has access to the forum
|
||||
if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'You do not have access to this forum.',
|
||||
'redirect' => Router::route('forums.index'),
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Run the function
|
||||
$forum->trackUpdateAll(ActiveUser::$user->id);
|
||||
|
||||
// Set render data
|
||||
Template::vars([
|
||||
'page' => [
|
||||
'message' => 'All topics have been marked as read.',
|
||||
'redirect' => Router::route('forums.forum', $forum->id),
|
||||
],
|
||||
]);
|
||||
|
||||
// Print page contents
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the position of a post in a topic.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function post($id = 0)
|
||||
{
|
||||
// Attempt to get the post
|
||||
$post = new Post($id);
|
||||
|
||||
// And attempt to get the forum
|
||||
$topic = new Topic($post->topic);
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($post->id == 0
|
||||
|| $topic->id == 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = Router::route('forums.index');
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Generate link
|
||||
$topicLink = Router::route('forums.topic', $topic->id);
|
||||
|
||||
// Get all post ids from the database
|
||||
$postIds = DB::table('posts')
|
||||
->where('topic_id', $topic->id)
|
||||
->get(['post_id']);
|
||||
$postIds = array_column($postIds, 'post_id');
|
||||
|
||||
// Find in array
|
||||
$postAt = ceil(array_search($post->id, $postIds) / 10);
|
||||
|
||||
// Only append the page variable if it's more than 1
|
||||
if ($postAt > 1) {
|
||||
$topicLink .= "?page={$postAt}";
|
||||
}
|
||||
|
||||
return header("Location: {$topicLink}#p{$post->id}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw text of a post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function postRaw($id = 0)
|
||||
{
|
||||
// Attempt to get the post
|
||||
$post = new Post($id);
|
||||
|
||||
// And attempt to get the forum
|
||||
$topic = new Topic($post->topic);
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($post->id == 0
|
||||
|| $topic->id == 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $post->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a topic.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createTopic($id = 0)
|
||||
{
|
||||
$title = isset($_POST['title']) ? $_POST['title'] : null;
|
||||
$text = isset($_POST['text']) ? $_POST['text'] : null;
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($id);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($forum->id === 0
|
||||
|| $forum->type !== 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)
|
||||
|| !$forum->permission(ForumPerms::REPLY, ActiveUser::$user->id)
|
||||
|| !$forum->permission(ForumPerms::CREATE_THREADS, ActiveUser::$user->id)) {
|
||||
$message = "This forum doesn't exist or you don't have access to it!";
|
||||
$redirect = Router::route('forums.index');
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
if ($text && $title) {
|
||||
// Length
|
||||
$titleLength = strlen($title);
|
||||
$textLength = strlen($text);
|
||||
$titleMin = config('forum.min_title_length');
|
||||
$titleMax = config('forum.max_title_length');
|
||||
$textMin = config('forum.min_post_length');
|
||||
$textMax = config('forum.max_post_length');
|
||||
|
||||
// Checks
|
||||
$titleTooShort = $titleLength < $titleMin;
|
||||
$titleTooLong = $titleLength > $titleMax;
|
||||
$textTooShort = $textLength < $textMin;
|
||||
$textTooLong = $textLength > $textMax;
|
||||
|
||||
// Check requirments
|
||||
if ($titleTooShort
|
||||
|| $titleTooLong
|
||||
|| $textTooShort
|
||||
|| $textTooLong) {
|
||||
$message = "";
|
||||
|
||||
if ($titleTooShort) {
|
||||
$message = "This title is too short!";
|
||||
} elseif ($titleTooLong) {
|
||||
$message = "This title is too long!";
|
||||
} elseif ($textTooShort) {
|
||||
$message = "Please make your post a little bit longer!";
|
||||
} elseif ($textTooLong) {
|
||||
$message = "Your post is too long, you're gonna have to cut a little!";
|
||||
}
|
||||
|
||||
$redirect = Router::route('forums.new', $forum->id);
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
if (!isset($_SESSION['replyText'])) {
|
||||
$_SESSION['replyText'] = [];
|
||||
}
|
||||
|
||||
$_SESSION['replyText']["f{$forum->id}"]["title"] = $title;
|
||||
$_SESSION['replyText']["f{$forum->id}"]["text"] = $text;
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
unset($_SESSION['replyText']["f{$forum->id}"]);
|
||||
|
||||
// Create the post
|
||||
$post = Post::create(
|
||||
$title,
|
||||
$text,
|
||||
ActiveUser::$user,
|
||||
0,
|
||||
$forum->id
|
||||
);
|
||||
|
||||
// Go to the post
|
||||
$postLink = Router::route('forums.post', $post->id);
|
||||
|
||||
// Head to the post
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
Template::vars(compact('forum'));
|
||||
|
||||
return Template::render('forum/topic');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function editPost($id = 0)
|
||||
{
|
||||
$title = isset($_POST['title']) ? $_POST['title'] : null;
|
||||
$text = isset($_POST['text']) ? $_POST['text'] : null;
|
||||
|
||||
// Attempt to get the post
|
||||
$post = new Post($id);
|
||||
|
||||
// Attempt to get the topic
|
||||
$topic = new Topic($post->topic);
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check permissions
|
||||
$noAccess = $post->id == 0
|
||||
|| $topic->id == 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id);
|
||||
|
||||
$noEdit = (
|
||||
$post->poster->id === ActiveUser::$user->id
|
||||
? !ActiveUser::$user->permission(ForumPerms::EDIT_OWN, Perms::FORUM)
|
||||
: !$forum->permission(ForumPerms::EDIT_ANY, ActiveUser::$user->id)
|
||||
) || (
|
||||
$topic->status === 1
|
||||
&& !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id)
|
||||
);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($noAccess || $noEdit) {
|
||||
if ($noDelete) {
|
||||
$message = "You aren't allowed to edit posts in this topic!";
|
||||
$redirect = Router::route('forums.post', $post->id);
|
||||
} else {
|
||||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = Router::route('forums.index');
|
||||
}
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Length
|
||||
$titleLength = strlen($title);
|
||||
$textLength = strlen($text);
|
||||
$titleMin = config('forum.min_title_length');
|
||||
$titleMax = config('forum.max_title_length');
|
||||
$textMin = config('forum.min_post_length');
|
||||
$textMax = config('forum.max_post_length');
|
||||
|
||||
// Checks
|
||||
$titleTooShort = $title !== null
|
||||
&& $post->id === $topic->firstPost()->id
|
||||
&& $titleLength < $titleMin;
|
||||
$titleTooLong = $title !== null
|
||||
&& $post->id === $topic->firstPost()->id
|
||||
&& $titleLength > $titleMax;
|
||||
$textTooShort = $textLength < $textMin;
|
||||
$textTooLong = $textLength > $textMax;
|
||||
|
||||
// Check requirments
|
||||
if ($titleTooShort
|
||||
|| $titleTooLong
|
||||
|| $textTooShort
|
||||
|| $textTooLong) {
|
||||
$message = "";
|
||||
|
||||
if ($titleTooShort) {
|
||||
$message = "This title is too short!";
|
||||
} elseif ($titleTooLong) {
|
||||
$message = "This title is too long!";
|
||||
} elseif ($textTooShort) {
|
||||
$message = "Please make your post a little bit longer!";
|
||||
} elseif ($textTooLong) {
|
||||
$message = "Your post is too long, you're gonna have to cut a little!";
|
||||
}
|
||||
|
||||
$redirect = Router::route('forums.post', $post->id);
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
if (!isset($_SESSION['replyText'])) {
|
||||
$_SESSION['replyText'] = [];
|
||||
}
|
||||
|
||||
$_SESSION['replyText']["t{$forum->id}"] = $text;
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
unset($_SESSION['replyText']["t{$forum->id}"]);
|
||||
|
||||
if ($post->id !== $topic->firstPost()->id || $title === null) {
|
||||
$title = "Re: {$topic->title}";
|
||||
} else {
|
||||
$topic->title = $title;
|
||||
$topic->update();
|
||||
}
|
||||
|
||||
// Create the post
|
||||
$post->subject = $title;
|
||||
$post->text = $text;
|
||||
$post->editTime = time();
|
||||
$post->editReason = '';
|
||||
$post->editUser = ActiveUser::$user;
|
||||
$post = $post->update();
|
||||
|
||||
// Go to the post
|
||||
$postLink = Router::route('forums.post', $post->id);
|
||||
|
||||
// Head to the post
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function deletePost($id = 0)
|
||||
{
|
||||
$action = isset($_POST['yes']) && isset($_POST['sessionid'])
|
||||
? $_POST['sessionid'] === session_id()
|
||||
: null;
|
||||
|
||||
// Attempt to get the post
|
||||
$post = new Post($id);
|
||||
|
||||
// And attempt to get the forum
|
||||
$topic = new Topic($post->topic);
|
||||
|
||||
// And attempt to get the forum
|
||||
$forum = new Forum($topic->forum);
|
||||
|
||||
// Check permissions
|
||||
$noAccess = $post->id == 0
|
||||
|| $topic->id == 0
|
||||
|| !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id);
|
||||
|
||||
$noDelete = (
|
||||
$post->poster->id === ActiveUser::$user->id
|
||||
? !ActiveUser::$user->permission(ForumPerms::DELETE_OWN, Perms::FORUM)
|
||||
: !$forum->permission(ForumPerms::DELETE_ANY, ActiveUser::$user->id)
|
||||
) || (
|
||||
$topic->status === 1
|
||||
&& !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id)
|
||||
);
|
||||
|
||||
// Check if the forum exists
|
||||
if ($noAccess || $noDelete) {
|
||||
if ($noDelete) {
|
||||
$message = "You aren't allowed to delete posts in this topic!";
|
||||
$redirect = Router::route('forums.post', $post->id);
|
||||
} else {
|
||||
$message = "This post doesn't exist or you don't have access to it!";
|
||||
$redirect = Router::route('forums.index');
|
||||
}
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
if ($action !== null) {
|
||||
if ($action) {
|
||||
// Set message
|
||||
$message = "Deleted the post!";
|
||||
|
||||
// Check if the topic only has 1 post
|
||||
if ($topic->replyCount() === 1) {
|
||||
// Delete the entire topic
|
||||
$topic->delete();
|
||||
|
||||
$redirect = Router::route('forums.forum', $forum->id);
|
||||
} else {
|
||||
// Just delete the post
|
||||
$post->delete();
|
||||
|
||||
$redirect = Router::route('forums.topic', $topic->id);
|
||||
}
|
||||
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
$postLink = Router::route('forums.post', $post->id);
|
||||
return header("Location: {$postLink}");
|
||||
}
|
||||
|
||||
$message = "Are you sure?";
|
||||
|
||||
Template::vars(compact('message'));
|
||||
|
||||
return Template::render('global/confirm');
|
||||
}
|
||||
}
|
37
app/Controllers/Manage/Controller.php
Normal file
37
app/Controllers/Manage/Controller.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the base controller for manage.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Controllers\Manage;
|
||||
|
||||
use Sakura\Controllers\Controller as BaseController;
|
||||
use Sakura\Template;
|
||||
|
||||
/**
|
||||
* Base management controller (which other controllers should extend on).
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class Controller extends BaseController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$navigation = $this->navigation();
|
||||
|
||||
Template::vars(compact('navigation'));
|
||||
}
|
||||
|
||||
public function navigation()
|
||||
{
|
||||
$nav = [];
|
||||
|
||||
// Overview
|
||||
$nav["Overview"]["Index"] = route('manage.overview.index');
|
||||
|
||||
return $nav;
|
||||
}
|
||||
}
|
46
app/Controllers/Manage/OverviewController.php
Normal file
46
app/Controllers/Manage/OverviewController.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the overview controller.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Controllers\Manage;
|
||||
|
||||
use Sakura\DB;
|
||||
|
||||
/**
|
||||
* Overview controller.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class OverviewController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('manage/overview/index');
|
||||
}
|
||||
|
||||
public function data()
|
||||
{
|
||||
$data = new \stdClass;
|
||||
|
||||
$data->postsCount = DB::table('posts')
|
||||
->count();
|
||||
|
||||
$data->topicsCount = DB::table('topics')
|
||||
->count();
|
||||
|
||||
$data->usersCount = DB::table('users')
|
||||
->count();
|
||||
|
||||
$data->commentsCount = DB::table('comments')
|
||||
->count();
|
||||
|
||||
$data->uploadsCount = DB::table('uploads')
|
||||
->count();
|
||||
|
||||
return $this->json($data);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ namespace Sakura;
|
|||
use Twig_Environment;
|
||||
use Twig_Extension_StringLoader;
|
||||
use Twig_Loader_Filesystem;
|
||||
use Twig_SimpleFilter;
|
||||
use Twig_SimpleFunction;
|
||||
|
||||
/**
|
||||
|
@ -51,10 +52,18 @@ class Template
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $utility = [
|
||||
protected static $utilityFunctions = [
|
||||
'route',
|
||||
'config',
|
||||
'session_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of utility filters to add to templating
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $utilityFilters = [
|
||||
'json_decode',
|
||||
'byte_symbol',
|
||||
];
|
||||
|
@ -99,8 +108,13 @@ class Template
|
|||
self::$engine->addExtension(new Twig_Extension_StringLoader());
|
||||
|
||||
// Add utility functions
|
||||
foreach (self::$utility as $utility) {
|
||||
self::$engine->addFunction(new Twig_SimpleFunction($utility, $utility));
|
||||
foreach (self::$utilityFunctions as $function) {
|
||||
self::$engine->addFunction(new Twig_SimpleFunction($function, $function));
|
||||
}
|
||||
|
||||
// Add utility filters
|
||||
foreach (self::$utilityFilters as $filter) {
|
||||
self::$engine->addFilter(new Twig_SimpleFilter($filter, $filter));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ elixir(function(mix) {
|
|||
mix
|
||||
.less('aitemu/master.less', 'public/css/aitemu.css')
|
||||
.less('yuuno/master.less', 'public/css/yuuno.css')
|
||||
.typescript('master/**/*.ts', 'public/js/master.js')
|
||||
.typescript('app/**/*.ts', 'public/js/app.js')
|
||||
.typescript('aitemu/**/*.ts', 'public/js/aitemu.js')
|
||||
.typescript('yuuno/**/*.ts', 'public/js/yuuno.js')
|
||||
.scripts([
|
||||
|
|
147
resources/assets/typescript/app/AJAX.ts
Normal file
147
resources/assets/typescript/app/AJAX.ts
Normal file
|
@ -0,0 +1,147 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class AJAX
|
||||
{
|
||||
// XMLHTTPRequest container
|
||||
private Request: XMLHttpRequest;
|
||||
private Callbacks: any;
|
||||
private Headers: any;
|
||||
private URL: string;
|
||||
private Send: string = null;
|
||||
private Asynchronous: boolean = true;
|
||||
|
||||
// Prepares the XMLHttpRequest and stuff
|
||||
constructor(async: boolean = true) {
|
||||
this.Request = new XMLHttpRequest();
|
||||
this.Callbacks = new Object();
|
||||
this.Headers = new Object();
|
||||
this.Asynchronous = async;
|
||||
}
|
||||
|
||||
// Start
|
||||
public Start(method: HTTPMethod, avoidCache: boolean = false): void {
|
||||
// Open the connection
|
||||
this.Request.open(HTTPMethod[method], this.URL + (avoidCache ? (this.URL.indexOf("?") < 0 ? '?' : '&') + "no-cache=" + Date.now() : ""), this.Asynchronous);
|
||||
|
||||
// Set headers
|
||||
this.PrepareHeaders();
|
||||
|
||||
// Watch the ready state
|
||||
this.Request.onreadystatechange = () => {
|
||||
// Only invoke when complete
|
||||
if (this.Request.readyState === 4) {
|
||||
// Check if a callback if present
|
||||
if ((typeof this.Callbacks[this.Request.status]).toLowerCase() === 'function') {
|
||||
this.Callbacks[this.Request.status](this);
|
||||
} else { // Else check if there's a generic fallback present
|
||||
if ((typeof this.Callbacks['0']).toLowerCase() === 'function') {
|
||||
// Call that
|
||||
this.Callbacks['0'](this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Request.send(this.Send);
|
||||
}
|
||||
|
||||
// Stop
|
||||
public Stop(): void {
|
||||
this.Request = null;
|
||||
}
|
||||
|
||||
// Set content type required for forms
|
||||
public Form(): void {
|
||||
this.ContentType("application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
// Add post data
|
||||
public SetSend(data: any): void {
|
||||
// Storage array
|
||||
var store: Array<string> = new Array<string>();
|
||||
|
||||
// Iterate over the object and them in the array with an equals sign inbetween
|
||||
for (var item in data) {
|
||||
store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item]));
|
||||
}
|
||||
|
||||
// Assign to send
|
||||
this.Send = store.join('&');
|
||||
}
|
||||
|
||||
// Set raw post
|
||||
public SetRawSend(data: string): void {
|
||||
this.Send = data;
|
||||
}
|
||||
|
||||
// Get response
|
||||
public Response(): string {
|
||||
return this.Request.responseText;
|
||||
}
|
||||
|
||||
// Automatically JSON parse the response
|
||||
public JSON(): Object {
|
||||
return JSON.parse(this.Response());
|
||||
}
|
||||
|
||||
// Get all headers
|
||||
public ResponseHeaders(): string {
|
||||
return this.Request.getAllResponseHeaders();
|
||||
}
|
||||
|
||||
// Get a header
|
||||
public ResponseHeader(name: string): string {
|
||||
return this.Request.getResponseHeader(name);
|
||||
}
|
||||
|
||||
// Set charset
|
||||
public ContentType(type: string, charset: string = null): void {
|
||||
this.AddHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8'));
|
||||
}
|
||||
|
||||
// Add a header
|
||||
public AddHeader(name: string, value: string): void {
|
||||
// Attempt to remove a previous instance
|
||||
this.RemoveHeader(name);
|
||||
|
||||
// Add the new header
|
||||
this.Headers[name] = value;
|
||||
}
|
||||
|
||||
// Remove a header
|
||||
public RemoveHeader(name: string): void {
|
||||
if ((typeof this.Headers[name]).toLowerCase() !== 'undefined') {
|
||||
delete this.Headers[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare Request headers
|
||||
public PrepareHeaders(): void {
|
||||
for (var header in this.Headers) {
|
||||
this.Request.setRequestHeader(header, this.Headers[header]);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a callback
|
||||
public AddCallback(status: number, callback: Function): void {
|
||||
// Attempt to remove previous instances
|
||||
this.RemoveCallback(status);
|
||||
|
||||
// Add the new callback
|
||||
this.Callbacks[status] = callback;
|
||||
}
|
||||
|
||||
// Delete a callback
|
||||
public RemoveCallback(status: number): void {
|
||||
// Delete the callback if present
|
||||
if ((typeof this.Callbacks[status]).toLowerCase() === 'function') {
|
||||
delete this.Callbacks[status];
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the URL
|
||||
public SetUrl(url: string): void {
|
||||
this.URL = url;
|
||||
}
|
||||
}
|
||||
}
|
87
resources/assets/typescript/app/Changelog.ts
Normal file
87
resources/assets/typescript/app/Changelog.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class Changelog
|
||||
{
|
||||
private static Client: AJAX;
|
||||
private static Element: DOM;
|
||||
private static Fetch: number = 0;
|
||||
private static Colours: string[] = [
|
||||
'inherit', // Unknown
|
||||
'#2A2', // Add
|
||||
'#2AA', // Update
|
||||
'#2AA', // Fix
|
||||
'#A22', // Remove
|
||||
'#62C', // Export
|
||||
'#C44', // Revert
|
||||
];
|
||||
|
||||
public static Build(target: DOM)
|
||||
{
|
||||
this.Client = new AJAX;
|
||||
this.Element = DOM.Create('table', 'changelog panelTable');
|
||||
|
||||
this.Element.Element.style.borderSpacing = '0 1px';
|
||||
|
||||
var title: DOM = DOM.Create('div', 'head'),
|
||||
link: DOM = DOM.Create('a', 'underline');
|
||||
|
||||
title.Element.style.marginBottom = '1px';
|
||||
|
||||
link.Text('Changelog');
|
||||
(<HTMLLinkElement>link.Element).href = Config.ChangelogUrl + '#r' + Config.Revision;
|
||||
(<HTMLLinkElement>link.Element).target = '_blank';
|
||||
|
||||
title.Append(link);
|
||||
target.Append(title);
|
||||
target.Append(this.Element);
|
||||
|
||||
this.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi);
|
||||
|
||||
this.Client.AddCallback(200, (client: AJAX) => {
|
||||
Changelog.Add(<IChangelogDate>client.JSON());
|
||||
|
||||
if (Changelog.Fetch < 2) {
|
||||
Changelog.Fetch++;
|
||||
Changelog.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi + Changelog.Fetch);
|
||||
Changelog.Client.Start(HTTPMethod.GET);
|
||||
}
|
||||
});
|
||||
|
||||
this.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi + Changelog.Fetch);
|
||||
this.Client.Start(HTTPMethod.GET);
|
||||
}
|
||||
|
||||
private static Add(changelog: IChangelogDate)
|
||||
{
|
||||
var header: DOM = DOM.Create('tr', 'changelog__row changelog__row--header'),
|
||||
headerInner: DOM = DOM.Create('th', 'changelog__header');
|
||||
|
||||
headerInner.Text(changelog.date);
|
||||
headerInner.Element.style.fontSize = '1.2em';
|
||||
(<HTMLTableHeaderCellElement>headerInner.Element).colSpan = 2;
|
||||
|
||||
header.Append(headerInner);
|
||||
this.Element.Append(header);
|
||||
|
||||
for (var _i in changelog.changes)
|
||||
{
|
||||
var change: IChangelogChange = changelog.changes[_i],
|
||||
row: DOM = DOM.Create('tr', 'changelog__row'),
|
||||
action: DOM = DOM.Create('td', 'changelog__column'),
|
||||
message: DOM = DOM.Create('td', 'changelog__column');
|
||||
|
||||
action.Text(change.action.name);
|
||||
action.Element.style.backgroundColor = this.Colours[change.action.id];
|
||||
action.Element.style.borderBottom = '1px solid ' + this.Colours[change.action.id];
|
||||
|
||||
message.Text(change.message);
|
||||
message.Element.style.borderBottom = '1px solid ' + this.Colours[change.action.id];
|
||||
|
||||
row.Append(action);
|
||||
row.Append(message);
|
||||
|
||||
this.Element.Append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
resources/assets/typescript/app/Comments.ts
Normal file
6
resources/assets/typescript/app/Comments.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class Comments
|
||||
{
|
||||
}
|
||||
}
|
21
resources/assets/typescript/app/Config.ts
Normal file
21
resources/assets/typescript/app/Config.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class Config
|
||||
{
|
||||
public static Revision: number = 0;
|
||||
public static SessionId: string = "";
|
||||
public static UserNameMinLength: number = 3;
|
||||
public static UserNameMaxLength: number = 16;
|
||||
public static PasswordMinEntropy: number = 48;
|
||||
public static LoggedIn: boolean = false;
|
||||
public static ChangelogUrl: string = "https://sakura.flash.moe/";
|
||||
public static ChangelogApi: string = "api.php/";
|
||||
|
||||
public static Set(config: Object): void
|
||||
{
|
||||
for (var key in config) {
|
||||
this[key] = config[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
resources/assets/typescript/app/Cookies.ts
Normal file
33
resources/assets/typescript/app/Cookies.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class Cookies
|
||||
{
|
||||
public static Get(name: string): string {
|
||||
// Do a regex on document.cookie
|
||||
var get: RegExpExecArray = new RegExp('(^|; )' + encodeURIComponent(name) + '=([^;]*)').exec(document.cookie);
|
||||
|
||||
// Return the value
|
||||
return get ? get[2] : '';
|
||||
}
|
||||
|
||||
public static Set(name: string, value: string): void {
|
||||
// Check if the cookie already exists
|
||||
if (this.Get(name).length > 1) {
|
||||
this.Delete(name);
|
||||
}
|
||||
|
||||
// Lifetime
|
||||
var life: Date = new Date();
|
||||
|
||||
// Cookies live for a year
|
||||
life.setFullYear(life.getFullYear() + 1);
|
||||
|
||||
// Assign the cookie
|
||||
document.cookie = name + '=' + value + ';expires=' + life.toUTCString();
|
||||
}
|
||||
|
||||
public static Delete(name: string): void {
|
||||
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
}
|
||||
}
|
||||
}
|
74
resources/assets/typescript/app/DOM.ts
Normal file
74
resources/assets/typescript/app/DOM.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class DOM
|
||||
{
|
||||
public Element: HTMLElement;
|
||||
|
||||
constructor(object: any, mode: DOMSelector) {
|
||||
switch (mode) {
|
||||
case DOMSelector.ID:
|
||||
this.Element = document.getElementById(object);
|
||||
break;
|
||||
|
||||
case DOMSelector.CLASS:
|
||||
this.Element = <HTMLElement>document.getElementsByClassName(object)[0];
|
||||
break;
|
||||
|
||||
case DOMSelector.ELEMENT:
|
||||
this.Element = object;
|
||||
break;
|
||||
|
||||
case DOMSelector.QUERY:
|
||||
this.Element = <HTMLElement>document.querySelector(object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static Create(element: string, className: string = null, id: string = null): DOM {
|
||||
var elem: HTMLElement = document.createElement(element),
|
||||
cont: DOM = new DOM(elem, DOMSelector.ELEMENT);
|
||||
|
||||
if (className !== null) {
|
||||
cont.SetClass(className);
|
||||
}
|
||||
|
||||
if (id !== null) {
|
||||
cont.SetId(id);
|
||||
}
|
||||
|
||||
return cont;
|
||||
}
|
||||
|
||||
public Text(text: string): void {
|
||||
this.Element.appendChild(document.createTextNode(text));
|
||||
}
|
||||
|
||||
public Append(element: DOM): void {
|
||||
this.Element.appendChild(element.Element);
|
||||
}
|
||||
|
||||
public Prepend(element: DOM, before: HTMLElement | Node = null): void {
|
||||
if (before === null) {
|
||||
before = this.Element.firstChild;
|
||||
}
|
||||
|
||||
if (this.Element.children.length) {
|
||||
this.Element.insertBefore(element.Element, before);
|
||||
} else {
|
||||
this.Append(element);
|
||||
}
|
||||
}
|
||||
|
||||
public SetId(id: string): void {
|
||||
this.Element.id = id;
|
||||
}
|
||||
|
||||
public SetClass(name: string): void {
|
||||
this.Element.className = name;
|
||||
}
|
||||
|
||||
public Remove(): void {
|
||||
this.Element.parentNode.removeChild(this.Element);
|
||||
}
|
||||
}
|
||||
}
|
10
resources/assets/typescript/app/DOMSelector.ts
Normal file
10
resources/assets/typescript/app/DOMSelector.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export enum DOMSelector
|
||||
{
|
||||
ID,
|
||||
CLASS,
|
||||
ELEMENT,
|
||||
QUERY
|
||||
}
|
||||
}
|
47
resources/assets/typescript/app/Friend.ts
Normal file
47
resources/assets/typescript/app/Friend.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
declare var ajaxBusyView: any;
|
||||
|
||||
namespace Sakura
|
||||
{
|
||||
export class Friend
|
||||
{
|
||||
private static Client: AJAX;
|
||||
|
||||
public static Init(): void
|
||||
{
|
||||
this.Client = new AJAX;
|
||||
this.Client.Form();
|
||||
}
|
||||
|
||||
public static Add(id: number): void
|
||||
{
|
||||
this.Mode("/friends/1/add".replace('1', id.toString()));
|
||||
}
|
||||
|
||||
public static Remove(id: number): void
|
||||
{
|
||||
this.Mode("/friends/1/remove".replace('1', id.toString()));
|
||||
}
|
||||
|
||||
private static Mode(url: string): void
|
||||
{
|
||||
this.Client.SetUrl(url);
|
||||
this.Client.SetSend({ "session": Config.SessionId });
|
||||
this.Client.AddCallback(200, (client: AJAX) => {
|
||||
var response: IFriendResponse = <IFriendResponse>client.JSON(),
|
||||
error: string = response.error || null;
|
||||
|
||||
if (error !== null) {
|
||||
ajaxBusyView(true, error, 'fail');
|
||||
|
||||
setTimeout(() => {
|
||||
ajaxBusyView(false);
|
||||
}, 1500);
|
||||
} else {
|
||||
ajaxBusyView(true, response.message, 'ok');
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
this.Client.Start(HTTPMethod.POST);
|
||||
}
|
||||
}
|
||||
}
|
11
resources/assets/typescript/app/HTTPMethod.ts
Normal file
11
resources/assets/typescript/app/HTTPMethod.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export enum HTTPMethod
|
||||
{
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE
|
||||
}
|
||||
}
|
8
resources/assets/typescript/app/IChangelogAction.ts
Normal file
8
resources/assets/typescript/app/IChangelogAction.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export interface IChangelogAction
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
}
|
12
resources/assets/typescript/app/IChangelogChange.ts
Normal file
12
resources/assets/typescript/app/IChangelogChange.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export interface IChangelogChange
|
||||
{
|
||||
id: number;
|
||||
action?: IChangelogAction;
|
||||
contributor?: IChangelogContributor;
|
||||
major: boolean;
|
||||
url: string;
|
||||
message: string;
|
||||
}
|
||||
}
|
9
resources/assets/typescript/app/IChangelogContributor.ts
Normal file
9
resources/assets/typescript/app/IChangelogContributor.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export interface IChangelogContributor
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
}
|
9
resources/assets/typescript/app/IChangelogDate.ts
Normal file
9
resources/assets/typescript/app/IChangelogDate.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export interface IChangelogDate
|
||||
{
|
||||
date: string;
|
||||
release?: IChangelogRelease;
|
||||
changes: IChangelogChange[];
|
||||
}
|
||||
}
|
9
resources/assets/typescript/app/IChangelogRelease.ts
Normal file
9
resources/assets/typescript/app/IChangelogRelease.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export interface IChangelogRelease
|
||||
{
|
||||
id: number;
|
||||
colour: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
9
resources/assets/typescript/app/IFriendResponse.ts
Normal file
9
resources/assets/typescript/app/IFriendResponse.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export interface IFriendResponse
|
||||
{
|
||||
message: string;
|
||||
level: number;
|
||||
error: string;
|
||||
}
|
||||
}
|
76
resources/assets/typescript/app/Legacy.ts
Normal file
76
resources/assets/typescript/app/Legacy.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
declare function escape(a);
|
||||
|
||||
namespace Sakura
|
||||
{
|
||||
export class Legacy
|
||||
{
|
||||
// Alternative for Math.log2() since it's still experimental
|
||||
public static log2(num: number): number {
|
||||
return Math.log(num) / Math.log(2);
|
||||
}
|
||||
|
||||
// Get the number of unique characters in a string
|
||||
public static unique(string: string): number {
|
||||
// Store the already found character
|
||||
var used: string[] = [];
|
||||
|
||||
// The amount of characters we've already found
|
||||
var count: number = 0;
|
||||
|
||||
// Count the amount of unique characters
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
// Check if we already counted this character
|
||||
if (used.indexOf(string[i]) == -1) {
|
||||
// Push the character into the used array
|
||||
used.push(string[i]);
|
||||
|
||||
// Up the count
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the count
|
||||
return count;
|
||||
}
|
||||
|
||||
// Calculate password entropy
|
||||
public static entropy(string: string): number {
|
||||
// Decode utf-8 encoded characters
|
||||
string = this.utf8_decode(string);
|
||||
|
||||
// Count the unique characters in the string
|
||||
var unique: number = this.unique(string);
|
||||
|
||||
// Do the entropy calculation
|
||||
return unique * this.log2(256);
|
||||
}
|
||||
|
||||
// Validate string lengths
|
||||
public static stringLength(string: string, minimum: number, maximum: number): boolean {
|
||||
// Get length of string
|
||||
var length = string.length;
|
||||
|
||||
// Check if it meets the minimum/maximum
|
||||
if (length < minimum || length > maximum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it passes both return true
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate email address formats
|
||||
public static validateEmail(email: string): boolean {
|
||||
// RFC compliant e-mail address regex
|
||||
var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/;
|
||||
|
||||
// Test it on the email var which'll return a boolean
|
||||
return re.test(email);
|
||||
}
|
||||
|
||||
// Decode a utf-8 string
|
||||
public static utf8_decode(string): string {
|
||||
return decodeURIComponent(escape(string));
|
||||
}
|
||||
}
|
||||
}
|
15
resources/assets/typescript/app/Main.ts
Normal file
15
resources/assets/typescript/app/Main.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class Main
|
||||
{
|
||||
public static Startup(): void {
|
||||
console.log(this.Supported());
|
||||
TimeAgo.Init();
|
||||
Friend.Init();
|
||||
}
|
||||
|
||||
public static Supported(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
56
resources/assets/typescript/app/TimeAgo.ts
Normal file
56
resources/assets/typescript/app/TimeAgo.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
namespace Sakura
|
||||
{
|
||||
export class TimeAgo
|
||||
{
|
||||
private static WatchClass: string = "time-ago";
|
||||
|
||||
public static Init(): void
|
||||
{
|
||||
var watchElements: NodeListOf<Element> = document.getElementsByClassName(this.WatchClass);
|
||||
|
||||
for (var _i in watchElements) {
|
||||
if ((typeof watchElements[_i]).toLowerCase() !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var elem: HTMLElement = <HTMLElement>watchElements[_i],
|
||||
date: Date = new Date(elem.getAttribute('dateTime') || elem.innerText);
|
||||
|
||||
elem.title = elem.innerText;
|
||||
elem.innerText = this.Parse(date);
|
||||
}
|
||||
}
|
||||
|
||||
public static Parse(date: Date, append: string = ' ago', none: string = 'Just now'): string {
|
||||
var time: number = (Date.now() - date.getTime()) / 1000;
|
||||
|
||||
if (time < 1) {
|
||||
return none;
|
||||
}
|
||||
|
||||
var times: Object = {
|
||||
31536000: ['year', 'a'],
|
||||
2592000: ['month', 'a'],
|
||||
604800: ['week', 'a'],
|
||||
86400: ['day', 'a'],
|
||||
3600: ['hour', 'an'],
|
||||
60: ['minute', 'a'],
|
||||
1: ['second', 'a']
|
||||
};
|
||||
|
||||
var timeKeys: string[] = Object.keys(times).reverse();
|
||||
|
||||
for (var i in timeKeys) {
|
||||
var calc: number = time / parseInt(timeKeys[i]);
|
||||
|
||||
if (calc >= 1) {
|
||||
var display: number = Math.floor(calc);
|
||||
|
||||
return (display === 1 ? times[timeKeys[i]][1] : display) + " " + times[timeKeys[i]][0] + (display === 1 ? '' : 's') + append;
|
||||
}
|
||||
}
|
||||
|
||||
return none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,322 +0,0 @@
|
|||
/*
|
||||
* Shared client side code
|
||||
*/
|
||||
|
||||
// Meta functions
|
||||
class Sakura {
|
||||
public static cookiePrefix: string = ""; // Cookie prefix, gets prepended to cookie names
|
||||
public static cookiePath: string = "/"; // Cookie path, can in most cases be left untouched
|
||||
|
||||
// Get or set a cookie value
|
||||
public static cookie(name: string, value: string = null): string {
|
||||
// If value is null only get the cookie's value
|
||||
if (value) {
|
||||
// Delete the old instance
|
||||
document.cookie = this.cookiePrefix + name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT; path=' + this.cookiePath;
|
||||
|
||||
// Assign the cookie
|
||||
document.cookie = this.cookiePrefix + name + '=' + value + '; path=' + this.cookiePath;
|
||||
|
||||
// Pass the value through
|
||||
return value;
|
||||
} else {
|
||||
// Perform a regex on document.cookie
|
||||
var get = new RegExp('(^|; )' + encodeURIComponent(this.cookiePrefix + name) + '=([^;]*)').exec(document.cookie);
|
||||
|
||||
// If anything was returned return it (professional phrasing)
|
||||
return get ? get[2] : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Unix timestamp
|
||||
public static epoch(): number {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
}
|
||||
|
||||
// Toggle a class
|
||||
public static toggleClass(element: HTMLElement, name: string): void {
|
||||
// Check if the class already exists and if not add it
|
||||
if (element.className.indexOf(name) < 0) {
|
||||
element.className += ' ' + name;
|
||||
} else { // If so remove it and kill additional spaces
|
||||
element.className = element.className.replace(name, '').trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove every element with a specific class name
|
||||
public static removeByClass(name: string): void {
|
||||
// Get the elements
|
||||
var objs = document.getElementsByClassName(name);
|
||||
|
||||
// Use a while loop to remove each element
|
||||
while (objs.length > 0) {
|
||||
objs[0].parentNode.removeChild(objs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a single element with a specific id
|
||||
public static removeById(id: string): void {
|
||||
// Get the element
|
||||
var obj = document.getElementById(id);
|
||||
|
||||
// If the element exists use the parent node to remove it
|
||||
if (typeof (obj) != "undefined" && obj !== null) {
|
||||
obj.parentNode.removeChild(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative for Math.log2() since it's still experimental
|
||||
public static log2(num: number): number {
|
||||
return Math.log(num) / Math.log(2);
|
||||
}
|
||||
|
||||
// Get the number of unique characters in a string
|
||||
public static unique(string: string): number {
|
||||
// Store the already found character
|
||||
var used: string[] = [];
|
||||
|
||||
// The amount of characters we've already found
|
||||
var count: number = 0;
|
||||
|
||||
// Count the amount of unique characters
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
// Check if we already counted this character
|
||||
if (used.indexOf(string[i]) == -1) {
|
||||
// Push the character into the used array
|
||||
used.push(string[i]);
|
||||
|
||||
// Up the count
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the count
|
||||
return count;
|
||||
}
|
||||
|
||||
// Calculate password entropy
|
||||
public static entropy(string: string): number {
|
||||
// Decode utf-8 encoded characters
|
||||
string = utf8.decode(string);
|
||||
|
||||
// Count the unique characters in the string
|
||||
var unique: number = this.unique(string);
|
||||
|
||||
// Do the entropy calculation
|
||||
return unique * this.log2(256);
|
||||
}
|
||||
|
||||
// Validate string lengths
|
||||
public static stringLength(string: string, minimum: number, maximum: number): boolean {
|
||||
// Get length of string
|
||||
var length = string.length;
|
||||
|
||||
// Check if it meets the minimum/maximum
|
||||
if (length < minimum || length > maximum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it passes both return true
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate email address formats
|
||||
public static validateEmail(email: string): boolean {
|
||||
// RFC compliant e-mail address regex
|
||||
var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/;
|
||||
|
||||
// Test it on the email var which'll return a boolean
|
||||
return re.test(email);
|
||||
}
|
||||
|
||||
// Calculate the time that has elapsed since a certain data (doesn't take leap years in account).
|
||||
public static timeElapsed(timestamp: number, append: string = ' ago', none: string = 'Just now'): string {
|
||||
// Subtract the entered timestamp from the current timestamp
|
||||
var time: number = this.epoch() - timestamp;
|
||||
|
||||
// If the new timestamp is below 1 return a standard string
|
||||
if (time < 1) {
|
||||
return none;
|
||||
}
|
||||
|
||||
// Times array
|
||||
var times: Object = {
|
||||
31536000: ['year', 'a'],
|
||||
2592000: ['month', 'a'],
|
||||
604800: ['week', 'a'],
|
||||
86400: ['day', 'a'],
|
||||
3600: ['hour', 'an'],
|
||||
60: ['minute', 'a'],
|
||||
1: ['second', 'a']
|
||||
};
|
||||
|
||||
//
|
||||
var timeKeys: string[] = Object.keys(times).reverse();
|
||||
|
||||
// Iterate over the times
|
||||
for (var i in timeKeys) {
|
||||
// Do a devision to check if the given timestamp fits in the current "type"
|
||||
var calc: number = time / parseInt(timeKeys[i]);
|
||||
|
||||
// Check if we have a match
|
||||
if (calc >= 1) {
|
||||
// Round the number
|
||||
var display: number = Math.floor(calc);
|
||||
|
||||
// Return the formatted string
|
||||
return (display === 1 ? times[timeKeys[i]][1] : display) + " " + times[timeKeys[i]][0] + (display === 1 ? '' : 's') + append;
|
||||
}
|
||||
}
|
||||
|
||||
// If everything fails just return none
|
||||
return none;
|
||||
}
|
||||
}
|
||||
|
||||
declare function unescape(a);
|
||||
declare function escape(a);
|
||||
|
||||
// UTF-8 functions
|
||||
class utf8 {
|
||||
// Encode a utf-8 string
|
||||
public static encode(string): string {
|
||||
return unescape(encodeURIComponent(string));
|
||||
}
|
||||
|
||||
// Decode a utf-8 string
|
||||
public static decode(string): string {
|
||||
return decodeURIComponent(escape(string));
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP methods
|
||||
enum HTTPMethods {
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE
|
||||
}
|
||||
|
||||
// AJAX functions
|
||||
class AJAX {
|
||||
// XMLHTTPRequest container
|
||||
private request: XMLHttpRequest;
|
||||
private callbacks: Object;
|
||||
private headers: Object;
|
||||
private url: string;
|
||||
private send: string = null;
|
||||
|
||||
// Prepares the XMLHttpRequest and stuff
|
||||
constructor() {
|
||||
this.request = new XMLHttpRequest();
|
||||
this.callbacks = new Object();
|
||||
this.headers = new Object();
|
||||
}
|
||||
|
||||
// Start
|
||||
public start(method: HTTPMethods): void {
|
||||
// Open the connection
|
||||
this.request.open(HTTPMethods[method], this.url, true);
|
||||
|
||||
// Set headers
|
||||
this.prepareHeaders();
|
||||
|
||||
// Watch the ready state
|
||||
this.request.onreadystatechange = () => {
|
||||
// Only invoke when complete
|
||||
if (this.request.readyState === 4) {
|
||||
// Check if a callback if present
|
||||
if ((typeof this.callbacks[this.request.status]).toLowerCase() === 'function') {
|
||||
this.callbacks[this.request.status]();
|
||||
} else { // Else check if there's a generic fallback present
|
||||
if ((typeof this.callbacks['0']).toLowerCase() === 'function') {
|
||||
// Call that
|
||||
this.callbacks['0']();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.request.send(this.send);
|
||||
}
|
||||
|
||||
// Stop
|
||||
public stop(): void {
|
||||
this.request = null;
|
||||
}
|
||||
|
||||
// Add post data
|
||||
public setSend(data: Object): void {
|
||||
// Storage array
|
||||
var store: Array<string> = new Array<string>();
|
||||
|
||||
// Iterate over the object and them in the array with an equals sign inbetween
|
||||
for (var item in data) {
|
||||
store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item]));
|
||||
}
|
||||
|
||||
// Assign to send
|
||||
this.send = store.join('&');
|
||||
}
|
||||
|
||||
// Set raw post
|
||||
public setRawSend(data: string) {
|
||||
this.send = data;
|
||||
}
|
||||
|
||||
// Get response
|
||||
public response(): string {
|
||||
return this.request.responseText;
|
||||
}
|
||||
|
||||
// Set charset
|
||||
public contentType(type: string, charset: string = null): void {
|
||||
this.addHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8'));
|
||||
}
|
||||
|
||||
// Add a header
|
||||
public addHeader(name: string, value: string): void {
|
||||
// Attempt to remove a previous instance
|
||||
this.removeHeader(name);
|
||||
|
||||
// Add the new header
|
||||
this.headers[name] = value;
|
||||
}
|
||||
|
||||
// Remove a header
|
||||
public removeHeader(name: string): void {
|
||||
if ((typeof this.headers[name]).toLowerCase() !== 'undefined') {
|
||||
delete this.headers[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare request headers
|
||||
public prepareHeaders(): void {
|
||||
for (var header in this.headers) {
|
||||
this.request.setRequestHeader(header, this.headers[header]);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a callback
|
||||
public addCallback(status: number, callback: Function): void {
|
||||
// Attempt to remove previous instances
|
||||
this.removeCallback(status);
|
||||
|
||||
// Add the new callback
|
||||
this.callbacks[status] = callback;
|
||||
}
|
||||
|
||||
// Delete a callback
|
||||
public removeCallback(status: number): void {
|
||||
// Delete the callback if present
|
||||
if ((typeof this.callbacks[status]).toLowerCase() === 'function') {
|
||||
delete this.callbacks[status];
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the URL
|
||||
public setUrl(url: string): void {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
10
resources/assets/typescript/yuuno/Main.ts
Normal file
10
resources/assets/typescript/yuuno/Main.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Yuuno
|
||||
{
|
||||
export class Main
|
||||
{
|
||||
public static Startup()
|
||||
{
|
||||
Sakura.Main.Startup();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -101,7 +101,7 @@ function notifyClose(id: string): void {
|
|||
|
||||
// Remove after 410 ms
|
||||
setTimeout(() => {
|
||||
Sakura.removeById(id);
|
||||
(new Sakura.DOM(id, Sakura.DOMSelector.ID)).Remove();
|
||||
}, 410);
|
||||
}
|
||||
|
||||
|
@ -122,13 +122,13 @@ function notifyRequest(session: string): void {
|
|||
}
|
||||
|
||||
// Create AJAX object
|
||||
var get: any = new AJAX();
|
||||
get.setUrl('/notifications');
|
||||
var get = new Sakura.AJAX();
|
||||
get.SetUrl('/notifications');
|
||||
|
||||
// Add callbacks
|
||||
get.addCallback(200, () => {
|
||||
get.AddCallback(200, () => {
|
||||
// Assign the parsed JSON
|
||||
var data: Notification = JSON.parse(get.response());
|
||||
var data: Notification = JSON.parse(get.Response());
|
||||
|
||||
// Check if nothing went wrong
|
||||
if ((typeof data).toLowerCase() === 'undefined') {
|
||||
|
@ -142,7 +142,7 @@ function notifyRequest(session: string): void {
|
|||
}
|
||||
});
|
||||
|
||||
get.start(HTTPMethods.GET);
|
||||
get.Start(Sakura.HTTPMethod.GET);
|
||||
}
|
||||
|
||||
// Show the full page busy window
|
||||
|
@ -216,7 +216,7 @@ function ajaxBusyView(show: boolean, message: string = null, type: string = null
|
|||
if (parseInt(cont.style.opacity) > 0) {
|
||||
cont.style.opacity = (parseInt(cont.style.opacity) - 0.1).toString();
|
||||
} else {
|
||||
Sakura.removeById('ajaxBusy');
|
||||
(new Sakura.DOM('ajaxBusy', Sakura.DOMSelector.ID)).Remove();
|
||||
clearInterval(out);
|
||||
}
|
||||
}, 10);
|
||||
|
@ -227,29 +227,29 @@ function ajaxBusyView(show: boolean, message: string = null, type: string = null
|
|||
// Making a post request using AJAX
|
||||
function ajaxPost(url: string, data: Object, callback: Function) {
|
||||
// Create AJAX
|
||||
var request: any = new AJAX();
|
||||
var request = new Sakura.AJAX;
|
||||
|
||||
// Set url
|
||||
request.setUrl(url);
|
||||
request.SetUrl(url);
|
||||
|
||||
// Add callbacks
|
||||
request.addCallback(200, function() {
|
||||
callback.call(request.response())
|
||||
request.AddCallback(200, function() {
|
||||
callback.call(request.Response())
|
||||
});
|
||||
request.addCallback(0, function() {
|
||||
request.AddCallback(0, function() {
|
||||
ajaxBusyView(false);
|
||||
|
||||
throw "POST Request failed";
|
||||
});
|
||||
|
||||
// Add header
|
||||
request.addHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
request.AddHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
|
||||
// Set the post data
|
||||
request.setSend(data);
|
||||
request.SetSend(data);
|
||||
|
||||
// Make the request
|
||||
request.start(HTTPMethods.POST);
|
||||
request.Start(Sakura.HTTPMethod.POST);
|
||||
|
||||
// Return the AJAX object
|
||||
return request;
|
||||
|
@ -399,7 +399,7 @@ function submitPostHandler(data: string, busyView: boolean, resetCaptcha: boolea
|
|||
|
||||
// Check if a password is within the minimum entropy value
|
||||
function checkPwdEntropy(pwd: string): boolean {
|
||||
return (Sakura.entropy(pwd) >= sakuraVars.minPwdEntropy);
|
||||
return (Sakura.Legacy.entropy(pwd) >= sakuraVars.minPwdEntropy);
|
||||
}
|
||||
|
||||
// Check registration variables
|
||||
|
@ -420,12 +420,12 @@ function registerVarCheck(id: string, mode: string, option: any = null): void {
|
|||
break;
|
||||
|
||||
case 'email':
|
||||
check = Sakura.validateEmail(input.value);
|
||||
check = Sakura.Legacy.validateEmail(input.value);
|
||||
break;
|
||||
|
||||
case 'username':
|
||||
default:
|
||||
check = Sakura.stringLength(input.value, sakuraVars.minUserLen, sakuraVars.maxUserLen);
|
||||
check = Sakura.Legacy.stringLength(input.value, sakuraVars.minUserLen, sakuraVars.maxUserLen);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Login' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Reactivate account' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Register' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Reset Password' %}
|
||||
|
||||
|
|
|
@ -26,22 +26,22 @@
|
|||
|
||||
{% block js %}
|
||||
<script type="text/javascript">
|
||||
var commentClient = new AJAX();
|
||||
var commentClient = new Sakura.AJAX();
|
||||
|
||||
commentClient.contentType("application/x-www-form-urlencoded");
|
||||
commentClient.ContentType("application/x-www-form-urlencoded");
|
||||
|
||||
function commentPost(element, url) {
|
||||
var text = element.querySelector('[name="text"]'),
|
||||
session = element.querySelector('[name="session"]');
|
||||
|
||||
commentClient.setSend({"text":text.value,"session":session.value});
|
||||
commentClient.SetSend({"text":text.value,"session":session.value});
|
||||
|
||||
text.value = '';
|
||||
|
||||
commentClient.setUrl(url);
|
||||
commentClient.SetUrl(url);
|
||||
|
||||
commentClient.addCallback(200, function () {
|
||||
var resp = JSON.parse(commentClient.response());
|
||||
commentClient.AddCallback(200, function () {
|
||||
var resp = JSON.parse(commentClient.Response());
|
||||
|
||||
if (resp.error) {
|
||||
ajaxBusyView(true, resp.error, 'fail');
|
||||
|
@ -53,10 +53,10 @@
|
|||
commentAdd(resp);
|
||||
}
|
||||
|
||||
commentClient.setRawSend("");
|
||||
commentClient.SetRawSend("");
|
||||
});
|
||||
|
||||
commentClient.start(HTTPMethods.POST);
|
||||
commentClient.Start(Sakura.HTTPMethod.POST);
|
||||
}
|
||||
|
||||
function commentAdd(obj) {
|
||||
|
@ -84,7 +84,7 @@
|
|||
|
||||
var controlsInner = document.createElement('ul');
|
||||
|
||||
if (Sakura.cookie('id') == obj.user) {
|
||||
if (Sakura.Cookies.Get(sakuraVars.cookie.prefix + 'id') == obj.user) {
|
||||
var controlsTrashContainer = document.createElement('li');
|
||||
var controlsTrash = document.createElement('a');
|
||||
controlsTrash.href = 'javascript:void(0);';
|
||||
|
@ -197,7 +197,7 @@
|
|||
|
||||
// Remove it if it already exists
|
||||
if (replyBox) {
|
||||
Sakura.removeById('comment-reply-container-' + id);
|
||||
(new Sakura.DOM('comment-reply-container-' + id, Sakura.DOMSelector.ID)).Remove();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -250,10 +250,10 @@
|
|||
function commentDelete(id) {
|
||||
var url = "{{ route('comments.comment.delete', 1) }}".replace(1, id);
|
||||
|
||||
commentClient.setUrl(url);
|
||||
commentClient.SetUrl(url);
|
||||
|
||||
commentClient.addCallback(200, function () {
|
||||
var resp = JSON.parse(commentClient.response());
|
||||
commentClient.AddCallback(200, function () {
|
||||
var resp = JSON.parse(commentClient.Response());
|
||||
|
||||
if (resp.error) {
|
||||
ajaxBusyView(true, resp.error, 'fail');
|
||||
|
@ -262,11 +262,11 @@
|
|||
ajaxBusyView(false);
|
||||
}, 1500);
|
||||
} else {
|
||||
Sakura.removeById("comment-" + id);
|
||||
(new Sakura.DOM("comment-" + id, Sakura.DOMSelector.ID)).Remove();
|
||||
}
|
||||
});
|
||||
|
||||
commentClient.start(HTTPMethods.POST);
|
||||
commentClient.Start(Sakura.HTTPMethod.POST);
|
||||
}
|
||||
|
||||
function commentVote(id, vote) {
|
||||
|
@ -274,12 +274,12 @@
|
|||
upvotes = document.getElementById("comment-" + id + "-likes"),
|
||||
downvotes = document.getElementById("comment-" + id + "-dislikes");
|
||||
|
||||
commentClient.setSend({"vote":vote});
|
||||
commentClient.SetSend({"vote":vote});
|
||||
|
||||
commentClient.setUrl(url);
|
||||
commentClient.SetUrl(url);
|
||||
|
||||
commentClient.addCallback(200, function () {
|
||||
var resp = JSON.parse(commentClient.response());
|
||||
commentClient.AddCallback(200, function () {
|
||||
var resp = JSON.parse(commentClient.Response());
|
||||
|
||||
if (resp.error) {
|
||||
ajaxBusyView(true, resp.error, 'fail');
|
||||
|
@ -292,10 +292,10 @@
|
|||
downvotes.innerText = resp.downvotes;
|
||||
}
|
||||
|
||||
commentClient.setRawSend("");
|
||||
commentClient.SetRawSend("");
|
||||
});
|
||||
|
||||
commentClient.start(HTTPMethods.POST);
|
||||
commentClient.Start(Sakura.HTTPMethod.POST);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
All active users in the past 2 minutes
|
||||
<table class="panelTable">
|
||||
{% for amount,onlineUser in stats.onlineUsers %}
|
||||
<tr><td style="text-align: left;"><a href="{{ route('user.profile', onlineUser.id) }}" style="font-weight: bold; color: {{ onlineUser.colour }};" class="default">{{ onlineUser.username }}</a></td><td style="text-align: right;"><time datetime="{{ onlineUser.lastOnline|date('r') }}">{{ onlineUser.lastOnline|date('D Y-m-d H:i:s T') }}</time></td></tr>
|
||||
<tr><td style="text-align: left;"><a href="{{ route('user.profile', onlineUser.id) }}" style="font-weight: bold; color: {{ onlineUser.colour }};" class="default">{{ onlineUser.username }}</a></td><td style="text-align: right;"><time class="time-ago" datetime="{{ onlineUser.lastOnline|date('r') }}">{{ onlineUser.lastOnline|date('D Y-m-d H:i:s T') }}</time></td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
</div>
|
||||
<div class="clear"></div>
|
||||
<div class="news-post-time">
|
||||
Posted <time datetime="{{ post.time|date('r') }}">{{ post.time|date('D Y-m-d H:i:s T') }}</time>
|
||||
Posted <time class="time-ago" datetime="{{ post.time|date('r') }}">{{ post.time|date('D Y-m-d H:i:s T') }}</time>
|
||||
{% if newsHideCommentCount is not defined %}<a class="default" href="{{ route('news.post', post.id) }}#comments">{{ post.commentCount }} comment{% if post.commentCount != 1 %}s{% endif %}</a>{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div>
|
||||
{% if forum.lastPost.id %}
|
||||
<a href="{{ route('forums.topic', forum.lastPost.topic) }}" class="default">{{ forum.lastPost.subject|slice(0, 30) }}{% if forum.lastPost.subject|length > 30 %}...{% endif %}</a><br>
|
||||
<time datetime="{{ forum.lastPost.time|date('r') }}">{{ forum.lastPost.time|date('D Y-m-d H:i:s T') }}</time> by {% if forum.lastPost.poster.id %}<a href="{{ route('user.profile', forum.lastPost.poster.id) }}" class="default" style="color: {{ forum.lastPost.poster.colour }}; text-shadow: 0 0 5px {% if forumlastPost.poster.colour != 'inherit' %}{{ forum.lastPost.poster.colour }}{% else %}#222{% endif %};">{{ forum.lastPost.poster.username }}</a>{% else %}[deleted user]{% endif %} <a href="{{ route('forums.post', forum.lastPost.id) }}" class="default fa fa-tag"></a>
|
||||
<time class="time-ago" datetime="{{ forum.lastPost.time|date('r') }}">{{ forum.lastPost.time|date('D Y-m-d H:i:s T') }}</time> by {% if forum.lastPost.poster.id %}<a href="{{ route('user.profile', forum.lastPost.poster.id) }}" class="default" style="color: {{ forum.lastPost.poster.colour }}; text-shadow: 0 0 5px {% if forumlastPost.poster.colour != 'inherit' %}{{ forum.lastPost.poster.colour }}{% else %}#222{% endif %};">{{ forum.lastPost.poster.username }}</a>{% else %}[deleted user]{% endif %} <a href="{{ route('forums.post', forum.lastPost.id) }}" class="default fa fa-tag"></a>
|
||||
{% else %}
|
||||
There are no posts in this forum.<br>
|
||||
{% endif %}
|
||||
|
|
|
@ -53,14 +53,14 @@
|
|||
rText = document.getElementById('previewText'),
|
||||
lastParsed = Date.now(),
|
||||
lastKeystroke = Date.now(),
|
||||
parser = new AJAX(),
|
||||
postFetch = new AJAX(),
|
||||
parser = new Sakura.AJAX(),
|
||||
postFetch = new Sakura.AJAX(),
|
||||
parserActive = false,
|
||||
op = {{ topic is defined ? topic.firstPost.id : 0 }},
|
||||
topicName = "{{ topic is defined ? topic.firstPost.subject : '' }}";
|
||||
|
||||
parser.setUrl("{{ route('helper.bbcode.parse') }}");
|
||||
parser.contentType("application/x-www-form-urlencoded");
|
||||
parser.SetUrl("{{ route('helper.bbcode.parse') }}");
|
||||
parser.ContentType("application/x-www-form-urlencoded");
|
||||
|
||||
pText.addEventListener("focus", function () {
|
||||
preview.style.display = null;
|
||||
|
@ -89,10 +89,10 @@
|
|||
} else {
|
||||
parserActive = true;
|
||||
|
||||
parser.setSend({"text":text});
|
||||
parser.SetSend({"text":text});
|
||||
|
||||
parser.addCallback(200, function () {
|
||||
rText.innerHTML = parser.response();
|
||||
parser.AddCallback(200, function () {
|
||||
rText.innerHTML = parser.Response();
|
||||
|
||||
var codeBlocks = rText.querySelectorAll("pre code");
|
||||
|
||||
|
@ -105,7 +105,7 @@
|
|||
parserActive = false;
|
||||
});
|
||||
|
||||
parser.start(HTTPMethods.POST);
|
||||
parser.Start(Sakura.HTTPMethod.POST);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
@ -156,10 +156,10 @@
|
|||
|
||||
url = "{{ route('forums.post.raw', '1') }}".replace('1', id);
|
||||
|
||||
postFetch.setUrl(url);
|
||||
postFetch.SetUrl(url);
|
||||
|
||||
postFetch.addCallback(200, function () {
|
||||
pText.value = postFetch.response();
|
||||
postFetch.AddCallback(200, function () {
|
||||
pText.value = postFetch.Response();
|
||||
pStopEdit.style.display = null;
|
||||
pForm.action = "{{ route('forums.post.edit', '1') }}".replace('1', id);
|
||||
pMode.innerText = 'Editing #' + id;
|
||||
|
@ -172,7 +172,7 @@
|
|||
pText.focus();
|
||||
});
|
||||
|
||||
postFetch.start(HTTPMethods.GET);
|
||||
postFetch.Start(Sakura.HTTPMethod.GET);
|
||||
}
|
||||
|
||||
function stopEdit() {
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
{% else %}
|
||||
[deleted user]
|
||||
{% endif %} <a href="{{ route('forums.post', topic.lastPost.id) }}" class="default fa fa-tag"></a><br>
|
||||
<time datetime="{{ topic.lastPost.time|date('r') }}">{{ topic.lastPost.time|date('D Y-m-d H:i:s T') }}</time>
|
||||
<time class="time-ago" datetime="{{ topic.lastPost.time|date('r') }}">{{ topic.lastPost.time|date('D Y-m-d H:i:s T') }}</time>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title %}Forums / {{ forum.name }}{% endset %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Forums' %}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
|||
<td style="text-align: left; border-bottom: 1px solid #9475b2;">
|
||||
<a href="{{ route('forums.topic', _t.id) }}" class="default">{{ _t.title }}</a>
|
||||
</td>
|
||||
<td class="rightAlign" style="border-bottom: 1px solid #9475b2;"><time datetime="{{ _t.lastPost.time|date('r') }}">{{ _t.lastPost.time|date('D Y-m-d H:i:s T') }}</time></td>
|
||||
<td class="rightAlign" style="border-bottom: 1px solid #9475b2;"><time class="time-ago" datetime="{{ _t.lastPost.time|date('r') }}">{{ _t.lastPost.time|date('D Y-m-d H:i:s T') }}</time></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
@ -36,7 +36,7 @@
|
|||
by
|
||||
<a href="{{ route('user.profile', _p.poster.id) }}" class="default"><span style="color: {{ _p.poster.colour }};">{{ _p.poster.username }}</span></a>
|
||||
</td>
|
||||
<td class="rightAlign" style="border-bottom: 1px solid #9475b2;"><time datetime="{{ _p.time|date('r') }}">{{ _p.time|date('D Y-m-d H:i:s T') }}</time></td>
|
||||
<td class="rightAlign" style="border-bottom: 1px solid #9475b2;"><time class="time-ago" datetime="{{ _p.time|date('r') }}">{{ _p.time|date('D Y-m-d H:i:s T') }}</time></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set forumBackLink %}{{ route('forums.forum', forum.id) }}{% endset %}
|
||||
|
||||
|
@ -97,7 +97,7 @@
|
|||
{% endif %}
|
||||
{% if not (post.poster.permission(constant('Sakura\\Perms\\Site::DEACTIVATED')) or post.poster.permission(constant('Sakura\\Perms\\Site::RESTRICTED')) or user.id == post.poster.id) %}
|
||||
<a class="fa fa-{% if user.isFriends(post.poster.id) == 2 %}heart{% else %}star{% endif %} friend-{{ post.poster.id }}-level" title="You are friends" {% if user.isFriends(post.poster.id) == 0 %}style="display: none;"{% endif %}></a>
|
||||
<a class="fa fa-user-{% if user.isFriends(post.poster.id) == 0 %}plus{% else %}times{% endif %} forum-friend-toggle friend-{{ post.poster.id }}-toggle" title="{% if user.isFriends(post.poster.id) == 0 %}Add {{ post.poster.username }} as a friend{% else %}Remove friend{% endif %}" href="javascript:void(0);" onclick="{% if user.isFriends(post.poster.id) == 0 %}addFriend({{ post.poster.id }}){% else %}removeFriend({{ post.poster.id }}){% endif %}"></a>
|
||||
<a class="fa fa-user-{% if user.isFriends(post.poster.id) == 0 %}plus{% else %}times{% endif %} forum-friend-toggle friend-{{ post.poster.id }}-toggle" title="{% if user.isFriends(post.poster.id) == 0 %}Add {{ post.poster.username }} as a friend{% else %}Remove friend{% endif %}" href="javascript:void(0);" onclick="Sakura.Friend.{% if user.isFriends(post.poster.id) == 0 %}Add({{ post.poster.id }}){% else %}Remove({{ post.poster.id }}){% endif %}"></a>
|
||||
<a class="fa fa-flag" title="Report {{ post.poster.username }}" href="{{ route('user.report', post.poster.id) }}"></a>
|
||||
{% endif %}
|
||||
<a class="fa fa-reply" title="Quote this post" href="javascript:void(0);" onclick="quotePost({{ post.id }});"></a>
|
||||
|
@ -111,7 +111,7 @@
|
|||
<a href="#p{{ post.id }}" class="clean">{{ post.subject|slice(0, 50) }}{% if post.subject|length > 50 %}...{% endif %}</a>
|
||||
</div>
|
||||
<div class="date">
|
||||
<a href="{{ route('forums.post', post.id) }}" class="clean">#{{ post.id }} - <time datetime="{{ post.time|date('r') }}">{{ post.time|date('D Y-m-d H:i:s T') }}</time></a>
|
||||
<a href="{{ route('forums.post', post.id) }}" class="clean">#{{ post.id }} - <time class="time-ago" datetime="{{ post.time|date('r') }}">{{ post.time|date('D Y-m-d H:i:s T') }}</time></a>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Confirmation' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Information' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Restricted' %}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content standalone bbcode">
|
||||
|
|
27
resources/views/yuuno/manage/master.twig
Normal file
27
resources/views/yuuno/manage/master.twig
Normal file
|
@ -0,0 +1,27 @@
|
|||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = category ~ ' / ' ~ mode %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content settings messages">
|
||||
<div class="content-right content-column">
|
||||
<div class="head">
|
||||
Navigation
|
||||
</div>
|
||||
<div class="right-menu-nav">
|
||||
{% for name, links in navigation %}
|
||||
<div>{{ name }}</div>
|
||||
{% for name,link in links %}
|
||||
<a href="{{ link }}">{{ name }}</a>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left content-column">
|
||||
<div class="head">{{ title }}</div>
|
||||
<div class="settings-explanation">{{ block('description') }}</div>
|
||||
{{ block('manageContent') }}
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
{% endblock %}
|
11
resources/views/yuuno/manage/overview/index.twig
Normal file
11
resources/views/yuuno/manage/overview/index.twig
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends 'manage/overview/master.twig' %}
|
||||
|
||||
{% set mode = 'Index' %}
|
||||
|
||||
{% block description %}
|
||||
<p>A quick overview on what's going on.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block manageContent %}
|
||||
<h1>wew</h1>
|
||||
{% endblock %}
|
3
resources/views/yuuno/manage/overview/master.twig
Normal file
3
resources/views/yuuno/manage/overview/master.twig
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% extends 'manage/master.twig' %}
|
||||
|
||||
{% set category = 'Overview' %}
|
|
@ -1,7 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- META -->
|
||||
<meta charset="utf-8">
|
||||
<title>{{ title|default(config('general.name')) }}</title>
|
||||
<meta name="description" content="{{ description|default(config('general.description')) }}">
|
||||
|
@ -10,13 +9,13 @@
|
|||
<meta http-equiv="refresh" content="{{ redirectTimeout ? redirectTimeout : '3' }}; URL={{ redirect }}">
|
||||
{% endif %}
|
||||
{{ block('meta') }}
|
||||
<!-- CSS -->
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/libraries.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/yuuno.css">
|
||||
{{ block('css') }}
|
||||
<!-- JS -->
|
||||
<script type="text/javascript" src="/js/master.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/js/app.js"></script>
|
||||
<script type="text/javascript" src="/js/yuuno.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Create an object so we can access certain settings from remote JavaScript files
|
||||
|
@ -35,9 +34,6 @@
|
|||
"checkLogin": {{ user.isActive ? 'true' : 'false' }}
|
||||
};
|
||||
|
||||
// Set cookie prefix and path
|
||||
Sakura.cookiePrefix = "{{ config('cookie.prefix') }}";
|
||||
|
||||
// Error reporter
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
notifyUI({
|
||||
|
@ -73,7 +69,7 @@
|
|||
<a class="menu-item avatar" href="{{ route('user.profile', user.id) }}" title="Logged in as {{ user.username }}" style="background-image: url('{{ route('file.avatar', user.id) }}'); width: auto; color: {{ user.colour }}; border-color: {{ user.colour }}; font-weight: 700;"></a>
|
||||
{#<a class="menu-item fa-envelope" href="#" title="Messages"></a>#}
|
||||
{% if user.permission(constant('Sakura\\Perms\\Manage::USE_MANAGE'), constant('Sakura\\Perms::MANAGE')) %}
|
||||
<a class="menu-item fa-gavel" href="#" title="Manage"></a>
|
||||
<a class="menu-item fa-gavel" href="{{ route('manage.index') }}" title="Manage"></a>
|
||||
{% endif %}
|
||||
<a class="menu-item fa-cogs" href="{{ route('settings.index') }}" title="Settings"></a>
|
||||
<a class="menu-item fa-sign-out" href="{{ route('auth.logout') }}?s={{ session_id() }}" title="Logout" id="headerLogoutLink"></a>
|
||||
|
@ -164,175 +160,26 @@
|
|||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
// Parse time elements
|
||||
var timeElems = document.getElementsByTagName('time');
|
||||
Sakura.Config.Set({
|
||||
Revision: {{ constant('SAKURA_VERSION') }},
|
||||
UserNameMinLength: {{ config('user.name_min') }},
|
||||
UserNameMaxLength: {{ config('user.name_max') }},
|
||||
PasswordMinEntropy: {{ config('user.pass_min_entropy') }},
|
||||
LoggedIn: {{ user.isActive ? 'true' : 'false' }},
|
||||
SessionId: "{{ session_id() }}",
|
||||
});
|
||||
|
||||
// Iterate over them
|
||||
for (var timeElem in timeElems) {
|
||||
console.log(timeElems[timeElem].dateTime);
|
||||
// Attempt to parse it
|
||||
var parsed = Date.parse(timeElems[timeElem].dateTime);
|
||||
Yuuno.Main.Startup();
|
||||
|
||||
// If it can be parsed do DOM edits
|
||||
if (!isNaN(parsed)) {
|
||||
timeElems[timeElem].title = timeElems[timeElem].innerText;
|
||||
timeElems[timeElem].innerText = Sakura.timeElapsed(Math.floor(parsed / 1000));
|
||||
}
|
||||
}
|
||||
|
||||
notifyRequest('{{ session_id() }}');
|
||||
|
||||
setInterval(function() {
|
||||
notifyRequest('{{ session_id() }}');
|
||||
}, 60000);
|
||||
|
||||
var friendClient = new AJAX();
|
||||
|
||||
friendClient.contentType("application/x-www-form-urlencoded");
|
||||
|
||||
function addFriend(id) {
|
||||
var url = "{{ route('friends.add', 1) }}".replace(1, id);
|
||||
friendMode(url, id);
|
||||
}
|
||||
|
||||
function removeFriend(id) {
|
||||
var url = "{{ route('friends.remove', 1) }}".replace(1, id);
|
||||
friendMode(url, id);
|
||||
}
|
||||
|
||||
function friendMode(url, id) {
|
||||
var level = document.querySelectorAll('.friend-' + id + '-level'),
|
||||
toggle = document.querySelectorAll('.friend-' + id + '-toggle');
|
||||
|
||||
friendClient.setUrl(url);
|
||||
|
||||
friendClient.setSend({"session":"{{ session_id() }}"});
|
||||
|
||||
friendClient.addCallback(200, function () {
|
||||
var resp = JSON.parse(friendClient.response());
|
||||
|
||||
friendClient.setRawSend("");
|
||||
|
||||
if (resp.error) {
|
||||
ajaxBusyView(true, resp.error, 'fail');
|
||||
|
||||
setTimeout(function () {
|
||||
ajaxBusyView(false);
|
||||
}, 1500);
|
||||
} else {
|
||||
ajaxBusyView(true, resp.message, 'ok');
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
friendClient.start(HTTPMethods.POST);
|
||||
}
|
||||
</script>
|
||||
{% if config('dev.show_changelog', true) and stats %}
|
||||
<script type="text/javascript">
|
||||
// Column colours for actions
|
||||
var changelogColours = [
|
||||
'inherit', // Unknown
|
||||
'#2A2', // Add
|
||||
'#2AA', // Update
|
||||
'#2AA', // Fix
|
||||
'#A22', // Remove
|
||||
'#62C', // Export
|
||||
'#C44' // Revert
|
||||
];
|
||||
|
||||
function yuunoCreateChangelog() {
|
||||
// Grab the index panel
|
||||
var _panel = document.getElementById('indexPanel');
|
||||
|
||||
// Create the head container
|
||||
var _cltitle = document.createElement('div');
|
||||
_cltitle.className = 'head';
|
||||
_cltitle.style.marginBottom = '1px';
|
||||
|
||||
// Create a link
|
||||
var _cllink = document.createElement('a');
|
||||
_cllink.className = 'underline';
|
||||
_cllink.target = '_blank';
|
||||
_cllink.href = 'https://sakura.flash.moe/#r{{ constant('SAKURA_VERSION') }}';
|
||||
|
||||
// Append everything
|
||||
_cllink.appendChild(document.createTextNode('Changelog'));
|
||||
_cltitle.appendChild(_cllink);
|
||||
_panel.appendChild(_cltitle);
|
||||
|
||||
// Create changelog table
|
||||
var _cltable = document.createElement('table');
|
||||
_cltable.className = 'panelTable';
|
||||
_cltable.style.borderSpacing = '0 1px';
|
||||
_cltable.id = 'yuunoChangelogTable';
|
||||
|
||||
// Append it to indexPanel
|
||||
indexPanel.appendChild(_cltable);
|
||||
}
|
||||
|
||||
function yuunoAddToChangelog(object) {
|
||||
_cltable = document.getElementById('yuunoChangelogTable');
|
||||
|
||||
// Create header
|
||||
var _hr = document.createElement('tr');
|
||||
var _hri = document.createElement('td');
|
||||
|
||||
// Set data
|
||||
_hri.appendChild(document.createTextNode(object.date));
|
||||
_hri.style.background = '#9475b2';
|
||||
_hri.style.color = '#306';
|
||||
_hri.style.fontWeight = '700';
|
||||
_hri.style.fontSize = '1.2em';
|
||||
_hri.setAttribute('colspan', '2');
|
||||
|
||||
// Append
|
||||
_hr.appendChild(_hri);
|
||||
_cltable.appendChild(_hr);
|
||||
|
||||
for (var _e in object.changes) {
|
||||
// Reassign _e
|
||||
_e = object.changes[_e];
|
||||
|
||||
// Create elements
|
||||
var _clr = document.createElement('tr');
|
||||
var _clca = document.createElement('td');
|
||||
var _clcm = document.createElement('td');
|
||||
|
||||
// Set data
|
||||
_clca.appendChild(document.createTextNode(_e.action.name));
|
||||
_clca.style.background = changelogColours[_e.action.id];
|
||||
_clca.style.borderBottom = '1px solid ' + changelogColours[_e.action.id];
|
||||
_clcm.style.borderBottom = '1px solid ' + changelogColours[_e.action.id];
|
||||
_clcm.appendChild(document.createTextNode(_e.message));
|
||||
|
||||
// Append
|
||||
_clr.appendChild(_clca);
|
||||
_clr.appendChild(_clcm);
|
||||
_cltable.appendChild(_clr);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
yuunoCreateChangelog();
|
||||
|
||||
var changelog = {},
|
||||
fetch = 0,
|
||||
clg = new AJAX();
|
||||
|
||||
clg.addCallback(200, function () {
|
||||
yuunoAddToChangelog(JSON.parse(clg.response()));
|
||||
if (fetch < 2) {
|
||||
fetch++;
|
||||
clg.setUrl('https://sakura.flash.moe/api.php/' + fetch);
|
||||
clg.start(HTTPMethods.GET);
|
||||
}
|
||||
});
|
||||
|
||||
clg.setUrl('https://sakura.flash.moe/api.php/' + fetch);
|
||||
clg.start(HTTPMethods.GET);
|
||||
});
|
||||
</script>
|
||||
Sakura.Changelog.Build(new Sakura.DOM('indexPanel', Sakura.DOMSelector.ID));
|
||||
{% endif %}
|
||||
|
||||
notifyRequest(Sakura.Config.SessionId);
|
||||
|
||||
setInterval(function() {
|
||||
notifyRequest(Sakura.Config.SessionId);
|
||||
}, 60000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'You are banned!' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Frequently Asked Questions' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="content homepage">
|
||||
|
|
|
@ -1 +1 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Search' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title %}{{ page.category }} / {{ page.mode }}{% endset %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'News' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = post.title %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = 'Purchase complete!' %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title %}Support {{ config('general.name') }}{% endset %}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{% block settingsContent %}
|
||||
<form enctype="multipart/form-data" method="post" action="{{ route('settings.account.username') }}">
|
||||
<h1 class="stylised" style="text-align: center; margin-top: 10px;{% if not eligible %} color: #c44;{% endif %}">You are {% if not eligible %}not {% endif %}eligible for a name change.</h1>
|
||||
<h3 style="text-align: center;">{% if user.getUsernameHistory %}Your last name change was <time datetime="{{ user.getUsernameHistory[0]['change_time']|date('r') }}">{{ user.getUsernameHistory[0]['change_time']|date('D Y-m-d H:i:s T') }}</time>.{% else %}This is your first username change.{% endif %}</h3>
|
||||
<h3 style="text-align: center;">{% if user.getUsernameHistory %}Your last name change was <time class="time-ago" datetime="{{ user.getUsernameHistory[0]['change_time']|date('r') }}">{{ user.getUsernameHistory[0]['change_time']|date('D Y-m-d H:i:s T') }}</time>.{% else %}This is your first username change.{% endif %}</h3>
|
||||
{% if eligible %}
|
||||
<div class="profile-field">
|
||||
<div><h2>Username</h2></div>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
{{ s.user_agent }}
|
||||
</td>
|
||||
<td>
|
||||
<time datetime="{{ s.session_start|date('r') }}">{{ s.session_start|date('D Y-m-d H:i:s T') }}</time>
|
||||
<time class="time-ago" datetime="{{ s.session_start|date('r') }}">{{ s.session_start|date('D Y-m-d H:i:s T') }}</time>
|
||||
</td>
|
||||
<td style="width: 90px;">
|
||||
<form method="post" action="{{ route('settings.advanced.sessions') }}">
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script type="text/javascript">
|
||||
var parser = new AJAX(),
|
||||
var parser = new Sakura.AJAX(),
|
||||
textMax = {{ maxLength }},
|
||||
form = document.getElementById("settingsEditor"),
|
||||
preview = document.getElementById("settingsPreview");
|
||||
|
||||
parser.setUrl("{{ route('helper.bbcode.parse') }}");
|
||||
parser.contentType("application/x-www-form-urlencoded");
|
||||
parser.SetUrl("{{ route('helper.bbcode.parse') }}");
|
||||
parser.ContentType("application/x-www-form-urlencoded");
|
||||
|
||||
function settingsPreview() {
|
||||
var text = form.value;
|
||||
|
@ -15,10 +15,10 @@
|
|||
} else if (text.length > textMax) {
|
||||
preview.innerHTML = "<span style='color: red;'>Too long!</span>";
|
||||
} else {
|
||||
parser.setSend({"text":text});
|
||||
parser.SetSend({"text":text});
|
||||
|
||||
parser.addCallback(200, function () {
|
||||
preview.innerHTML = parser.response();
|
||||
parser.AddCallback(200, function () {
|
||||
preview.innerHTML = parser.Response();
|
||||
|
||||
var codeBlocks = preview.querySelectorAll("pre code");
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
parser.start(HTTPMethods.POST);
|
||||
parser.Start(Sakura.HTTPMethod.POST);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="friends-list-name" style="color: {{ friend.colour }};">{{ friend.username }}</div>
|
||||
</a>
|
||||
<div class="friends-list-actions">
|
||||
<a class="remove fill fa fa-remove" title="Remove friend" href="javascript:void(0);" onclick="removeFriend({{ friend.id }});"></a>
|
||||
<a class="remove fill fa fa-remove" title="Remove friend" href="javascript:void(0);" onclick="Sakura.Friend.Remove({{ friend.id }});"></a>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
<div class="friends-list-name" style="color: {{ friend.colour }};">{{ friend.username }}</div>
|
||||
</a>
|
||||
<div class="friends-list-actions">
|
||||
<a class="add fa fa-check" title="Add friend" href="javascript:void(0);" onclick="addFriend({{ friend.id }});"></a>
|
||||
<a class="remove fa fa-remove" title="Remove friend" href="javascript:void(0);" onclick="removeFriend({{ friend.id }});"></a>
|
||||
<a class="add fa fa-check" title="Add friend" href="javascript:void(0);" onclick="Sakura.Friend.Add({{ friend.id }});"></a>
|
||||
<a class="remove fa fa-remove" title="Remove friend" href="javascript:void(0);" onclick="Sakura.Friend.Remove({{ friend.id }});"></a>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set title = category ~ ' / ' ~ mode %}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="notif-hist-time">
|
||||
<time datetime="{{ alert.time|date('r') }}">{{ alert.time|date('D Y-m-d H:i:s T') }}</time>
|
||||
<time class="time-ago" datetime="{{ alert.time|date('r') }}">{{ alert.time|date('D Y-m-d H:i:s T') }}</time>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set sorts = ['boxes', 'rectangles', 'list'] %}
|
||||
{% set sort = get.sort in sorts ? get.sort : sorts[0] %}
|
||||
|
@ -80,10 +80,10 @@
|
|||
<a href="{{ route('user.profile', user.id) }}" class="default" style="font-weight: bold; color: {{ user.colour }}; text-shadow: 0 0 5px {{ user.colour }};">{{ user.username }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<time datetime="{{ user.registered|date('r') }}">{{ user.registered|date('D Y-m-d H:i:s T') }}</time>
|
||||
<time class="time-ago" datetime="{{ user.registered|date('r') }}">{{ user.registered|date('D Y-m-d H:i:s T') }}</time>
|
||||
</td>
|
||||
<td>
|
||||
{% if user.lastOnline == 0 %}<i>Never logged in.</i>{% else %}<time datetime="{{ user.lastOnline|date('r') }}">{{ user.lastOnline|date('D Y-m-d H:i:s T') }}</time>{% endif %}
|
||||
{% if user.lastOnline == 0 %}<i>Never logged in.</i>{% else %}<time class="time-ago" datetime="{{ user.lastOnline|date('r') }}">{{ user.lastOnline|date('D Y-m-d H:i:s T') }}</time>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ user.title }}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% set profileHidden = profile.permission(constant('Sakura\\Perms\\Site::DEACTIVATED')) or (profile.permission(constant('Sakura\\Perms\\Site::RESTRICTED')) and (user.id != profile.id and not user.permission(constant('Sakura\\Perms\\Manage::USE_MANAGE'), constant('Sakura\\Perms::MANAGE')))) %}
|
||||
|
||||
|
@ -84,12 +84,12 @@
|
|||
{% if profile.isPremium %}<img src="/images/tenshi.png" alt="Tenshi" style="vertical-align: middle;"> {% endif %}<img src="/images/flags/{{ profile.country|lower }}.png" alt="{{ profile.country }}" style="vertical-align: middle;" title="{{ profile.country(true) }}"> <span style="font-size: .8em;">{{ profile.title }}</span>
|
||||
</div>
|
||||
<div class="new-profile-dates">
|
||||
<b>Joined</b> <time datetime="{{ profile.registered|date('r') }}">{{ profile.registered|date('D Y-m-d H:i:s T') }}</time>
|
||||
<b>Joined</b> <time class="time-ago" datetime="{{ profile.registered|date('r') }}">{{ profile.registered|date('D Y-m-d H:i:s T') }}</time>
|
||||
<br>
|
||||
{% if profile.lastOnline < 1 %}
|
||||
<b>{{ profile.username }} hasn't logged in yet.</b>
|
||||
{% else %}
|
||||
<b>Last online</b> <time datetime="{{ profile.lastOnline|date('r') }}">{{ profile.lastOnline|date('D Y-m-d H:i:s T') }}</time>
|
||||
<b>Last online</b> <time class="time-ago" datetime="{{ profile.lastOnline|date('r') }}">{{ profile.lastOnline|date('D Y-m-d H:i:s T') }}</time>
|
||||
{% endif %}
|
||||
{% if profile.birthday != '0000-00-00' and profile.birthday|split('-')[0] > 0 %}
|
||||
<br><b>Age</b> <span title="{{ profile.birthday }}">{{ profile.birthday(true) }} years old</span>
|
||||
|
@ -114,7 +114,7 @@
|
|||
<a class="fa fa-pencil-square-o" title="Edit your profile" href="{{ route('settings.general.profile') }}"></a>
|
||||
{% else %}
|
||||
{% if user.isFriends(profile.id) != 0 %}<a class="fa fa-{% if user.isFriends(profile.id) == 2 %}heart{% else %}star{% endif %}" title="You are friends"></a>{% endif %}
|
||||
<a class="fa fa-user-{% if user.isFriends(profile.id) == 0 %}plus{% else %}times{% endif %}" title="{% if user.isFriends(profile.id) == 0 %}Add {{ profile.username }} as a friend{% else %}Remove friend{% endif %}" href="javascript:void(0);" onclick="{% if user.isFriends(profile.id) == 0 %}addFriend({{ profile.id }}){% else %}removeFriend({{ profile.id }}){% endif %}"></a>
|
||||
<a class="fa fa-user-{% if user.isFriends(profile.id) == 0 %}plus{% else %}times{% endif %}" title="{% if user.isFriends(profile.id) == 0 %}Add {{ profile.username }} as a friend{% else %}Remove friend{% endif %}" href="javascript:void(0);" onclick="Sakura.Friend.{% if user.isFriends(profile.id) == 0 %}Add({{ profile.id }}){% else %}Remove({{ profile.id }}){% endif %}"></a>
|
||||
<a class="fa fa-exclamation-circle" title="Report {{ profile.username }}" href="{{ route('user.report', profile.id) }}"></a>
|
||||
{% endif %}
|
||||
{% if user.permission(constant('Sakura\\Perms\\Manage::CAN_RESTRICT_USERS'), constant('Sakura\\Perms::MANAGE')) %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'global/master.twig' %}
|
||||
{% extends 'master.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="stylised" style="text-align: center; margin: 2em auto;">I'll actually make reporting a thing, someday...</h1>
|
||||
|
|
50
routes.php
50
routes.php
|
@ -75,8 +75,8 @@ Router::group(['before' => 'maintenance'], function () {
|
|||
'chat' => 'chat.redirect',
|
||||
//'irc' => 'chat.irc',
|
||||
'feedback' => 'forums.index',
|
||||
//'mcp' => 'manage.index',
|
||||
//'mcptest' => 'manage.index',
|
||||
'mcp' => 'manage.index',
|
||||
'mcptest' => 'manage.index',
|
||||
//'report' => 'report.something',
|
||||
//'osu' => 'eventual link to flashii team',
|
||||
//'filehost' => '???',
|
||||
|
@ -120,12 +120,12 @@ Router::group(['before' => 'maintenance'], function () {
|
|||
Router::group(['prefix' => 'forum'], function () {
|
||||
// Post
|
||||
Router::group(['prefix' => 'post'], function () {
|
||||
Router::get('/{id:i}', 'ForumController@post', 'forums.post');
|
||||
Router::get('/{id:i}', 'Forum.PostController@find', 'forums.post');
|
||||
Router::group(['before' => 'loginCheck'], function () {
|
||||
Router::get('/{id:i}/raw', 'ForumController@postRaw', 'forums.post.raw');
|
||||
Router::get('/{id:i}/delete', 'ForumController@deletePost', 'forums.post.delete');
|
||||
Router::post('/{id:i}/delete', 'ForumController@deletePost', 'forums.post.delete');
|
||||
Router::post('/{id:i}/edit', 'ForumController@editPost', 'forums.post.edit');
|
||||
Router::get('/{id:i}/raw', 'Forum.PostController@raw', 'forums.post.raw');
|
||||
Router::get('/{id:i}/delete', 'Forum.PostController@delete', 'forums.post.delete');
|
||||
Router::post('/{id:i}/delete', 'Forum.PostController@delete', 'forums.post.delete');
|
||||
Router::post('/{id:i}/edit', 'Forum.PostController@edit', 'forums.post.edit');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -142,12 +142,12 @@ Router::group(['before' => 'maintenance'], function () {
|
|||
});
|
||||
|
||||
// Forum
|
||||
Router::get('/', 'ForumController@index', 'forums.index');
|
||||
Router::get('/{id:i}', 'ForumController@forum', 'forums.forum');
|
||||
Router::get('/', 'Forum.ForumController@index', 'forums.index');
|
||||
Router::get('/{id:i}', 'Forum.ForumController@forum', 'forums.forum');
|
||||
Router::group(['before' => 'loginCheck'], function () {
|
||||
Router::get('/{id:i}/mark', 'ForumController@markForumRead', 'forums.mark');
|
||||
Router::get('/{id:i}/new', 'ForumController@createTopic', 'forums.new');
|
||||
Router::post('/{id:i}/new', 'ForumController@createTopic', 'forums.new');
|
||||
Router::get('/{id:i}/mark', 'Forum.ForumController@markRead', 'forums.mark');
|
||||
Router::get('/{id:i}/new', 'Forum.TopicController@create', 'forums.new');
|
||||
Router::post('/{id:i}/new', 'Forum.TopicController@create', 'forums.new');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -308,16 +308,26 @@ Router::group(['before' => 'maintenance'], function () {
|
|||
});
|
||||
});
|
||||
|
||||
// Settings
|
||||
Router::group(['prefix' => 'manage', 'before' => 'loginCheck'], function () {
|
||||
Router::get('/', function () {
|
||||
$route = Router::route('manage.overview');
|
||||
return header("Location: {$route}");
|
||||
}, 'manage.index');
|
||||
|
||||
// Overview section
|
||||
Router::group(['prefix' => 'overview'], function () {
|
||||
Router::get('/', function () {
|
||||
$route = Router::route('manage.overview.index');
|
||||
return header("Location: {$route}");
|
||||
}, 'manage.overview');
|
||||
|
||||
Router::get('/index', 'Manage.OverviewController@index', 'manage.overview.index');
|
||||
Router::get('/data', 'Manage.OverviewController@data', 'manage.overview.data');
|
||||
});
|
||||
});
|
||||
// Management
|
||||
/*
|
||||
* General
|
||||
* - Dashboard
|
||||
* - Info pages (possibly deprecate with wiki)
|
||||
* Configuration
|
||||
* - General
|
||||
* - Files
|
||||
* - User
|
||||
* - Mail
|
||||
* Forums
|
||||
* - Manage
|
||||
* - Settings
|
||||
|
|
Reference in a new issue