summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore1
-rw-r--r--askbot/context.py12
-rw-r--r--askbot/forms.py28
-rw-r--r--askbot/management/commands/askbot_add_test_content.py16
-rw-r--r--askbot/media/js/post.js637
-rw-r--r--askbot/media/js/utils.js17
-rw-r--r--askbot/media/style/style.less52
-rw-r--r--askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py2
-rw-r--r--askbot/models/post.py12
-rw-r--r--askbot/models/repute.py30
-rw-r--r--askbot/search/postgresql/__init__.py5
-rw-r--r--askbot/templates/badge.html6
-rw-r--r--askbot/templates/badges.html6
-rw-r--r--askbot/templates/embed/ask_by_widget.html2
-rw-r--r--askbot/templates/meta/bottom_scripts.html3
-rw-r--r--askbot/templates/question.html42
-rw-r--r--askbot/templates/question/content.html6
-rw-r--r--askbot/templates/question/new_answer_form.html3
-rw-r--r--askbot/templates/question/sharing_prompt_phrase.html11
-rw-r--r--askbot/templates/question/subscribe_by_email_prompt.html13
-rw-r--r--askbot/templates/user_profile/user_recent.html4
-rw-r--r--askbot/templates/user_profile/user_stats.html4
-rw-r--r--askbot/utils/html.py10
-rw-r--r--askbot/views/commands.py7
-rw-r--r--askbot/views/writers.py66
25 files changed, 658 insertions, 337 deletions
diff --git a/.gitignore b/.gitignore
index b8629b7b..69fad9a8 100755
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ lint
env
/static
django
+tinymce
lamson
django/*
nbproject
diff --git a/askbot/context.py b/askbot/context.py
index fba17b5f..3e0bf657 100644
--- a/askbot/context.py
+++ b/askbot/context.py
@@ -55,8 +55,18 @@ def application_settings(request):
my_settings['LOGOUT_REDIRECT_URL'] = url_utils.get_logout_redirect_url()
my_settings['USE_ASKBOT_LOGIN_SYSTEM'] = 'askbot.deps.django_authopenid' \
in settings.INSTALLED_APPS
+
+ current_language = get_language()
+
+ #for some languages we will start searching for shorter words
+ if current_language == 'ja':
+ min_search_word_length = 2
+ else:
+ min_search_word_length = my_settings['MIN_SEARCH_WORD_LENGTH']
+
context = {
- 'current_language_code': get_language(),
+ 'min_search_word_length': min_search_word_length,
+ 'current_language_code': current_language,
'settings': my_settings,
'skin': get_skin(request),
'moderation_items': api.get_info_on_moderation_items(request.user),
diff --git a/askbot/forms.py b/askbot/forms.py
index 5e9a7850..0fafec53 100644
--- a/askbot/forms.py
+++ b/askbot/forms.py
@@ -295,9 +295,11 @@ class EditorField(forms.CharField):
self.user = user
editor_attrs = kwargs.pop('editor_attrs', {})
+ widget_attrs = kwargs.pop('attrs', {})
+ widget_attrs.setdefault('id', 'editor')
+
super(EditorField, self).__init__(*args, **kwargs)
self.required = True
- widget_attrs = {'id': 'editor'}
if askbot_settings.EDITOR_TYPE == 'markdown':
self.widget = forms.Textarea(attrs=widget_attrs)
elif askbot_settings.EDITOR_TYPE == 'tinymce':
@@ -506,17 +508,18 @@ class SummaryField(forms.CharField):
'field is optional)'
)
-
class EditorForm(forms.Form):
"""form with one field - `editor`
the field must be created dynamically, so it's added
in the __init__() function"""
- def __init__(self, user=None, editor_attrs=None):
+ def __init__(self, attrs=None, user=None, editor_attrs=None):
super(EditorForm, self).__init__()
editor_attrs = editor_attrs or {}
self.fields['editor'] = EditorField(
- user=user, editor_attrs=editor_attrs
+ attrs=attrs,
+ editor_attrs=editor_attrs,
+ user=user
)
@@ -1339,10 +1342,6 @@ class EditAnswerForm(PostAsSomeoneForm, PostPrivatelyForm):
else:
return False
-class EditCommentForm(forms.Form):
- comment_id = forms.IntegerField()
- suppress_email = SuppressEmailField()
-
class EditTagWikiForm(forms.Form):
text = forms.CharField(required=False)
tag_id = forms.IntegerField()
@@ -1689,5 +1688,18 @@ class BulkTagSubscriptionForm(forms.Form):
if askbot_settings.GROUPS_ENABLED:
self.fields['groups'] = forms.ModelMultipleChoiceField(queryset=Group.objects.exclude_personal())
+class GetCommentsForPostForm(forms.Form):
+ post_id = forms.IntegerField()
+
+class NewCommentForm(forms.Form):
+ comment = forms.CharField()
+ post_id = forms.IntegerField()
+
+class EditCommentForm(forms.Form):
+ comment_id = forms.IntegerField()
+ comment = forms.CharField()
+ suppress_email = SuppressEmailField()
+
+
class DeleteCommentForm(forms.Form):
comment_id = forms.IntegerField()
diff --git a/askbot/management/commands/askbot_add_test_content.py b/askbot/management/commands/askbot_add_test_content.py
index 0efd8c1f..a09fb086 100644
--- a/askbot/management/commands/askbot_add_test_content.py
+++ b/askbot/management/commands/askbot_add_test_content.py
@@ -1,3 +1,4 @@
+import sys
from askbot.conf import settings as askbot_settings
from askbot.models import User
from askbot.utils.console import choice_dialog
@@ -17,7 +18,10 @@ NUM_COMMENTS = 20
# karma. This can be calculated dynamically - max of MIN_REP_TO_... settings
INITIAL_REPUTATION = 500
-BAD_STUFF = "<script>alert('hohoho')</script>"
+if '--nospam' in sys.argv:
+ BAD_STUFF = ''
+else:
+ BAD_STUFF = "<script>alert('hohoho')</script>"
# Defining template inputs.
USERNAME_TEMPLATE = BAD_STUFF + "test_user_%s"
@@ -47,8 +51,14 @@ ALERT_SETTINGS_KEYS = (
class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list + (
- make_option('--noinput', action='store_false', dest='interactive', default=True,
- help='Do not prompt the user for input of any kind.'),
+ make_option(
+ '--noinput', action='store_false', dest='interactive', default=True,
+ help='Do not prompt the user for input of any kind.'
+ ),
+ make_option(
+ '--nospam', action='store_true', dest='nospam', default=False,
+ help='Do not add XSS snippets'
+ )
)
def save_alert_settings(self):
diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js
index 5b9bf4ee..666fe70b 100644
--- a/askbot/media/js/post.js
+++ b/askbot/media/js/post.js
@@ -1411,50 +1411,115 @@ DeletePostLink.prototype.decorate = function(element){
this.setHandler(this.getDeleteHandler());
}
-//constructor for the form
+/**
+ * Form for editing and posting new comment
+ * supports 3 editors: markdown, tinymce and plain textarea.
+ * There is only one instance of this form in use on the question page.
+ * It can be attached to any comment on the page, or to a new blank
+ * comment.
+ */
var EditCommentForm = function(){
WrappedElement.call(this);
this._comment = null;
this._comment_widget = null;
this._element = null;
+ this._editorReady = false;
this._text = '';
- this._id = 'edit-comment-form';
};
inherits(EditCommentForm, WrappedElement);
-EditCommentForm.prototype.getElement = function(){
- EditCommentForm.superClass_.getElement.call(this);
- this._textarea.val(this._text);
- return this._element;
+EditCommentForm.prototype.setWaitingStatus = function(isWaiting) {
+ if (isWaiting === true) {
+ this._editor.getElement().hide();
+ this._submit_btn.hide();
+ this._cancel_btn.hide();
+ } else {
+ this._editor.getElement().show();
+ this._submit_btn.show();
+ this._cancel_btn.show();
+ }
+};
+
+EditCommentForm.prototype.startEditor = function() {
+ var editorId = 'comment-editor-' + getNewInt();
+ var opts = {
+ mode: 'exact',
+ content_css: mediaUrl('media/style/tinymce/comments-content.css'),
+ elements: editorId,
+ plugins: 'autoresize',
+ theme_advanced_buttons1: 'bold, italic, |, link, |, numlist, bullist',
+ theme_advanced_buttons2: '',
+ theme_advanced_path: false,
+ plugins: '',
+ width: '100%',
+ height: '60px'
+ };
+ var editor = new TinyMCE(opts);
+ editor.setId(editorId);
+ editor.setText(this._text);
+ //@todo: remove global variable maxCommentLength
+ this._editorBox.prepend(editor.getElement());
+ editor.start();
+ editor.focus();
+ this._editor = editor;
+
+ return;
+
+ //todo: make this work for tinymce
+ var updateCounter = this.getCounterUpdater();
+ var escapeHandler = makeKeyHandler(27, this.getCancelHandler());
+
+ //todo: try this on the div
+ editor.getElement().blur(updateCounter)
+ .focus(updateCounter)
+ .keyup(updateCounter)
+ .keyup(escapeHandler);
+
+ if (askbot['settings']['saveCommentOnEnter']){
+ var save_handler = makeKeyHandler(13, this.getSaveHandler());
+ editor.getElement().keydown(save_handler);
+ }
};
+/**
+ * attaches comment editor to a particular comment
+ */
EditCommentForm.prototype.attachTo = function(comment, mode){
this._comment = comment;
- this._type = mode;
+ this._type = mode;//action: 'add' or 'edit'
this._comment_widget = comment.getContainerWidget();
this._text = comment.getText();
comment.getElement().after(this.getElement());
comment.getElement().hide();
- this._comment_widget.hideButton();
+ this._comment_widget.hideButton();//hide add comment button
+ //fix up the comment submit button, depending on the mode
if (this._type == 'add'){
this._submit_btn.html(gettext('add comment'));
+ this._minorEditBox.hide();
}
else {
this._submit_btn.html(gettext('save comment'));
+ this._minorEditBox.show();
}
+ //enable the editor
this.getElement().show();
this.enableForm();
- this.focus();
- putCursorAtEnd(this._textarea);
+ this.startEditor();
+ this._editor.setText(this._text);
+ //this._editor.focus();
+ //this._editor.putCursorAtEnd();
+ setupButtonEventHandlers(this._submit_btn, this.getSaveHandler());
+ setupButtonEventHandlers(this._cancel_btn, this.getCancelHandler());
};
EditCommentForm.prototype.getCounterUpdater = function(){
//returns event handler
var counter = this._text_counter;
+ var editor = this._editor;
var handler = function(){
- var textarea = $(this);
- var length = textarea.val() ? textarea.val().length : 0;
+ var length = editor.getText().length;
var length1 = maxCommentLength - 100;
+
if (length1 < 0){
length1 = Math.round(0.7*maxCommentLength);
}
@@ -1463,38 +1528,49 @@ EditCommentForm.prototype.getCounterUpdater = function(){
length2 = Math.round(0.9*maxCommentLength);
}
- //todo:
- //1) use class instead of color - move color def to css
+ /* todo make smooth color transition, from gray to red
+ * or rather - from start color to end color */
var color = 'maroon';
var chars = 10;
if (length === 0){
- var feedback = interpolate(gettext('%s title minchars'), [chars]);
- }
- else if (length < 10){
- var feedback = interpolate(gettext('enter %s more characters'), [chars - length]);
- }
- else {
- color = length > length2 ? "#f00" : length > length1 ? "#f60" : "#999"
- chars = maxCommentLength - length
- var feedback = interpolate(gettext('%s characters left'), [chars])
+ var feedback = interpolate(gettext('enter at least %s characters'), [chars]);
+ } else if (length < 10){
+ var feedback = interpolate(gettext('enter at least %s more characters'), [chars - length]);
+ } else {
+ if (length > length2) {
+ color = '#f00';
+ } else if (length > length1) {
+ color = '#f60';
+ } else {
+ color = '#999';
+ }
+ chars = maxCommentLength - length;
+ var feedback = interpolate(gettext('%s characters left'), [chars]);
}
- counter.html(feedback).css('color', color)
+ counter.html(feedback);
+ counter.css('color', color);
};
return handler;
};
+/**
+ * @todo: clean up this method so it does just one thing
+ */
EditCommentForm.prototype.canCancel = function(){
if (this._element === null){
return true;
}
- var ctext = $.trim(this._textarea.val());
+ if (this._editor === undefined) {
+ return true;
+ };
+ var ctext = this._editor.getText();
if ($.trim(ctext) == $.trim(this._text)){
return true;
}
else if (this.confirmAbandon()){
return true;
}
- this.focus();
+ this._editor.focus();
return false;
};
@@ -1515,12 +1591,17 @@ EditCommentForm.prototype.detach = function(){
this._comment.getContainerWidget().showButton();
if (this._comment.isBlank()){
this._comment.dispose();
- }
- else {
+ } else {
this._comment.getElement().show();
}
this.reset();
this._element = this._element.detach();
+
+ this._editor.dispose();
+ this._editor = undefined;
+
+ removeButtonEventHandlers(this._submit_btn);
+ removeButtonEventHandlers(this._cancel_btn);
};
EditCommentForm.prototype.createDom = function(){
@@ -1528,46 +1609,41 @@ EditCommentForm.prototype.createDom = function(){
this._element.attr('class', 'post-comments');
var div = $('<div></div>');
- this._textarea = $('<textarea></textarea>');
- this._textarea.attr('id', this._id);
+ this._element.append(div);
- /*
- this._help_text = $('<span></span>').attr('class', 'help-text');
- this._help_text.html(gettext('Markdown is allowed in the comments'));
- div.append(this._help_text);
+ /** a stub container for the editor */
+ this._editorBox = div;
+ /**
+ * editor itself will live at this._editor
+ * and will be initialized by the attachTo()
+ */
- this._help_text = $('<div></div>').attr('class', 'clearfix');
- div.append(this._help_text);
- */
+ this._controlsBox = this.makeElement('div');
+ div.append(this._controlsBox);
- this._element.append(div);
- div.append(this._textarea);
this._text_counter = $('<span></span>').attr('class', 'counter');
- div.append(this._text_counter);
+ this._controlsBox.append(this._text_counter);
+
this._submit_btn = $('<button class="submit"></button>');
- div.append(this._submit_btn);
+ this._controlsBox.append(this._submit_btn);
this._cancel_btn = $('<button class="submit"></button>');
this._cancel_btn.html(gettext('cancel'));
- div.append(this._cancel_btn);
-
- setupButtonEventHandlers(this._submit_btn, this.getSaveHandler());
- setupButtonEventHandlers(this._cancel_btn, this.getCancelHandler());
-
- var update_counter = this.getCounterUpdater();
- var escape_handler = makeKeyHandler(27, this.getCancelHandler());
- this._textarea.attr('name', 'comment')
- .attr('cols', 60)
- .attr('rows', 5)
- .attr('maxlength', maxCommentLength)
- .blur(update_counter)
- .focus(update_counter)
- .keyup(update_counter)
- .keyup(escape_handler);
- if (askbot['settings']['saveCommentOnEnter']){
- var save_handler = makeKeyHandler(13, this.getSaveHandler());
- this._textarea.keydown(save_handler);
+ this._controlsBox.append(this._cancel_btn);
+
+ //if email alerts are enabled, add a checkbox "suppress_email"
+ if (askbot['settings']['enableEmailAlerts'] === true) {
+ this._minorEditBox = this.makeElement('div');
+ this._controlsBox.append(this._minorEditBox);
+ var checkBox = this.makeElement('input');
+ checkBox.attr('type', 'checkbox');
+ checkBox.attr('name', 'suppress_email');
+ this._minorEditBox.append(checkBox);
+ var label = this.makeElement('label');
+ label.attr('for', 'suppress_email');
+ label.html(gettext("minor edit (don't send alerts)"));
+ this._minorEditBox.append(label);
}
- this._textarea.val(this._text);
+
};
EditCommentForm.prototype.isEnabled = function() {
@@ -1587,38 +1663,59 @@ EditCommentForm.prototype.disableForm = function() {
EditCommentForm.prototype.reset = function(){
this._comment = null;
this._text = '';
- this._textarea.val('');
+ this._editor.setText('');
this.enableForm();
};
EditCommentForm.prototype.confirmAbandon = function(){
- this.focus(true);
- this._textarea.addClass('highlight');
- var answer = confirm(gettext("Are you sure you don't want to post this comment?"));
- this._textarea.removeClass('highlight');
+ this._editor.focus();
+ this._editor.getElement().scrollTop();
+ this._editor.setHighlight(true);
+ var answer = confirm(
+ gettext("Are you sure you don't want to post this comment?")
+ );
+ this._editor.setHighlight(false);
return answer;
};
-EditCommentForm.prototype.focus = function(hard){
- this._textarea.focus();
- if (hard === true){
- $(this._textarea).scrollTop();
- }
+EditCommentForm.prototype.getSuppressEmail = function() {
+ return this._element.find('input[name="suppress_email"]').is(':checked');
+};
+
+EditCommentForm.prototype.setSuppressEmail = function(bool) {
+ this._element.find('input[name="suppress_email"]').prop('checked', bool);
};
EditCommentForm.prototype.getSaveHandler = function(){
var me = this;
+ var editor = this._editor;
return function(){
if (me.isEnabled() === false) {//prevent double submits
return false;
}
- var text = me._textarea.val();
+ me.disableForm();
+
+ var text = editor.getText();
if (text.length < 10){
- me.focus();
+ editor.focus();
return false;
}
+ //display the comment and show that it is not yet saved
+ me.setWaitingStatus(true);
+ me._comment.getElement().show();
+ var commentData = me._comment.getData();
+ var timestamp = commentData['comment_added_at'] || gettext('just now');
+ var userName = commentData['user_display_name'] || askbot['data']['userName'];
+ me._comment.setContent({
+ 'html': text,
+ 'user_display_name': userName,
+ 'comment_added_at': timestamp
+ });
+ me._comment.setDraftStatus(true);
+ me._comment.getContainerWidget().showButton();
+
var post_data = {
comment: text
};
@@ -1626,6 +1723,8 @@ EditCommentForm.prototype.getSaveHandler = function(){
if (me._type == 'edit'){
post_data['comment_id'] = me._comment.getId();
post_url = askbot['urls']['editComment'];
+ post_data['suppress_email'] = me.getSuppressEmail();
+ me.setSuppressEmail(false);
}
else {
post_data['post_type'] = me._comment.getParentType();
@@ -1633,8 +1732,6 @@ EditCommentForm.prototype.getSaveHandler = function(){
post_url = askbot['urls']['postComments'];
}
- me.disableForm();
-
$.ajax({
type: "POST",
url: post_url,
@@ -1642,19 +1739,21 @@ EditCommentForm.prototype.getSaveHandler = function(){
data: post_data,
success: function(json) {
//type is 'edit' or 'add'
+ me._comment.setDraftStatus(false);
if (me._type == 'add'){
me._comment.dispose();
me._comment.getContainerWidget().reRenderComments(json);
- }
- else {
+ } else {
me._comment.setContent(json);
- me._comment.getElement().show();
}
+ me.setWaitingStatus(false);
me.detach();
},
error: function(xhr, textStatus, errorThrown) {
me._comment.getElement().show();
showMessage(me._comment.getElement(), xhr.responseText, 'after');
+ me._comment.setDraftStatus(false);
+ me.setWaitingStatus(false);
me.detach();
me.enableForm();
}
@@ -1663,9 +1762,6 @@ EditCommentForm.prototype.getSaveHandler = function(){
};
};
-//a single instance to reuse
-var editCommentForm = new EditCommentForm();
-
var Comment = function(widget, data){
WrappedElement.call(this);
this._container_widget = widget;
@@ -1675,6 +1771,7 @@ var Comment = function(widget, data){
this._is_convertible = askbot['data']['userIsAdminOrMod'];
this.convert_link = null;
this._delete_prompt = gettext('delete this comment');
+ this._editorForm = undefined;
if (data && data['is_deletable']){
this._deletable = data['is_deletable'];
}
@@ -1690,11 +1787,38 @@ var Comment = function(widget, data){
};
inherits(Comment, WrappedElement);
+Comment.prototype.getData = function() {
+ return this._data;
+};
+
+Comment.prototype.startEditing = function() {
+ var form = this._editorForm || new EditCommentForm();
+ this._editorForm = form;
+ // if new comment:
+ if (this.isBlank()) {
+ form.attachTo(this, 'add');
+ } else {
+ form.attachTo(this, 'edit');
+ }
+};
+
Comment.prototype.decorate = function(element){
this._element = $(element);
var parent_type = this._element.parent().parent().attr('id').split('-')[2];
var comment_id = this._element.attr('id').replace('comment-','');
this._data = {id: comment_id};
+
+ var timestamp = this._element.find('abbr.timeago');
+ this._data['comment_added_at'] = timestamp.attr('title');
+ var userLink = this._element.find('a.author');
+ this._data['user_display_name'] = userLink.html();
+ // @todo: read other data
+
+ var commentBody = this._element.find('.comment-body');
+ if (commentBody.length > 0) {
+ this._comment_body = commentBody;
+ }
+
var delete_img = this._element.find('span.delete-icon');
if (delete_img.length > 0){
this._deletable = true;
@@ -1716,12 +1840,31 @@ Comment.prototype.decorate = function(element){
this._convert_link.decorate(convert_link);
}
+ var deleter = this._element.find('.comment-delete');
+ if (deleter.length > 0) {
+ this._comment_delete = deleter;
+ };
+
var vote = new CommentVoteButton(this);
vote.decorate(this._element.find('.comment-votes .upvote'));
this._blank = false;
};
+Comment.prototype.setDraftStatus = function(isDraft) {
+ return;
+ //@todo: implement nice feedback about posting in progress
+ //maybe it should be an element that lasts at least a second
+ //to avoid the possible brief flash
+ if (isDraft === true) {
+ this._normalBackground = this._element.css('background');
+ this._element.css('background', 'rgb(255, 243, 195)');
+ } else {
+ this._element.css('background', this._normalBackground);
+ }
+};
+
+
Comment.prototype.isBlank = function(){
return this._blank;
};
@@ -1751,42 +1894,77 @@ Comment.prototype.getParentId = function(){
return this._container_widget.getPostId();
};
+/**
+ * this function is basically an "updateDom"
+ * for which we don't have the convention
+ */
Comment.prototype.setContent = function(data){
- this._data = data || this._data;
- this._element.html('');
- this._element.attr('class', 'comment');
+ this._data = $.extend(this._data, data);
+ this._element.addClass('comment');
this._element.attr('id', 'comment-' + this._data['id']);
- var votes = this.makeElement('div');
- votes.addClass('comment-votes');
+ // 1) create the votes element if it is not there
+ var votesBox = this._element.find('.comment-votes');
+ if (votesBox.length === 0) {
+ votesBox = this.makeElement('div');
+ votesBox.addClass('comment-votes');
+ this._element.append(votesBox);
- var vote = new CommentVoteButton(this);
- if (this._data['upvoted_by_user']){
- vote.setVoted(true);
+ var vote = new CommentVoteButton(this);
+ if (this._data['upvoted_by_user']){
+ vote.setVoted(true);
+ }
+ vote.setScore(this._data['score']);
+ var voteElement = vote.getElement();
+
+ votesBox.append(vote.getElement());
+ }
+
+ // 2) create the comment deleter if it is not there
+ if (this._comment_delete === undefined) {
+ this._comment_delete = $('<div class="comment-delete"></div>');
+ if (this._deletable){
+ this._delete_icon = new DeleteIcon(this._delete_prompt);
+ this._delete_icon.setHandler(this.getDeleteHandler());
+ this._comment_delete.append(this._delete_icon.getElement());
+ }
+ this._element.append(this._comment_delete);
}
- vote.setScore(this._data['score']);
- votes.append(vote.getElement());
-
- this._element.append(votes);
- this._comment_delete = $('<div class="comment-delete"></div>');
- if (this._deletable){
- this._delete_icon = new DeleteIcon(this._delete_prompt);
- this._delete_icon.setHandler(this.getDeleteHandler());
- this._comment_delete.append(this._delete_icon.getElement());
+ // 3) create or replace the comment body
+ if (this._comment_body === undefined) {
+ this._comment_body = $('<div class="comment-body"></div>');
+ this._element.append(this._comment_body);
+ }
+ if (askbot['settings']['editorType'] === 'tinymce') {
+ var theComment = $('<div/>');
+ theComment.html(this._data['html']);
+ //sanitize, just in case
+ this._comment_body.empty();
+ this._comment_body.append(theComment);
+ this._data['text'] = this._data['html'];
+ } else {
+ this._comment_body.empty();
+ this._comment_body.html(this._data['html']);
}
- this._element.append(this._comment_delete);
-
- this._comment_body = $('<div class="comment-body"></div>');
- this._comment_body.html(this._data['html']);
//this._comment_body.append(' &ndash; ');
+ // 4) create user link if absent
+ if (this._user_link !== undefined) {
+ this._user_link.detach();
+ this._user_link = undefined;
+ }
this._user_link = $('<a></a>').attr('class', 'author');
this._user_link.attr('href', this._data['user_url']);
this._user_link.html(this._data['user_display_name']);
this._comment_body.append(' ');
this._comment_body.append(this._user_link);
+ // 5) create or update the timestamp
+ if (this._comment_added_at !== undefined) {
+ this._comment_added_at.detach();
+ this._comment_added_at = undefined;
+ }
this._comment_body.append(' (');
this._comment_added_at = $('<abbr class="timeago"></abbr>');
this._comment_added_at.html(this._data['comment_added_at']);
@@ -1795,18 +1973,22 @@ Comment.prototype.setContent = function(data){
this._comment_body.append(this._comment_added_at);
this._comment_body.append(')');
- if (this._editable){
+ if (this._editable) {
+ if (this._edit_link !== undefined) {
+ this._edit_link.dispose();
+ }
this._edit_link = new EditLink();
this._edit_link.setHandler(this.getEditHandler())
this._comment_body.append(this._edit_link.getElement());
}
- if (this._is_convertible){
+ if (this._is_convertible) {
+ if (this._convert_link !== undefined) {
+ this._convert_link.dispose();
+ }
this._convert_link = new CommentConvertLink(this._data['id']);
this._comment_body.append(this._convert_link.getElement());
}
- this._element.append(this._comment_body);
-
this._blank = false;
};
@@ -1873,20 +2055,12 @@ Comment.prototype.getText = function(){
}
Comment.prototype.getEditHandler = function(){
- var comment = this;
+ var me = this;
return function(){
- if (editCommentForm.canCancel()){
- editCommentForm.detach();
- if (comment.hasText()){
- editCommentForm.attachTo(comment, 'edit');
- }
- else {
- comment.loadText(
- function(){
- editCommentForm.attachTo(comment, 'edit');
- }
- );
- }
+ if (me.hasText()){
+ me.startEditing();
+ } else {
+ me.loadText(function(){ me.startEditing() });
}
};
};
@@ -1979,28 +2153,65 @@ PostCommentsWidget.prototype.showButton = function(){
}
PostCommentsWidget.prototype.startNewComment = function(){
- var comment = new Comment(this);
+ var opts = {
+ 'is_deletable': true,
+ 'is_editable': true
+ };
+ var comment = new Comment(this, opts);
this._cbox.append(comment.getElement());
- editCommentForm.attachTo(comment, 'add');
+ comment.startEditing();
};
PostCommentsWidget.prototype.needToReload = function(){
return this._is_truncated;
};
+PostCommentsWidget.prototype.userCanPost = function() {
+ var data = askbot['data'];
+ if (data['userIsAuthenticated']) {
+ //true if admin, post owner or high rep user
+ if (data['userIsAdminOrMod']) {
+ return true;
+ } else if (data['userReputation'] >= askbot['settings']['minRepToPostComment']) {
+ return true;
+ } else if (this.getPostId() in data['user_posts']) {
+ return true;
+ }
+ }
+ return false;
+};
+
PostCommentsWidget.prototype.getActivateHandler = function(){
var me = this;
+ var button = this._activate_button;
return function() {
- if (editCommentForm.canCancel()){
- editCommentForm.detach();
- if (me.needToReload()){
- me.reloadAllComments(function(json){
- me.reRenderComments(json);
- me.startNewComment();
- });
- }
- else {
+ if (me.needToReload()){
+ me.reloadAllComments(function(json){
+ me.reRenderComments(json);
+ //2) change button text to "post a comment"
+ button.html(gettext('post a comment'));
+ });
+ }
+ else {
+ //if user can't post, we tell him something and refuse
+ if (me.userCanPost()) {
me.startNewComment();
+ } else {
+ if (askbot['data']['userIsAuthenticated']) {
+ var template = gettext(
+ 'You can always leave comments under your own posts.<br/>' +
+ 'However, to post comments anywhere, karma should be at least %s,<br/> ' +
+ 'and at the moment your karma is %s.<br/>'
+ );
+ var context = [
+ askbot['settings']['minRepToPostComment'],
+ askbot['data']['userReputation']
+ ];
+ var message = interpolate(template, context);
+ } else {
+ var message = gettext('please sign in or register to post comments');
+ }
+ showMessage(button, message, 'after');
}
}
};
@@ -2170,15 +2381,81 @@ QASwapper.prototype.startSwapping = function(){
/**
* @constructor
+ * a simple textarea-based editor
*/
-var WMD = function(){
+var SimpleEditor = function(attrs) {
WrappedElement.call(this);
+ attrs = attrs || {};
+ this._rows = attrs['rows'] || 10;
+ this._cols = attrs['cols'] || 60;
+ this._maxlength = attrs['maxlength'] || 1000;
+};
+inherits(SimpleEditor, WrappedElement);
+
+SimpleEditor.prototype.focus = function() {
+ this._textarea.focus();
+};
+
+SimpleEditor.prototype.putCursorAtEnd = function() {
+ putCursorAtEnd(this._textarea);
+};
+
+/**
+ * a noop function
+ */
+SimpleEditor.prototype.start = function() {};
+
+SimpleEditor.prototype.setHighlight = function(isHighlighted) {
+ if (isHighlighted === true) {
+ this._textarea.addClass('highlight');
+ } else {
+ this._textarea.removeClass('highlight');
+ }
+};
+
+SimpleEditor.prototype.getText = function() {
+ return $.trim(this._textarea.val());
+};
+
+SimpleEditor.prototype.setText = function(text) {
+ this._text = text;
+ if (this._textarea) {
+ this._textarea.val(text);
+ };
+};
+
+/**
+ * a textarea inside a div,
+ * the reason for this is that we subclass this
+ * in WMD, and that one requires a more complex structure
+ */
+SimpleEditor.prototype.createDom = function() {
+ this._element = this.makeElement('div');
+ var textarea = this.makeElement('textarea');
+ this._element.append(textarea);
+ if (this._text) {
+ textarea.val(this._text);
+ };
+ textarea.attr({
+ 'cols': this._cols,
+ 'rows': this._rows,
+ 'maxlength': this._maxlength
+ });
+}
+
+
+/**
+ * @constructor
+ * a wrapper for the WMD editor
+ */
+var WMD = function(){
+ SimpleEditor.call(this);
this._text = undefined;
this._enabled_buttons = 'bold italic link blockquote code ' +
'image attachment ol ul heading hr';
this._is_previewer_enabled = true;
};
-inherits(WMD, WrappedElement);
+inherits(WMD, SimpleEditor);
WMD.prototype.setEnabledButtons = function(buttons){
this._enabled_buttons = buttons;
@@ -2210,8 +2487,8 @@ WMD.prototype.createDom = function(){
wmd_container.append(editor);
this._textarea = editor;
- if (this._markdown){
- editor.val(this._markdown);
+ if (this._text){
+ editor.val(this._text);
}
var previewer = this.makeElement('div')
@@ -2224,17 +2501,6 @@ WMD.prototype.createDom = function(){
}
};
-WMD.prototype.setText = function(text){
- this._markdown = text;
- if (this._textarea){
- this._textarea.val(text);
- }
-};
-
-WMD.prototype.getText = function(){
- return this._textarea.val();
-};
-
WMD.prototype.start = function(){
Attacklab.Util.startEditor(true, this._enabled_buttons);
};
@@ -2245,60 +2511,75 @@ WMD.prototype.start = function(){
var TinyMCE = function(config) {
WrappedElement.call(this);
this._config = config || {};
+ this._id = 'editor';//desired id of the textarea
};
inherits(TinyMCE, WrappedElement);
/* 3 dummy functions to match WMD api */
TinyMCE.prototype.setEnabledButtons = function() {};
+
TinyMCE.prototype.start = function() {
- this.loadEditor();
+ //copy the options, because we need to modify them
+ var opts = $.extend({}, this._config);
+ var me = this;
+ var extraOpts = {
+ 'mode': 'exact',
+ 'elements': this._id,
+ };
+ opts = $.extend(opts, extraOpts);
+ tinyMCE.init(opts);
+ $('.mceStatusbar').remove();
};
TinyMCE.prototype.setPreviewerEnabled = function() {};
+TinyMCE.prototype.setHighlight = function() {};
+TinyMCE.prototype.putCursorAtEnd = function() {};
+
+TinyMCE.prototype.focus = function() {
+ tinymce.execCommand('mceFocus', false, this._id);
+
+ //@todo: make this general to all editors
+ var winH = $(window).height();
+ var winY = $(window).scrollTop();
+ var edY = this._element.offset().top;
+ var edH = this._element.height();
+
+ //if editor bottom is below viewport
+ var isBelow = ((edY + edH) > (winY + winH));
+ var isAbove = (edY < winY);
+ if (isBelow || isAbove) {
+ //then center on screen
+ $(window).scrollTop(edY - edH/2 - winY/2);
+ }
+
+};
+
+TinyMCE.prototype.setId = function(id) {
+ this._id = id;
+};
TinyMCE.prototype.setText = function(text) {
this._text = text;
+ if (this.isLoaded()) {
+ tinymce.get(this._id).setContent(text);
+ }
};
TinyMCE.prototype.getText = function() {
return tinyMCE.activeEditor.getContent();
};
-TinyMCE.prototype.loadEditor = function() {
- var config = JSON.stringify(this._config);
- var data = {config: config};
- var editorBox = this._element;
- var me = this;
- $.ajax({
- async: false,
- type: 'GET',
- dataType: 'json',
- cache: false,
- url: askbot['urls']['getEditor'],
- data: data,
- success: function(data) {
- if (data['success']) {
- editorBox.html(data['html']);
- editorBox.find('textarea').val(me._text);//@todo: fixme
- $.each(data['scripts'], function(idx, scriptData) {
- var scriptElement = me.makeElement('script');
- scriptElement.attr('type', 'text/javascript');
- if (scriptData['src']) {
- scriptElement.attr('src', scriptData['src']);
- }
- if (scriptData['contents']) {
- scriptElement.html(scriptData['contents']);
- }
- $('head').append(scriptElement);
- });
- }
- }
- });
+TinyMCE.prototype.isLoaded = function() {
+ return (tinymce.get(this._id) !== undefined);
};
TinyMCE.prototype.createDom = function() {
var editorBox = this.makeElement('div');
editorBox.addClass('wmd-container');
this._element = editorBox;
+ var textarea = this.makeElement('textarea');
+ textarea.attr('id', this._id);
+ textarea.addClass('editor');
+ this._element.append(textarea);
};
/**
@@ -2461,12 +2742,10 @@ TagWikiEditor.prototype.decorate = function(element){
var editor = new WMD();
} else {
var editor = new TinyMCE({//override defaults
- mode: 'exact',
- elements: 'editor',
theme_advanced_buttons1: 'bold, italic, |, link, |, numlist, bullist',
theme_advanced_buttons2: '',
- plugins: '',
- width: '200'
+ theme_advanced_path: false,
+ plugins: ''
});
}
if (this._enabled_editor_buttons){
diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js
index 21fb9467..8ce4e205 100644
--- a/askbot/media/js/utils.js
+++ b/askbot/media/js/utils.js
@@ -28,6 +28,13 @@ var animateHashes = function(){
}
};
+var getNewInt = function() {
+ var num = askbot['data']['uniqueInt'] || 0;
+ num = num + 1;
+ askbot['data']['uniqueInt'] = num;
+ return num;
+};
+
var getUniqueValues = function(values) {
var uniques = new Object();
var out = new Array();
@@ -137,6 +144,14 @@ var setupButtonEventHandlers = function(button, callback){
button.click(callback);
};
+var removeButtonEventHandlers = function(button) {
+ button.unbind('click');
+ button.unbind('keydown');
+};
+
+var decodeHtml = function(encodedText) {
+ return $('<div/>').html(encodedText).text();
+};
var putCursorAtEnd = function(element){
var el = $(element).get()[0];
@@ -363,7 +378,7 @@ WrappedElement.prototype.getElement = function(){
return this._element;
};
WrappedElement.prototype.inDocument = function(){
- return this._in_document;
+ return (this._element && this._element.is(':hidden') === false);
};
WrappedElement.prototype.enterDocument = function(){
return this._in_document = true;
diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less
index 70808714..85b18ac1 100644
--- a/askbot/media/style/style.less
+++ b/askbot/media/style/style.less
@@ -146,6 +146,13 @@ html {
visibility: hidden;
}
+.invisible {
+ margin: -1px 0 0 -1px;
+ height: 1px;
+ overflow: hidden;
+ width: 1px;
+}
+
.badges a {
color: #763333;
text-decoration: underline;
@@ -1848,6 +1855,10 @@ ul#related-tags li {
width: 723px;
width: 100%;
}
+ .post-comments .wmd-container {
+ margin-bottom: 8px;
+ margin-left: -2px;
+ }
#editor {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
@@ -2016,9 +2027,9 @@ ul#related-tags li {
/* ----- Question template ----- */
-.question-page{
+.question-page {
- h1{
+ h1 {
padding-top:0px;
font-family:@main-font;
@@ -2081,6 +2092,10 @@ ul#related-tags li {
width:20px;
vertical-align:top;
}
+ .answer-table .mceEditor td,
+ #question-table .mceEditor td {
+ width: auto;
+ }
.question-body, .answer-body {
overflow: auto;
margin-top:10px;
@@ -2393,6 +2408,15 @@ ul#related-tags li {
vertical-align: top;
width: 100px;
}
+
+ input[name="suppress_email"] {
+ margin: 4px 5px 0 0;
+ width: auto;
+ }
+ label[for="suppress_email"] {
+ vertical-align: top;
+ }
+
button.submit {
height: 26px;
line-height: 26px;
@@ -2403,7 +2427,6 @@ ul#related-tags li {
display: inline-block;
width: 245px;
float:right;
- color:#b6a475 !important;
vertical-align: top;
font-family:@body-font;
float:right;
@@ -3199,28 +3222,29 @@ ins .post-tag, ins p, ins {
.vote-notification {
z-index: 1;
+ background-color: #8e0000;
+ color: white;
cursor: pointer;
display: none;
- position: absolute;
font-family:@secondary-font;
font-size:14px;
font-weight:normal;
- color: white;
- background-color: #8e0000;
- text-align: center;
padding-bottom:10px;
+ position: absolute;
+ text-align: center;
.box-shadow(0px, 2px, 4px, #370000);
.rounded-corners(4px);
h3{
background:url(../images/notification.png) repeat-x top;
- padding:10px 10px 10px 10px;
- font-size:13px;
- margin-bottom:5px;
- border-top:#8e0000 1px solid;
- color:#fff;
- font-weight:normal;
- .rounded-corners-top(4px);
+ padding:10px 10px 10px 10px;
+ font-size:13px;
+ margin-bottom:5px;
+ border-top:#8e0000 1px solid;
+ color:#fff;
+ line-height: 20px;
+ font-weight:normal;
+ .rounded-corners-top(4px);
}
a {
color: #fb7321;
diff --git a/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py b/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py
index 70ef2f8d..2c58d82a 100644
--- a/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py
+++ b/askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py
@@ -25,8 +25,8 @@ class Migration(SchemaMigration):
# Changing field 'BadgeData.slug'
db.alter_column('askbot_badgedata', 'slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50))
-
# Adding unique constraint on 'BadgeData', fields ['slug']
+ return
try:#work around the South 0.7.3 bug
db.start_transaction()
db.create_unique('askbot_badgedata', ['slug'])
diff --git a/askbot/models/post.py b/askbot/models/post.py
index 65ea535d..e6c26154 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -1,7 +1,6 @@
from collections import defaultdict
import datetime
import operator
-import cgi
import logging
from django.utils.html import strip_tags
@@ -35,7 +34,7 @@ from askbot.models.tag import tags_match_some_wildcard
from askbot.conf import settings as askbot_settings
from askbot import exceptions
from askbot.utils import markup
-from askbot.utils.html import sanitize_html
+from askbot.utils.html import sanitize_html, strip_tags
from askbot.models.base import BaseQuerySetManager, DraftContent
#todo: maybe merge askbot.utils.markup and forum.utils.html
@@ -425,25 +424,24 @@ class Post(models.Model):
if self.post_type in ('question', 'answer', 'tag_wiki', 'reject_reason'):
_urlize = False
_use_markdown = (askbot_settings.EDITOR_TYPE == 'markdown')
- _escape_html = False #markdow does the escaping
elif self.is_comment():
_urlize = True
_use_markdown = (askbot_settings.EDITOR_TYPE == 'markdown')
- _escape_html = True
else:
raise NotImplementedError
text = self.text
- if _escape_html:
- text = cgi.escape(text)
-
if _urlize:
text = html.urlize(text)
if _use_markdown:
text = sanitize_html(markup.get_parser().convert(text))
+ if askbot_settings.EDITOR_TYPE == 'tinymce':
+ #todo: see what can be done with the "object" tag
+ text = strip_tags(text, ['script', 'style', 'link'])
+
#todo, add markdown parser call conditional on
#self.use_markdown flag
post_html = text
diff --git a/askbot/models/repute.py b/askbot/models/repute.py
index e48773e6..5e9c295f 100644
--- a/askbot/models/repute.py
+++ b/askbot/models/repute.py
@@ -91,7 +91,9 @@ class BadgeData(models.Model):
"""Awarded for notable actions performed on the site by Users."""
slug = models.SlugField(max_length=50, unique=True)
awarded_count = models.PositiveIntegerField(default=0)
- awarded_to = models.ManyToManyField(User, through='Award', related_name='badges')
+ awarded_to = models.ManyToManyField(
+ User, through='Award', related_name='badges'
+ )
def _get_meta_data(self):
"""retrieves badge metadata stored
@@ -99,16 +101,13 @@ class BadgeData(models.Model):
from askbot.models import badges
return badges.get_badge(self.slug)
- @property
- def name(self):
+ def get_name(self):
return self._get_meta_data().name
- @property
- def description(self):
+ def get_description(self):
return self._get_meta_data().description
- @property
- def css_class(self):
+ def get_css_class(self):
return self._get_meta_data().css_class
def get_type_display(self):
@@ -125,19 +124,6 @@ class BadgeData(models.Model):
def get_absolute_url(self):
return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
-class AwardManager(models.Manager):
- def get_recent_awards(self):
- awards = super(AwardManager, self).extra(
- select={'badge_id': 'badge.id', 'badge_name':'badge.name',
- 'badge_description': 'badge.description', 'badge_type': 'badge.type',
- 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
- },
- tables=['award', 'badge', 'auth_user'],
- order_by=['-awarded_at'],
- where=['auth_user.id=award.user_id AND badge_id=badge.id'],
- ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
- return awards
-
class Award(models.Model):
"""The awarding of a Badge to a User."""
user = models.ForeignKey(User, related_name='award_user')
@@ -148,10 +134,8 @@ class Award(models.Model):
awarded_at = models.DateTimeField(default=datetime.datetime.now)
notified = models.BooleanField(default=False)
- objects = AwardManager()
-
def __unicode__(self):
- return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
+ return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.get_name(), self.awarded_at)
class Meta:
app_label = 'askbot'
diff --git a/askbot/search/postgresql/__init__.py b/askbot/search/postgresql/__init__.py
index e42190a8..d71b824f 100644
--- a/askbot/search/postgresql/__init__.py
+++ b/askbot/search/postgresql/__init__.py
@@ -65,6 +65,11 @@ def run_full_text_search(query_set, query_text, text_search_vector_name):
language_code = get_language()
+ #a hack with japanese search for the short queries
+ if language_code == 'ja' and len(query_text) in (1, 2):
+ mul = 4/len(query_text) #4 for 1 and 2 for 2
+ query_text = (query_text + ' ')*mul
+
#the table name is a hack, because user does not have the language code
is_multilingual = getattr(django_settings, 'ASKBOT_MULTILINGUAL', True)
if is_multilingual and table_name == 'askbot_thread':
diff --git a/askbot/templates/badge.html b/askbot/templates/badge.html
index b2c4ce8b..aebf5450 100644
--- a/askbot/templates/badge.html
+++ b/askbot/templates/badge.html
@@ -2,11 +2,11 @@
{% import "macros.html" as macros %}
{%from "macros.html" import gravatar %}
<!-- template badge.html -->
-{% block title %}{% spaceless %}{% trans name=badge.name %}{{name}}{% endtrans %} - {% trans %}Badge{% endtrans %}{% endspaceless %}{% endblock %}
+{% block title %}{% spaceless %}{% trans name=badge.get_name() %}{{name}}{% endtrans %} - {% trans %}Badge{% endtrans %}{% endspaceless %}{% endblock %}
{% block content %}
-<h1 class="section-title">{% trans name=badge.name %}Badge "{{name}}"{% endtrans %}</h1>
+<h1 class="section-title">{% trans name=badge.get_name() %}Badge "{{name}}"{% endtrans %}</h1>
<p>
- <a href="{{badge.get_absolute_url()}}" title="{{ badge.get_type_display() }} : {% trans description=badge.description %}{{description}}{% endtrans %}" class="medal"><span class="{{ badge.css_class }}">&#9679;</span>&nbsp;{% trans name=badge.name%}{{name}}{% endtrans %}</a> {% trans description=badge.description %}{{description}}{% endtrans %}
+ <a href="{{badge.get_absolute_url()}}" title="{{ badge.get_type_display() }} : {% trans description=badge.get_description() %}{{description}}{% endtrans %}" class="medal"><span class="{{ badge.get_css_class() }}">&#9679;</span>&nbsp;{% trans name=badge.get_name() %}{{name}}{% endtrans %}</a> {% trans description=badge.get_description() %}{{description}}{% endtrans %}
</p>
<div>
{% if badge.awarded_count %}
diff --git a/askbot/templates/badges.html b/askbot/templates/badges.html
index e669b7d4..112adc61 100644
--- a/askbot/templates/badges.html
+++ b/askbot/templates/badges.html
@@ -17,11 +17,11 @@
{% endif %}
<div style="float:left;width:230px;">
<a href="{{badge.get_absolute_url()}}"
- title="{{badge.get_type_display()}} : {{badge.description}}"
- class="medal"><span class="{{ badge.css_class }}">&#9679;</span>&nbsp;{{badge.name}}</a><strong>
+ title="{{ badge.get_type_display() }} : {{ badge.get_description() }}"
+ class="medal"><span class="{{ badge.get_css_class() }}">&#9679;</span>&nbsp;{{ badge.get_name() }}</a><strong>
&#215; {{ badge.awarded_count|intcomma }}</strong>
</div>
- <p style="float:left;margin-top:8px;">{{badge.description}}</p>
+ <p style="float:left;margin-top:8px;">{{ badge.get_description() }}</p>
</div>
{% endfor %}
</div>
diff --git a/askbot/templates/embed/ask_by_widget.html b/askbot/templates/embed/ask_by_widget.html
index dc3db806..dae9f598 100644
--- a/askbot/templates/embed/ask_by_widget.html
+++ b/askbot/templates/embed/ask_by_widget.html
@@ -209,7 +209,7 @@
<script type="text/javascript" src='{{"/js/live_search_new_thread.js"|media}}'></script>
<script type="text/javascript" charset="utf-8">
- askbot['settings']['minSearchWordLength'] = {{settings.MIN_SEARCH_WORD_LENGTH}};
+ askbot['settings']['minSearchWordLength'] = {{ min_search_word_length }};
askbot['urls']['titleSearch'] = '{% url title_search %}';
askbot['urls']['upload'] = '{% url upload %}';
$(document).ready(function(){
diff --git a/askbot/templates/meta/bottom_scripts.html b/askbot/templates/meta/bottom_scripts.html
index 805974cc..78e23d89 100644
--- a/askbot/templates/meta/bottom_scripts.html
+++ b/askbot/templates/meta/bottom_scripts.html
@@ -27,7 +27,7 @@
askbot['urls']['questions'] = '{% url "questions" %}';
askbot['settings']['groupsEnabled'] = {{ settings.GROUPS_ENABLED|as_js_bool }};
askbot['settings']['static_url'] = '{{ settings.STATIC_URL }}';
- askbot['settings']['minSearchWordLength'] = {{ settings.MIN_SEARCH_WORD_LENGTH }};
+ askbot['settings']['minSearchWordLength'] = {{ min_search_word_length }};
askbot['settings']['mathjaxEnabled'] = {{ settings.ENABLE_MATHJAX|as_js_bool }};
askbot['settings']['sharingSuffixText'] = '{{ settings.SHARING_SUFFIX_TEXT|escape }}';
askbot['data']['haveFlashNotifications'] = {{ user_messages|as_js_bool }};
@@ -58,6 +58,7 @@
{% endif %}
<script type="text/javascript">
/*<![CDATA[*/
+ $('.mceStatusbar').remove();//a hack to remove the tinyMCE status bar
$(document).ready(function(){
// focus input on the search bar endcomment
var activeTab = askbot['data']['activeTab'];
diff --git a/askbot/templates/question.html b/askbot/templates/question.html
index 74e101b2..5fa32206 100644
--- a/askbot/templates/question.html
+++ b/askbot/templates/question.html
@@ -178,48 +178,15 @@
}
}
function render_add_comment_button(post_id, extra_comment_count){
- var can_add = false;
- if (data['user_posts'] === undefined) {
- return;
- }
- {% if user_can_post_comment %}
- can_add = true;
- {% else %}
- if (data['user_posts'] && post_id in data['user_posts']){
- can_add = true;
- }
- {% endif %}
- var add_comment_btn = document.getElementById(
- 'add-comment-to-post-' + post_id
- );
- if (can_add === false){
- add_comment_btn.parentNode.removeChild(add_comment_btn);
- return;
- }
-
- var text = '';
if (extra_comment_count > 0){
- if (can_add){
- text =
- "{% trans %}post a comment / <strong>some</strong> more{% endtrans %}";
- } else {
- text =
- "{% trans %}see <strong>some</strong> more{% endtrans%}";
- }
+ var text = "{% trans %}see more comments{% endtrans%}";
} else {
- if (can_add){
- text = "{% trans %}post a comment{% endtrans %}";
- }
+ var text = "{% trans %}post a comment{% endtrans %}";
}
+ var add_comment_btn = document.getElementById('add-comment-to-post-' + post_id);
add_comment_btn.innerHTML = text;
- //add the count
- for (node in add_comment_btn.childNodes){
- if (node.nodeName === 'strong'){
- node.innerHTML = extra_comment_count;
- break;
- }
- }
}
+
function render_add_answer_button(){
var add_answer_btn = document.getElementById('add-answer-btn');
if (askbot['data']['userIsAuthenticated']){
@@ -339,4 +306,3 @@
</script>
#}
{% endblock %}
-
diff --git a/askbot/templates/question/content.html b/askbot/templates/question/content.html
index 4481fb9a..11813813 100644
--- a/askbot/templates/question/content.html
+++ b/askbot/templates/question/content.html
@@ -20,8 +20,6 @@
{{ macros.paginator(paginator_context, anchor='#sort-top') }}
<div class="clean"></div>
-{% else %}
- {% include "question/sharing_prompt_phrase.html" %}
{% endif %}
{# buttons below cannot be cached yet #}
@@ -31,6 +29,10 @@
href="{% url "edit_answer" previous_answer.id %}"
>{% trans %}Edit Your Previous Answer{% endtrans %}</a>
<span>{% trans %}(only one answer per question is allowed){% endtrans %}</span>
+ <div style="invisible">
+ {# hidden because we still need js from the tinymce widget #}
+ {% include "question/new_answer_form.html" %}
+ </div>
{% else %}
{% include "question/new_answer_form.html" %}
{% endif %}
diff --git a/askbot/templates/question/new_answer_form.html b/askbot/templates/question/new_answer_form.html
index f9559c4d..2235f4f4 100644
--- a/askbot/templates/question/new_answer_form.html
+++ b/askbot/templates/question/new_answer_form.html
@@ -3,9 +3,6 @@
action="{% url answer question.id %}"
method="post"
>{% csrf_token %}
- {# ==== START: question/subscribe_by_email_prompt.html ==== #}
- {% include "question/subscribe_by_email_prompt.html" %}
- {# ==== END: question/subscribe_by_email_prompt.html ==== #}
<div style="clear:both"></div>
{% if request.user.is_anonymous() and settings.ALLOW_POSTING_BEFORE_LOGGING_IN == False %}
{% if not thread.closed %}
diff --git a/askbot/templates/question/sharing_prompt_phrase.html b/askbot/templates/question/sharing_prompt_phrase.html
deleted file mode 100644
index 2e68d1f3..00000000
--- a/askbot/templates/question/sharing_prompt_phrase.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% set question_url=(settings.APP_URL|strip_path + question.get_absolute_url())|urlencode %}
-<h2 class="share-question">{% trans %}Know someone who can answer? Share a <a href="{{ question_url }}">link</a> to this question via{% endtrans %}
- {% if settings.ENABLE_SHARING_TWITTER %}{{ macros.share(site = 'twitter', site_label = 'Twitter') }},{% endif %}
- {% if settings.ENABLE_SHARING_FACEBOOK %}{{ macros.share(site = 'facebook', site_label = 'Facebook') }},{% endif %}
- {% if settings.ENABLE_SHARING_LINKEDIN %}{{ macros.share(site = 'linkedin', site_label = 'LinkedIn') }},{% endif %}
- {% if settings.ENABLE_SHARING_IDENTICA %}{{ macros.share(site = 'identica', site_label = 'Identi.ca') }},{% endif %}
- {%- if settings.ENABLE_SHARING_TWITTER or settings.ENABLE_SHARING_FACEBOOK or settings.ENABLE_SHARING_LINKEDIN or settings.ENABLE_SHARING_IDENTICA -%}
- {% trans %} or{% endtrans %}
- {% endif %}
- <a href="mailto:?subject={{ settings.APP_SHORT_NAME|urlencode }}&amp;body={{ question_url }}">{% trans %}email{% endtrans %}</a>.
-</h2>
diff --git a/askbot/templates/question/subscribe_by_email_prompt.html b/askbot/templates/question/subscribe_by_email_prompt.html
deleted file mode 100644
index 6a77601c..00000000
--- a/askbot/templates/question/subscribe_by_email_prompt.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% if request.user.is_authenticated() %}
- <p>
- {{ answer.email_notify }}
- <label for="question-subscribe-updates">
- {% trans %}Email me when there are any new answers{% endtrans %}
- </label>
- </p>
-{% else %}
- <p>
- {{ answer.email_notify }}
- <label>{% trans %}<span class='strong'>Here</span> (once you log in) you will be able to sign up for the periodic email updates about this question.{% endtrans %}</label>
- </p>
-{% endif %}
diff --git a/askbot/templates/user_profile/user_recent.html b/askbot/templates/user_profile/user_recent.html
index 8eae673d..deac051b 100644
--- a/askbot/templates/user_profile/user_recent.html
+++ b/askbot/templates/user_profile/user_recent.html
@@ -15,9 +15,9 @@
<div style="float:left;overflow:hidden;">
{% if act.is_badge %}
<a href="{{act.badge.get_absolute_url()}}"
- title="{{ act.badge.get_type_display() }} : {% trans description=act.badge.description %}{{description}}{% endtrans %}"
+ title="{{ act.badge.get_type_display() }} : {% trans description=act.badge.get_description() %}{{description}}{% endtrans %}"
class="medal">
- <span class="{{ act.badge.css_class }}">&#9679;</span>&nbsp;{% trans name=act.badge.name %}{{name}}{% endtrans %}
+ <span class="{{ act.badge.get_css_class() }}">&#9679;</span>&nbsp;{% trans name=act.badge.get_name() %}{{name}}{% endtrans %}
</a>
{% if act.content_object.post_type == 'question' %}
{% set question=act.content_object %}
diff --git a/askbot/templates/user_profile/user_stats.html b/askbot/templates/user_profile/user_stats.html
index c042b5fb..812f3411 100644
--- a/askbot/templates/user_profile/user_stats.html
+++ b/askbot/templates/user_profile/user_stats.html
@@ -115,9 +115,9 @@
{% for badge, badge_user_awards in badges %}
<a
href="{{badge.get_absolute_url()}}"
- title="{% trans description=badge.description %}{{description}}{% endtrans %}"
+ title="{% trans description=badge.get_description() %}{{description}}{% endtrans %}"
class="medal"
- ><span class="{{ badge.css_class }}">&#9679;</span>&nbsp;{% trans name=badge.name %}{{name}}{% endtrans %}
+ ><span class="{{ badge.get_css_class() }}">&#9679;</span>&nbsp;{% trans name=badge.get_name() %}{{name}}{% endtrans %}
</a>&nbsp;
<span class="tag-number">&#215;
<span class="badge-context-toggle">{{ badge_user_awards|length|intcomma }}</span>
diff --git a/askbot/utils/html.py b/askbot/utils/html.py
index 1d76fdb7..549f22bf 100644
--- a/askbot/utils/html.py
+++ b/askbot/utils/html.py
@@ -94,7 +94,15 @@ def replace_links_with_text(html):
link.replaceWith(format_url_replacement(url, text))
return unicode(soup.find('body').renderContents(), 'utf-8')
-
+
+def strip_tags(html, tags=None):
+ """strips tags from given html output"""
+ assert(tags != None)
+ soup = BeautifulSoup(html)
+ for tag in tags:
+ tag_matches = soup.find_all(tag)
+ map(lambda v: v.replaceWith(''), tag_matches)
+ return unicode(soup.find('body').renderContents(), 'utf-8')
def sanitize_html(html):
"""Sanitizes an HTML fragment."""
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index f810a750..133ef70e 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -1463,7 +1463,12 @@ def get_editor(request):
if 'config' not in request.GET:
return HttpResponseForbidden()
config = simplejson.loads(request.GET['config'])
- form = forms.EditorForm(editor_attrs=config, user=request.user)
+ element_id = request.GET.get('id', 'editor')
+ form = forms.EditorForm(
+ attrs={'id': element_id},
+ editor_attrs=config,
+ user=request.user
+ )
editor_html = render_text_into_skin(
'{{ form.media }} {{ form.editor }}',
{'form': form},
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index 58530522..896ef09d 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -658,7 +658,7 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po
is_deletable = True
except exceptions.PermissionDenied:
is_deletable = False
- is_editable = template_filters.can_edit_comment(comment.author, comment)
+ is_editable = template_filters.can_edit_comment(user, comment)
else:
is_deletable = False
is_editable = False
@@ -687,6 +687,10 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po
@csrf.csrf_exempt
@decorators.check_spam('comment')
def post_comments(request):#generic ajax handler to load comments to an object
+ """todo: fixme: post_comments is ambigous:
+ means either get comments for post or
+ add a new comment to post
+ """
# only support get post comments by ajax now
post_type = request.REQUEST.get('post_type', '')
@@ -695,11 +699,27 @@ def post_comments(request):#generic ajax handler to load comments to an object
user = request.user
- id = request.REQUEST['post_id']
- obj = get_object_or_404(models.Post, id=id)
+ if request.method == 'POST':
+ form = forms.NewCommentForm(request.POST)
+ elif request.method == 'GET':
+ form = forms.GetCommentsForPostForm(request.GET)
+
+ if form.is_valid() == False:
+ return HttpResponseBadRequest(
+ _('This content is forbidden'),
+ mimetype='application/json'
+ )
+
+ post_id = form.cleaned_data['post_id']
+ try:
+ post = models.Post.objects.get(id=post_id)
+ except models.Post.DoesNotExist:
+ return HttpResponseBadRequest(
+ _('Post not found'), mimetype='application/json'
+ )
if request.method == "GET":
- response = __generate_comments_json(obj, user)
+ response = __generate_comments_json(post, user)
elif request.method == "POST":
try:
if user.is_anonymous():
@@ -708,47 +728,55 @@ def post_comments(request):#generic ajax handler to load comments to an object
'<a href="%(sign_in_url)s">sign in</a>.') % \
{'sign_in_url': url_utils.get_login_url()}
raise exceptions.PermissionDenied(msg)
- user.post_comment(parent_post=obj, body_text=request.POST.get('comment'))
- response = __generate_comments_json(obj, user)
+ user.post_comment(
+ parent_post=post, body_text=form.cleaned_data['comment']
+ )
+ response = __generate_comments_json(post, user)
except exceptions.PermissionDenied, e:
response = HttpResponseForbidden(unicode(e), mimetype="application/json")
return response
-@csrf.csrf_exempt
+#@csrf.csrf_exempt
@decorators.ajax_only
-@decorators.check_spam('comment')
+#@decorators.check_spam('comment')
def edit_comment(request):
+
if request.user.is_anonymous():
raise exceptions.PermissionDenied(_('Sorry, anonymous users cannot edit comments'))
form = forms.EditCommentForm(request.POST)
if form.is_valid() == False:
- return HttpResponseBadRequest()
-
- comment_id = form.cleaned_data['comment_id']
- suppress_email = form.cleaned_data['suppress_email']
+ raise exceptions.PermissionDenied('This content is forbidden')
- comment_post = models.Post.objects.get(post_type='comment', id=comment_id)
+ comment_post = models.Post.objects.get(
+ post_type='comment',
+ id=form.cleaned_data['comment_id']
+ )
request.user.edit_comment(
comment_post=comment_post,
- body_text = request.POST['comment'],
- suppress_email=suppress_email
+ body_text=form.cleaned_data['comment'],
+ suppress_email=form.cleaned_data['suppress_email']
)
- is_deletable = template_filters.can_delete_comment(comment_post.author, comment_post)
- is_editable = template_filters.can_edit_comment(comment_post.author, comment_post)
+ is_deletable = template_filters.can_delete_comment(
+ comment_post.author, comment_post)
+
+ is_editable = template_filters.can_edit_comment(
+ comment_post.author, comment_post)
+
tz = ' ' + template_filters.TIMEZONE_STR
tz = template_filters.TIMEZONE_STR
+ timestamp = str(comment_post.added_at.replace(microsecond=0)) + tz
return {
'id' : comment_post.id,
'object_id': comment_post.parent.id,
- 'comment_added_at': str(comment_post.added_at.replace(microsecond = 0)) + tz,
+ 'comment_added_at': timestamp,
'html': comment_post.html,
- 'user_display_name': comment_post.author.username,
+ 'user_display_name': escape(comment_post.author.username),
'user_url': comment_post.author.get_profile_url(),
'user_id': comment_post.author.id,
'is_deletable': is_deletable,