Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 5 additions & 21 deletions app/Activity/CommentRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use BookStack\Activity\Models\Comment;
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity as ActivityService;
use League\CommonMark\CommonMarkConverter;
use BookStack\Util\HtmlDescriptionFilter;

class CommentRepo
{
Expand All @@ -20,13 +20,12 @@ public function getById(int $id): Comment
/**
* Create a new comment on an entity.
*/
public function create(Entity $entity, string $text, ?int $parent_id): Comment
public function create(Entity $entity, string $html, ?int $parent_id): Comment
{
$userId = user()->id;
$comment = new Comment();

$comment->text = $text;
$comment->html = $this->commentToHtml($text);
$comment->html = HtmlDescriptionFilter::filterFromString($html);
$comment->created_by = $userId;
$comment->updated_by = $userId;
$comment->local_id = $this->getNextLocalId($entity);
Expand All @@ -42,11 +41,10 @@ public function create(Entity $entity, string $text, ?int $parent_id): Comment
/**
* Update an existing comment.
*/
public function update(Comment $comment, string $text): Comment
public function update(Comment $comment, string $html): Comment
{
$comment->updated_by = user()->id;
$comment->text = $text;
$comment->html = $this->commentToHtml($text);
$comment->html = HtmlDescriptionFilter::filterFromString($html);
$comment->save();

ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
Expand All @@ -64,20 +62,6 @@ public function delete(Comment $comment): void
ActivityService::add(ActivityType::COMMENT_DELETE, $comment);
}

/**
* Convert the given comment Markdown to HTML.
*/
public function commentToHtml(string $commentText): string
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'max_nesting_level' => 10,
'allow_unsafe_links' => false,
]);

return $converter->convert($commentText);
}

