summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGustavo A. Gómez Farhat <gustavo.gomez.farhat@gmail.com>2013-04-08 09:04:47 -0500
committerGustavo A. Gómez Farhat <gustavo.gomez.farhat@gmail.com>2013-04-08 09:04:47 -0500
commitc3ef1da21c0b46699dbf6f3dc1e53e6d35bccadd (patch)
treed29686dba3f73660310a8e2d781cac49ea4344ca
parent80dc8af009a09d849004583fed36f47f6a33484f (diff)
parent0906b8d3e2e0c4097cb4ad5f1d21c2bf411c179e (diff)
downloadaskbot-c3ef1da21c0b46699dbf6f3dc1e53e6d35bccadd.tar.gz
askbot-c3ef1da21c0b46699dbf6f3dc1e53e6d35bccadd.tar.bz2
askbot-c3ef1da21c0b46699dbf6f3dc1e53e6d35bccadd.zip
Merge branch 'master' of github.com:ASKBOT/askbot-devel
-rwxr-xr-x.gitignore1
-rw-r--r--askbot/conf/forum_data_rules.py23
-rw-r--r--askbot/context.py14
-rw-r--r--askbot/doc/source/changelog.rst5
-rw-r--r--askbot/forms.py28
-rw-r--r--askbot/management/commands/askbot_add_test_content.py16
-rw-r--r--askbot/management/commands/askbot_import_jive.py139
-rw-r--r--askbot/media/js/live_search.js24
-rw-r--r--askbot/media/js/post.js710
-rw-r--r--askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js4
-rw-r--r--askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js3
-rw-r--r--askbot/media/js/utils.js261
-rw-r--r--askbot/media/js/wmd/wmd.css67
-rw-r--r--askbot/media/js/wmd/wmd.js140
-rw-r--r--askbot/media/style/style.css98
-rw-r--r--askbot/media/style/style.less116
-rw-r--r--askbot/migrations/0032_auto__del_field_badgedata_multiple__del_field_badgedata_description__d.py2
-rw-r--r--askbot/models/post.py46
-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.html13
-rw-r--r--askbot/templates/meta/html_head_javascript.html10
-rw-r--r--askbot/templates/question.html43
-rw-r--r--askbot/templates/question/content.html6
-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/utils/markup.py17
-rw-r--r--askbot/views/commands.py7
-rw-r--r--askbot/views/writers.py75
34 files changed, 1414 insertions, 534 deletions
diff --git a/.gitignore b/.gitignore
index e2e3cd09..c108ec09 100755
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ env
/custom_settings
/static
django
+tinymce
lamson
django/*
nbproject
diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py
index b318e4a2..362f4735 100644
--- a/askbot/conf/forum_data_rules.py
+++ b/askbot/conf/forum_data_rules.py
@@ -28,6 +28,21 @@ settings.register(
)
)
+COMMENTS_EDITOR_CHOICES = (
+ ('plain-text', 'Plain text editor'),
+ ('rich-text', 'Same editor as for questions and answers')
+)
+
+settings.register(
+ livesettings.StringValue(
+ FORUM_DATA_RULES,
+ 'COMMENTS_EDITOR_TYPE',
+ default='plain-text',
+ choices=COMMENTS_EDITOR_CHOICES,
+ description=_('Editor for the comments')
+ )
+)
+
settings.register(
livesettings.BooleanValue(
FORUM_DATA_RULES,
@@ -330,8 +345,12 @@ settings.register(
livesettings.BooleanValue(
FORUM_DATA_RULES,
'SAVE_COMMENT_ON_ENTER',
- default = True,
- description = _('Save comment by pressing <Enter> key')
+ default=False,
+ description=_('Save comment by pressing <Enter> key'),
+ help_text=_(
+ 'This may be useful when only one-line comments '
+ 'are desired. Will not work with TinyMCE editor.'
+ )
)
)
diff --git a/askbot/context.py b/askbot/context.py
index fba17b5f..abd283e1 100644
--- a/askbot/context.py
+++ b/askbot/context.py
@@ -55,8 +55,20 @@ 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':
+ #we need to open the search box and show info message about
+ #the japanese lang search
+ min_search_word_length = 1
+ 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/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index 7b65ee07..a99864c7 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,7 +3,10 @@ Changes in Askbot
Development version
-------------------
-* Added instant search to the tags page
+* Management command `askbot_import_jive` to import data from Jive forums.
+* Added possibility to choose editor for comments: plain text, or same as
+ editor used for the questions or answers: WMD or TinyMCE.
+* Added ajax search to the tags page
* Added a placeholder template for the custom javascript on the question page
* Allowed to disable the big "ask" button.
* Some support for the media compression (Tyler Mandry)
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/management/commands/askbot_import_jive.py b/askbot/management/commands/askbot_import_jive.py
new file mode 100644
index 00000000..90bbfd98
--- /dev/null
+++ b/askbot/management/commands/askbot_import_jive.py
@@ -0,0 +1,139 @@
+from askbot import models
+from askbot.conf import settings as askbot_settings
+from askbot.utils.console import ProgressBar
+from askbot.utils.slug import slugify
+from bs4 import BeautifulSoup
+from django.core.management.base import BaseCommand, CommandError
+from django.db import transaction
+from django.forms import EmailField, ValidationError
+from datetime import datetime
+
+"""
+Jive --> CategoryList --> Category --> ForumList --> Forum
+ <Name>ouaou</Name>
+ <CreationDate>2008-05-06-0249</CreationDate>
+ <ModifiedDate/>
+ <ThreadList>
+ <Thread id="4046">
+ <CreationDate>2013/03/08 01:50:42.54 CST</CreationDate>
+ <ModifiedDate>2013/03/12 23:44:45.528 CDT</ModifiedDate>
+ <Message id="16809">
+ <Subject>Need help setting up mirror space</Subject>
+ <Body>Body text</Body>
+ <Username>jfawcett</Username>
+ <CreationDate>2013/03/08 01:50:42.54 CST</CreationDate>
+ <ModifiedDate>2013/03/08 01:50:42.54 CST</ModifiedDate>
+ <MessageList>
+ </MessageList>
+ </Message>
+ </Thread>
+"""
+
+def parse_date(date_str):
+ return datetime.strptime(date_str[:-8], '%Y/%m/%d %H:%M:%S')
+
+class Command(BaseCommand):
+ args = '<jive-dump.xml>'
+
+ def __init__(self, *args, **kwargs):
+ super(Command, self).__init__(*args, **kwargs)
+ #relax certain settings
+ askbot_settings.update('LIMIT_ONE_ANSWER_PER_USER', False)
+ askbot_settings.update('MAX_COMMENT_LENGTH', 1000000)
+ askbot_settings.update('MIN_REP_TO_LEAVE_COMMENTS', 1)
+ self.bad_email_count = 0
+
+ def handle(self, *args, **kwargs):
+ assert len(args) == 1, 'Dump file name is required'
+ xml = open(args[0], 'r').read()
+ try:
+ import lxml
+ soup = BeautifulSoup(xml, 'lxml')
+ except ImportError:
+ soup = BeautifulSoup(xml)
+
+ self.import_users(soup.find_all('user'))
+ self.import_forums(soup.find_all('forum'))
+
+ @transaction.commit_manually
+ def import_users(self, user_soup):
+ """import users from jive to askbot"""
+
+ message = 'Importing users:'
+ for user in ProgressBar(iter(user_soup), len(user_soup), message):
+ username = user.find('username').text
+ real_name = user.find('name').text
+ try:
+ email = EmailField().clean(user.find('email').text)
+ except ValidationError:
+ email = 'unknown%d@example.com' % self.bad_email_count
+ self.bad_email_count += 1
+
+ joined_timestamp = parse_date(user.find('creationdate').text)
+ user = models.User(
+ username=username,
+ email=email,
+ real_name=real_name,
+ date_joined=joined_timestamp
+ )
+ user.save()
+ transaction.commit()
+
+ def import_forums(self, forum_soup):
+ """import forums by associating each with a special tag,
+ and then importing all threads for the tag"""
+ admin = models.User.objects.get(id=1)
+ for forum in forum_soup:
+ threads_soup = forum.find_all('thread')
+ self.import_threads(threads_soup, forum.find('name').text)
+
+ @transaction.commit_manually
+ def import_threads(self, threads, tag_name):
+ message = 'Importing threads for %s' % tag_name
+ for thread in ProgressBar(iter(threads), len(threads), message):
+ self.import_thread(thread, tag_name)
+ transaction.commit()
+
+ def import_thread(self, thread, tag_name):
+ """import individual thread"""
+ question_soup = thread.message
+ title, body, timestamp, user = self.parse_post(question_soup)
+ #post question
+ question = user.post_question(
+ title=title,
+ body_text=body,
+ timestamp=timestamp,
+ tags=tag_name
+ )
+ #post answers
+ if not question_soup.messagelist:
+ return
+
+ for answer_soup in question_soup.messagelist.find_all('message', recursive=False):
+ title, body, timestamp, user = self.parse_post(answer_soup)
+ answer = user.post_answer(
+ question=question,
+ body_text=body,
+ timestamp=timestamp
+ )
+ comments = answer_soup.find_all('message')
+ for comment in comments:
+ title, body, timestamp, user = self.parse_post(comment)
+ user.post_comment(
+ parent_post=answer,
+ body_text=body,
+ timestamp=timestamp
+ )
+
+ def parse_post(self, post):
+ title = post.find('subject').text
+ added_at = parse_date(post.find('creationdate').text)
+ username = post.find('username').text
+ try:
+ user = models.User.objects.get(username=username)
+ except models.User.DoesNotExist:
+ email = 'unknown%d@example.com' % self.bad_email_count
+ self.bad_email_count += 1
+ user = models.User(username=username, email=email)
+ user.save()
+ return title, post.text, added_at, user
diff --git a/askbot/media/js/live_search.js b/askbot/media/js/live_search.js
index b744e69e..98e01179 100644
--- a/askbot/media/js/live_search.js
+++ b/askbot/media/js/live_search.js
@@ -149,11 +149,31 @@ SearchDropMenu.prototype.hideWaitIcon = function() {
}
};
+SearchDropMenu.prototype.hideHeader = function() {
+ if (this._header) {
+ this._header.hide();
+ }
+};
+
+SearchDropMenu.prototype.showHeader = function() {
+ if (this._header) {
+ this._header.show();
+ }
+};
+
SearchDropMenu.prototype.createDom = function() {
this._element = this.makeElement('div');
this._element.addClass('search-drop-menu');
this._element.hide();
+ if (askbot['data']['languageCode'] === 'ja') {
+ var warning = this.makeElement('p');
+ this._header = warning;
+ warning.addClass('header');
+ warning.html(gettext('To see search results, 2 or more characters may be required'));
+ this._element.append(warning);
+ }
+
this._resultsList = this.makeElement('ul');
this._element.append(this._resultsList);
this._element.addClass('empty');
@@ -503,6 +523,9 @@ FullTextSearch.prototype.getSearchQuery = function() {
FullTextSearch.prototype.renderTitleSearchResult = function(data) {
var menu = this._dropMenu;
menu.hideWaitIcon();
+ if (data.length > 0) {
+ menu.hideHeader();
+ }
menu.setData(data);
menu.render();
menu.show();
@@ -789,6 +812,7 @@ FullTextSearch.prototype.makeKeyDownHandler = function() {
past the minimum length to trigger search */
dropMenu.show();
dropMenu.showWaitIcon();
+ dropMenu.showHeader();
} else {
//close drop menu if we were deleting the query
dropMenu.reset();
diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js
index 1fb1203f..4e1e8da2 100644
--- a/askbot/media/js/post.js
+++ b/askbot/media/js/post.js
@@ -1410,50 +1410,160 @@ 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();
+ this._minorEditBox.hide();
+ this._element.hide();
+ } else {
+ this._element.show();
+ this._editor.getElement().show();
+ this._submit_btn.show();
+ this._cancel_btn.show();
+ this._minorEditBox.show();
+ }
};
+EditCommentForm.prototype.getEditorType = function() {
+ if (askbot['settings']['commentsEditorType'] === 'rich-text') {
+ return askbot['settings']['editorType'];
+ } else {
+ return 'plain-text';
+ }
+};
+
+EditCommentForm.prototype.startTinyMCEEditor = function() {
+ var editorId = this.makeId('comment-editor');
+ var opts = {
+ mode: 'exact',
+ content_css: mediaUrl('media/style/tinymce/comments-content.css'),
+ elements: editorId,
+ plugins: 'autoresize',
+ theme: 'advanced',
+ theme_advanced_toolbar_location: 'top',
+ theme_advanced_toolbar_align: 'left',
+ 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);
+ this._editorBox.prepend(editor.getElement());
+ editor.start();
+ this._editor = editor;
+};
+
+EditCommentForm.prototype.startWMDEditor = function() {
+ var editor = new WMD();
+ editor.setEnabledButtons('bold italic link code ol ul');
+ editor.setPreviewerEnabled(false);
+ editor.setText(this._text);
+ this._editorBox.prepend(editor.getElement());//attach DOM before start
+ editor.start();//have to start after attaching DOM
+ this._editor = editor;
+};
+
+EditCommentForm.prototype.startSimpleEditor = function() {
+ this._editor = new SimpleEditor();
+ this._editorBox.prepend(this._editor.getElement());
+};
+
+EditCommentForm.prototype.startEditor = function() {
+ var editorType = this.getEditorType();
+ if (editorType === 'tinymce') {
+ this.startTinyMCEEditor();
+ //@todo: implement save on enter and character counter in tinyMCE
+ return;
+ } else if (editorType === 'markdown') {
+ this.startWMDEditor();
+ } else {
+ this.startSimpleEditor();
+ }
+
+ //code below is common to SimpleEditor and WMD
+ var editorElement = this._editor.getElement();
+ var updateCounter = this.getCounterUpdater();
+ var escapeHandler = makeKeyHandler(27, this.getCancelHandler());
+ //todo: try this on the div
+ var editor = this._editor;
+ //this should be set on the textarea!
+ editorElement.blur(updateCounter);
+ editorElement.focus(updateCounter);
+ editorElement.keyup(updateCounter)
+ editorElement.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'));
+ if (this._minorEditBox) {
+ this._minorEditBox.hide();
+ }
}
else {
this._submit_btn.html(gettext('save comment'));
+ if (this._minorEditBox) {
+ 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);
}
@@ -1462,46 +1572,58 @@ 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 true;
};
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()){
+ } else if (this.confirmAbandon()){
return true;
}
- this.focus();
+ this._editor.focus();
return false;
};
EditCommentForm.prototype.getCancelHandler = function(){
var form = this;
- return function(){
+ return function(evt){
if (form.canCancel()){
form.detach();
+ evt.preventDefault();
}
return false;
};
@@ -1514,12 +1636,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(){
@@ -1527,46 +1654,43 @@ 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');
+ this._controlsBox.addClass('edit-comment-buttons');
+ 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._minorEditBox.addClass('checkbox');
+ 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() {
@@ -1586,38 +1710,60 @@ 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': editor.getHtml(),
+ 'text': text,
+ 'user_display_name': userName,
+ 'comment_added_at': timestamp
+ });
+ me._comment.setDraftStatus(true);
+ me._comment.getContainerWidget().showButton();
+
var post_data = {
comment: text
};
@@ -1625,6 +1771,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();
@@ -1632,8 +1780,6 @@ EditCommentForm.prototype.getSaveHandler = function(){
post_url = askbot['urls']['postComments'];
}
- me.disableForm();
-
$.ajax({
type: "POST",
url: post_url,
@@ -1641,19 +1787,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();
}
@@ -1662,9 +1810,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;
@@ -1674,6 +1819,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'];
}
@@ -1689,11 +1835,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;
@@ -1715,12 +1888,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;
};
@@ -1750,42 +1942,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']);
@@ -1794,18 +2021,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;
};
@@ -1872,20 +2103,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() });
}
};
};
@@ -1978,28 +2201,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');
}
}
};
@@ -2169,15 +2429,89 @@ 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.getHtml = function() {
+ return '<div class="transient-comment">' + this.getText() + '</div>';
+};
+
+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');
+ this._element.addClass('wmd-container');
+ var textarea = this.makeElement('textarea');
+ this._element.append(textarea);
+ this._textarea = 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);
+
+//@todo: implement getHtml method that runs text through showdown renderer
WMD.prototype.setEnabledButtons = function(buttons){
this._enabled_buttons = buttons;
@@ -2200,21 +2534,21 @@ WMD.prototype.createDom = function(){
this._element.append(wmd_container);
var wmd_buttons = this.makeElement('div')
- .attr('id', 'wmd-button-bar')
+ .attr('id', this.makeId('wmd-button-bar'))
.addClass('wmd-panel');
wmd_container.append(wmd_buttons);
var editor = this.makeElement('textarea')
- .attr('id', 'editor');
+ .attr('id', this.makeId('editor'));
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')
- .attr('id', 'previewer')
+ .attr('id', this.makeId('previewer'))
.addClass('wmd-preview');
wmd_container.append(previewer);
this._previewer = previewer;
@@ -2223,19 +2557,8 @@ 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);
+ Attacklab.Util.startEditor(true, this._enabled_buttons, this.getIdSeed());
};
/**
@@ -2244,60 +2567,77 @@ 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.getHtml = TinyMCE.prototype.getText;
+
+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);
};
/**
@@ -2460,12 +2800,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/tinymce/plugins/askbot_attachment/editor_plugin.js b/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js
index d1ef13b4..5f996804 100644
--- a/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js
+++ b/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js
@@ -27,11 +27,9 @@
}
};
- var modalMenuHeadline = gettext('Insert a file');
-
var createDialog = function() {
var dialog = new FileUploadDialog();
- dialog.setHeadingText(modalMenuHeadline);
+ dialog.setFileType('attachment');
dialog.setPostUploadHandler(insertIntoDom);
dialog.setInputId('askbot_attachment_input');
dialog.setUrlInputTooltip(gettext('Or paste file url here'));
diff --git a/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js b/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js
index 7fa6b6be..0cd70473 100644
--- a/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js
+++ b/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js
@@ -27,11 +27,8 @@
}
};
- var modalMenuHeadline = gettext('Upload an image');
-
var createDialog = function() {
var dialog = new FileUploadDialog();
- dialog.setHeadingText(modalMenuHeadline);
dialog.setPostUploadHandler(insertIntoDom);
dialog.setUrlInputTooltip('Or paste image url here');
dialog.setInputId('askbot_imageuploader_input');
diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js
index 6a00d364..ee094691 100644
--- a/askbot/media/js/utils.js
+++ b/askbot/media/js/utils.js
@@ -28,6 +28,22 @@ var animateHashes = function(){
}
};
+/**
+ * @param {string} id_token - any token
+ * @param {string} unique_seed - the unique part
+ * @returns {string} unique id that can be used in DOM
+ */
+var askbotMakeId = function(id_token, unique_seed) {
+ return id_token + '-' + unique_seed;
+};
+
+var getNewUniqueInt = 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 +153,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];
@@ -307,12 +331,33 @@ var inherits = function(childCtor, parentCtor) {
var WrappedElement = function(){
this._element = null;
this._in_document = false;
+ this._idSeed = null;
};
/* note that we do not call inherits() here
* See TippedInput as an example of a subclass
*/
/**
+ * returns a unique integer for any instance of WrappedElement
+ * which can be used to construct a unique id for use in the DOM
+ * @return {string}
+ */
+WrappedElement.prototype.getIdSeed = function() {
+ var seed = this._idSeed || parseInt(getNewUniqueInt());
+ this._idSeed = seed;
+ return seed;
+};
+
+/**
+ * returns unique ide based on the prefix and the id seed
+ * @param {string} prefix
+ * @return {string}
+ */
+WrappedElement.prototype.makeId = function(prefix) {
+ return askbotMakeId(prefix, this.getIdSeed());
+};
+
+/**
* notice that we use ObjCls.prototype.someMethod = function()
* notation - as we use Javascript's prototypal inheritance
* explicitly. The point of this is to be able to eventually
@@ -363,7 +408,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;
@@ -888,6 +933,7 @@ var ModalDialog = function() {
var me = this;
this._reject_handler = function() { me.hide(); };
this._content_element = undefined;
+ this._headerEnabled = true;
};
inherits(ModalDialog, WrappedElement);
@@ -950,19 +996,21 @@ ModalDialog.prototype.createDom = function() {
element.addClass('modal');
//1) create header
- var header = this.makeElement('div')
- header.addClass('modal-header');
- element.append(header);
+ if (this._headerEnabled) {
+ var header = this.makeElement('div')
+ header.addClass('modal-header');
+ element.append(header);
+ var close_link = this.makeElement('div');
+ close_link.addClass('close');
+ close_link.attr('data-dismiss', 'modal');
+ close_link.html('x');
+ header.append(close_link);
+ var title = this.makeElement('h3');
+ title.html(this._heading_text);
+ header.append(title);
+ }
- var close_link = this.makeElement('div');
- close_link.addClass('close');
- close_link.attr('data-dismiss', 'modal');
- close_link.html('x');
- header.append(close_link);
- var title = this.makeElement('h3');
- title.html(this._heading_text);
- header.append(title);
//2) create content
var body = this.makeElement('div')
@@ -979,13 +1027,13 @@ ModalDialog.prototype.createDom = function() {
element.append(footer);
var accept_btn = this.makeElement('button');
- accept_btn.addClass('btn btn-primary');
+ accept_btn.addClass('submit');
accept_btn.html(this._accept_button_text);
footer.append(accept_btn);
if (this._reject_button_text) {
var reject_btn = this.makeElement('button');
- reject_btn.addClass('btn cancel');
+ reject_btn.addClass('submit cancel');
reject_btn.html(this._reject_button_text);
footer.append(reject_btn);
}
@@ -995,7 +1043,9 @@ ModalDialog.prototype.createDom = function() {
if (this._reject_button_text) {
setupButtonEventHandlers(reject_btn, this._reject_handler);
}
- setupButtonEventHandlers(close_link, this._reject_handler);
+ if (this._headerEnabled) {
+ setupButtonEventHandlers(close_link, this._reject_handler);
+ }
this.hide();
};
@@ -1005,10 +1055,27 @@ ModalDialog.prototype.createDom = function() {
*/
var FileUploadDialog = function() {
ModalDialog.call(this);
- self._post_upload_handler = undefined;
+ this._post_upload_handler = undefined;
+ this._fileType = 'image';
+ this._headerEnabled = false;
};
inherits(FileUploadDialog, ModalDialog);
+/**
+ * allowed values: 'image', 'attachment'
+ */
+FileUploadDialog.prototype.setFileType = function(fileType) {
+ this._fileType = fileType;
+};
+
+FileUploadDialog.prototype.getFileType = function() {
+ return this._fileType;
+};
+
+FileUploadDialog.prototype.setButtonText = function(text) {
+ this._fakeInput.val(text);
+};
+
FileUploadDialog.prototype.setPostUploadHandler = function(handler) {
this._post_upload_handler = handler;
};
@@ -1025,6 +1092,10 @@ FileUploadDialog.prototype.getInputId = function() {
return this._input_id;
};
+FileUploadDialog.prototype.setLabelText= function(text) {
+ this._label.html(text);
+};
+
FileUploadDialog.prototype.setUrlInputTooltip = function(text) {
this._url_input_tooltip = text;
};
@@ -1048,30 +1119,112 @@ FileUploadDialog.prototype.resetInputs = function() {
this._upload_input.val('');
};
+FileUploadDialog.prototype.getInputElement = function() {
+ return $('#' + this.getInputId());
+};
+
+FileUploadDialog.prototype.installFileUploadHandler = function(handler) {
+ var upload_input = this.getInputElement();
+ upload_input.unbind('change');
+ //todo: fix this - make event handler reinstall work
+ upload_input.change(handler);
+};
+
FileUploadDialog.prototype.show = function() {
//hack around the ajaxFileUpload plugin
FileUploadDialog.superClass_.show.call(this);
- var upload_input = this._upload_input;
- upload_input.unbind('change');
- //todo: fix this - make event handler reinstall work
- upload_input.change(this.getStartUploadHandler());
+ var handler = this.getStartUploadHandler();
+ this.installFileUploadHandler(handler);
};
-FileUploadDialog.prototype.getStartUploadHandler = function(){
- /* startUploadHandler is passed in to re-install the event handler
- * which is removed by the ajaxFileUpload jQuery extension
- */
+FileUploadDialog.prototype.getUrlInputElement = function() {
+ return this._url_input.getElement();
+};
+
+/*
+ * argument startUploadHandler is very special it must
+ * be a function calling this one!!! Todo: see if there
+ * is a more civilized way to do this.
+ */
+FileUploadDialog.prototype.startFileUpload = function(startUploadHandler) {
+
var spinner = this._spinner;
- var uploadInputId = this.getInputId();
- var urlInput = this._url_input;
+ var label = this._label;
+
+ spinner.ajaxStart(function(){
+ spinner.show();
+ label.hide();
+ });
+ spinner.ajaxComplete(function(){
+ spinner.hide();
+ label.show();
+ });
+
+ /* important!!! upload input must be loaded by id
+ * because ajaxFileUpload monkey-patches the upload form */
+ var uploadInput = this.getInputElement();
+ uploadInput.ajaxStart(function(){ uploadInput.hide(); });
+ uploadInput.ajaxComplete(function(){ uploadInput.show(); });
+
+ //var localFilePath = upload_input.val();
+
+ var me = this;
+
+ $.ajaxFileUpload({
+ url: askbot['urls']['upload'],
+ secureuri: false,//todo: check on https
+ fileElementId: this.getInputId(),
+ dataType: 'xml',
+ success: function (data, status) {
+
+ var fileURL = $(data).find('file_url').text();
+ var origFileName = $(data).find('orig_file_name').text();
+ var newStatus = interpolate(
+ gettext('Uploaded file: %s'),
+ [origFileName]
+ );
+ /*
+ * hopefully a fix for the "fakepath" issue
+ * https://www.mediawiki.org/wiki/Special:Code/MediaWiki/83225
+ */
+ fileURL = fileURL.replace(/\w:.*\\(.*)$/,'$1');
+ var error = $(data).find('error').text();
+ if (error != ''){
+ alert(error);
+ } else {
+ me.getUrlInputElement().attr('value', fileURL);
+ me.setLabelText(newStatus);
+ if (me.getFileType() === 'image') {
+ var buttonText = gettext('Choose a different image');
+ } else {
+ var buttonText = gettext('Choose a different file');
+ }
+ me.setButtonText(buttonText);
+ }
+
+ /* re-install this as the upload extension
+ * will remove the handler to prevent double uploading
+ * this hack is a manipulation around the
+ * ajaxFileUpload jQuery plugin. */
+ me.installFileUploadHandler(startUploadHandler);
+ },
+ error: function (data, status, e) {
+ /* re-install this as the upload extension
+ * will remove the handler to prevent double uploading */
+ me.installFileUploadHandler(startUploadHandler);
+ }
+ });
+ return false;
+};
+
+FileUploadDialog.prototype.getStartUploadHandler = function(){
+ var me = this;
var handler = function() {
- var options = {
- 'spinner': spinner,
- 'uploadInputId': uploadInputId,
- 'urlInput': urlInput.getElement(),
- 'startUploadHandler': handler//pass in itself
- };
- return ajaxFileUpload(options);
+ /* the trick is that we need inside the function call
+ * to have a reference to itself
+ * in order to reinstall the handler later
+ * because ajaxFileUpload jquery extension might be destroying it */
+ return me.startFileUpload(handler);
};
return handler;
};
@@ -1098,10 +1251,11 @@ FileUploadDialog.prototype.createDom = function() {
superClass.createDom.call(this);
var form = this.makeElement('form');
+ form.addClass('ajax-file-upload');
form.css('margin-bottom', 0);
this.prependContent(form);
- // File upload button
+ // Browser native file upload field
var upload_input = this.makeElement('input');
upload_input.attr({
id: this._input_id,
@@ -1111,9 +1265,32 @@ FileUploadDialog.prototype.createDom = function() {
});
form.append(upload_input);
this._upload_input = upload_input;
- form.append($('<br/>'));
- // The url input text box
+ var fakeInput = this.makeElement('input');
+ fakeInput.attr('type', 'button');
+ fakeInput.addClass('submit');
+ fakeInput.addClass('fake-file-input');
+ if (this._fileType === 'image') {
+ var buttonText = gettext('Choose an image to insert');
+ } else {
+ var buttonText = gettext('Choose a file to insert');
+ }
+ fakeInput.val(buttonText);
+ this._fakeInput = fakeInput;
+ form.append(fakeInput);
+
+ setupButtonEventHandlers(fakeInput, function() { upload_input.click() });
+
+ // Label which will also serve as status display
+ var label = this.makeElement('label');
+ label.attr('for', this._input_id);
+ var types = askbot['settings']['allowedUploadFileTypes'];
+ types = types.join(', ');
+ label.html(gettext('Allowed file types are:') + ' ' + types + '.');
+ form.append(label);
+ this._label = label;
+
+ // The url input text box, probably unused in fact
var url_input = new TippedInput();
url_input.setInstruction(this._url_input_tooltip || gettext('Or paste file url here'));
var url_input_element = url_input.getElement();
@@ -1125,15 +1302,6 @@ FileUploadDialog.prototype.createDom = function() {
//form.append($('<br/>'));
this._url_input = url_input;
- var label = this.makeElement('label');
- label.attr('for', this._input_id);
-
- var types = askbot['settings']['allowedUploadFileTypes'];
- types = types.join(', ');
- label.html(gettext('Allowed file types are:') + ' ' + types + '.');
- form.append(label);
- form.append($('<br/>'));
-
/* //Description input box
var descr_input = new TippedInput();
descr_input.setInstruction(gettext('Describe the image here'));
@@ -1143,8 +1311,9 @@ FileUploadDialog.prototype.createDom = function() {
this._description_input = descr_input;
*/
var spinner = this.makeElement('img');
- spinner.attr('src', mediaUrl('media/images/indicator.gif'));
+ spinner.attr('src', mediaUrl('media/images/ajax-loader.gif'));
spinner.css('display', 'none');
+ spinner.addClass('spinner');
form.append(spinner);
this._spinner = spinner;
diff --git a/askbot/media/js/wmd/wmd.css b/askbot/media/js/wmd/wmd.css
index 678d70f3..eeb6adec 100644
--- a/askbot/media/js/wmd/wmd.css
+++ b/askbot/media/js/wmd/wmd.css
@@ -3,46 +3,39 @@
background-color: White
}
*/
-.wmd-panel
-{
+.wmd-panel {
}
-#wmd-button-bar
-{
+.wmd-button-bar {
background: url(images/editor-toolbar-background.png) repeat-x bottom;
- height: 30px;
+ height: 25px;
border: 0;
display: block;
}
-#wmd-input
-{
+.wmd-input {
height: 500px;
background-color: Gainsboro;
border: 1px solid DarkGray;
margin-top: -20px;
}
-#wmd-preview
-{
- background-color: LightSkyBlue;
+.wmd-preview {
+ background-color: #f5f5f5;
}
-#wmd-output
-{
+.wmd-output {
background-color: Pink;
}
-#wmd-button-row
-{
+.wmd-button-row {
position: relative;
- margin: 10px 2px 0 2px;
+ margin: 5px 2px;
padding: 0px;
height: 20px;
}
-.wmd-spacer
-{
+.wmd-spacer {
width: 1px;
height: 20px;
margin-left: 2px;
@@ -53,8 +46,7 @@
list-style: none;
}
-.wmd-button
-{
+.wmd-button {
width: 20px;
height: 20px;
margin-left: 5px;
@@ -68,8 +60,7 @@
list-style: none;
}
-.wmd-button > a
-{
+.wmd-button > a {
width: 20px;
height: 20px;
margin-left: 5px;
@@ -81,23 +72,23 @@
/* sprite button slicing style information */
-#wmd-button-bar #wmd-bold-button {left: 0px; background-position: 0px 0;}
-#wmd-button-bar #wmd-italic-button {left: 25px; background-position: -20px 0;}
-#wmd-button-bar #wmd-spacer1 {left: 50px;}
-#wmd-button-bar #wmd-link-button {left: 75px; background-position: -40px 0;}
-#wmd-button-bar #wmd-quote-button {left: 100px; background-position: -60px 0;}
-#wmd-button-bar #wmd-code-button {left: 125px; background-position: -80px 0;}
-#wmd-button-bar #wmd-image-button {left: 150px; background-position: -100px 0;}
-#wmd-button-bar #wmd-attachment-button {left: 175px; background-position: -120px 0;}
-#wmd-button-bar #wmd-spacer2 {left: 200px;}
-#wmd-button-bar #wmd-olist-button {left: 225px; background-position: -140px 0;}
-#wmd-button-bar #wmd-ulist-button {left: 250px; background-position: -160px 0;}
-#wmd-button-bar #wmd-heading-button {left: 275px; background-position: -180px 0;}
-#wmd-button-bar #wmd-hr-button {left: 300px; background-position: -200px 0;}
-#wmd-button-bar #wmd-spacer3 {left: 325px;}
-#wmd-button-bar #wmd-undo-button {left: 350px; background-position: -220px 0;}
-#wmd-button-bar #wmd-redo-button {left: 375px; background-position: -240px 0;}
-#wmd-button-bar #wmd-help-button {right: 0px; background-position: -260px 0;}
+.wmd-button-bar .wmd-bold-button {left: 0px; background-position: 0px 0;}
+.wmd-button-bar .wmd-italic-button {left: 25px; background-position: -20px 0;}
+.wmd-button-bar .wmd-spacer1 {left: 50px;}
+.wmd-button-bar .wmd-link-button {left: 75px; background-position: -40px 0;}
+.wmd-button-bar .wmd-quote-button {left: 100px; background-position: -60px 0;}
+.wmd-button-bar .wmd-code-button {left: 125px; background-position: -80px 0;}
+.wmd-button-bar .wmd-image-button {left: 150px; background-position: -100px 0;}
+.wmd-button-bar .wmd-attachment-button {left: 175px; background-position: -120px 0;}
+.wmd-button-bar .wmd-spacer2 {left: 200px;}
+.wmd-button-bar .wmd-olist-button {left: 225px; background-position: -140px 0;}
+.wmd-button-bar .wmd-ulist-button {left: 250px; background-position: -160px 0;}
+.wmd-button-bar .wmd-heading-button {left: 275px; background-position: -180px 0;}
+.wmd-button-bar .wmd-hr-button {left: 300px; background-position: -200px 0;}
+.wmd-button-bar .wmd-spacer3 {left: 325px;}
+.wmd-button-bar .wmd-undo-button {left: 350px; background-position: -220px 0;}
+.wmd-button-bar .wmd-redo-button {left: 375px; background-position: -240px 0;}
+.wmd-button-bar .wmd-help-button {right: 0px; background-position: -260px 0;}
.wmd-prompt-background
diff --git a/askbot/media/js/wmd/wmd.js b/askbot/media/js/wmd/wmd.js
index ad56aee3..02ebf0c9 100644
--- a/askbot/media/js/wmd/wmd.js
+++ b/askbot/media/js/wmd/wmd.js
@@ -79,10 +79,10 @@ Attacklab.wmdBase = function(){
// A collection of the important regions on the page.
// Cached so we don't have to keep traversing the DOM.
wmd.PanelCollection = function(){
- this.buttonBar = doc.getElementById("wmd-button-bar");
- this.preview = doc.getElementById("previewer");
- this.output = doc.getElementById("wmd-output");
- this.input = doc.getElementById("editor");
+ this.buttonBar = doc.getElementById(util.makeId("wmd-button-bar"));
+ this.preview = doc.getElementById(util.makeId("previewer"));
+ this.output = doc.getElementById(util.makeId("wmd-output"));
+ this.input = doc.getElementById(util.makeId("editor"));
};
// This PanelCollection object can't be filled until after the page
@@ -195,7 +195,7 @@ Attacklab.wmdBase = function(){
var imgPath = imageDirectory + img;
var elem = doc.createElement("img");
- elem.className = "wmd-button";
+ elem.className = "wmd-button wmd-image-button";
elem.src = imgPath;
return elem;
@@ -276,24 +276,21 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
// so we make the whole window transparent.
//
// Is this necessary on modern konqueror browsers?
- if (global.isKonqueror){
+ if (global.isKonqueror) {
style.backgroundColor = "transparent";
- }
- else if (global.isIE){
+ } else if (global.isIE) {
style.filter = "alpha(opacity=50)";
- }
- else {
+ } else {
style.opacity = "0.5";
}
var pageSize = position.getPageSize();
style.height = pageSize[1] + "px";
- if(global.isIE){
+ if(global.isIE) {
style.left = doc.documentElement.scrollLeft;
style.width = doc.documentElement.clientWidth;
- }
- else {
+ } else {
style.left = "0";
style.width = "100%";
}
@@ -333,7 +330,7 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
// The input text box
input = doc.createElement("input");
if(dialogType == 'image' || dialogType == 'file'){
- input.id = "image-url";
+ input.id = util.makeId("image-url");
}
input.type = "text";
if (dialogType == 'file'){
@@ -932,8 +929,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
var setUndoRedoButtonStates = function(){
if(undoMgr){
- setupButton(document.getElementById("wmd-undo-button"), undoMgr.canUndo());
- setupButton(document.getElementById("wmd-redo-button"), undoMgr.canRedo());
+ setupButton(document.getElementById(util.makeId("wmd-undo-button")), undoMgr.canUndo());
+ setupButton(document.getElementById(util.makeId("wmd-redo-button")), undoMgr.canRedo());
}
};
@@ -981,19 +978,21 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
};
var makeSpritedButtonRow = function(){
- var buttonBar = document.getElementById("wmd-button-bar");
+ var buttonBar = document.getElementById(util.makeId("wmd-button-bar"));
+ buttonBar.className = 'wmd-button-bar';
var normalYShift = "0px";
var disabledYShift = "-20px";
var highlightYShift = "-40px";
var buttonRow = document.createElement("ul");
- buttonRow.id = "wmd-button-row";
+ buttonRow.className = 'wmd-button-row';
+ buttonRow.id = util.makeId("wmd-button-row");
buttonRow = buttonBar.appendChild(buttonRow);
if (isButtonUsed('bold')){
var boldButton = document.createElement("li");
- boldButton.className = "wmd-button";
- boldButton.id = "wmd-bold-button";
+ boldButton.className = "wmd-button wmd-bold-button";
+ boldButton.id = util.makeId("wmd-bold-button");
boldButton.title = toolbar_strong_label;
boldButton.XShift = "0px";
boldButton.textOp = command.doBold;
@@ -1003,8 +1002,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('italic')){
var italicButton = document.createElement("li");
- italicButton.className = "wmd-button";
- italicButton.id = "wmd-italic-button";
+ italicButton.className = "wmd-button wmd-italic-button";
+ italicButton.id = util.makeId("wmd-italic-button");
italicButton.title = toolbar_emphasis_label;
italicButton.XShift = "-20px";
italicButton.textOp = command.doItalic;
@@ -1020,15 +1019,15 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
isButtonUsed('attachment')
) {
var spacer1 = document.createElement("li");
- spacer1.className = "wmd-spacer";
- spacer1.id = "wmd-spacer1";
+ spacer1.className = "wmd-spacer wmd-spacer1";
+ spacer1.id = util.makeId("wmd-spacer1");
buttonRow.appendChild(spacer1);
}
if (isButtonUsed('link')){
var linkButton = document.createElement("li");
- linkButton.className = "wmd-button";
- linkButton.id = "wmd-link-button";
+ linkButton.className = "wmd-button wmd-link-button";
+ linkButton.id = util.makeId("wmd-link-button");
linkButton.title = toolbar_hyperlink_label;
linkButton.XShift = "-40px";
linkButton.textOp = function(chunk, postProcessing){
@@ -1040,8 +1039,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('blockquote')){
var quoteButton = document.createElement("li");
- quoteButton.className = "wmd-button";
- quoteButton.id = "wmd-quote-button";
+ quoteButton.className = "wmd-button wmd-quote-button";
+ quoteButton.id = util.makeId("wmd-quote-button");
quoteButton.title = toolbar_blockquote_label;
quoteButton.XShift = "-60px";
quoteButton.textOp = command.doBlockquote;
@@ -1051,8 +1050,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('code')){
var codeButton = document.createElement("li");
- codeButton.className = "wmd-button";
- codeButton.id = "wmd-code-button";
+ codeButton.className = "wmd-button wmd-code-button";
+ codeButton.id = util.makeId("wmd-code-button");
codeButton.title = toolbar_code_label;
codeButton.XShift = "-80px";
codeButton.textOp = command.doCode;
@@ -1062,8 +1061,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('image')){
var imageButton = document.createElement("li");
- imageButton.className = "wmd-button";
- imageButton.id = "wmd-image-button";
+ imageButton.className = "wmd-button wmd-image-button";
+ imageButton.id = util.makeId("wmd-image-button");
imageButton.title = toolbar_image_label;
imageButton.XShift = "-100px";
imageButton.textOp = function(chunk, postProcessing){
@@ -1075,8 +1074,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('attachment')){
var attachmentButton = document.createElement("li");
- attachmentButton.className = "wmd-button";
- attachmentButton.id = "wmd-attachment-button";
+ attachmentButton.className = "wmd-button wmd-attachment-button";
+ attachmentButton.id = util.makeId("wmd-attachment-button");
attachmentButton.title = toolbar_attachment_label;
attachmentButton.XShift = "-120px";
attachmentButton.textOp = function(chunk, postProcessing){
@@ -1093,15 +1092,15 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
isButtonUsed('hr')
) {
var spacer2 = document.createElement("li");
- spacer2.className = "wmd-spacer";
- spacer2.id = "wmd-spacer2";
+ spacer2.className = "wmd-spacer wmd-spacer2";
+ spacer2.id = util.makeId("wmd-spacer2");
buttonRow.appendChild(spacer2);
}
if (isButtonUsed('ol')) {
var olistButton = document.createElement("li");
- olistButton.className = "wmd-button";
- olistButton.id = "wmd-olist-button";
+ olistButton.className = "wmd-button wmd-olist-button";
+ olistButton.id = util.makeId("wmd-olist-button");
olistButton.title = toolbar_numbered_label;
olistButton.XShift = "-140px";
olistButton.textOp = function(chunk, postProcessing){
@@ -1113,8 +1112,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('ul')) {
var ulistButton = document.createElement("li");
- ulistButton.className = "wmd-button";
- ulistButton.id = "wmd-ulist-button";
+ ulistButton.className = "wmd-button wmd-ulist-button";
+ ulistButton.id = util.makeId("wmd-ulist-button");
ulistButton.title = toolbar_bulleted_label;
ulistButton.XShift = "-160px";
ulistButton.textOp = function(chunk, postProcessing){
@@ -1126,8 +1125,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('heading')) {
var headingButton = document.createElement("li");
- headingButton.className = "wmd-button";
- headingButton.id = "wmd-heading-button";
+ headingButton.className = "wmd-button wmd-heading-button";
+ headingButton.id = util.makeId("wmd-heading-button");
headingButton.title = toolbar_heading_label;
headingButton.XShift = "-180px";
headingButton.textOp = command.doHeading;
@@ -1137,8 +1136,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('hr')) {
var hrButton = document.createElement("li");
- hrButton.className = "wmd-button";
- hrButton.id = "wmd-hr-button";
+ hrButton.className = "wmd-button wmd-hr-button";
+ hrButton.id = util.makeId("wmd-hr-button");
hrButton.title = toolbar_horizontal_label;
hrButton.XShift = "-200px";
hrButton.textOp = command.doHorizontalRule;
@@ -1148,13 +1147,13 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
if (isButtonUsed('undo')){
var spacer3 = document.createElement("li");
- spacer3.className = "wmd-spacer";
- spacer3.id = "wmd-spacer3";
+ spacer3.className = "wmd-spacer wmd-spacer3";
+ spacer3.id = util.makeId("wmd-spacer3");
buttonRow.appendChild(spacer3);
var undoButton = document.createElement("li");
- undoButton.className = "wmd-button";
- undoButton.id = "wmd-undo-button";
+ undoButton.className = "wmd-button wmd-undo-button";
+ undoButton.id = util.makeId("wmd-undo-button");
undoButton.title = toolbar_undo_label;
undoButton.XShift = "-220px";
undoButton.execute = function(manager){
@@ -1164,8 +1163,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
buttonRow.appendChild(undoButton);
var redoButton = document.createElement("li");
- redoButton.className = "wmd-button";
- redoButton.id = "wmd-redo-button";
+ redoButton.className = "wmd-button wmd-redo-button";
+ redoButton.id = util.makeId("wmd-redo-button");
redoButton.title = toolbar_redo_label;
if (/win/.test(nav.platform.toLowerCase())) {
redoButton.title = toolbar_redo_label;
@@ -1184,8 +1183,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
}
/*
var helpButton = document.createElement("li");
- helpButton.className = "wmd-button";
- helpButton.id = "wmd-help-button";
+ helpButton.className = "wmd-button wmd-help-button";
+ helpButton.id = util.makeId("wmd-help-button");
helpButton.XShift = "-240px";
helpButton.isHelp = true;
@@ -1239,44 +1238,44 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
switch(keyCodeStr) {
case "b":
- doClick(document.getElementById("wmd-bold-button"));
+ doClick(document.getElementById(util.makeId("wmd-bold-button")));
break;
case "i":
- doClick(document.getElementById("wmd-italic-button"));
+ doClick(document.getElementById(util.makeId("wmd-italic-button")));
break;
case "l":
- doClick(document.getElementById("wmd-link-button"));
+ doClick(document.getElementById(util.makeId("wmd-link-button")));
break;
case ".":
- doClick(document.getElementById("wmd-quote-button"));
+ doClick(document.getElementById(util.makeId("wmd-quote-button")));
break;
case "k":
- doClick(document.getElementById("wmd-code-button"));
+ doClick(document.getElementById(util.makeId("wmd-code-button")));
break;
case "g":
- doClick(document.getElementById("wmd-image-button"));
+ doClick(document.getElementById(util.makeId("wmd-image-button")));
break;
case "o":
- doClick(document.getElementById("wmd-olist-button"));
+ doClick(document.getElementById(util.makeId("wmd-olist-button")));
break;
case "u":
- doClick(document.getElementById("wmd-ulist-button"));
+ doClick(document.getElementById(util.makeId("wmd-ulist-button")));
break;
case "h":
- doClick(document.getElementById("wmd-heading-button"));
+ doClick(document.getElementById(util.makeId("wmd-heading-button")));
break;
case "r":
- doClick(document.getElementById("wmd-hr-button"));
+ doClick(document.getElementById(util.makeId("wmd-hr-button")));
break;
case "y":
- doClick(document.getElementById("wmd-redo-button"));
+ doClick(document.getElementById(util.makeId("wmd-redo-button")));
break;
case "z":
if(key.shiftKey) {
- doClick(document.getElementById("wmd-redo-button"));
+ doClick(document.getElementById(util.makeId("wmd-redo-button")));
}
else {
- doClick(document.getElementById("wmd-undo-button"));
+ doClick(document.getElementById(util.makeId("wmd-undo-button")));
}
break;
default:
@@ -1877,8 +1876,17 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){
wmd.wmd.editor = wmd.editor;
wmd.wmd.previewManager = wmd.previewManager;
};
+
+ util.makeId = function(idToken) {
+ if (wmd.wmd_env['idSeed']) {
+ return askbotMakeId(idToken, wmd.wmd_env['idSeed']);
+ }
+ return idToken;
+ };
- util.startEditor = function(start_now, buttons){
+ util.startEditor = function(start_now, buttons, idSeed){
+
+ wmd.wmd_env['idSeed'] = idSeed;
if (wmd.wmd_env.autostart === false) {
util.makeAPI();
diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css
index 3b52c293..2c4f4209 100644
--- a/askbot/media/style/style.css
+++ b/askbot/media/style/style.css
@@ -144,6 +144,12 @@ html {
height: 0;
visibility: hidden;
}
+.invisible {
+ margin: -1px 0 0 -1px;
+ height: 1px;
+ overflow: hidden;
+ width: 1px;
+}
.badges a {
color: #763333;
text-decoration: underline;
@@ -635,19 +641,20 @@ input[type="submit"],
input[type="button"],
input[type="reset"],
.button {
+ border: 0 !important;
+ border-top: #eaf2f3 1px solid;
cursor: pointer;
color: #4a757f;
- height: 27px;
font-family: 'Open Sans Condensed', Arial, sans-serif;
font-size: 14px;
font-weight: bold;
+ height: 27px;
+ margin-right: 10px;
text-align: center;
text-decoration: none;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
- border: 0 !important;
- border-top: #eaf2f3 1px solid;
background-color: #d1e2e5;
background-repeat: no-repeat;
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#d1e2e5), color-stop(25%, #d1e2e5), to(#a9c2c7));
@@ -703,6 +710,39 @@ input[type="submit"].link {
input[type="submit"].link:hover {
text-decoration: underline;
}
+form.ajax-file-upload {
+ height: 60px;
+ position: relative;
+}
+form.ajax-file-upload input[type="file"],
+form.ajax-file-upload input.fake-file-input {
+ cursor: pointer;
+ height: 32px;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+form.ajax-file-upload input[type="file"] {
+ z-index: 2;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+ filter: alpha(opacity=0);
+ -moz-opacity: 0;
+ -khtml-opacity: 0;
+ opacity: 0;
+}
+form.ajax-file-upload input.fake-file-input {
+ z-index: 1;
+}
+form.ajax-file-upload label,
+form.ajax-file-upload img.spinner {
+ bottom: 0;
+ left: 3px;
+ position: absolute;
+}
+form.ajax-file-upload img.spinner {
+ bottom: 6px;
+ left: 10px;
+}
#askButton {
/* check blocks/secondary_header.html and widgets/ask_button.html*/
@@ -1775,6 +1815,13 @@ ul#related-tags li {
width: 723px;
width: 100%;
}
+.ask-page .post-comments .wmd-container,
+.question-page .post-comments .wmd-container,
+.edit-question-page .post-comments .wmd-container,
+.edit-answer-page .post-comments .wmd-container {
+ margin-bottom: 8px;
+ margin-left: -2px;
+}
.ask-page #editor,
.question-page #editor,
.edit-question-page #editor,
@@ -1992,6 +2039,10 @@ ul#related-tags li {
width: 20px;
vertical-align: top;
}
+.question-page .answer-table .mceEditor td,
+.question-page #question-table .mceEditor td {
+ width: auto;
+}
.question-page .question-body,
.question-page .answer-body {
overflow: auto;
@@ -2270,6 +2321,12 @@ ul#related-tags li {
width: 100%;
margin: 3px 0 20px 5px;
}
+.question-page .comments .edit-comment-buttons {
+ margin-left: -4px;
+}
+.question-page .comments .edit-comment-buttons .checkbox {
+ margin: 3px;
+}
.question-page .comments .controls a {
border: none;
color: #988e4c;
@@ -2307,15 +2364,24 @@ ul#related-tags li {
.question-page .comments textarea {
box-sizing: border-box;
border: #cce6ec 3px solid;
+ color: #666;
font-family: Arial;
font-size: 13px;
height: 54px;
line-height: 1.3;
- margin: -1px 0 7px 1px;
+ margin: -1px 0 0 1px;
outline: none;
overflow: auto;
- padding: 0px 19px 2px 3px;
- width: 100%;
+ padding: 5px 19px 2px 3px;
+ width: 99.6%;
+}
+.question-page .comments .wmd-container textarea {
+ border: none;
+}
+.question-page .comments .transient-comment {
+ margin-bottom: 3px;
+ /* match paragraph style */
+
}
.question-page .comments input {
margin-left: 10px;
@@ -2323,6 +2389,13 @@ ul#related-tags li {
vertical-align: top;
width: 100px;
}
+.question-page .comments input[name="suppress_email"] {
+ margin: 4px 5px 0 0;
+ width: auto;
+}
+.question-page .comments label[for="suppress_email"] {
+ vertical-align: top;
+}
.question-page .comments button.submit {
height: 26px;
line-height: 26px;
@@ -2333,7 +2406,6 @@ ul#related-tags li {
display: inline-block;
width: 245px;
float: right;
- color: #b6a475 !important;
vertical-align: top;
font-family: Arial;
float: right;
@@ -2349,6 +2421,9 @@ ul#related-tags li {
font-size: 11px;
min-height: 25px;
}
+.question-page .comments .comment:last-child {
+ border-bottom: none;
+}
.question-page .comments div.comment:hover {
background-color: #efefef;
}
@@ -3115,16 +3190,16 @@ body.main-page ins {
/* ----- Red Popup notification ----- */
.vote-notification {
z-index: 1;
+ background-color: #8e0000;
+ color: white;
cursor: pointer;
display: none;
- position: absolute;
font-family: Arial;
font-size: 14px;
font-weight: normal;
- color: white;
- background-color: #8e0000;
- text-align: center;
padding-bottom: 10px;
+ position: absolute;
+ text-align: center;
-webkit-box-shadow: 0px 2px 4px #370000;
-moz-box-shadow: 0px 2px 4px #370000;
box-shadow: 0px 2px 4px #370000;
@@ -3141,6 +3216,7 @@ body.main-page ins {
margin-bottom: 5px;
border-top: #8e0000 1px solid;
color: #fff;
+ line-height: 20px;
font-weight: normal;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less
index a4c3a6b2..5ee2eab3 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;
@@ -683,17 +690,18 @@ input[type="submit"],
input[type="button"],
input[type="reset"],
.button {
+ border: 0 !important;
+ border-top: #eaf2f3 1px solid;
cursor: pointer;
color: @button-label;
- height: 27px;
font-family: @main-font;
font-size: 14px;
font-weight: bold;
+ height: 27px;
+ margin-right: 10px;
text-align: center;
text-decoration: none;
.text-shadow(0px,1px,0px,#c6d9dd);
- border: 0 !important;
- border-top: #eaf2f3 1px solid;
.linear-gradient(#d1e2e5,#a9c2c7);
.rounded-corners(4px);
.box-shadow(1px, 1px, 2px, #636363)
@@ -727,6 +735,40 @@ input[type="submit"].link:hover {
text-decoration: underline;
}
+form.ajax-file-upload {
+ height: 60px;
+ position: relative;
+ input[type="file"],
+ input.fake-file-input {
+ cursor: pointer;
+ height: 32px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ input[type="file"] {
+ z-index: 2;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+ filter: alpha(opacity=0);
+ -moz-opacity: 0;
+ -khtml-opacity: 0;
+ opacity: 0;
+ }
+ input.fake-file-input {
+ z-index: 1;
+ }
+ label,
+ img.spinner {
+ bottom: 0;
+ left: 3px;
+ position: absolute;
+ }
+ img.spinner {
+ bottom: 6px;
+ left: 10px;
+ }
+}
+
#askButton { /* check blocks/secondary_header.html and widgets/ask_button.html*/
float:right;
font-size: 20px;
@@ -1873,6 +1915,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;
@@ -2041,9 +2087,9 @@ ul#related-tags li {
/* ----- Question template ----- */
-.question-page{
+.question-page {
- h1{
+ h1 {
padding-top:0px;
font-family:@main-font;
@@ -2106,6 +2152,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;
@@ -2364,6 +2414,13 @@ ul#related-tags li {
width: 100%;
margin: 3px 0 20px 5px;
}
+
+ .edit-comment-buttons {
+ margin-left: -4px;
+ .checkbox {
+ margin: 3px;
+ }
+ }
.controls a {
border: none;
@@ -2402,15 +2459,22 @@ ul#related-tags li {
textarea {
box-sizing: border-box;
border: #cce6ec 3px solid;
+ color: #666;
font-family: @body-font;
font-size: 13px;
height: 54px;
line-height: 1.3;
- margin: -1px 0 7px 1px;
+ margin: -1px 0 0 1px;
outline: none;
overflow:auto;
- padding: 0px 19px 2px 3px;
- width:100%;
+ padding: 5px 19px 2px 3px;
+ width: 99.6%;
+ }
+ .wmd-container textarea {
+ border: none;
+ }
+ .transient-comment {
+ margin-bottom: 3px; /* match paragraph style */
}
input {
margin-left: 10px;
@@ -2418,6 +2482,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;
@@ -2428,7 +2501,6 @@ ul#related-tags li {
display: inline-block;
width: 245px;
float:right;
- color:#b6a475 !important;
vertical-align: top;
font-family:@body-font;
float:right;
@@ -2444,6 +2516,9 @@ ul#related-tags li {
font-size: 11px;
min-height: 25px;
}
+ .comment:last-child {
+ border-bottom: none;
+ }
div.comment:hover {
background-color: #efefef;
}
@@ -3232,28 +3307,29 @@ body.main-page 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..42905f65 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -1,10 +1,8 @@
from collections import defaultdict
import datetime
import operator
-import cgi
import logging
-from django.utils.html import strip_tags
from django.contrib.sitemaps import ping_google
from django.utils import html
from django.conf import settings as django_settings
@@ -35,7 +33,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
@@ -422,27 +420,8 @@ class Post(models.Model):
removed_mentions - list of mention <Activity> objects - for removed ones
"""
- 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))
+ text_converter = self.get_text_converter()
+ text = text_converter(self.text)
#todo, add markdown parser call conditional on
#self.use_markdown flag
@@ -616,6 +595,25 @@ class Post(models.Model):
return answer
+ def get_text_converter(self):
+ have_simple_comment = (
+ self.is_comment() and
+ askbot_settings.COMMENTS_EDITOR_TYPE == 'plain-text'
+ )
+ if have_simple_comment:
+ parser_type = 'plain-text'
+ else:
+ parser_type = askbot_settings.EDITOR_TYPE
+
+ if parser_type == 'plain-text':
+ return markup.plain_text_input_converter
+ elif parser_type == 'markdown':
+ return markup.markdown_input_converter
+ elif parser_type == 'tinymce':
+ return markup.tinymce_input_converter
+ else:
+ raise NotImplementedError
+
def has_group(self, group):
"""true if post belongs to the group"""
return self.groups.filter(id=group.id).exists()
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 86a2490b..aa63560f 100644
--- a/askbot/templates/meta/bottom_scripts.html
+++ b/askbot/templates/meta/bottom_scripts.html
@@ -27,9 +27,19 @@
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']['maxCommentLength'] = {{ settings.MAX_COMMENT_LENGTH }};
+ askbot['settings']['editorType'] = '{{ settings.EDITOR_TYPE }}';
+ askbot['settings']['commentsEditorType'] = '{{ settings.COMMENTS_EDITOR_TYPE }}';
+ {% if settings.ALLOWED_UPLOAD_FILE_TYPES %}
+ askbot['settings']['allowedUploadFileTypes'] = [
+ "{{ settings.ALLOWED_UPLOAD_FILE_TYPES|join('", "')|replace('.','') }}"
+ ];
+ {% else %}
+ askbot['settings']['allowedUploadFileTypes'] = [];
+ {% endif %}
askbot['data']['haveFlashNotifications'] = {{ user_messages|as_js_bool }};
askbot['data']['activeTab'] = '{{ active_tab }}';
{% if search_state %}
@@ -58,6 +68,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/meta/html_head_javascript.html b/askbot/templates/meta/html_head_javascript.html
index 67f0ec88..965dd350 100644
--- a/askbot/templates/meta/html_head_javascript.html
+++ b/askbot/templates/meta/html_head_javascript.html
@@ -3,6 +3,7 @@
var askbot = {};
askbot['data'] = {};
askbot['data']['userIsAuthenticated'] = {{ request.user.is_authenticated()|as_js_bool }};
+ askbot['data']['languageCode'] = '{{ current_language_code }}';
{% if request.user.is_authenticated() %}
askbot['data']['userId'] = {{ request.user.id }};
askbot['data']['userName'] = '{{ request.user.username }}';
@@ -12,17 +13,8 @@
{% else %}
askbot['data']['userReputation'] = 0;
{% endif %}
- askbot['data']['maxCommentLength'] = {{ settings.MAX_COMMENT_LENGTH }};
askbot['urls'] = {};
askbot['settings'] = {};
- askbot['settings']['editorType'] = '{{ settings.EDITOR_TYPE }}';
- {% if settings.ALLOWED_UPLOAD_FILE_TYPES %}
- askbot['settings']['allowedUploadFileTypes'] = [
- "{{ settings.ALLOWED_UPLOAD_FILE_TYPES|join('", "')|replace('.','') }}"
- ];
- {% else %}
- askbot['settings']['allowedUploadFileTypes'] = [];
- {% endif %}
askbot['messages'] = {};
</script>
<script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>
diff --git a/askbot/templates/question.html b/askbot/templates/question.html
index 98cb2502..c11fba04 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']){
@@ -312,6 +279,7 @@
askbot['settings']['saveCommentOnEnter'] = {{ settings.SAVE_COMMENT_ON_ENTER|as_js_bool }};
askbot['settings']['tagSource'] = '{{ settings.TAG_SOURCE }}';
askbot['settings']['enableSharingGoogle'] = {{ settings.ENABLE_SHARING_GOOGLE|as_js_bool }};
+ askbot['settings']['enableEmailAlerts'] = {{ settings.ENABLE_EMAIL_ALERTS|as_js_bool }};
</script>
{% include "meta/editor_data.html" %}
{% compress js %}
@@ -339,4 +307,3 @@
</script>
#}
{% endblock %}
-
diff --git a/askbot/templates/question/content.html b/askbot/templates/question/content.html
index 82185919..d07c3727 100644
--- a/askbot/templates/question/content.html
+++ b/askbot/templates/question/content.html
@@ -24,11 +24,17 @@
{# buttons below cannot be cached yet #}
{% if user_already_gave_answer %}
+<div style="margin-top: 15px">
<a
class="button submit"
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 class="invisible">
+ {# hidden because we still need js from the tinymce widget #}
+ {% include "question/new_answer_form.html" %}
+ </div>
+</div>
{% else %}
{% include "question/new_answer_form.html" %}
{% endif %}
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/utils/markup.py b/askbot/utils/markup.py
index ac96ec74..b96cf42d 100644
--- a/askbot/utils/markup.py
+++ b/askbot/utils/markup.py
@@ -7,6 +7,8 @@ import re
import logging
from askbot import const
from askbot.conf import settings as askbot_settings
+from askbot.utils.html import sanitize_html, strip_tags
+from django.utils.html import urlize
from markdown2 import Markdown
#url taken from http://regexlib.com/REDetails.aspx?regexp_id=501 by Brian Bothwell
URL_RE = re.compile("((?<!(href|.src|data)=['\"])((http|https|ftp)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*))")
@@ -189,3 +191,18 @@ def mentionize_text(text, anticipated_authors):
#append the rest of text that did not have @ symbols
output += text
return mentioned_authors, output
+
+def plain_text_input_converter(text):
+ """plain text to html converter"""
+ return sanitize_html(urlize('<p>' + text + '</p>'))
+
+def markdown_input_converter(text):
+ """markdown to html converter"""
+ text = urlize(text)
+ text = get_parser().convert(text)
+ return sanitize_html(text)
+
+def tinymce_input_converter(text):
+ """tinymce input to production html converter"""
+ text = urlize(text)
+ return strip_tags(text, ['script', 'style', 'link'])
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 4ad7ea7b..e8d50cea 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -62,7 +62,6 @@ ANSWERS_PAGE_SIZE = 10
def upload(request):#ajax upload file to a question or answer
"""view that handles file upload via Ajax
"""
-
# check upload permission
result = ''
error = ''
@@ -81,10 +80,11 @@ def upload(request):#ajax upload file to a question or answer
raise exceptions.PermissionDenied('invalid upload file name prefix')
#todo: check file type
- f = request.FILES['file-upload']#take first file
+ uploaded_file = request.FILES['file-upload']#take first file
+ orig_file_name = uploaded_file.name
#todo: extension checking should be replaced with mimetype checking
#and this must be part of the form validation
- file_extension = os.path.splitext(f.name)[1].lower()
+ file_extension = os.path.splitext(orig_file_name)[1].lower()
if not file_extension in settings.ASKBOT_ALLOWED_UPLOAD_FILE_TYPES:
file_types = "', '".join(settings.ASKBOT_ALLOWED_UPLOAD_FILE_TYPES)
msg = _("allowed file types are '%(file_types)s'") % \
@@ -93,7 +93,7 @@ def upload(request):#ajax upload file to a question or answer
# generate new file name and storage object
file_storage, new_file_name, file_url = store_file(
- f, file_name_prefix
+ uploaded_file, file_name_prefix
)
# check file size
# byte
@@ -123,7 +123,7 @@ def upload(request):#ajax upload file to a question or answer
#})
#return HttpResponse(data, mimetype = 'application/json')
xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url><orig_file_name><![CDATA[%s]]></orig_file_name></result>"
- xml = xml_template % (result, error, file_url, f.name)
+ xml = xml_template % (result, error, file_url, orig_file_name)
return HttpResponse(xml, mimetype="application/xml")
@@ -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,54 @@ 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,