Also:
- changed URL schema for posts from /posts/action/id to /posts/id/action
- moved XXXController::locateXXX methods to Model_XXX::locate
This commit is contained in:
Marcin Kurczewski 2013-10-17 22:57:32 +02:00
parent ffc373e871
commit ee050cfd01
24 changed files with 437 additions and 86 deletions

View file

@ -18,6 +18,11 @@ thumbStyle=outside
endlessScrolling=1
maxSearchTokens=4
[comments]
minLength = 5
maxLength = 2000
commentsPerPage = 20
[registration]
staffActivation = 0
passMinLength = 5
@ -79,4 +84,6 @@ deleteUser.own=registered
deleteUser.all=nobody
listComments=anonymous
addComment=registered
listTags=anonymous

View file

@ -0,0 +1,16 @@
.comment-group .post-wrapper {
float: left;
}
.comment-group .comments {
float: left;
}
.comment-group .post img {
margin-right: 1em;
margin-bottom: 1em;
}
.comment-group {
border-bottom: 1px solid #eee;
margin-bottom: 1em;
}

View file

@ -0,0 +1,30 @@
.comment {
margin: 0 0 1em 0;
}
.comment .body,
.comment .avatar {
float: left;
}
.comment .header {
margin-bottom: 0.2em;
}
.comment .avatar a {
display: inline-block;
}
.comment .avatar img {
width: 40px;
height: 40px;
background-image: url('http://www.gravatar.com/avatar/0?f=y&d=mm&s=40');
margin-right: 0.5em;
}
.comment {
clear: left;
}
.comment .date:before {
content: ' on ';
margin: 0 0.2em;
}
.comment .date {
color: silver;
font-size: small;
}

View file

@ -155,6 +155,14 @@ footer {
overflow: hidden;
}
.footer-unit {
padding: 0.5em 1em;
border: 1px solid #eee;
border-bottom: 0;
padding-bottom: 0;
margin: 1em 0 2em 0;
}
h1, h2, h3 {

View file

@ -1,20 +1,3 @@
.posts img {
border: 1px solid #ddd;
box-shadow: 0.25em 0.25em #eee;
margin: 0.5em;
padding: 0;
width: 140px;
height: 140px;
}
.posts .post-type-flash img {
border-color: #dd5;
box-shadow: 0.25em 0.25em #eeb, 0.1em 0.1em 0.5em 0.1em rgba(238,238,187,0.5);
}
.posts a:focus img,
.posts a:hover img {
border: 1px solid firebrick;
box-shadow: 0.25em 0.25em pink;
opacity: .9;
}

View file

@ -0,0 +1,19 @@
.post img {
border: 1px solid #ddd;
box-shadow: 0.25em 0.25em #eee;
padding: 0;
width: 140px;
height: 140px;
}
.post-type-flash img {
border-color: #dd5;
box-shadow: 0.25em 0.25em #eeb, 0.1em 0.1em 0.5em 0.1em rgba(238,238,187,0.5);
}
.post:focus img,
.post:hover img {
border: 1px solid firebrick;
box-shadow: 0.25em 0.25em pink;
opacity: .9;
}

View file

@ -90,15 +90,11 @@ i.icon-dl {
margin: 2px;
}
form.edit {
form.edit-post,
form.edit-comment {
display: none;
padding: 0.5em 1em;
border: 1px solid #eee;
border-bottom: 0;
padding-bottom: 0;
margin: 1em 0;
}
form.edit .safety label:not(.left) {
form.edit-post .safety label:not(.left) {
margin-right: 0.75em;
}
ul.tagit {
@ -107,3 +103,10 @@ ul.tagit {
margin: 0;
font-size: 1em;
}
.preview {
border: 1px solid yellow;
background: url('../img/preview.png') lemonchiffon;
padding: 0.5em;
display: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -85,4 +85,20 @@ $(function()
}
});
});
//attach data from submit buttons to forms before .submit() gets called
$(':submit').each(function()
{
$(this).click(function()
{
var form = $(this).closest('form');
form.find('.faux-submit').remove();
var input = $('<input class="faux-submit" type="hidden"/>').attr({
name: $(this).attr('name'),
value: $(this).val()
});
form.append(input);
});
});
});

View file

