Deep comment link to select and mark comment in content (#5168)

* Deep comment link to select and mark comment in content

* Update CHANGELOG_DEV.md

* Use warning color for selected comment

* Permalink for comments

* Display comment permalink in modal window

* Small change for comment permalink title

* Remove container name from comment permalink

* Convert comment controls to menu widgets

* Small changes

* Use MenuLinks instead of custom widgets

* Fix encoding

* Fix opening modal window with comment permalink

Co-authored-by: Lucas Bartholemy <luke-@users.noreply.github.com>
Co-authored-by: Lucas Bartholemy <lucas@bartholemy.com>
This commit is contained in:
Yuriy Bakhtin 2021-10-04 19:48:34 +03:00 committed by GitHub
parent d1ac039729
commit f9d8c317b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 339 additions and 81 deletions

View File

@ -1,6 +1,7 @@
1.10.0-beta.1 (Unreleased)
--------------------------
- Enh #4399: Deep comment link to select and mark comment in content
- Enh #4242: More failsave module loading when reading module config
- Enh #5197: Default .htaccess - Remove Options +FollowSymLinks
- Enh #4495: Allow to lock comments per content

View File

@ -8,9 +8,11 @@
namespace humhub\modules\comment\activities;
use humhub\modules\comment\models\Comment;
use Yii;
use humhub\modules\activity\components\BaseActivity;
use humhub\modules\activity\interfaces\ConfigurableActivityInterface;
use yii\helpers\Url;
/**
* NewComment activity
@ -30,6 +32,11 @@ class NewComment extends BaseActivity implements ConfigurableActivityInterface
*/
public $viewName = "newComment";
/**
* @var Comment
*/
public $source;
/**
* @inheritdoc
*/
@ -46,4 +53,12 @@ class NewComment extends BaseActivity implements ConfigurableActivityInterface
return Yii::t('CommentModule.base', 'Whenever a new comment was written.');
}
/**
* @inheritdoc
*/
public function getUrl()
{
return Url::to(['/comment/perma', 'id' => $this->source->id], true);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\comment\controllers;
use humhub\components\Controller;
use humhub\modules\comment\models\Comment;
use yii\web\NotFoundHttpException;
/**
* PermaController provides URL to view a Comment.
*
* @package humhub.modules_core.comment.controllers
* @since 1.10
*/
class PermaController extends Controller
{
/**
* Action to process comment permalink URL
*
* @param $id
* @return \yii\console\Response|\yii\web\Response
* @throws NotFoundHttpException
*/
public function actionIndex($id)
{
$comment = Comment::findOne(['id' => $id]);
if (!$comment || !$comment->canRead()) {
throw new NotFoundHttpException();
}
return $this->redirect($comment->content->container->createUrl(null, [
'contentId' => $comment->content->id,
'commentId' => $comment->id,
]));
}
}

View File

@ -201,10 +201,11 @@ class Comment extends ContentAddonActiveRecord implements ContentOwner
* @param $model
* @param $id
* @param int|null $limit when null the default limit will used
* @param int|null $currentCommentId ID of the current Comment which should be visible on the limited result
*
* @return Comment[] the comments
*/
public static function GetCommentsLimited($model, $id, $limit = null)
public static function GetCommentsLimited($model, $id, $limit = null, $currentCommentId = null)
{
if ($limit === null) {
/** @var Module $module */
@ -220,7 +221,11 @@ class Comment extends ContentAddonActiveRecord implements ContentOwner
$query = Comment::find();
$query->offset($commentCount - $limit);
if (Comment::findOne(['id' => $currentCommentId])) {
$query->orderBy('`comment`.`id` <= ' . (intval($currentCommentId) + intval($limit) - 1));
} else {
$query->orderBy('created_at ASC');
}
$query->limit($limit);
$query->where(['object_model' => $model, 'object_id' => $id]);
$query->joinWith('user');

View File

@ -121,10 +121,12 @@ humhub.module('comment', function (module, require, $) {
return Widget.instance(this.$.find('div.humhub-ui-richtext:first'));
};
Comment.prototype.delete = function () {
Comment.prototype.delete = function (evt) {
var $form = this.$.parent().siblings('.comment_create');
var hideHr = !this.isNestedComment() && $form.length && !this.$.siblings('.media').length;
this.$.data('content-delete-url', evt.$trigger.data('content-delete-url'));
this.super('delete', {modal: module.config.modal.delteConfirm}).then(function ($confirm) {
if ($confirm) {
module.log.success('success.delete');

View File

@ -8,7 +8,6 @@
namespace humhub\modules\comment\widgets;
use yii\helpers\Url;
use humhub\components\Widget;
/**
@ -28,28 +27,26 @@ class Comment extends Widget
*/
public $justEdited = false;
/**
* @var string Default style class of div wrapper around Comment block
*/
public $defaultClass = 'media';
/**
* @var string Additional style class of div wrapper around Comment block
*/
public $additionalClass = '';
/**
* @inheritdoc
*/
public function run()
{
$deleteUrl = Url::to(['/comment/comment/delete',
'objectModel' => $this->comment->object_model, 'objectId' => $this->comment->object_id, 'id' => $this->comment->id]);
$editUrl = Url::to(['/comment/comment/edit',
'objectModel' => $this->comment->object_model, 'objectId' => $this->comment->object_id, 'id' => $this->comment->id]);
$loadUrl = Url::to(['/comment/comment/load',
'objectModel' => $this->comment->object_model, 'objectId' => $this->comment->object_id, 'id' => $this->comment->id]);
return $this->render('comment', [
'comment' => $this->comment,
'user' => $this->comment->user,
'justEdited' => $this->justEdited,
'deleteUrl' => $deleteUrl,
'editUrl' => $editUrl,
'loadUrl' => $loadUrl,
'createdAt' => $this->comment->created_at,
'canEdit' => $this->comment->canEdit(),
'canDelete' => $this->comment->canDelete(),
'class' => trim($this->defaultClass . ' ' . $this->additionalClass),
]);
}

View File

@ -0,0 +1,95 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\comment\widgets;
use humhub\libs\Html;
use humhub\modules\comment\models\Comment;
use humhub\modules\ui\menu\MenuEntry;
use humhub\modules\ui\menu\MenuLink;
use humhub\modules\ui\menu\WidgetMenuEntry;
use humhub\modules\ui\menu\widgets\Menu;
use Yii;
use yii\helpers\Url;
/**
* This widget renders the controls menu for a Comment.
*
* @since 1.10
*/
class CommentControls extends Menu
{
/**
* @var Comment
*/
public $comment;
/**
* @inheritdoc
*/
public $template = '@comment/widgets/views/commentControls';
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->initControls();
}
public function initControls()
{
$this->addEntry(new MenuLink([
'label' => Yii::t('CommentModule.base', 'Permalink'),
'icon' => 'link',
'url' => '#',
'htmlOptions' => [
'data-action-click' => 'content.permalink',
'data-content-permalink' => Url::to(['/comment/perma', 'id' => $this->comment->id], true),
'data-content-permalink-title' => Yii::t('CommentModule.base', '<strong>Permalink</strong> to this comment'),
],
'sortOrder' => 100,
]));
if ($this->comment->canEdit()) {
$this->addEntry(new EditLink(['sortOrder' => 200, 'comment' => $this->comment]));
}
if ($this->comment->canDelete()) {
$deleteUrl = Url::to(['/comment/comment/delete', 'objectModel' => $this->comment->object_model,
'objectId' => $this->comment->object_id,
'id' => $this->comment->id,
]);
$this->addEntry(new MenuLink([
'label' => Yii::t('CommentModule.base', 'Delete'),
'icon' => 'delete',
'url' => '#',
'htmlOptions' => [
'data-action-click' => 'delete',
'data-content-delete-url' => $deleteUrl,
],
'sortOrder' => 300,
]));
}
}
/**
* @inheritdoc
*/
public function getAttributes()
{
return [
'class' => 'nav nav-pills preferences'
];
}
}

View File

@ -36,11 +36,14 @@ class Comments extends Widget
$objectModel = get_class($this->object);
$objectId = $this->object->getPrimaryKey();
$streamQuery = Yii::$app->request->getQueryParam('StreamQuery');
$currentCommentId = empty($streamQuery['commentId']) ? null : $streamQuery['commentId'];
// Count all Comments
$commentCount = CommentModel::GetCommentCount($objectModel, $objectId);
$comments = [];
if ($commentCount !== 0) {
$comments = CommentModel::GetCommentsLimited($objectModel, $objectId, $module->commentsPreviewMax);
$comments = CommentModel::GetCommentsLimited($objectModel, $objectId, $module->commentsPreviewMax, $currentCommentId);
}
$isLimited = ($commentCount > $module->commentsPreviewMax);
@ -48,6 +51,7 @@ class Comments extends Widget
return $this->render('comments', [
'object' => $this->object,
'comments' => $comments,
'currentCommentId' => $currentCommentId,
'objectModel' => $objectModel,
'objectId' => $objectId,
'id' => $this->object->getUniqueId(),

View File

@ -0,0 +1,53 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\comment\widgets;
use humhub\libs\Html;
use humhub\modules\comment\models\Comment;
use humhub\modules\ui\icon\widgets\Icon;
use humhub\modules\ui\menu\WidgetMenuEntry;
use Yii;
use yii\helpers\Url;
/**
* EditLink for Comment
*
* @since 1.10
*/
class EditLink extends WidgetMenuEntry
{
/**
* @var Comment $comment
*/
public $comment;
/**
* @inheritdoc
*/
public function renderEntry($extraHtmlOptions = [])
{
$editUrl = Url::to(['/comment/comment/edit',
'objectModel' => $this->comment->object_model,
'objectId' => $this->comment->object_id,
'id' => $this->comment->id,
]);
$loadUrl = Url::to(['/comment/comment/load',
'objectModel' => $this->comment->object_model,
'objectId' => $this->comment->object_id,
'id' => $this->comment->id,
]);
return Html::a(Icon::get('edit') . ' ' . Yii::t('CommentModule.base', 'Edit'), '#',
['class' => 'comment-edit-link', 'data-action-click' => 'edit', 'data-action-url' => $editUrl]) .
Html::a(Icon::get('edit') . ' ' . Yii::t('CommentModule.base', 'Cancel Edit'), '#',
['class' => 'comment-cancel-edit-link', 'data-action-click' => 'cancelEdit', 'data-action-url' => $loadUrl, 'style' => 'display:none']);
}
}

View File

@ -1,72 +1,28 @@
<?php
use humhub\libs\Html;
use humhub\modules\comment\widgets\CommentControls;
use humhub\modules\content\widgets\UpdatedIcon;
use humhub\modules\ui\icon\widgets\Icon;
use humhub\modules\comment\widgets\CommentEntryLinks;
use humhub\widgets\TimeAgo;
use humhub\modules\content\widgets\richtext\RichText;
use humhub\modules\user\widgets\Image as UserImage;
use humhub\modules\file\widgets\ShowFiles;
use humhub\modules\comment\widgets\Comments;
use humhub\modules\comment\models\Comment;
/* @var $this \humhub\modules\ui\view\components\View */
/* @var $comment \humhub\modules\comment\models\Comment */
/* @var $deleteUrl string */
/* @var $editUrl string */
/* @var $loadUrl string */
/* @var $user \humhub\modules\user\models\User */
/* @var $canEdit bool */
/* @var $canDelete bool */
/* @var $createdAt string */
/* @var $updatedAt string */
/** @var \humhub\modules\comment\Module $module */
$module = Yii::$app->getModule('comment');
/* @var $class string */
?>
<div class="media" id="comment_<?= $comment->id; ?>"
data-action-component="comment.Comment"
data-content-delete-url="<?= $deleteUrl ?>">
<div class="<?= $class; ?>" id="comment_<?= $comment->id; ?>"
data-action-component="comment.Comment">
<hr class="comment-separator">
<?php if ($canEdit || $canDelete) : ?>
<div class="comment-entry-loader pull-right"></div>
<ul class="nav nav-pills preferences">
<li class="dropdown ">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"
aria-label="<?= Yii::t('base', 'Toggle comment menu'); ?>" aria-haspopup="true">
<?= Icon::get('dropdownToggle') ?>
</a>
<?= CommentControls::widget(['comment' => $comment]) ?>
<ul class="dropdown-menu pull-right">
<?php if ($canEdit): ?>
<li>
<a href="#" class="comment-edit-link" data-action-click="edit"
data-action-url="<?= $editUrl ?>">
<?= Icon::get('edit') ?> <?= Yii::t('CommentModule.base', 'Edit') ?>
</a>
<a href="#" class="comment-cancel-edit-link" data-action-click="cancelEdit"
data-action-url="<?= $loadUrl ?>" style="display:none;">
<?= Icon::get('edit') ?> <?= Yii::t('CommentModule.base', 'Cancel Edit') ?>
</a>
</li>
<?php endif; ?>
<?php if ($canDelete): ?>
<li>
<a href="#" data-action-click="delete">
<?= Icon::get('delete') ?> <?= Yii::t('CommentModule.base', 'Delete') ?>
</a>
</li>
<?php endif; ?>
</ul>
</li>
</ul>
<?php endif; ?>
<?= UserImage::widget(['user' => $user, 'width' => 25, 'htmlOptions' => ['class' => 'pull-left', 'data-contentcontainer-id' => $user->contentcontainer_id]]); ?>
<div>
<div class="media-body">

View File

@ -0,0 +1,32 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\libs\Html;
use humhub\modules\ui\icon\widgets\Icon;
use humhub\modules\ui\menu\MenuEntry;
/* @var MenuEntry[] $entries */
/* @var array $options */
?>
<div class="comment-entry-loader pull-right"></div>
<?= Html::beginTag('ul', $options) ?>
<li class="dropdown ">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"
aria-label="<?= Yii::t('base', 'Toggle comment menu'); ?>" aria-haspopup="true">
<?= Icon::get('dropdownToggle') ?>
</a>
<ul class="dropdown-menu pull-right">
<?php foreach ($entries as $entry) : ?>
<li>
<?= $entry->render() ?>
</li>
<?php endforeach; ?>
</ul>
</li>
<?= Html::endTag('ul')?>

View File

@ -8,6 +8,7 @@ use humhub\libs\Html;
/* @var $this \humhub\modules\ui\view\components\View */
/* @var $object \humhub\modules\content\components\ContentActiveRecord */
/* @var $comments \humhub\modules\comment\models\Comment[] */
/* @var $currentCommentId int */
/* @var $objectModel string */
/* @var $objectId int */
/* @var $id string unqiue object id */
@ -28,7 +29,10 @@ use humhub\libs\Html;
<?php endif; ?>
<?php foreach ($comments as $comment) : ?>
<?= Comment::widget(['comment' => $comment]); ?>
<?= Comment::widget([
'comment' => $comment,
'additionalClass' => ($currentCommentId == $comment->id ? 'comment-current' : ''),
]); ?>
<?php endforeach; ?>
</div>

View File

@ -43,6 +43,7 @@ class PermaController extends Controller
public function actionIndex()
{
$id = (int)Yii::$app->request->get('id');
$commentId = (int)Yii::$app->request->get('commentId');
$content = Content::findOne(['id' => $id]);
if ($content !== null) {
@ -50,7 +51,11 @@ class PermaController extends Controller
if (method_exists($content->getPolymorphicRelation(), 'getUrl')) {
$url = $content->getPolymorphicRelation()->getUrl();
} elseif ($content->container !== null) {
$url = $content->container->createUrl(null, ['contentId' => $id]);
$urlParams = ['contentId' => $id];
if ($commentId) {
$urlParams['commentId'] = $commentId;
}
$url = $content->container->createUrl(null, $urlParams);
}
if (!empty($url)) {

View File

@ -117,7 +117,7 @@ humhub.module('content', function (module, require, $) {
options.permalink = evt.$trigger.data('content-permalink');
modal.global.set({
header: options.head,
header: evt.$trigger.data('content-permalink-title') || options.head,
body: string.template(module.templates.permalinkBody, options),
footer: string.template(module.templates.permalinkFooter, options),
size: 'normal'

View File

@ -48,6 +48,13 @@ humhub.module('stream.Stream', function (module, require, $) {
*/
var DATA_STREAM_CONTENTID = 'stream-contentid';
/**
* If a data-stream-commentid is set on the stream the Comment will be marked
* @type String
*/
var DATA_STREAM_COMMENTID = 'stream-commentid';
var StreamState = function (stream) {
this.stream = stream;
this.lastContentId = 0;
@ -163,8 +170,9 @@ humhub.module('stream.Stream', function (module, require, $) {
*/
Stream.prototype.init = function () {
if (this.state) {
// When reloading the stream we ignroe the content id
// When reloading the stream we ignore the content id
this.$.data(DATA_STREAM_CONTENTID, null);
this.$.data(DATA_STREAM_COMMENTID, null);
}
this.state = new StreamState(this);
@ -229,12 +237,19 @@ humhub.module('stream.Stream', function (module, require, $) {
Stream.prototype.loadInit = function () {
// content Id data is only relevant for the first request
var contentId = this.$.data(DATA_STREAM_CONTENTID);
var commentId = this.$.data(DATA_STREAM_COMMENTID);
this.state.firstRequest = new StreamRequest(this, {
var requestData = {
contentId: contentId,
viewContext: contentId ? 'detail' : null,
limit: this.options.initLoadCount
});
};
if (commentId) {
requestData.commentId = commentId;
}
this.state.firstRequest = new StreamRequest(this, requestData);
return this.state.firstRequest.load();
};

View File

@ -41,6 +41,9 @@ humhub.module('stream.StreamRequest', function (module, require, $) {
StreamRequest.prototype.initOptions = function(options) {
this.contentId = this.options.contentId;
if (this.options.commentId) {
this.commentId = this.options.commentId;
}
this.viewContext = this.options.viewContext;
this.loader = object.defaultValue(this.options.loader, !object.isDefined(this.options.insertAfter));
this.url = object.defaultValue(this.options.url, this.stream.options.stream);
@ -130,6 +133,9 @@ humhub.module('stream.StreamRequest', function (module, require, $) {
}
data[this.buildRequestDataKey('contentId')] = this.contentId;
if (this.commentId) {
data[this.buildRequestDataKey('commentId')] = this.commentId;
}
data[this.buildRequestDataKey('suppressionsOnly')] = this.suppressionsOnly;
if(this.options.data) {

View File

@ -127,15 +127,19 @@ class StreamViewer extends JsWidget
'stream-empty-filter-class' => $this->messageStreamEmptyWithFiltersCss
];
if (!empty(Yii::$app->request->getQueryParam('contentId'))) {
$result['stream-contentid'] = Yii::$app->request->getQueryParam('contentId');
$contentId = (int) Yii::$app->request->getQueryParam('contentId');
if ($contentId > 0) {
$result['stream-contentid'] = $contentId;
}
if (Yii::$app->request->getQueryParam('topicId')) {
$topic = Topic::findOne((int) Yii::$app->request->getQueryParam('topicId'));
if ($topic) {
$result['stream-topic'] = ['id' => $topic->id, 'name' => $topic->name];
$commentId = (int) Yii::$app->request->getQueryParam('commentId');
if ($commentId > 0) {
$result['stream-commentid'] = $commentId;
}
$topicId = (int) Yii::$app->request->getQueryParam('topicId');
if ($topicId > 0 && $topic = Topic::findOne($topicId)) {
$result['stream-topic'] = ['id' => $topic->id, 'name' => $topic->name];
}
return $result;

View File

@ -31,6 +31,26 @@
display: none;
right: -3px;
}
&.comment-current {
background: fadeout(@warning, 75%);
padding: 0 5px 5px 5px;
margin: 0 -5px -5px -5px;
border-radius: 3px;
hr {
position: relative;
top: -6px;
}
&:first-of-type {
padding-top: 5px;
margin-top: -5px;
> .nav.preferences {
margin-top: 10px;
}
}
> .nav.preferences {
margin-right: 10px;
}
}
}
/*-- Since v1.2 overflow: visible */

File diff suppressed because one or more lines are too long