/**
* Get the next local ID relative to the linked entity.
*/
Expand Down
17 changes: 10 additions & 7 deletions app/Activity/Controllers/CommentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public function __construct(
*/
public function savePageComment(Request $request, int $pageId)
{
$this->validate($request, [
'text' => ['required', 'string'],
$input = $this->validate($request, [
'html' => ['required', 'string'],
'parent_id' => ['nullable', 'integer'],
]);

Expand All @@ -39,7 +39,7 @@ public function savePageComment(Request $request, int $pageId)

// Create a new comment.
$this->checkPermission('comment-create-all');
$comment = $this->commentRepo->create($page, $request->get('text'), $request->get('parent_id'));
$comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null);

return view('comments.comment-branch', [
'readOnly' => false,
Expand All @@ -57,17 +57,20 @@ public function savePageComment(Request $request, int $pageId)
*/
public function update(Request $request, int $commentId)
{
$this->validate($request, [
'text' => ['required', 'string'],
$input = $this->validate($request, [
'html' => ['required', 'string'],
]);

$comment = $this->commentRepo->getById($commentId);
$this->checkOwnablePermission('page-view', $comment->entity);
$this->checkOwnablePermission('comment-update', $comment);

$comment = $this->commentRepo->update($comment, $request->get('text'));
$comment = $this->commentRepo->update($comment, $input['html']);

return view('comments.comment', ['comment' => $comment, 'readOnly' => false]);
return view('comments.comment', [
'comment' => $comment,
'readOnly' => false,
]);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions app/Activity/Models/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

use BookStack\App\Model;
use BookStack\Users\Models\HasCreatorAndUpdater;
use BookStack\Util\HtmlContentFilter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;

/**
* @property int $id
* @property string $text
* @property string $text - Deprecated & now unused (#4821)
* @property string $html
* @property int|null $parent_id - Relates to local_id, not id
* @property int $local_id
Expand All @@ -24,7 +25,7 @@ class Comment extends Model implements Loggable
use HasFactory;
use HasCreatorAndUpdater;

protected $fillable = ['text', 'parent_id'];
protected $fillable = ['parent_id'];
protected $appends = ['created', 'updated'];

/**
Expand Down Expand Up @@ -73,4 +74,9 @@ public function logDescriptor(): string
{
return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})";
}

public function safeHtml(): string
{
return HtmlContentFilter::removeScriptsFromHtmlString($this->html ?? '');
}
}
11 changes: 11 additions & 0 deletions app/Activity/Tools/CommentTree.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ public function get(): array
return $this->tree;
}

public function canUpdateAny(): bool
{
foreach ($this->comments as $comment) {
if (userCan('comment-update', $comment)) {
return true;
}
}

return false;
}

/**
* @param Comment[] $comments
*/
Expand Down
49 changes: 0 additions & 49 deletions app/Console/Commands/RegenerateCommentContentCommand.php

This file was deleted.

2 changes: 1 addition & 1 deletion database/factories/Activity/Models/CommentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public function definition()

return [
'html' => $html,
'text' => $text,
'parent_id' => null,
'local_id' => 1,
];
}
}
31 changes: 27 additions & 4 deletions resources/js/components/page-comment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Component} from './component';
import {getLoading, htmlToDom} from '../services/dom';
import {buildForInput} from '../wysiwyg/config';

export class PageComment extends Component {

Expand All @@ -11,7 +12,12 @@ export class PageComment extends Component {
this.deletedText = this.$opts.deletedText;
this.updatedText = this.$opts.updatedText;

// Element References
// Editor reference and text options
this.wysiwygEditor = null;
this.wysiwygLanguage = this.$opts.wysiwygLanguage;
this.wysiwygTextDirection = this.$opts.wysiwygTextDirection;

// Element references
this.container = this.$el;
this.contentContainer = this.$refs.contentContainer;
this.form = this.$refs.form;
Expand Down Expand Up @@ -50,8 +56,25 @@ export class PageComment extends Component {

startEdit() {
this.toggleEditMode(true);
const lineCount = this.$refs.input.value.split('\n').length;
this.$refs.input.style.height = `${(lineCount * 20) + 40}px`;

if (this.wysiwygEditor) {
this.wysiwygEditor.focus();
return;
}

const config = buildForInput({
language: this.wysiwygLanguage,
containerElement: this.input,
darkMode: document.documentElement.classList.contains('dark-mode'),
textDirection: this.wysiwygTextDirection,
translations: {},
translationMap: window.editor_translations,
});

window.tinymce.init(config).then(editors => {
this.wysiwygEditor = editors[0];
setTimeout(() => this.wysiwygEditor.focus(), 50);
});
}

async update(event) {
Expand All @@ -60,7 +83,7 @@ export class PageComment extends Component {
this.form.toggleAttribute('hidden', true);

const reqData = {
text: this.input.value,
html: this.wysiwygEditor.getContent(),
parent_id: this.parentId || null,
};

Expand Down
43 changes: 38 additions & 5 deletions resources/js/components/page-comments.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Component} from './component';
import {getLoading, htmlToDom} from '../services/dom';
import {buildForInput} from '../wysiwyg/config';

export class PageComments extends Component {

Expand All @@ -21,6 +22,11 @@ export class PageComments extends Component {
this.hideFormButton = this.$refs.hideFormButton;
this.removeReplyToButton = this.$refs.removeReplyToButton;

// WYSIWYG options
this.wysiwygLanguage = this.$opts.wysiwygLanguage;
this.wysiwygTextDirection = this.$opts.wysiwygTextDirection;
this.wysiwygEditor = null;

// Translations
this.createdText = this.$opts.createdText;
this.countText = this.$opts.countText;
Expand Down Expand Up @@ -59,9 +65,8 @@ export class PageComments extends Component {
this.form.after(loading);
this.form.toggleAttribute('hidden', true);

const text = this.formInput.value;
const reqData = {
text,
html: this.wysiwygEditor.getContent(),
parent_id: this.parentId || null,
};

Expand All @@ -86,19 +91,19 @@ export class PageComments extends Component {
}

resetForm() {
this.removeEditor();
this.formInput.value = '';
this.parentId = null;
this.replyToRow.toggleAttribute('hidden', true);
this.container.append(this.formContainer);
}

showForm() {
this.removeEditor();
this.formContainer.toggleAttribute('hidden', false);
this.addButtonContainer.toggleAttribute('hidden', true);
this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
setTimeout(() => {
this.formInput.focus();
}, 100);
this.loadEditor();
}

hideForm() {
Expand All @@ -112,6 +117,34 @@ export class PageComments extends Component {
this.addButtonContainer.toggleAttribute('hidden', false);
}

loadEditor() {
if (this.wysiwygEditor) {
this.wysiwygEditor.focus();
return;
}

const config = buildForInput({
language: this.wysiwygLanguage,
containerElement: this.formInput,
darkMode: document.documentElement.classList.contains('dark-mode'),
textDirection: this.wysiwygTextDirection,
translations: {},
translationMap: window.editor_translations,
});

window.tinymce.init(config).then(editors => {
this.wysiwygEditor = editors[0];
setTimeout(() => this.wysiwygEditor.focus(), 50);
});
}

removeEditor() {
if (this.wysiwygEditor) {
this.wysiwygEditor.remove();
this.wysiwygEditor = null;
}
}

getCommentCount() {
return this.container.querySelectorAll('[component="page-comment"]').length;
}
Expand Down
7 changes: 2 additions & 5 deletions resources/js/components/wysiwyg-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ export class WysiwygInput extends Component {
language: this.$opts.language,
containerElement: this.elem,
darkMode: document.documentElement.classList.contains('dark-mode'),
textDirection: this.textDirection,
translations: {
imageUploadErrorText: this.$opts.imageUploadErrorText,
serverUploadLimitText: this.$opts.serverUploadLimitText,
},
textDirection: this.$opts.textDirection,
translations: {},
translationMap: window.editor_translations,
});

Expand Down
1 change: 1 addition & 0 deletions resources/js/wysiwyg/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ export function buildForInput(options) {
toolbar: 'bold italic link bullist numlist',
content_style: getContentStyle(options),
file_picker_types: 'file',
valid_elements: 'p,a[href|title],ol,ul,li,strong,em,br',
file_picker_callback: filePickerCallback,
init_instance_callback(editor) {
addCustomHeadContent(editor.getDoc());
Expand Down
7 changes: 7 additions & 0 deletions resources/sass/_tinymce.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
display: block;
}

.wysiwyg-input.mce-content-body:before {
padding: 1rem;
top: 4px;
font-style: italic;
@include lightDark(color, rgba(34,47,62,.5), rgba(155,155,155,.5))
}

// Default styles for our custom root nodes
.page-content.mce-content-body doc-root {
display: block;
Expand Down
Loading