@ -23,11 +23,11 @@ $(function()
$('.tags input').tagit(tagItOptions);
e.preventDefault();
$('form.edit').slideDown();
$('form.edit-post').slideDown();
});
});
$('form.edit').submit(function(e)
$('form.edit-post').submit(function(e)
{
e.preventDefault();
@ -65,4 +65,59 @@ $(function()
$.ajax(ajaxData);
});
$('form.add-comment, form.edit-comment').submit(function(e)
{
e.preventDefault();
var formDom = $(this);
if (formDom.hasClass('inactive'))
return;
formDom.addClass('inactive');
formDom.find(':input').attr('readonly', true);
var url = formDom.attr('action') + '?json';
var fd = new FormData(formDom[0]);
var preview = false;
$.each(formDom.serializeArray(), function(i, x)
{
if (x.name == 'sender' && x.value == 'preview')
preview = true;
});
var ajaxData =
{
url: url,
data: fd,
processData: false,
contentType: false,
type: 'POST',
success: function(data)
{
if (data['success'])
{
if (preview)
{
formDom.find('.preview').html(data['textPreview']).show();
formDom.find(':input').attr('readonly', false);
formDom.removeClass('inactive');
}
else
{
window.location.reload();
}
}
else
{
alert(data['errorMessage']);
formDom.find(':input').attr('readonly', false);
formDom.removeClass('inactive');
}
}
};
$.ajax(ajaxData);
});
});

View file

@ -3,11 +3,79 @@ class CommentController
{
/**
* @route /comments
* @route /comments/{page}
* @validate page [0-9]+
*/
public function listAction()
public function listAction($page)
{
$this->context->activeSection = 'comments';
$this->context->stylesheets []= 'post-small.css';
$this->context->stylesheets []= 'comment-list.css';
$this->context->stylesheets []= 'comment-small.css';
$this->context->subTitle = 'comments';
throw new SimpleException('Not implemented');
if ($this->config->browsing->endlessScrolling)
$this->context->scripts []= 'paginator-endless.js';
$page = intval($page);
$commentsPerPage = intval($this->config->comments->commentsPerPage);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ListComments);
$buildDbQuery = function($dbQuery)
{
$dbQuery->from('comment');
$dbQuery->orderBy('comment_date')->desc();
};
$countDbQuery = R::$f->begin();
$countDbQuery->select('COUNT(1)')->as('count');
$buildDbQuery($countDbQuery);
$commentCount = intval($countDbQuery->get('row')['count']);
$pageCount = ceil($commentCount / $commentsPerPage);
$page = max(1, min($pageCount, $page));
$searchDbQuery = R::$f->begin();
$searchDbQuery->select('comment.*');
$buildDbQuery($searchDbQuery);
$searchDbQuery->limit('?')->put($commentsPerPage);
$searchDbQuery->offset('?')->put(($page - 1) * $commentsPerPage);
$comments = $searchDbQuery->get();
$comments = R::convertToBeans('comment', $comments);
R::preload($comments, ['commenter' => 'user']);
$this->context->postGroups = true;
$this->context->transport->paginator = new StdClass;
$this->context->transport->paginator->page = $page;
$this->context->transport->paginator->pageCount = $pageCount;
$this->context->transport->paginator->entityCount = $commentCount;
$this->context->transport->paginator->entities = $comments;
$this->context->transport->paginator->params = func_get_args();
$this->context->transport->comments = $comments;
}
/**
* @route /post/{postId}/add-comment
* @valdiate postId [0-9]+
*/
public function addAction($postId)
{
PrivilegesHelper::confirmWithException($this->context->user, Privilege::AddComment);
if ($this->config->registration->needEmailForCommenting)
PrivilegesHelper::confirmEmail($this->context->user);
$post = Model_Post::locate($postId);
$text = InputHelper::get('text');
if (!empty($text))
{
$text = Model_Comment::validateText($text);
$comment = R::dispense('comment');
$comment->post = $post;
$comment->commenter = $this->context->user;
$comment->comment_date = time();
$comment->text = $text;
if (InputHelper::get('sender') != 'preview')
R::store($comment);
$this->context->transport->textPreview = $comment->getText();
$this->context->transport->success = true;
}
}
}

View file

