mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Updated some comment elements and standardised more JS
- Updated comment routes to be simpler. - Updated comments JS to align better with updated component system. - Documented available global JS functions/services. - Removed redundant controller method. - Added window.$events helpers for validation messages and success/error. - Updated JS events system to not be class based for simplicity. - Added window.trans_plural method to handle pluralisation/replacements where you already have the translation string itself. Fixes #1836
This commit is contained in:
parent
2c0fdf83c1
commit
7590ecd37c
@ -8,6 +8,7 @@ use Illuminate\Foundation\Validation\ValidatesRequests;
|
|||||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
abstract class Controller extends BaseController
|
abstract class Controller extends BaseController
|
||||||
{
|
{
|
||||||
@ -132,23 +133,6 @@ abstract class Controller extends BaseController
|
|||||||
return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode);
|
return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the response for when a request fails validation.
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param array $errors
|
|
||||||
* @return \Symfony\Component\HttpFoundation\Response
|
|
||||||
*/
|
|
||||||
protected function buildFailedValidationResponse(Request $request, array $errors)
|
|
||||||
{
|
|
||||||
if ($request->expectsJson()) {
|
|
||||||
return response()->json(['validation' => $errors], 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->to($this->getRedirectUrl())
|
|
||||||
->withInput($request->input())
|
|
||||||
->withErrors($errors, $this->errorBag());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a response that forces a download in the browser.
|
* Create a response that forces a download in the browser.
|
||||||
* @param string $content
|
* @param string $content
|
||||||
|
@ -59,4 +59,41 @@ Will result with `this.$opts` being:
|
|||||||
"delay": "500",
|
"delay": "500",
|
||||||
"show": ""
|
"show": ""
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Global Helpers
|
||||||
|
|
||||||
|
There are various global helper libraries which can be used in components:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// HTTP service
|
||||||
|
window.$http.get(url, params);
|
||||||
|
window.$http.post(url, data);
|
||||||
|
window.$http.put(url, data);
|
||||||
|
window.$http.delete(url, data);
|
||||||
|
window.$http.patch(url, data);
|
||||||
|
|
||||||
|
// Global event system
|
||||||
|
// Emit a global event
|
||||||
|
window.$events.emit(eventName, eventData);
|
||||||
|
// Listen to a global event
|
||||||
|
window.$events.listen(eventName, callback);
|
||||||
|
// Show a success message
|
||||||
|
window.$events.success(message);
|
||||||
|
// Show an error message
|
||||||
|
window.$events.error(message);
|
||||||
|
// Show validation errors, if existing, as an error notification
|
||||||
|
window.$events.showValidationErrors(error);
|
||||||
|
|
||||||
|
// Translator
|
||||||
|
// Take the given plural text and count to decide on what plural option
|
||||||
|
// to use, Similar to laravel's trans_choice function but instead
|
||||||
|
// takes the direction directly instead of a translation key.
|
||||||
|
window.trans_plural(translationString, count, replacements);
|
||||||
|
|
||||||
|
// Component System
|
||||||
|
// Parse and initialise any components from the given root el down.
|
||||||
|
window.components.init(rootEl);
|
||||||
|
// Get the first active component of the given name
|
||||||
|
window.components.first(name);
|
||||||
```
|
```
|
@ -1,16 +1,31 @@
|
|||||||
import {scrollAndHighlightElement} from "../services/util";
|
import {scrollAndHighlightElement} from "../services/util";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends {Component}
|
||||||
|
*/
|
||||||
class PageComments {
|
class PageComments {
|
||||||
|
|
||||||
constructor(elem) {
|
setup() {
|
||||||
this.elem = elem;
|
this.elem = this.$el;
|
||||||
this.pageId = Number(elem.getAttribute('page-id'));
|
this.pageId = Number(this.$opts.pageId);
|
||||||
|
|
||||||
|
// Element references
|
||||||
|
this.container = this.$refs.commentContainer;
|
||||||
|
this.formContainer = this.$refs.formContainer;
|
||||||
|
this.commentCountBar = this.$refs.commentCountBar;
|
||||||
|
this.addButtonContainer = this.$refs.addButtonContainer;
|
||||||
|
this.replyToRow = this.$refs.replyToRow;
|
||||||
|
|
||||||
|
// Translations
|
||||||
|
this.updatedText = this.$opts.updatedText;
|
||||||
|
this.deletedText = this.$opts.deletedText;
|
||||||
|
this.createdText = this.$opts.createdText;
|
||||||
|
this.countText = this.$opts.countText;
|
||||||
|
|
||||||
|
// Internal State
|
||||||
this.editingComment = null;
|
this.editingComment = null;
|
||||||
this.parentId = null;
|
this.parentId = null;
|
||||||
|
|
||||||
this.container = elem.querySelector('[comment-container]');
|
|
||||||
this.formContainer = elem.querySelector('[comment-form-container]');
|
|
||||||
|
|
||||||
if (this.formContainer) {
|
if (this.formContainer) {
|
||||||
this.form = this.formContainer.querySelector('form');
|
this.form = this.formContainer.querySelector('form');
|
||||||
this.formInput = this.form.querySelector('textarea');
|
this.formInput = this.form.querySelector('textarea');
|
||||||
@ -32,13 +47,14 @@ class PageComments {
|
|||||||
if (actionElem === null) return;
|
if (actionElem === null) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let action = actionElem.getAttribute('action');
|
const action = actionElem.getAttribute('action');
|
||||||
if (action === 'edit') this.editComment(actionElem.closest('[comment]'));
|
const comment = actionElem.closest('[comment]');
|
||||||
|
if (action === 'edit') this.editComment(comment);
|
||||||
if (action === 'closeUpdateForm') this.closeUpdateForm();
|
if (action === 'closeUpdateForm') this.closeUpdateForm();
|
||||||
if (action === 'delete') this.deleteComment(actionElem.closest('[comment]'));
|
if (action === 'delete') this.deleteComment(comment);
|
||||||
if (action === 'addComment') this.showForm();
|
if (action === 'addComment') this.showForm();
|
||||||
if (action === 'hideForm') this.hideForm();
|
if (action === 'hideForm') this.hideForm();
|
||||||
if (action === 'reply') this.setReply(actionElem.closest('[comment]'));
|
if (action === 'reply') this.setReply(comment);
|
||||||
if (action === 'remove-reply-to') this.removeReplyTo();
|
if (action === 'remove-reply-to') this.removeReplyTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,14 +85,15 @@ class PageComments {
|
|||||||
};
|
};
|
||||||
this.showLoading(form);
|
this.showLoading(form);
|
||||||
let commentId = this.editingComment.getAttribute('comment');
|
let commentId = this.editingComment.getAttribute('comment');
|
||||||
window.$http.put(`/ajax/comment/${commentId}`, reqData).then(resp => {
|
window.$http.put(`/comment/${commentId}`, reqData).then(resp => {
|
||||||
let newComment = document.createElement('div');
|
let newComment = document.createElement('div');
|
||||||
newComment.innerHTML = resp.data;
|
newComment.innerHTML = resp.data;
|
||||||
this.editingComment.innerHTML = newComment.children[0].innerHTML;
|
this.editingComment.innerHTML = newComment.children[0].innerHTML;
|
||||||
window.$events.emit('success', window.trans('entities.comment_updated_success'));
|
window.$events.success(this.updatedText);
|
||||||
window.components.init(this.editingComment);
|
window.components.init(this.editingComment);
|
||||||
this.closeUpdateForm();
|
this.closeUpdateForm();
|
||||||
this.editingComment = null;
|
this.editingComment = null;
|
||||||
|
}).catch(window.$events.showValidationErrors).then(() => {
|
||||||
this.hideLoading(form);
|
this.hideLoading(form);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -84,9 +101,9 @@ class PageComments {
|
|||||||
deleteComment(commentElem) {
|
deleteComment(commentElem) {
|
||||||
let id = commentElem.getAttribute('comment');
|
let id = commentElem.getAttribute('comment');
|
||||||
this.showLoading(commentElem.querySelector('[comment-content]'));
|
this.showLoading(commentElem.querySelector('[comment-content]'));
|
||||||
window.$http.delete(`/ajax/comment/${id}`).then(resp => {
|
window.$http.delete(`/comment/${id}`).then(resp => {
|
||||||
commentElem.parentNode.removeChild(commentElem);
|
commentElem.parentNode.removeChild(commentElem);
|
||||||
window.$events.emit('success', window.trans('entities.comment_deleted_success'));
|
window.$events.success(this.deletedText);
|
||||||
this.updateCount();
|
this.updateCount();
|
||||||
this.hideForm();
|
this.hideForm();
|
||||||
});
|
});
|
||||||
@ -101,21 +118,24 @@ class PageComments {
|
|||||||
parent_id: this.parentId || null,
|
parent_id: this.parentId || null,
|
||||||
};
|
};
|
||||||
this.showLoading(this.form);
|
this.showLoading(this.form);
|
||||||
window.$http.post(`/ajax/page/${this.pageId}/comment`, reqData).then(resp => {
|
window.$http.post(`/comment/${this.pageId}`, reqData).then(resp => {
|
||||||
let newComment = document.createElement('div');
|
let newComment = document.createElement('div');
|
||||||
newComment.innerHTML = resp.data;
|
newComment.innerHTML = resp.data;
|
||||||
let newElem = newComment.children[0];
|
let newElem = newComment.children[0];
|
||||||
this.container.appendChild(newElem);
|
this.container.appendChild(newElem);
|
||||||
window.components.init(newElem);
|
window.components.init(newElem);
|
||||||
window.$events.emit('success', window.trans('entities.comment_created_success'));
|
window.$events.success(this.createdText);
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
this.updateCount();
|
this.updateCount();
|
||||||
|
}).catch(err => {
|
||||||
|
window.$events.showValidationErrors(err);
|
||||||
|
this.hideLoading(this.form);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCount() {
|
updateCount() {
|
||||||
let count = this.container.children.length;
|
let count = this.container.children.length;
|
||||||
this.elem.querySelector('[comments-title]').textContent = window.trans_choice('entities.comment_count', count, {count});
|
this.elem.querySelector('[comments-title]').textContent = window.trans_plural(this.countText, count, {count});
|
||||||
}
|
}
|
||||||
|
|
||||||
resetForm() {
|
resetForm() {
|
||||||
@ -129,7 +149,7 @@ class PageComments {
|
|||||||
showForm() {
|
showForm() {
|
||||||
this.formContainer.style.display = 'block';
|
this.formContainer.style.display = 'block';
|
||||||
this.formContainer.parentNode.style.display = 'block';
|
this.formContainer.parentNode.style.display = 'block';
|
||||||
this.elem.querySelector('[comment-add-button-container]').style.display = 'none';
|
this.addButtonContainer.style.display = 'none';
|
||||||
this.formInput.focus();
|
this.formInput.focus();
|
||||||
this.formInput.scrollIntoView({behavior: "smooth"});
|
this.formInput.scrollIntoView({behavior: "smooth"});
|
||||||
}
|
}
|
||||||
@ -137,14 +157,12 @@ class PageComments {
|
|||||||
hideForm() {
|
hideForm() {
|
||||||
this.formContainer.style.display = 'none';
|
this.formContainer.style.display = 'none';
|
||||||
this.formContainer.parentNode.style.display = 'none';
|
this.formContainer.parentNode.style.display = 'none';
|
||||||
const addButtonContainer = this.elem.querySelector('[comment-add-button-container]');
|
|
||||||
if (this.getCommentCount() > 0) {
|
if (this.getCommentCount() > 0) {
|
||||||
this.elem.appendChild(addButtonContainer)
|
this.elem.appendChild(this.addButtonContainer)
|
||||||
} else {
|
} else {
|
||||||
const countBar = this.elem.querySelector('[comment-count-bar]');
|
this.commentCountBar.appendChild(this.addButtonContainer);
|
||||||
countBar.appendChild(addButtonContainer);
|
|
||||||
}
|
}
|
||||||
addButtonContainer.style.display = 'block';
|
this.addButtonContainer.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommentCount() {
|
getCommentCount() {
|
||||||
@ -154,15 +172,15 @@ class PageComments {
|
|||||||
setReply(commentElem) {
|
setReply(commentElem) {
|
||||||
this.showForm();
|
this.showForm();
|
||||||
this.parentId = Number(commentElem.getAttribute('local-id'));
|
this.parentId = Number(commentElem.getAttribute('local-id'));
|
||||||
this.elem.querySelector('[comment-form-reply-to]').style.display = 'block';
|
this.replyToRow.style.display = 'block';
|
||||||
let replyLink = this.elem.querySelector('[comment-form-reply-to] a');
|
const replyLink = this.replyToRow.querySelector('a');
|
||||||
replyLink.textContent = `#${this.parentId}`;
|
replyLink.textContent = `#${this.parentId}`;
|
||||||
replyLink.href = `#comment${this.parentId}`;
|
replyLink.href = `#comment${this.parentId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeReplyTo() {
|
removeReplyTo() {
|
||||||
this.parentId = null;
|
this.parentId = null;
|
||||||
this.elem.querySelector('[comment-form-reply-to]').style.display = 'none';
|
this.replyToRow.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoading(formElem) {
|
showLoading(formElem) {
|
||||||
|
@ -7,11 +7,10 @@ window.baseUrl = function(path) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Set events and http services on window
|
// Set events and http services on window
|
||||||
import Events from "./services/events"
|
import events from "./services/events"
|
||||||
import httpInstance from "./services/http"
|
import httpInstance from "./services/http"
|
||||||
const eventManager = new Events();
|
|
||||||
window.$http = httpInstance;
|
window.$http = httpInstance;
|
||||||
window.$events = eventManager;
|
window.$events = events;
|
||||||
|
|
||||||
// Translation setup
|
// Translation setup
|
||||||
// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
|
// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
|
||||||
@ -19,6 +18,7 @@ import Translations from "./services/translations"
|
|||||||
const translator = new Translations();
|
const translator = new Translations();
|
||||||
window.trans = translator.get.bind(translator);
|
window.trans = translator.get.bind(translator);
|
||||||
window.trans_choice = translator.getPlural.bind(translator);
|
window.trans_choice = translator.getPlural.bind(translator);
|
||||||
|
window.trans_plural = translator.parsePlural.bind(translator);
|
||||||
|
|
||||||
// Load Components
|
// Load Components
|
||||||
import components from "./components"
|
import components from "./components"
|
||||||
|
@ -1,55 +1,66 @@
|
|||||||
|
const listeners = {};
|
||||||
|
const stack = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple global events manager
|
* Emit a custom event for any handlers to pick-up.
|
||||||
|
* @param {String} eventName
|
||||||
|
* @param {*} eventData
|
||||||
*/
|
*/
|
||||||
class Events {
|
function emit(eventName, eventData) {
|
||||||
constructor() {
|
stack.push({name: eventName, data: eventData});
|
||||||
this.listeners = {};
|
if (typeof listeners[eventName] === 'undefined') return this;
|
||||||
this.stack = [];
|
let eventsToStart = listeners[eventName];
|
||||||
}
|
for (let i = 0; i < eventsToStart.length; i++) {
|
||||||
|
let event = eventsToStart[i];
|
||||||
/**
|
event(eventData);
|
||||||
* Emit a custom event for any handlers to pick-up.
|
|
||||||
* @param {String} eventName
|
|
||||||
* @param {*} eventData
|
|
||||||
* @returns {Events}
|
|
||||||
*/
|
|
||||||
emit(eventName, eventData) {
|
|
||||||
this.stack.push({name: eventName, data: eventData});
|
|
||||||
if (typeof this.listeners[eventName] === 'undefined') return this;
|
|
||||||
let eventsToStart = this.listeners[eventName];
|
|
||||||
for (let i = 0; i < eventsToStart.length; i++) {
|
|
||||||
let event = eventsToStart[i];
|
|
||||||
event(eventData);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to a custom event and run the given callback when that event occurs.
|
|
||||||
* @param {String} eventName
|
|
||||||
* @param {Function} callback
|
|
||||||
* @returns {Events}
|
|
||||||
*/
|
|
||||||
listen(eventName, callback) {
|
|
||||||
if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
|
|
||||||
this.listeners[eventName].push(callback);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit an event for public use.
|
|
||||||
* Sends the event via the native DOM event handling system.
|
|
||||||
* @param {Element} targetElement
|
|
||||||
* @param {String} eventName
|
|
||||||
* @param {Object} eventData
|
|
||||||
*/
|
|
||||||
emitPublic(targetElement, eventName, eventData) {
|
|
||||||
const event = new CustomEvent(eventName, {
|
|
||||||
detail: eventData,
|
|
||||||
bubbles: true
|
|
||||||
});
|
|
||||||
targetElement.dispatchEvent(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Events;
|
/**
|
||||||
|
* Listen to a custom event and run the given callback when that event occurs.
|
||||||
|
* @param {String} eventName
|
||||||
|
* @param {Function} callback
|
||||||
|
* @returns {Events}
|
||||||
|
*/
|
||||||
|
function listen(eventName, callback) {
|
||||||
|
if (typeof listeners[eventName] === 'undefined') listeners[eventName] = [];
|
||||||
|
listeners[eventName].push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event for public use.
|
||||||
|
* Sends the event via the native DOM event handling system.
|
||||||
|
* @param {Element} targetElement
|
||||||
|
* @param {String} eventName
|
||||||
|
* @param {Object} eventData
|
||||||
|
*/
|
||||||
|
function emitPublic(targetElement, eventName, eventData) {
|
||||||
|
const event = new CustomEvent(eventName, {
|
||||||
|
detail: eventData,
|
||||||
|
bubbles: true
|
||||||
|
});
|
||||||
|
targetElement.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify of a http error.
|
||||||
|
* Check for standard scenarios such as validation errors and
|
||||||
|
* formats an error notification accordingly.
|
||||||
|
* @param {Error} error
|
||||||
|
*/
|
||||||
|
function showValidationErrors(error) {
|
||||||
|
if (!error.status) return;
|
||||||
|
if (error.status === 422 && error.data) {
|
||||||
|
const message = Object.values(error.data).flat().join('\n');
|
||||||
|
emit('error', message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emit,
|
||||||
|
emitPublic,
|
||||||
|
listen,
|
||||||
|
success: (msg) => emit('success', msg),
|
||||||
|
error: (msg) => emit('error', msg),
|
||||||
|
showValidationErrors,
|
||||||
|
}
|
@ -69,7 +69,10 @@ async function dataRequest(method, url, data = null) {
|
|||||||
|
|
||||||
// Send data as JSON if a plain object
|
// Send data as JSON if a plain object
|
||||||
if (typeof data === 'object' && !(data instanceof FormData)) {
|
if (typeof data === 'object' && !(data instanceof FormData)) {
|
||||||
options.headers = {'Content-Type': 'application/json'};
|
options.headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
};
|
||||||
options.body = JSON.stringify(data);
|
options.body = JSON.stringify(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,19 @@ class Translator {
|
|||||||
*/
|
*/
|
||||||
getPlural(key, count, replacements) {
|
getPlural(key, count, replacements) {
|
||||||
const text = this.getTransText(key);
|
const text = this.getTransText(key);
|
||||||
const splitText = text.split('|');
|
return this.parsePlural(text, count, replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given translation and find the correct plural option
|
||||||
|
* to use. Similar format at laravel's 'trans_choice' helper.
|
||||||
|
* @param {String} translation
|
||||||
|
* @param {Number} count
|
||||||
|
* @param {Object} replacements
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
parsePlural(translation, count, replacements) {
|
||||||
|
const splitText = translation.split('|');
|
||||||
const exactCountRegex = /^{([0-9]+)}/;
|
const exactCountRegex = /^{([0-9]+)}/;
|
||||||
const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
|
const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
|
||||||
let result = null;
|
let result = null;
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
<section page-comments page-id="{{ $page->id }}" class="comments-list" aria-label="{{ trans('entities.comments') }}">
|
<section component="page-comments"
|
||||||
|
option:page-comments:page-id="{{ $page->id }}"
|
||||||
|
option:page-comments:updated-text="{{ trans('entities.comment_updated_success') }}"
|
||||||
|
option:page-comments:deleted-text="{{ trans('entities.comment_deleted_success') }}"
|
||||||
|
option:page-comments:created-text="{{ trans('entities.comment_created_success') }}"
|
||||||
|
option:page-comments:count-text="{{ trans('entities.comment_count') }}"
|
||||||
|
class="comments-list"
|
||||||
|
aria-label="{{ trans('entities.comments') }}">
|
||||||
|
|
||||||
@exposeTranslations([
|
<div refs="page-comments@commentCountBar" class="grid half left-focus v-center no-row-gap">
|
||||||
'entities.comment_updated_success',
|
|
||||||
'entities.comment_deleted_success',
|
|
||||||
'entities.comment_created_success',
|
|
||||||
'entities.comment_count',
|
|
||||||
])
|
|
||||||
|
|
||||||
<div comment-count-bar class="grid half left-focus v-center no-row-gap">
|
|
||||||
<h5 comments-title>{{ trans_choice('entities.comment_count', count($page->comments), ['count' => count($page->comments)]) }}</h5>
|
<h5 comments-title>{{ trans_choice('entities.comment_count', count($page->comments), ['count' => count($page->comments)]) }}</h5>
|
||||||
@if (count($page->comments) === 0 && userCan('comment-create-all'))
|
@if (count($page->comments) === 0 && userCan('comment-create-all'))
|
||||||
<div class="text-m-right" comment-add-button-container>
|
<div class="text-m-right" refs="page-comments@addButtonContainer">
|
||||||
<button type="button" action="addComment"
|
<button type="button" action="addComment"
|
||||||
class="button outline">{{ trans('entities.comment_add') }}</button>
|
class="button outline">{{ trans('entities.comment_add') }}</button>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="comment-container" comment-container>
|
<div refs="page-comments@commentContainer" class="comment-container">
|
||||||
@foreach($page->comments as $comment)
|
@foreach($page->comments as $comment)
|
||||||
@include('comments.comment', ['comment' => $comment])
|
@include('comments.comment', ['comment' => $comment])
|
||||||
@endforeach
|
@endforeach
|
||||||
@ -27,7 +27,7 @@
|
|||||||
@include('comments.create')
|
@include('comments.create')
|
||||||
|
|
||||||
@if (count($page->comments) > 0)
|
@if (count($page->comments) > 0)
|
||||||
<div class="text-right" comment-add-button-container>
|
<div refs="page-comments@addButtonContainer" class="text-right">
|
||||||
<button type="button" action="addComment"
|
<button type="button" action="addComment"
|
||||||
class="button outline">{{ trans('entities.comment_add') }}</button>
|
class="button outline">{{ trans('entities.comment_add') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<div class="comment-box" comment-box style="display:none;">
|
<div class="comment-box" style="display:none;">
|
||||||
|
|
||||||
<div class="header p-s">{{ trans('entities.comment_new') }}</div>
|
<div class="header p-s">{{ trans('entities.comment_new') }}</div>
|
||||||
<div comment-form-reply-to class="reply-row primary-background-light text-muted px-s py-xs mb-s" style="display: none;">
|
<div refs="page-comments@replyToRow" class="reply-row primary-background-light text-muted px-s py-xs mb-s" style="display: none;">
|
||||||
<div class="grid left-focus v-center">
|
<div class="grid left-focus v-center">
|
||||||
<div>
|
<div>
|
||||||
{!! trans('entities.comment_in_reply_to', ['commentId' => '<a href=""></a>']) !!}
|
{!! trans('entities.comment_in_reply_to', ['commentId' => '<a href=""></a>']) !!}
|
||||||
@ -10,7 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content px-s" comment-form-container>
|
|
||||||
|
<div refs="page-comments@formContainer" class="content px-s">
|
||||||
<form novalidate>
|
<form novalidate>
|
||||||
<div class="form-group description-input">
|
<div class="form-group description-input">
|
||||||
<textarea name="markdown" rows="3"
|
<textarea name="markdown" rows="3"
|
||||||
@ -26,4 +28,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
@ -135,9 +135,9 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax');
|
Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax');
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
Route::post('/ajax/page/{pageId}/comment', 'CommentController@savePageComment');
|
Route::post('/comment/{pageId}', 'CommentController@savePageComment');
|
||||||
Route::put('/ajax/comment/{id}', 'CommentController@update');
|
Route::put('/comment/{id}', 'CommentController@update');
|
||||||
Route::delete('/ajax/comment/{id}', 'CommentController@destroy');
|
Route::delete('/comment/{id}', 'CommentController@destroy');
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
Route::get('/link/{id}', 'PageController@redirectFromLink');
|
Route::get('/link/{id}', 'PageController@redirectFromLink');
|
||||||
|
@ -13,7 +13,7 @@ class CommentTest extends TestCase
|
|||||||
$page = Page::first();
|
$page = Page::first();
|
||||||
|
|
||||||
$comment = factory(Comment::class)->make(['parent_id' => 2]);
|
$comment = factory(Comment::class)->make(['parent_id' => 2]);
|
||||||
$resp = $this->postJson("/ajax/page/$page->id/comment", $comment->getAttributes());
|
$resp = $this->postJson("/comment/$page->id", $comment->getAttributes());
|
||||||
|
|
||||||
$resp->assertStatus(200);
|
$resp->assertStatus(200);
|
||||||
$resp->assertSee($comment->text);
|
$resp->assertSee($comment->text);
|
||||||
@ -36,11 +36,11 @@ class CommentTest extends TestCase
|
|||||||
$page = Page::first();
|
$page = Page::first();
|
||||||
|
|
||||||
$comment = factory(Comment::class)->make();
|
$comment = factory(Comment::class)->make();
|
||||||
$this->postJson("/ajax/page/$page->id/comment", $comment->getAttributes());
|
$this->postJson("/comment/$page->id", $comment->getAttributes());
|
||||||
|
|
||||||
$comment = $page->comments()->first();
|
$comment = $page->comments()->first();
|
||||||
$newText = 'updated text content';
|
$newText = 'updated text content';
|
||||||
$resp = $this->putJson("/ajax/comment/$comment->id", [
|
$resp = $this->putJson("/comment/$comment->id", [
|
||||||
'text' => $newText,
|
'text' => $newText,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -60,11 +60,11 @@ class CommentTest extends TestCase
|
|||||||
$page = Page::first();
|
$page = Page::first();
|
||||||
|
|
||||||
$comment = factory(Comment::class)->make();
|
$comment = factory(Comment::class)->make();
|
||||||
$this->postJson("/ajax/page/$page->id/comment", $comment->getAttributes());
|
$this->postJson("/comment/$page->id", $comment->getAttributes());
|
||||||
|
|
||||||
$comment = $page->comments()->first();
|
$comment = $page->comments()->first();
|
||||||
|
|
||||||
$resp = $this->delete("/ajax/comment/$comment->id");
|
$resp = $this->delete("/comment/$comment->id");
|
||||||
$resp->assertStatus(200);
|
$resp->assertStatus(200);
|
||||||
|
|
||||||
$this->assertDatabaseMissing('comments', [
|
$this->assertDatabaseMissing('comments', [
|
||||||
@ -75,7 +75,7 @@ class CommentTest extends TestCase
|
|||||||
public function test_comments_converts_markdown_input_to_html()
|
public function test_comments_converts_markdown_input_to_html()
|
||||||
{
|
{
|
||||||
$page = Page::first();
|
$page = Page::first();
|
||||||
$this->asAdmin()->postJson("/ajax/page/$page->id/comment", [
|
$this->asAdmin()->postJson("/comment/$page->id", [
|
||||||
'text' => '# My Title',
|
'text' => '# My Title',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ class CommentTest extends TestCase
|
|||||||
$page = Page::first();
|
$page = Page::first();
|
||||||
|
|
||||||
$script = '<script>const a = "script";</script>\n\n# sometextinthecomment';
|
$script = '<script>const a = "script";</script>\n\n# sometextinthecomment';
|
||||||
$this->postJson("/ajax/page/$page->id/comment", [
|
$this->postJson("/comment/$page->id", [
|
||||||
'text' => $script,
|
'text' => $script,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ class CommentTest extends TestCase
|
|||||||
$pageView->assertSee('sometextinthecomment');
|
$pageView->assertSee('sometextinthecomment');
|
||||||
|
|
||||||
$comment = $page->comments()->first();
|
$comment = $page->comments()->first();
|
||||||
$this->putJson("/ajax/comment/$comment->id", [
|
$this->putJson("/comment/$comment->id", [
|
||||||
'text' => $script . 'updated',
|
'text' => $script . 'updated',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user