Merged in isqua/chrome_point_plus/ajax_comments (pull request #41)

Вынести ajax-комментарии, добавить биндинг на отправку формы
This commit is contained in:
isqua 2015-02-07 00:21:04 +03:00
commit 13ee04dcc2
2 changed files with 213 additions and 94 deletions

View File

@ -503,100 +503,10 @@ function PointPlus(ppVersion) {
if (options.is('option_ajax')) {
// Comments
if (options.is('option_ajax_comments')) {
// Removing old bindings
// Dirty hack for page context
$('#comments').replaceWith($('#comments').clone());
// Binding new
$('#comments').on('keypress.pp', '.reply-form textarea', function (evt) {
var $textarea = $(this);
var $post;
var $form;
var processClass = 'pp-progress'
var csRf;
if ((evt.keyCode === 10 || evt.keyCode === 13) && (evt.ctrlKey || evt.metaKey)) {
evt.stopPropagation();
evt.preventDefault();
$post = $textarea.parents('.post').first();
csRf = $textarea.siblings('input[name="csrf_token"]').val();
$textarea.prop('disabled', true);
$form = $textarea.parent();
$form.addClass(processClass);
$.ajax({
type: 'POST',
url: '/api/post/' + $post.data('id'),
data: {
text: $textarea.val(),
comment_id: $post.data('comment-id')
},
error: function(req, status, error) {
console.error('AJAX request error while sending the comment: %s', error);
console.log('Status: %s', status);
alert(chrome.i18n.getMessage('msg_comment_send_failed') + '\n' + error);
$textarea.prop('disabled', false);
$form.removeClass(processClass);
},
/**
* @param {object} data Response data
* @param {number} data.comment_id ID of the created comment
* @param {string} data.id ID of the post
* @param {string} textStatus Text of request status
*/
success: function(data, textStatus) {
console.log('data %O', data);
console.log('status %O', textStatus);
if (textStatus === 'success') {
// Hiding form
$('#reply-' + $post.data('id') + '_' + $post.data('comment-id')).prop('checked', false);
// Creating the comment HTML
create_comment_elements({
id: data.comment_id,
toId: $post.data('comment-id') || null,
postId: $post.data('id'),
author: $('#name h1').text(),
text: $textarea.val(),
fadeOut: true
}, function($comment) {
// If list mode or not addressed to other comment
if ($('#comments #tree-switch a').eq(0).hasClass('active') || ($post.data('comment-id') === undefined)) {
// Adding to the end of the list
$('.content-wrap #comments #post-reply').before($comment);
} else {
// Check for children
$parentCommentChildren = $post.next('.comments');
// @fixme Find a bug with lost indentation of new comment
// If child comment already exist
if ($parentCommentChildren.length) {
console.log('Child comments found. Appending...');
$parentCommentChildren.append($comment);
} else {
console.log('No child comments found. Creating...');
$post.after($('<div>').addClass('comments').append($comment));
}
}
});
// Cleaning textarea
$textarea.val('');
$textarea.prop('disabled', false);
$form.removeClass(processClass);
}
},
beforeSend: function (xhr) {
xhr.setRequestHeader('X-CSRF', csRf);
}
});
}
messenger.js({
file: 'modules/ajax-comments.js'
}, function() {
var ajaxComments = new AjaxComments();
});
}
}

View File

@ -0,0 +1,209 @@
/**
* Находит элемент #comments, и если он есть, начинает слушать на нём события
* @constructor
*/
function AjaxComments() {
var comments = document.querySelector('#comments');
if (comments) {
this._comments = comments;
this.listen(comments);
}
this.listenFirstComments();
}
/**
* Вешает обработчики. Магия в последнем параметре addEventListener
*/
AjaxComments.prototype.listen = function(elem) {
elem.addEventListener('submit', this.onSubmit.bind(this), true);
elem.addEventListener('keypress', this.onKeypress.bind(this), true);
};
/**
* Слушает отправки первых комментариев
*/
AjaxComments.prototype.listenFirstComments = function() {
var posts = document.querySelectorAll('.post-content');
// Чтобы не ловить события на чём попало и мочь использовать useCapture, приходится
// получать все post-content и на каждый вешать обработчики.
Array.prototype.forEach.call(posts, this.listen.bind(this));
};
/**
* Обрабатывает событие отправки формы
* @param {Event} event Событие отправки
*/
AjaxComments.prototype.onSubmit = function(event) {
var $form = $(event.target);
var proc;
event.preventDefault();
event.stopPropagation();
if ($form.hasClass('reply-form')) {
proc = new AjaxCommentProcessor($form);
}
};
/**
* Обрабатывает нажатия кнопок. Если это сочетание Ctrl|+Enter, отправляет коммент
* @param {Event} event Событие нажатия кнопки
*/
AjaxComments.prototype.onKeypress = function(event) {
var $form;
var proc;
if (this.isProperKeys(event)) {
event.preventDefault();
event.stopPropagation();
$form = $(event.target).closest('.reply-form');
if ($form.length) {
proc = new AjaxCommentProcessor($form);
}
}
};
/**
* Проверяет, что нажато нужное сочетание клавишь
* @param {Event} event Событие нажатия
* @return {Boolean}
*/
AjaxComments.prototype.isProperKeys = function(event) {
return (event.keyCode === 10 || event.keyCode === 13) && (event.ctrlKey || event.metaKey);
};
/**
* Создаётся при каждой отправке комментария
* @param {jQuery} $form Элемент формы, на которой это произошло
*/
function AjaxCommentProcessor($form) {
this._$form = $form;
this._$post = $form.closest('.post');
this._$textarea = $form.find('textarea');
this._text = this._$textarea.val();
this._CSRF = $form.get(0).elements.csrf_token.value;
this._postId = this._$post.data('id');
this._commentId = this._$post.data('comment-id');
this.sendComment();
}
/**
* Отправляет комментарий
*/
AjaxCommentProcessor.prototype.sendComment = function() {
this.setProgress(true);
$.ajax({
type: 'POST',
url: '/api/post/' + this._postId,
data: {
text: this._text,
comment_id: this._commentId
},
beforeSend: this.beforeSend.bind(this),
error: this.onError.bind(this),
success: this.onSuccess.bind(this)
});
};
/**
* Подкладывает CSRF-токен в заголовки запроса
* @param {XMLHttpRequest} xhr Объект запроса
*/
AjaxCommentProcessor.prototype.beforeSend = function(xhr) {
xhr.setRequestHeader('X-CSRF', this._CSRF);
};
/**
* Скрывает форму отправки комментария
* @return {[type]} [description]
*/
AjaxCommentProcessor.prototype.hideForm = function() {
this._$form.prev().prop('checked', false);
};
/**
* Создаёт новый комментарий, скрывает форму, снимает прогресс.
* @param {Object} data Ответ сервера
* @param {String} textStatus Статус ответа
*/
AjaxCommentProcessor.prototype.onSuccess = function(data, textStatus) {
var $textarea = this._$textarea;
if (textStatus === 'success') {
this.hideForm();
// Creating the comment HTML
create_comment_elements({
id: data.comment_id,
toId: this._commentId || null,
postId: this._postId,
author: $('#name h1').text(),
text: this._text,
fadeOut: true
}, this.insertComment.bind(this));
// Cleaning textarea
$textarea.val('');
this.setProgress(false);
}
};
/**
* Вставляет комментарий в DOM
* @param {jQuery} $comment
*/
AjaxCommentProcessor.prototype.insertComment = function($comment) {
var $parentCommentChildren;
if ($('#comments #tree-switch a').eq(0).hasClass('active') || (this._commentId === undefined)) {
// Adding to the end of the list
$('.content-wrap #comments #post-reply').before($comment);
} else {
// Check for children
$parentCommentChildren = this._$post.next('.comments');
// @fixme Find a bug with lost indentation of new comment
// If child comment already exist
if ($parentCommentChildren.length) {
console.log('Child comments found. Appending...');
$parentCommentChildren.append($comment);
} else {
console.log('No child comments found. Creating...');
this._$post.after(
$('<div>')
.addClass('comments')
.append($comment)
);
}
}
};
/**
* Показывает алерт с ошибкой и снимает прогресс, если коммент не отправился
* @param {*} req
* @param {*} status
* @param {String} error
*/
AjaxCommentProcessor.prototype.onError = function(req, status, error) {
alert(chrome.i18n.getMessage('msg_comment_send_failed') + '\n' + error);
this.setProgress(false);
};
/**
* Устанавливает прогресс
* @param {Boolean} isProgress true включить прогресс, false  отключить
*/
AjaxCommentProcessor.prototype.setProgress = function(isProgress) {
this._$textarea.prop('disabled', isProgress);
this._$form.toggleClass('pp-progress', isProgress);
};