@ -8,23 +8,6 @@ class PostController
$callback();
}
private static function locatePost($key, $disallowNumeric = false)
{
if (is_numeric($key) and !$disallowNumeric)
{
$post = R::findOne('post', 'id = ?', [$key]);
if (!$post)
throw new SimpleException('Invalid post ID "' . $key . '"');
}
else
{
$post = R::findOne('post', 'name = ?', [$key]);
if (!$post)
throw new SimpleException('Invalid post name "' . $key . '"');
}
return $post;
}
private static function serializeTags($post)
{
$x = [];
@ -74,6 +57,7 @@ class PostController
*/
public function listAction($query = null, $page = 1)
{
$this->context->stylesheets []= 'post-small.css';
$this->context->stylesheets []= 'post-list.css';
$this->context->stylesheets []= 'paginator.css';
if ($this->config->browsing->endlessScrolling)
@ -255,11 +239,11 @@ class PostController
/**
* @route /post/edit/{id}
* @route /post/{id}/edit
*/
public function editAction($id)
{
$post = self::locatePost($id);
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
$edited = false;
$secondary = $post->uploader->id == $this->context->user->id ? 'own' : 'all';
@ -323,11 +307,11 @@ class PostController
/**
* @route /post/hide/{id}
* @route /post/{id}/hide
*/
public function hideAction($id)
{
$post = self::locatePost($id);
$post = Model_Post::locate($id);
$secondary = $post->uploader->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::HidePost, $secondary);
$post->hidden = true;
@ -336,11 +320,11 @@ class PostController
}
/**
* @route /post/unhide/{id}
* @route /post/{id}/unhide
*/
public function unhideAction($id)
{
$post = self::locatePost($id);
$post = Model_Post::locate($id);
$secondary = $post->uploader->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::HidePost, $secondary);
$post->hidden = false;
@ -349,11 +333,11 @@ class PostController
}
/**
* @route /post/delete/{id}
* @route /post/{id}/delete
*/
public function deleteAction($id)
{
$post = self::locatePost($id);
$post = Model_Post::locate($id);
$secondary = $post->uploader->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::DeletePost, $secondary);
//remove stuff from auxiliary tables
@ -367,12 +351,12 @@ class PostController
/**
* @route /post/add-fav/{id}
* @route /post/fav-add/{id}
* @route /post/{id}/add-fav
* @route /post/{id}/fav-add
*/
public function addFavoriteAction($id)
{
$post = self::locatePost($id);
$post = Model_Post::locate($id);
R::preload($post, ['favoritee' => 'user']);
if (!$this->context->loggedIn)
@ -389,12 +373,12 @@ class PostController
}
/**
* @route /post/rem-fav/{id}
* @route /post/fav-rem/{id}
* @route /post/{id}/rem-fav
* @route /post/{id}/fav-rem
*/
public function remFavoriteAction($id)
{
$post = self::locatePost($id);
$post = Model_Post::locate($id);
R::preload($post, ['favoritee' => 'user']);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::FavoritePost);
@ -422,8 +406,13 @@ class PostController
*/
public function viewAction($id)
{
$post = self::locatePost($id);
R::preload($post, ['favoritee' => 'user', 'uploader' => 'user', 'tag']);
$post = Model_Post::locate($id);
R::preload($post, [
'favoritee' => 'user',
'uploader' => 'user',
'tag',
'comment',
'ownComment.commenter' => 'user']);
if ($post->hidden)
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ViewPost, 'hidden');
@ -471,6 +460,7 @@ class PostController
$this->context->transport->tagDistribution[$row['name']] = $row['count'];
$this->context->stylesheets []= 'post-view.css';
$this->context->stylesheets []= 'comment-small.css';
$this->context->scripts []= 'post-view.js';
$this->context->subTitle = 'showing @' . $post->id;
$this->context->favorite = $favorite;
@ -484,12 +474,12 @@ class PostController
/**
* Action that renders the thumbnail of the requested file and sends it to user.
* @route /post/thumb/{id}
* @route /post/{id}/thumb
*/
public function thumbAction($id)
{
$this->context->layoutName = 'layout-file';
$post = self::locatePost($id);
$post = Model_Post::locate($id);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ViewPost);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ViewPost, PostSafety::toString($post->safety));
@ -556,12 +546,12 @@ class PostController
/**
* Action that renders the requested file itself and sends it to user.
* @route /post/retrieve/{name}
* @route /post/{name}/retrieve
*/
public function retrieveAction($name)
{
$this->context->layoutName = 'layout-file';
$post = self::locatePost($name, true);
$post = Model_Post::locate($name, true);
R::preload($post, ['tag']);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::RetrievePost);

View file

