1
0
Fork 0
mirror of https://github.com/Oreolek/ifhub.club.git synced 2024-07-01 05:55:02 +03:00

Доработка комментариев

* Удален экшн ActionComments (функционал будет перенесен в активность)
* Добавлен компонент comment
* Доработан js код комментариев + оформлен как jQuery Widget
* Fixes #466 При ajax подгрузке комментов не работает голосование
This commit is contained in:
Denis Shakhov 2014-06-11 15:58:32 +07:00
parent e6a92aaa98
commit 130dcac352
22 changed files with 1296 additions and 1078 deletions

View file

@ -523,6 +523,7 @@ $config['head']['default']['js'] = array(
"___path.application.web___/frontend/common/js/userfeed.js",
"___path.application.web___/frontend/common/js/activity.js",
"___path.application.web___/frontend/common/js/toolbar.js",
"___path.application.web___/frontend/common/js/toolbar.comments.js",
"___path.application.web___/frontend/common/js/topic.js",
"___path.application.web___/frontend/common/js/admin.js",
"___path.application.web___/frontend/common/js/userfield.js",

File diff suppressed because it is too large Load diff

View file

@ -466,10 +466,11 @@ return array(
* Комментарии
*/
'comments' => array(
'comments_declension' => 'комментарий;комментария;комментариев',
'count_new' => 'Число новых комментариев',
'title' => 'Комментарии',
'subscribe' => 'Подписаться на новые комментарии',
'comments_declension' => '%%count%% комментарий;%%count%% комментария;%%count%% комментариев',
'count_new' => 'Число новых комментариев',
'title' => 'Комментарии',
'subscribe' => 'Подписаться',
'unsubscribe' => 'Отписаться',
// Комментарий
'comment' => array(

View file

@ -5,18 +5,25 @@
{extends 'layouts/layout.base.tpl'}
{block 'layout_content'}
{* Топик *}
{include 'topics/topic.tpl'}
{include 'comments/comment_tree.tpl'
{* Комментарии *}
{include 'components/comment/comment-list.tpl'
sClasses = 'js-comments-topic'
iTargetId = $oTopic->getId()
iAuthorId = $oTopic->getUserId()
aComments = $aComments
sAuthorNotice = $aLang.topic_author
sTargetType = 'topic'
iCountComment = $oTopic->getCountComment()
sDateReadLast = $oTopic->getDateRead()
bForbidNewComment = $oTopic->getForbidComment()
bForbidAdd = $oTopic->getForbidComment()
sNoticeNotAllow = $aLang.topic_comment_notallow
sNoticeCommentAdd = $aLang.topic_comment_add
bAllowSubscribe = true
oSubscribeComment = $oTopic->getSubscribeNewComment()
aPagingCmt = $aPagingCmt}
aPagingCmt = $aPagingCmt
bShowVote = true
bShowFavourite = true}
{/block}

View file

@ -1,7 +0,0 @@
{extends file='layouts/layout.base.tpl'}
{block name='layout_page_title'}{$aLang.comments_all}{/block}
{block name='layout_content'}
{include file='comments/comment_list.tpl'}
{/block}

View file

@ -13,11 +13,14 @@
/**
* Блок со списком комментариев
*/
.comments { margin-bottom: 30px; }
.comments { }
.comments-header { margin-bottom: 20px; }
.comments-title { font-size: 24px; margin-bottom: 5px; }
.comments-actions { padding: 0; background-color: transparent; }
.comment-list { margin-bottom: 30px; }
/**
* Вспомогательный блок-обертка
@ -28,7 +31,7 @@
/**
* Предпросмотр текста комментария
*/
.comment-preview { padding: 15px; margin: 0 0 10px 0; border: 1px solid #eee; }
.comment-preview { padding: 15px; margin: 10px 0 10px 0; border: 1px solid #eee; }
.comment-wrapper .comment-preview { margin-left: 25px; }
@ -45,66 +48,108 @@
*/
.comment {
min-height: 48px;
padding: 10px 10px 10px 68px;
padding: 15px 15px 15px 80px;
margin-bottom: 2px;
position: relative;
border-top: 1px solid #eee;
background: #fff;
background: #fafafa;
}
.comment.comment-self { background: #c5f7ea; }
.comment--self { background: #c5f7ea; }
.comment.comment-new { background: #fbfba8; }
.comment--new { background: #fbfba8; }
.comment.comment-current { background: #a5e7fa; }
.comment--current { background: #a5e7fa; }
.comment.comment-bad { opacity: 0.3; filter: alpha(opacity=30); }
.comment.comment-bad:hover { opacity: 1; filter: alpha(opacity=100); }
.comment--bad { opacity: 0.3; filter: alpha(opacity=30); }
.comment--bad:hover { opacity: 1; filter: alpha(opacity=100); }
.comment.comment-deleted { background: #efd5d5; }
.ls-user-role-not-admin .comment.comment-deleted { padding: 10px 15px; min-height: 0; background: #f7f7f7; color: #888; }
.comment.comment--deleted { background: #efd5d5; }
.ls-user-role-not-admin .comment.comment--deleted {
padding: 10px 15px;
min-height: 0;
background: #f7f7f7;
color: #888;
}
.comment.comment-list-item { margin-bottom: 20px; }
.comment.comment-list-item .vote .vote-up,
.comment.comment-list-item .vote .vote-down { display: none; }
/* Аватар */
.comment-avatar { position: absolute; top: 10px; left: 10px; }
.comment-avatar {
position: absolute;
top: 15px;
left: 15px;
width: 50px;
height: 50px;
border-radius: 50%;
overflow: hidden;
}
.comment-avatar img { width: 50px; height: 50px; }
/* Информация */
.comment-info { padding: 0 70px 0 0; margin-bottom: 10px; line-height: 1.3em; position: relative; overflow: hidden; }
.comment-info li { float: left; margin-right: 10px; }
.comment-info a { text-decoration: none; }
.comment-date a { color: #999; border-color: #999; }
.comment-info .vote { position: absolute; top: 0; right: 0; margin: 0; }
.comment-scroll-to-child { display: none; }
.comment-username { font-weight: bold; }
.comment-username-author { background: #2891D3; padding: 0 3px; }
.comment-username-author a { color: #fff; }
.comment-info { padding: 0 70px 0 0; margin-bottom: 15px; line-height: 1.3em; position: relative; }
.comment-info li { float: left; margin-right: 10px; }
.comment-info a { text-decoration: none; }
/* Содержимое комментария */
.comment-content.text { font-size: 13px; }
/* Логин */
.comment-info .comment-username { float: none; font: 20px/1.3em "Open Sans", sans-serif; margin-bottom: 5px; }
/* Избранное */
.comment-favourite {
display: none;
position: absolute;
width: 50px;
top: 65px;
left: -65px;
text-align: center;
}
.comment-favourite.favourite--added,
.comment-favourite.favourite--has-counter,
.comment:hover .comment-favourite { display: block; }
/* Дата */
.comment-date a { color: #999; }
.comment-date a:hover { color: #777; }
/* Голосование */
.comment-vote { position: absolute; top: 0; right: 0; margin: 0; }
.comment-vote.vote--not-voted.vote--count-zero { display: none; }
.comment:hover .comment-vote { display: block; }
/* Прокрутка к дочернему комментарию */
.comment-scroll-to { cursor: pointer; }
.comment-scroll-to-child { display: none; }
/* Текст комментария */
.comment-content.text { font-size: 13px; line-height: 1.7em; }
.comment-content.text blockquote { background: #fff; border-color: #ccc; padding: 5px 10px; margin-bottom: 5px; }
/* Кнопки */
.comment-actions { margin-top: 10px; }
.comment-actions li { display: inline; margin-right: 10px; }
/* Действия */
.comment-actions li { float: left; margin: 10px 10px 0 0; }
/* Сворачивание */
.comment-fold { background: #CBCBF3; }
.comment-fold { background: #CBCBF3; display: none; }
.comment-fold.open { background: transparent; }
/* Информация о редактировании */
.comment-edit-info { margin-top: 10px; font-size: 11px; opacity: .5; }
/**
* Форма комментирования
*
* @template comments/comment.form.tpl
*/
.comment-form { padding: 15px; background: #fafafa; }
.comment-form { padding: 15px; margin-bottom: 2px; background: #fafafa; }
.comment-form textarea { height: 150px; }
.comment-wrapper .comment-form { margin-left: 25px; }
.comment-reply-root { font-size: 20px; margin-bottom: 15px; }
.comment-reply-root { font-size: 20px; margin-bottom: 0; }
.comment-reply-root + .comment-form { margin-top: 15px; }
/**

View file

@ -98,18 +98,6 @@ jQuery(document).ready(function($){
$(window)._scrollable();
/**
* Toolbar
*/
$('.js-toolbar').toolbar({
target: '.grid-role-wrapper',
offsetX: 20
});
ls.toolbar.topic.init(); // Тул-бар топиков
ls.toolbar.up.init(); // Кнопка "UP"
/**
* Code highlight
*/
@ -137,7 +125,33 @@ jQuery(document).ready(function($){
/**
* Comments
*/
ls.comments.init();
$('.js-comments-topic').lsComments({
urls: {
add: aRouter['blog'] + 'ajaxaddcomment/',
load: aRouter['blog'] + 'ajaxresponsecomment/'
},
});
$('.js-comments-talk').lsComments({
urls: {
add: aRouter['talk'] + 'ajaxaddcomment/',
load: aRouter['talk'] + 'ajaxresponsecomment/'
},
});
/**
* Toolbar
*/
$('.js-toolbar').toolbar({
target: '.grid-role-wrapper',
offsetX: 20
});
ls.toolbar.topic.init(); // Тул-бар топиков
ls.toolbar.up.init(); // Кнопка "UP"
$('.js-toolbar-comments').lsToolbarComments();
/**
@ -217,10 +231,12 @@ jQuery(document).ready(function($){
});
// Голосование за комментарий
$('.js-vote-comment').vote({
urls: {
vote: aRouter['ajax'] + 'vote/comment/'
}
$('.js-vote-comment').livequery(function () {
$(this).vote({
urls: {
vote: aRouter['ajax'] + 'vote/comment/'
}
});
});

View file

@ -6,7 +6,7 @@
{extends file='blocks/block.aside.base.tpl'}
{block name='block_title'}<a href="{router page='comments'}" title="{$aLang.block_stream_comments_all}">{$aLang.block_stream}</a>{/block}
{block name='block_title'}<a href="{router page='stream'}">{$aLang.stream_menu}</a>{/block}
{block name='block_type'}stream{/block}
{block name='block_class'}block-nopadding{/block}

View file

@ -20,8 +20,11 @@
<a href="{if Config::Get('module.comment.nested_per_page')}{router page='comments'}{else}{$oTopic->getUrl()}#comment{/if}{$oComment->getId()}">{$oTopic->getTitle()|escape:'html'}</a>
<p>
<time datetime="{date_format date=$oComment->getDate() format='c'}">{date_format date=$oComment->getDate() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}</time> |
{$oTopic->getCountComment()}&nbsp;{$oTopic->getCountComment()|declension:$aLang.comments.comments_declension}
<time datetime="{date_format date=$oComment->getDate() format='c'}">
{date_format date=$oComment->getDate() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}
</time> |
{lang name='comments.comments_declension' count=$oTopic->getCountComment() plural=true}
</p>
</li>
{/foreach}
@ -29,5 +32,5 @@
</div>
<footer class="block-footer">
<a href="{router page='comments'}">{$aLang.block_stream_comments_all}</a> | <a href="{router page='rss'}allcomments/">RSS</a>
<a href="{router page='rss'}allcomments/">RSS</a>
</footer>

View file

@ -19,8 +19,11 @@
<a href="{$oTopic->getUrl()}">{$oTopic->getTitle()|escape:'html'}</a>
<p>
<time datetime="{date_format date=$oTopic->getDate() format='c'}">{date_format date=$oTopic->getDateAdd() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}</time> |
{$oTopic->getCountComment()} {$oTopic->getCountComment()|declension:$aLang.comments.comments_declension}
<time datetime="{date_format date=$oTopic->getDate() format='c'}">
{date_format date=$oTopic->getDateAdd() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}
</time> |
{lang name='comments.comments_declension' count=$oTopic->getCountComment() plural=true}
</p>
</li>
{/foreach}
@ -28,5 +31,5 @@
</div>
<footer class="block-footer">
<a href="{router page='index'}new/">{$aLang.block_stream_topics_all}</a> | <a href="{router page='rss'}new/">RSS</a>
<a href="{router page='rss'}new/">RSS</a>
</footer>

View file

@ -1,35 +0,0 @@
{**
* Форма комментирования
*
* @param integer $iTargetId
* @param string $sTargetType
*
* @styles css/comments.css
*}
{* Подключение редактора *}
{* Форма *}
<form method="post" class="comment-form js-comment-form" enctype="multipart/form-data" data-target-id="{$iTargetId}" data-target-type="{$sTargetType}">
{hook run='form_add_comment_begin'}
{* Текст комментария *}
{include 'components/editor/editor.tpl' sSet='light' sName='comment_text' sId='form_comment_text' bShowHelp=false sMediaTargetType='comment'}
{hook run='form_add_comment_end'}
{* Скрытые поля *}
{include 'components/field/field.hidden.tpl' sName='reply' sValue='0' sId='form_comment_reply'}
{include 'components/field/field.hidden.tpl' sName='cmt_target_id' sValue=$iTargetId}
{* Кнопки создания *}
{include 'components/button/button.tpl' sName='submit_comment' sText=$aLang.common.add sMods='primary' sClasses='js-comment-form-submit'}
{* Кнопки редактирования *}
{include 'components/button/button.tpl' sName='submit_comment' sType='button' sText=$aLang.common.save sMods='primary' sClasses='js-comment-form-update-submit hide'}
{include 'components/button/button.tpl' sName='submit_comment' sType='button' sText=$aLang.common.cancel sClasses='js-comment-form-update-cancel fl-r hide'}
{* Общие кнопки *}
{include 'components/button/button.tpl' sText=$aLang.common.preview_text sType='button' sClasses='js-comment-form-preview'}
</form>

View file

@ -1,130 +0,0 @@
{**
* Комментарий
*
* @param boolean bAllowNewComment true если разрешно добавлять новые комментарии
* @param boolean bOneComment
* @param boolean bNoCommentFavourites true если не нужно выводить кнопку добавления в избранное
* @param integer iAuthorId ID автора топика
* @param boolean bList true если комментарий выводится в списках (например на странице Избранные комментарии)
*
* @styles css/comments.css
*}
{$oUser = $oComment->getUser()}
{* Выводим ссылки на блог и топик в котором находится комментарий (только в списках) *}
{if $bList}
{$oTopic = $oComment->getTarget()}
{$oBlog = $oTopic->getBlog()}
<div class="comment-path">
<a href="{$oBlog->getUrlFull()}" class="comment-path-blog">{$oBlog->getTitle()|escape}</a> &rarr;
<a href="{$oTopic->getUrl()}">{$oTopic->getTitle()|escape}</a>
<a href="{$oTopic->getUrl()}#comments">({$oTopic->getCountComment()})</a>
</div>
{/if}
{* Комментарий *}
<section data-id="{$oComment->getId()}" id="comment{$oComment->getId()}" class="js-comment comment open
{if ! $bList}
{if $oComment->isBad()}
comment-bad
{/if}
{if $oComment->getDelete()}
comment-deleted
{elseif $oUserCurrent and $oComment->getUserId() == $oUserCurrent->getId()}
comment-self
{elseif $sDateReadLast <= $oComment->getDate()}
comment-new
{/if}
{else}
comment-list-item
{/if}">
{if ! $oComment->getDelete() or ($oUserCurrent and $oUserCurrent->isAdministrator())}
{* Аватар пользователя *}
<a href="{$oUser->getUserWebPath()}">
<img src="{$oUser->getProfileAvatarPath(48)}" alt="{$oUser->getDisplayName()}" class="comment-avatar" />
</a>
{* Информация *}
<ul class="comment-info">
{* Автор комментария *}
<li class="comment-username {if $iAuthorId == $oUser->getId()}comment-username-author{/if}" title="{if $sAuthorNotice}{$sAuthorNotice}{/if}">
<a href="{$oUser->getUserWebPath()}">{$oUser->getDisplayName()}</a>
</li>
{* Дата *}
<li class="comment-date">
<a href="{if Config::Get('module.comment.use_nested')}{router page='comments'}{else}#comment{/if}{$oComment->getId()}" class="link-dotted" title="{$aLang.comments.comment.url}">
<time datetime="{date_format date=$oComment->getDate() format='c'}">{date_format date=$oComment->getDate() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}</time>
</a>
</li>
{* Прокрутка к родительскии/дочернии комментариям *}
{if ! $bList and $oComment->getPid()}
<li class="comment-scroll-to comment-scroll-to-parent js-comment-scroll-to-parent" title="{$aLang.comments.comment.scroll_to_parent}" data-id="{$oComment->getId()}" data-parent-id="{$oComment->getPid()}">↑</li>
{/if}
<li class="comment-scroll-to comment-scroll-to-child js-comment-scroll-to-child" title="{$aLang.comments.comment.scroll_to_child}">↓</li>
{* Голосование *}
{if $oComment->getTargetType() != 'talk'}
<li>{include 'components/vote/vote.tpl' sClasses='js-vote-comment' oObject=$oComment bIsLocked=($oUserCurrent && $oUserCurrent->getId() == $oUser->getId())}</li>
{/if}
{* Избранное *}
{if $oUserCurrent and ! $bNoCommentFavourites}
<li>{include 'components/favourite/favourite.tpl' sClasses='js-favourite-comment' oObject=$oComment}</li>
{/if}
</ul>
{* Текст комментария *}
<div id="comment_content_id_{$oComment->getId()}" class="comment-content text">
{$oComment->getText()}
</div>
{* Информация о редактировании *}
{if $oComment->getDateEdit()}
<div>
{$aLang.comments.comment.edit_info}: {date_format date=$oComment->getDateEdit() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}
{if $oComment->getCountEdit()>1}
({$oComment->getCountEdit()} {$oComment->getCountEdit()|declension:$aLang.common.times_declension})
{/if}
</div>
{/if}
{* Кнопки ответа, удаления и т.д. *}
{if $oUserCurrent}
<ul class="comment-actions">
{if ! $bList and ! $oComment->getDelete() and ! $bAllowNewComment}
<li><a href="#" class="link-dotted js-comment-reply" data-id="{$oComment->getId()}">{$aLang.comments.comment.reply}</a></li>
{/if}
<li class="link-dotted comment-fold js-comment-fold open" data-id="{$oComment->getId()}" style="display: none"><a href="#">{$aLang.comments.folding.fold}</a></li>
{if $oComment->IsAllowEdit()}
<li>
<a href="#" class="link-dotted js-comment-update" data-id="{$oComment->getId()}">
{$aLang.common.edit}
{if $oComment->getEditTimeRemaining()}
(<span class="js-comment-update-timer" data-seconds="{$oComment->getEditTimeRemaining()}"></span>)
{/if}
</a>
</li>
{/if}
{if $oComment->IsAllowDelete()}
<li><a href="#" class="link-dotted js-comment-remove" data-id="{$oComment->getId()}">{($oComment->getDelete()) ? $aLang.comments.comment.restore : $aLang.common.remove}</a></li>
{/if}
{hook run='comment_action' comment=$oComment}
</ul>
{/if}
{else}
{$aLang.comments.comment.deleted}
{/if}
</section>

View file

@ -1,11 +0,0 @@
{**
* Список комментариев
*
* @styles css/comments.css
*}
{foreach $aComments as $oComment}
{include file='comments/comment.tpl' bList=true}
{/foreach}
{include 'components/pagination/pagination.tpl' aPaging=$aPaging}

View file

@ -1,86 +0,0 @@
{**
* Пагинация комментариев
*
* @styles assets/css/common.css
*}
{if $aPagingCmt and $aPagingCmt.iCountPage>1}
{if $aPagingCmt.sGetParams}
{$sGetSep = '&'}
{else}
{$sGetSep = '?'}
{/if}
<nav class="pagination pagination-comments js-pagination" role="navigation">
<ul class="pagination--list">
{if $aPagingCmt.iPrevPage}
<li class="pagination--item pagination--prev">
<a class="pagination--item-inner pagination--item-link js-pagination-prev"
href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$aPagingCmt.iPrevPage}"
title="{$aLang.paging_previos}">&larr; {$aLang.paging_previos}</a>
</li>
{else}
<li class="pagination--item pagination--prev">
<span class="pagination--item-inner pagination--item-text">&larr; {$aLang.paging_previos}</span>
</li>
{/if}
{if $aPagingCmt.iNextPage}
<li class="pagination--item pagination--next">
<a class="pagination--item-inner pagination--item-link js-pagination-next"
href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$aPagingCmt.iNextPage}"
title="{$aLang.paging_next}">{$aLang.paging_next} &rarr;</a>
</li>
{else}
<li class="pagination--item pagination--next">
<span class="pagination--item-inner pagination--item-text">{$aLang.paging_next} &rarr;</span>
</li>
{/if}
</ul>
<ul class="pagination--list">
{if Config::Get('module.comment.nested_page_reverse')}
{if $aPagingCmt.iCurrentPage > 1}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage=1">{$aLang.paging_first}</a></li>
{/if}
{foreach $aPagingCmt.aPagesLeft as $iPage}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$iPage}">{$iPage}</a></li>
{/foreach}
<li class="pagination--item active"><span class="pagination--item-inner pagination--item-text">{$aPagingCmt.iCurrentPage}</span></li>
{foreach $aPagingCmt.aPagesRight as $iPage}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$iPage}">{$iPage}</a></li>
{/foreach}
{if $aPagingCmt.iCurrentPage < $aPagingCmt.iCountPage}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$aPagingCmt.iCountPage}" title="{$aLang.paging_last}">{$aLang.paging_last}</a></li>
{/if}
{else}
{if $aPagingCmt.iCurrentPage < $aPagingCmt.iCountPage}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$aPagingCmt.iCountPage}">{$aLang.paging_last}</a></li>
{/if}
{foreach $aPagingCmt.aPagesRight as $iPage}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$iPage}">{$iPage}</a></li>
{/foreach}
<li class="pagination--item active"><span class="pagination--item-inner pagination--item-text">{$aPagingCmt.iCurrentPage}</span></li>
{foreach $aPagingCmt.aPagesLeft as $iPage}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage={$iPage}">{$iPage}</a></li>
{/foreach}
{if $aPagingCmt.iCurrentPage > 1}
<li class="pagination--item"><a class="pagination--item-inner pagination--item-link" href="{$aPagingCmt.sGetParams}{$sGetSep}cmtpage=1">{$aLang.paging_first}</a></li>
{/if}
{/if}
</ul>
</nav>
{/if}

View file

@ -1,104 +0,0 @@
{**
* Комментарии
*
* @styles css/comments.css
*}
{* Добавляем в тулбар кнопку обновления комментариев *}
{add_block group='toolbar' name='toolbar/toolbar.comment.tpl'
aPagingCmt = $aPagingCmt
iTargetId = $iTargetId
sTargetType = $sTargetType
iMaxIdComment = $iMaxIdComment}
{hook run='comment_tree_begin' iTargetId=$iTargetId sTargetType=$sTargetType}
{**
* Комментарии
*}
<div class="comments js-comments" id="comments">
{**
* Хидер
*}
{if ! $bForbidNewComment || ( $bForbidNewComment && $iCountComment )}
<header class="comments-header">
<h3 class="comments-title js-comments-title">{$iCountComment} {$iCountComment|declension:$aLang.comments.comments_declension}</h3>
{* Подписка на комментарии *}
{if $bAllowSubscribe and $oUserCurrent}
<p><label class="comments-subscribe">
<input
type="checkbox"
id="comment_subscribe"
class="input-checkbox"
onchange="ls.subscribe.toggle('{$sTargetType}_new_comment','{$iTargetId}','',this.checked);"
{if $oSubscribeComment and $oSubscribeComment->getStatus()}checked{/if}>
{$aLang.comments.subscribe}
</label></p><br>
{/if}
{* Свернуть/развернуть все *}
<a href="#" class="link-dotted js-comments-fold-all">{$aLang.comments.folding.fold_all}</a> |
<a href="#" class="link-dotted js-comments-unfold-all">{$aLang.comments.folding.unfold_all}</a>
</header>
{/if}
{**
* Комментарии
*}
{$iCurrentLevel = -1}
{$iMaxLevel = Config::Get('module.comment.max_tree')}
{foreach $aComments as $oComment}
{$iCommentLevel = $oComment->getLevel()}
{if $iCommentLevel > $iMaxLevel}
{$iCommentLevel = $iMaxLevel}
{/if}
{if $iCurrentLevel > $iCommentLevel}
{section name=closelist1 loop=$iCurrentLevel - $iCommentLevel + 1}</div>{/section}
{elseif $iCurrentLevel == $iCommentLevel && ! $oComment@first}
</div>
{/if}
<div class="comment-wrapper js-comment-wrapper" data-id="{$oComment->getId()}">
{include './comment.tpl'}
{$iCurrentLevel = $iCommentLevel}
{if $oComment@last}
{section name=closelist2 loop=$iCurrentLevel + 1}</div>{/section}
{/if}
{/foreach}
</div>
{**
* Пагинация
*}
{include './comment_pagination.tpl' aPagingCmt=$aPagingCmt}
{hook run='comment_tree_end' iTargetId=$iTargetId sTargetType=$sTargetType}
{**
* Форма добавления комментария
*}
{if $bForbidNewComment}
{include 'components/alert/alert.tpl' sMods='info' mAlerts=$sNoticeNotAllow}
{else}
{if $oUserCurrent}
{* Ссылка открывающая форму *}
<h4 class="comment-reply-root js-comment-reply js-comment-reply-root" data-id="0">
<a href="#" class="link-dotted">{$sNoticeCommentAdd}</a>
</h4>
{include './comment.form.tpl'}
{else}
{include 'components/alert/alert.tpl' sMods='info' mAlerts=$aLang.comments.alerts.unregistered}
{/if}
{/if}

View file

@ -105,7 +105,10 @@
{* Не показываем если комментирование запрещено и кол-во комментариев равно нулю *}
{if $bTopicList && ( ! $oTopic->getForbidComment() || ( $oTopic->getForbidComment() && $oEntry->getCountComment() ) )}
<li class="topic-info-item topic-info-item-comments">
<a href="{$oEntry->getUrl()}#comments" title="{$aLang.topic_comment_read}">{$oEntry->getCountComment()} {$oEntry->getCountComment()|declension:$aLang.comments.comments_declension}</a>
<a href="{$oEntry->getUrl()}#comments" title="{$aLang.topic_comment_read}">
{lang name='comments.comments_declension' count=$oEntry->getCountComment() plural=true}
</a>
{if $oEntry->getCountCommentNew()}<span>+{$oEntry->getCountCommentNew()}</span>{/if}
</li>
{/if}

View file

@ -0,0 +1,63 @@
{**
* Форма комментирования
*
* @param integer $iTargetId
* @param string $sTargetType
* @param string $sClasses Дополнительные классы
* @param string $sAttributes Атрибуты
* @param string $sMods Модификаторы
* @param string $sEditorSet (light) Стиль редактора
*
* @styles css/comments.css
*}
{* Название компонента *}
{$sComponent = 'comment-form'}
{* Переменные *}
{$iTargetId = $smarty.local.iTargetId}
{$sTargetType = $smarty.local.sTargetType}
{* Форма *}
<form method = "post"
class = "{$sComponent} {mod name=$sComponent mods=$sMods} {$smarty.local.classes} js-comment-form"
enctype = "multipart/form-data"
data-target-id = "{$iTargetId}"
data-target-type = "{$sTargetType}"
{$smarty.local.sAttributes}>
{block 'comment-form'}
{hook run='comment-form-begin'}
{block 'comment-form-fields'}
{* Скрытые поля *}
{include 'components/field/field.hidden.tpl' sName='reply' sValue='0' sId='form_comment_reply'}
{include 'components/field/field.hidden.tpl' sName='cmt_target_id' sValue=$iTargetId}
{* Текст комментария *}
{include 'components/editor/editor.tpl'
sSet = $smarty.local.sEditorSet|default:'light'
sName = 'comment_text'
sInputClasses = 'js-comment-form-text'
bShowHelp = false
sMediaTargetType = 'comment'}
{/block}
{hook run='comment-form-end'}
{**
* Кнопки
*}
{* Кнопка добавления *}
{include 'components/button/button.tpl' sName='submit_comment' sText=$aLang.common.add sMods='primary' sClasses='js-comment-form-submit'}
{* Кнопки редактирования *}
{include 'components/button/button.tpl' sName='submit_comment' sType='button' sText=$aLang.common.save sMods='primary' sClasses='js-comment-form-update-submit hide'}
{include 'components/button/button.tpl' sName='submit_comment' sType='button' sText=$aLang.common.cancel sClasses='js-comment-form-update-cancel fl-r hide'}
{* Кнопка превью текста *}
{include 'components/button/button.tpl' sText=$aLang.common.preview_text sType='button' sClasses='js-comment-form-preview'}
{/block}
</form>

View file

@ -0,0 +1,102 @@
{**
* Комментарии
*
* @param string $sTargetType
* @param integer $iTargetId
* @param array $aComments
* @param boolean $bForbidAdd
* @param string $sHeading
* @param integer $iCountComment
* @param boolean $bAllowSubscribe
*
* @styles css/comments.css
*}
{block 'comment-list-options'}
{$iTargetId = $smarty.local.iTargetId}
{$sTargetType = $smarty.local.sTargetType}
{$iCountComment = $smarty.local.iCountComment}
{/block}
{add_block group='toolbar' name='toolbar/toolbar.comment.tpl' target='.js-comment'}
<div class="comments js-comments {$smarty.local.sClasses}"
id="comments"
data-target-type="{$sTargetType}"
data-target-id="{$iTargetId}"
data-comment-last-id="{$iMaxIdComment}">
{**
* Заголовок
*}
<header class="comments-header">
<h3 class="comments-title js-comments-title">
{lang name='comments.comments_declension' count=$iCountComment plural=true}
</h3>
</header>
{**
* Экшнбар
*}
{* Свернуть/развернуть все комментарии *}
{$aItems = [ [ 'classes' => 'js-comments-fold-all-toggle', 'text' => $aLang.comments.folding.fold_all ] ]}
{* Подписка на комментарии *}
{if $bAllowSubscribe and $oUserCurrent}
{* Подписан пользователь на комментарии или нет *}
{$bIsSubscribed = $oSubscribeComment && $oSubscribeComment->getStatus()}
{$aItems[] = [
'classes' => "comments-subscribe js-comments-subscribe {if $bIsSubscribed}active{/if}",
'attributes' => "data-type=\"{$sTargetType}\" data-target-id=\"{$iTargetId}\"",
'text' => ( $bIsSubscribed ) ? $aLang.comments.unsubscribe : $aLang.comments.subscribe
]}
{/if}
{* TODO: Добавить хук *}
{include 'components/actionbar/actionbar.tpl' aItems=$aItems sClasses='comments-actions'}
{**
* Комментарии
*}
<div class="comment-list js-comment-list" data-target-type="{$sTargetType}" data-target-id="{$iTargetId}">
{include './comment-tree.tpl'
aComments = $smarty.local.aComments
bForbidAdd = $bForbidAdd
bShowFavourite = $smarty.local.bShowFavourite
bShowVote = $smarty.local.bShowVote}
</div>
{**
* TODO: Пагинация
*}
{*include 'comments/comment_pagination.tpl' aPagingCmt=$aPagingCmt*}
{**
* Форма добавления комментария
*}
{* Проверяем запрещено комментирование или нет *}
{if $bForbidAdd}
{include 'components/alert/alert.tpl' sMods='info' mAlerts=$sNoticeNotAllow}
{* Если разрешено то показываем форму добавления комментария *}
{else}
{if $oUserCurrent}
{* Кнопка открывающая форму *}
<h4 class="comment-reply-root js-comment-reply js-comment-reply-root" data-id="0">
<a href="#" class="link-dotted">{$sNoticeCommentAdd}</a>
</h4>
{* Форма добавления комментария *}
{include './comment-form.tpl' sTargetType=$sTargetType iTargetId=$iTargetId}
{else}
{include 'components/alert/alert.tpl' sMods='info' mAlerts=$aLang.comments.alerts.unregistered}
{/if}
{/if}
</div>

View file

@ -0,0 +1,62 @@
{**
* Дерево комментариев
*
* @component comment
* @styles css/comments.css
* @scripts js/comments.js
*
* @param array $aComments Комментарии
* @param string $sClasses Дополнительные классы
* @param string $sAttributes Атрибуты
* @param string $sMods
* @param boolean $bShowVote (true) Показывать или нет голосование
* @param boolean $bShowReply (true) Показывать или нет кнопку Ответить
* @param integer $iAuthorId
* @param string $sDateReadLast
*}
{* Текущая вложенность *}
{$iCurrentLevel = -1}
{* Максимальная вложенность *}
{$iMaxLevel = $smarty.local.iMaxLevel|default:Config::Get('module.comment.max_tree')}
{* Добавляем возможность переопределить стандартный шаблон комментария *}
{$sTemplate = $smarty.local.template|default:'./comment.tpl'}
{* Построение дерева комментариев *}
{foreach $smarty.local.aComments as $oComment}
{* Ограничиваем вложенность комментария максимальным значением *}
{$iCommentLevel = ( $oComment->getLevel() > $iMaxLevel ) ? $iMaxLevel : $oComment->getLevel()}
{* Закрываем блоки-обертки *}
{if $iCurrentLevel > $iCommentLevel}
{section closewrappers1 loop=$iCurrentLevel - $iCommentLevel + 1}</div>{/section}
{elseif $iCurrentLevel == $iCommentLevel && ! $oComment@first}
</div>
{/if}
{* Устанавливаем текущий уровень вложенности *}
{$iCurrentLevel = $iCommentLevel}
{* Вспомогательный блок-обертка *}
<div class="comment-wrapper js-comment-wrapper" data-id="{$oComment->getId()}">
{* Комментарий *}
{include "$sTemplate"
oComment = $oComment
bShowVote = $smarty.local.bShowVote
bShowReply = ! $smarty.local.bForbidAdd
bShowFavourite = $smarty.local.bShowFavourite
sDateReadLast = $sDateReadLast
bIsHidden = $oComment->getDelete()
bShowScroll = true
bShowEdit = true}
{* Закрываем блоки-обертки после последнего комментария *}
{if $oComment@last}
{section closewrappers2 loop=$iCurrentLevel + 1}</div>{/section}
{/if}
{foreachelse}
{include 'components/alert/alert.tpl' sMods='empty' mAlerts=$aLang.common.empty}
{/foreach}

View file

@ -0,0 +1,190 @@
{**
* Комментарий
*
* @component comment
* @styles css/comments.css
* @scripts js/comments.js
*
* @param object $oComment Комментарий
* @param string $sClasses Дополнительные классы
* @param string $sAttributes Атрибуты
* @param string $sMods Модификаторы
* @param boolean $bShowVote (true) Показывать или нет голосование
* @param boolean $bShowReply (true) Показывать или нет кнопку Ответить
* @param integer $iAuthorId
* @param string $sDateReadLast
*}
{* Название компонента *}
{$sComponent = 'comment'}
{* Переменные *}
{$oComment = $smarty.local.oComment}
{$sMods = $smarty.local.sMods}
{$bShowEdit = $smarty.local.bShowEdit|default:true}
{$bIsHidden = $smarty.local.bIsHidden}
{$oUser = $oComment->getUser()}
{$iCommentId = $oComment->getId()}
{* Получаем ссылку на комментарий *}
{* TODO: Вынести в бэкенд *}
{$sPermalink = ( Config::Get('module.comment.use_nested') ) ? "{router page='comments'}{$iCommentId}" : "#comment{$iCommentId}"}
{**
* Добавляем модификаторы
*}
{* Комментарий с отрицательным рейтингом *}
{if $smarty.local.bShowVote && $oComment->isBad()}
{$sMods = "$sMods bad"}
{/if}
{* Автор комментария является автором объекта к которому оставлен комментарий *}
{if $smarty.local.iAuthorId == $oUser->getId()}
{$sMods = "$sMods author"}
{/if}
{* Комментарий удален *}
{if $bIsHidden}
{$sMods = "$sMods deleted"}
{* Комментарий текущего залогиненого пользователя *}
{elseif $oUserCurrent && $oComment->getUserId() == $oUserCurrent->getId()}
{$sMods = "$sMods self"}
{* Непрочитанный комментарий *}
{elseif $smarty.local.sDateReadLast && strtotime($smarty.local.sDateReadLast) <= strtotime($oComment->getDate())}
{$sMods = "$sMods new"}
{/if}
{**
* Комментарий
* Атрибут id используется для ссылки на комментарий через хэш в урл #comment123
*}
<section class = "{$sComponent} {mod name=$sComponent mods=$sMods} {$smarty.local.sClasses} open js-{$sComponent}"
id = "comment{$iCommentId}"
data-id = "{$iCommentId}"
{$smarty.local.sAttributes}>
{* Показываем удаленные комментарии только администраторам *}
{if ! $bIsHidden || ( $oUserCurrent && $oUserCurrent->isAdministrator() )}
{* Аватар пользователя *}
<a href="{$oUser->getUserWebPath()}" class="{$sComponent}-avatar">
<img src="{$oUser->getProfileAvatarPath(64)}" alt="{$oUser->getDisplayName()}" />
</a>
{* Информация *}
<ul class="{$sComponent}-info clearfix">
{* Автор комментария *}
<li class="{$sComponent}-username">
<a href="{$oUser->getUserWebPath()}">
{$oUser->getDisplayName()}
</a>
</li>
{* Дата добавления комментария *}
{* Так же является ссылкой на комментарий *}
<li class="{$sComponent}-date">
<a href="{$sPermalink}" title="{$aLang.comments.comment.url}">
<time datetime="{date_format date=$oComment->getDate() format='c'}">
{date_format date=$oComment->getDate() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}
</time>
</a>
</li>
{* Прокрутка к родительскому комментарию *}
{if $smarty.local.bShowScroll}
{if $oComment->getPid()}
<li class = "{$sComponent}-scroll-to {$sComponent}-scroll-to-parent js-comment-scroll-to-parent"
title = "{$aLang.comments.comment.scroll_to_parent}"
data-id = "{$iCommentId}"
data-parent-id = "{$oComment->getPid()}">↑</li>
{/if}
{* Прокрутка к дочернему комментарию *}
<li class = "{$sComponent}-scroll-to {$sComponent}-scroll-to-child js-comment-scroll-to-child"
title = "{$aLang.comments.comment.scroll_to_child}">↓</li>
{/if}
{* Голосование *}
{if $smarty.local.bShowVote}
<li>
{* Блокируем голосование для гостей или если залогиненый пользователь является автором комментария*}
{include 'components/vote/vote.tpl'
sClasses = "{$sComponent}-vote js-vote-{$sComponent}"
oObject = $oComment
bIsLocked = ($oUserCurrent && $oUserCurrent->getId() == $oUser->getId())}
</li>
{/if}
{* Избранное *}
{if $oUserCurrent && $smarty.local.bShowFavourite}
<li>
{include 'components/favourite/favourite.tpl' sClasses='comment-favourite js-favourite-comment' oObject=$oComment}
</li>
{/if}
</ul>
{* Текст комментария *}
<div class="{$sComponent}-content text">
{$oComment->getText()}
</div>
{* Информация о редактировании *}
{if $oComment->getDateEdit()}
<div class="{$sComponent}-edit-info">
{$aLang.comments.comment.edit_info}:
<span class="{$sComponent}-edit-info-time js-{$sComponent}-edit-time">
{date_format date=$oComment->getDateEdit() hours_back="12" minutes_back="60" now="60" day="day H:i" format="j F Y, H:i"}
</span>
{if $oComment->getCountEdit() > 1}
({$oComment->getCountEdit()} {$oComment->getCountEdit()|declension:$aLang.common.times_declension})
{/if}
</div>
{/if}
{* Действия *}
<ul class="comment-actions clearfix">
{* Ответить *}
{if $oUserCurrent && ! $bIsHidden && $smarty.local.bShowReply|default:true}
<li>
<a href="#" class="link-dotted js-comment-reply" data-id="{$iCommentId}">{$aLang.comments.comment.reply}</a>
</li>
{/if}
{* Сворачивание *}
<li class="comment-fold js-comment-fold open" data-id="{$iCommentId}">
<a href="#" class="link-dotted">{$aLang.comments.folding.fold}</a>
</li>
{* Редактировать *}
{if $smarty.local.bShowEdit && $oUserCurrent && $oComment->IsAllowEdit()}
<li>
<a href="#" class="link-dotted js-comment-update" data-id="{$iCommentId}">
{$aLang.common.edit}
{* Отображение времени отведенного для редактирования *}
{* Используется плагин jquery.timers *}
{if $oComment->getEditTimeRemaining()}
(<span class="js-comment-update-timer" data-seconds="{$oComment->getEditTimeRemaining()}">...</span>)
{/if}
</a>
</li>
{/if}
{* Удалить *}
{if $oUserCurrent && $oComment->IsAllowDelete()}
<li>
<a href="#" class="link-dotted js-comment-remove" data-id="{$iCommentId}">
{( $bIsHidden ) ? $aLang.comments.comment.restore : $aLang.common.remove}
</a>
</li>
{/if}
</ul>
{else}
{$aLang.comments.comment.deleted}
{/if}
</section>

View file

@ -0,0 +1,40 @@
{**
* Тестирование компонента comment
*}
{extends 'layouts/layout.base.tpl'}
{block 'layout_options'}
{$bNoSidebar = true}
{/block}
{block 'layout_page_title'}
Component <span>comment</span>
{/block}
{block 'layout_content'}
{function test_heading}
<br><h3>{$text}</h3>
{/function}
{* Полная версия *}
{test_heading text='Default'}
<div class="comments js-comments" id="comments">
<div class="comment-wrapper js-comment-wrapper" data-id="{$comment1->getId()}">
{include 'components/comment/comment.tpl'
oComment = $comment1
bShowVote = true
sDateLastRead = '2014-01-01 00:00:00'}
</div>
<div class="comment-wrapper js-comment-wrapper" data-id="{$comment2->getId()}">
{include 'components/comment/comment.tpl'
comment = $comment2
bShowVote = true
sDateLastRead = '2014-01-01 00:00:00'}
</div>
{include 'comments/comment.form.tpl'}
</div>
{/block}

View file

@ -11,7 +11,7 @@
{block 'layout_head' append}
<script>
ls.lang.load({json var = $aLangJs});
ls.lang.load({lang_load name="comments.comments_declension, comments.folding.unfold, comments.folding.fold, poll.notices.error_answers_max, blog.blog, favourite.add, favourite.remove, geo_select_city, geo_select_region, blog.add.fields.type.note_open, blog.add.fields.type.note_close, common.success.add, common.success.remove"});
ls.lang.load({lang_load name="comments.comments_declension, comments.unsubscribe, comments.subscribe, comments.folding.unfold, comments.folding.fold, comments.folding.unfold_all, comments.folding.fold_all, poll.notices.error_answers_max, blog.blog, favourite.add, favourite.remove, geo_select_city, geo_select_region, blog.add.fields.type.note_open, blog.add.fields.type.note_close, common.success.add, common.success.remove"});
ls.registry.set('comment_max_tree', {json var=Config::Get('module.comment.max_tree')});
ls.registry.set('block_stream_show_tip', {json var=Config::Get('block.stream.show_tip')});