@ -1,14 +1,6 @@
<?php
class UserController
{
private static function locateUser($key)
{
$user = R::findOne('user', 'name = ?', [$key]);
if (!$user)
throw new SimpleException('Invalid user name "' . $key . '"');
return $user;
}
private static function sendEmailConfirmation(&$user)
{
$regConfig = \Chibi\Registry::getConfig()->registration;
@ -134,7 +126,7 @@ class UserController
*/
public function banAction($name)
{
$user = self::locateUser($name);
$user = Model_User::locate($name);
$secondary = $user->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::BanUser, $secondary);
$user->banned = true;
@ -148,7 +140,7 @@ class UserController
*/
public function unbanAction($name)
{
$user = self::locateUser($name);
$user = Model_User::locate($name);
$secondary = $user->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::BanUser, $secondary);
$user->banned = false;
@ -162,7 +154,7 @@ class UserController
*/
public function acceptRegistrationAction($name)
{
$user = self::locateUser($name);
$user = Model_User::locate($name);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::AcceptUserRegistration);
$user->staff_confirmed = true;
R::store($user);
@ -178,7 +170,7 @@ class UserController
*/
public function deleteAction($name)
{
$user = self::locateUser($name);
$user = Model_User::locate($name);
$secondary = $user->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ViewUser, $secondary);
PrivilegesHelper::confirmWithException($this->context->user, Privilege::DeleteUser, $secondary);
@ -219,7 +211,7 @@ class UserController
try
{
$user = self::locateUser($name);
$user = Model_User::locate($name);
$edited = false;
$secondary = $user->id == $this->context->user->id ? 'own' : 'all';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ViewUser, $secondary);
@ -297,7 +289,7 @@ class UserController
}
catch (Exception $e)
{
$this->context->transport->user = self::locateUser($name);
$this->context->transport->user = Model_User::locate($name);
throw $e;
}
}
@ -314,7 +306,7 @@ class UserController
public function viewAction($name, $tab, $page)
{
$postsPerPage = intval($this->config->browsing->postsPerPage);
$user = self::locateUser($name);
$user = Model_User::locate($name);
if ($tab === null)
$tab = 'favs';
if ($page === null)

View file

@ -141,4 +141,9 @@ class TextHelper
return json_encode($obj);
}
public static function parseMarkdown($text)
{
return \Michelf\Markdown::defaultTransform($text);
}
}

View file

@ -0,0 +1,22 @@
<?php
class Model_Comment extends RedBean_SimpleModel
{
public static function validateText($text)
{
$text = trim($text);
$config = \Chibi\Registry::getConfig();
if (strlen($text) < $config->comments->minLength)
throw new SimpleException(sprintf('Comment must have at least %d characters', $config->comments->minLength));
if (strlen($text) > $config->comments->maxLength)
throw new SimpleException(sprintf('Comment must have at most %d characters', $config->comments->maxLength));
return $text;
}
public function getText()
{
return TextHelper::parseMarkdown($this->text);
}
}

View file

@ -1,6 +1,23 @@
<?php
class Model_Post extends RedBean_SimpleModel
{
public static function locate($key, $disallowNumeric = false)
{
if (is_numeric($key) and !$disallowNumeric)
{
$post = R::findOne('post', 'id = ?', [$key]);
if (!$post)
throw new SimpleException('Invalid post ID "' . $key . '"');
}
else
{
$post = R::findOne('post', 'name = ?', [$key]);
if (!$post)
throw new SimpleException('Invalid post name "' . $key . '"');
}
return $post;
}
public static function validateSafety($safety)
{
$safety = intval($safety);

View file

@ -1,6 +1,14 @@
<?php
class Model_User extends RedBean_SimpleModel
{
public static function locate($key)
{
$user = R::findOne('user', 'name = ?', [$key]);
if (!$user)
throw new SimpleException('Invalid user name "' . $key . '"');
return $user;
}
public function getAvatarUrl($size = 32)
{
$subject = !empty($this->email_confirmed)

View file

@ -24,5 +24,7 @@ class Privilege extends Enum
const DeleteUser = 19;
const ListComments = 20;
const AddComment = 23;
const ListTags = 21;
}

View file

@ -0,0 +1,41 @@
<?php if (empty($this->context->transport->comments)): ?>
<p class="alert alert-warning">No comments to show.</p>
<?php else: ?>
<div class="comments paginator-content">
<?php
$groups = [];
$posts = [];
$currentGroupPostId = null;
$currentGroup = null;
foreach ($this->context->transport->comments as $comment)
{
if ($comment->post_id != $currentGroupPostId)
{
unset($currentGroup);
$currentGroup = [];
$currentGroupPostId = $comment->post_id;
$posts[$comment->post_id] = $comment->post;
$groups[] = &$currentGroup;
}
$currentGroup []= $comment;
}
?>
<?php foreach ($groups as $group): ?>
<div class="comment-group">
<div class="post-wrapper">
<?php $this->context->post = $posts[reset($group)->post_id] ?>
<?php echo $this->renderFile('post-small') ?>
</div>
<div class="comments">
<?php foreach ($group as $comment): ?>
<?php $this->context->comment = $comment ?>
<?php echo $this->renderFile('comment-small') ?>
<?php endforeach ?>
</div>
<div class="clear"></div>
</div>
<?php endforeach ?>
</div>
<?php $this->renderFile('paginator') ?>
<?php endif ?>

View file

@ -0,0 +1,35 @@
<div class="comment">
<div class="avatar">
<?php if ($this->context->comment->commenter): ?>
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
<img src="<?php echo $this->context->comment->commenter->getAvatarUrl(40) ?>" alt="<?php echo $this->context->comment->commenter->name ?: '[deleted user]' ?>"/>
</a>
<?php else: ?>
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="[deleted user]">
<?php endif ?>
</div>
<div class="body">
<div class="header">
<span class="nickname">
<?php if ($this->context->comment->commenter): ?>
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
<?php echo $this->context->comment->commenter->name ?>
</a>
<?php else: ?>
<div class="nickname">[deleted user]</div>
<?php endif ?>
</span>
<span class="date">
<?php echo date('Y-m-d H:i', $this->context->comment->comment_date) ?>
</span>
</div>
<div class="content">
<?php echo $this->context->comment->getText() ?>
</div>
</div>
<div class="clear"></div>
</div>

View file

@ -3,9 +3,8 @@
<?php else: ?>
<div class="posts paginator-content">
<?php foreach ($this->context->transport->posts as $post): ?>
<a class="post post-type-<?php echo TextHelper::camelCaseToHumanCase(PostType::toString($post['type'])) ?>" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $post['id']]) ?>">
<img src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['id' => $post['id']]) ?>" alt="@<?php echo $post['id'] ?>"/>
</a>
<?php $this->context->post = $post ?>
<?php echo $this->renderFile('post-small') ?>
<?php endforeach ?>
</div>

View file

@ -0,0 +1,3 @@
<a class="post post-type-<?php echo TextHelper::camelCaseToHumanCase(PostType::toString($this->context->post['type'])) ?>" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post['id']]) ?>">
<img src="<?php echo \Chibi\UrlHelper::route('post', 'thumb', ['id' => $this->context->post['id']]) ?>" alt="@<?php echo $this->context->post['id'] ?>"/>
</a>

View file

@ -201,7 +201,7 @@
</div>
<?php if ($canEditAnything): ?>
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit aligned">
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post aligned footer-unit">
<h1>edit post</h1>
<?php if ($editPostPrivileges[Privilege::EditPostSafety]): ?>
<div class="safety">
@ -236,4 +236,35 @@
</div>
</form>
<?php endif ?>
<div class="comments footer-unit">
<h1>comments (<?php echo $this->context->transport->post->countOwn('comment') ?>)</h1>
<?php if (empty($this->context->transport->post->ownComment)): ?>
<p class="alert alert-warning">No comments to show.</p>
<?php else: ?>
<div class="comments">
<?php foreach ($this->context->transport->post->ownComment as $comment): ?>
<?php $this->context->comment = $comment ?>
<?php echo $this->renderFile('comment-small') ?>
<?php endforeach ?>
</div>
<?php endif ?>
</div>
<?php if (PrivilegesHelper::confirm($this->context->user, Privilege::AddComment)): ?>
<form action="<?php echo \Chibi\UrlHelper::route('comment', 'add', ['postId' => $this->context->transport->post->id]) ?>" method="post" class="add-comment aligned footer-unit">
<h1>add comment</h1>
<div class="preview"></div>
<div class="text">
<textarea name="text" cols="50" rows="3"></textarea>
</div>
<div>
<button name="sender" type="submit" value="preview">Preview</button>&nbsp;
<button name="sender" type="submit" value="submit">Submit</button>
</div>
</form>
<?php endif ?>
</div>

View file

@ -8,6 +8,7 @@ function trueStartTime()
}
trueStartTime();
require_once 'lib/php-markdown/Michelf/Markdown.php';
require_once 'lib/redbean/RedBean/redbean.inc.php';
require_once 'lib/chibi-core/Facade.php';
require_once 'lib/chibi-core/Registry.php';
@ -42,7 +43,7 @@ function configFactory()
$config = configFactory();
R::setup('sqlite:' . $config->main->dbPath);
R::dependencies(['tag' => ['post'], 'favoritee' => ['post', 'user']]);
R::dependencies(['tag' => ['post'], 'favoritee' => ['post', 'user'], 'comment' => ['post', 'user']]);
//wire models
\Chibi\AutoLoader::init([$config->chibi->userCodeDir, __DIR__]);