/*
Scripts for cnprog.com
Project Name: Lanai
All Rights Resevred 2008. CNPROG.COM
*/
var lanai = {
/**
* Finds any
tags which aren't registered for
* pretty printing, adds the appropriate class name and invokes prettify.
*/
highlightSyntax: function(){
var styled = false;
$("pre code").parent().each(function(){
if (!$(this).hasClass('prettyprint')){
$(this).addClass('prettyprint');
styled = true;
}
});
if (styled){
prettyPrint();
}
}
};
//todo: clean-up now there is utils:WaitIcon
function appendLoader(element) {
loading = gettext('loading...')
element.append('');
}
function removeLoader() {
$("img.ajax-loader").remove();
}
function setSubmitButtonDisabled(form, isDisabled) {
form.find('input[type="submit"]').attr("disabled", isDisabled);
}
function enableSubmitButton(form) {
setSubmitButtonDisabled(form, false);
}
function disableSubmitButton(form) {
setSubmitButtonDisabled(form, true);
}
function setupFormValidation(form, validationRules, validationMessages, onSubmitCallback) {
enableSubmitButton(form);
form.validate({
debug: true,
rules: (validationRules ? validationRules : {}),
messages: (validationMessages ? validationMessages : {}),
errorElement: "span",
errorClass: "form-error",
errorPlacement: function(error, element) {
var span = element.next().find("span.form-error");
if (span.length === 0) {
span = element.parent().find("span.form-error");
if (span.length === 0){
//for resizable textarea
var element_id = element.attr('id');
span = $('label[for="' + element_id + '"]');
}
}
span.replaceWith(error);
},
submitHandler: function(form_dom) {
disableSubmitButton($(form_dom));
if (onSubmitCallback){
onSubmitCallback();
}
else{
form_dom.submit();
}
}
});
}
/**
* generic tag cleaning function, settings
* are from askbot live settings and askbot.const
*/
var cleanTag = function(tag_name, settings) {
var tag_regex = new RegExp(settings['tag_regex']);
if (tag_regex.test(tag_name) === false) {
throw settings['messages']['wrong_chars']
}
var max_length = settings['max_tag_length'];
if (tag_name.length > max_length) {
throw interpolate(
ngettext(
'must be shorter than %(max_chars)s character',
'must be shorter than %(max_chars)s characters',
max_length
),
{'max_chars': max_length },
true
);
}
if (settings['force_lowercase_tags']) {
return tag_name.toLowerCase();
} else {
return tag_name;
}
};
var validateTagLength = function(value){
var tags = getUniqueWords(value);
var are_tags_ok = true;
$.each(tags, function(index, value){
if (value.length > askbot['settings']['maxTagLength']){
are_tags_ok = false;
}
});
return are_tags_ok;
};
var validateTagCount = function(value){
var tags = getUniqueWords(value);
return (tags.length <= askbot['settings']['maxTagsPerPost']);
};
$.validator.addMethod('limit_tag_count', validateTagCount);
$.validator.addMethod('limit_tag_length', validateTagLength);
var CPValidator = function() {
return {
getQuestionFormRules : function() {
return {
tags: {
required: askbot['settings']['tagsAreRequired'],
maxlength: 105,
limit_tag_count: true,
limit_tag_length: true
},
text: {
minlength: askbot['settings']['minQuestionBodyLength']
},
title: {
minlength: askbot['settings']['minTitleLength']
}
};
},
getQuestionFormMessages: function(){
return {
tags: {
required: " " + gettext('tags cannot be empty'),
maxlength: askbot['messages']['tagLimits'],
limit_tag_count: askbot['messages']['maxTagsPerPost'],
limit_tag_length: askbot['messages']['maxTagLength']
},
text: {
required: " " + gettext('details are required'),
minlength: interpolate(
ngettext(
'details must have > %s character',
'details must have > %s characters',
askbot['settings']['minQuestionBodyLength']
),
[askbot['settings']['minQuestionBodyLength'], ]
)
},
title: {
required: " " + gettext('enter your question'),
minlength: interpolate(
ngettext(
'question must have > %s character',
'question must have > %s characters',
askbot['settings']['minTitleLength']
),
[askbot['settings']['minTitleLength'], ]
)
}
};
},
getAnswerFormRules : function(){
return {
text: {
minlength: askbot['settings']['minAnswerBodyLength']
},
};
},
getAnswerFormMessages: function(){
return {
text: {
required: " " + gettext('content cannot be empty'),
minlength: interpolate(
ngettext(
'answer must be > %s character',
'answer must be > %s characters',
askbot['settings']['minAnswerBodyLength']
),
[askbot['settings']['minAnswerBodyLength'], ]
)
},
}
}
};
}();
/**
* @constructor
*/
var ThreadUsersDialog = function() {
SimpleControl.call(this);
this._heading_text = 'Add heading with the setHeadingText()';
};
inherits(ThreadUsersDialog, SimpleControl);
ThreadUsersDialog.prototype.setHeadingText = function(text) {
this._heading_text = text;
};
ThreadUsersDialog.prototype.showUsers = function(html) {
this._dialog.setContent(html);
this._dialog.show();
};
ThreadUsersDialog.prototype.startShowingUsers = function() {
var me = this;
var threadId = this._threadId;
var url = this._url;
$.ajax({
type: 'GET',
data: {'thread_id': threadId},
dataType: 'json',
url: url,
cache: false,
success: function(data){
if (data['success'] == true){
me.showUsers(data['html']);
} else {
showMessage(me.getElement(), data['message'], 'after');
}
}
});
};
ThreadUsersDialog.prototype.decorate = function(element) {
this._element = element;
ThreadUsersDialog.superClass_.decorate.call(this, element);
this._threadId = element.data('threadId');
this._url = element.data('url');
var dialog = new ModalDialog();
dialog.setRejectButtonText('');
dialog.setAcceptButtonText(gettext('Back to the question'));
dialog.setHeadingText(this._heading_text);
dialog.setAcceptHandler(function(){ dialog.hide(); });
var dialog_element = dialog.getElement();
$(dialog_element).find('.modal-footer').css('text-align', 'center');
$(document).append(dialog_element);
this._dialog = dialog;
var me = this;
this.setHandler(function(){
me.startShowingUsers();
});
};
/**
* @constructor
*/
var DraftPost = function() {
WrappedElement.call(this);
};
inherits(DraftPost, WrappedElement);
/**
* @return {string}
*/
DraftPost.prototype.getUrl = function() {
throw 'Not Implemented';
};
/**
* @return {boolean}
*/
DraftPost.prototype.shouldSave = function() {
throw 'Not Implemented';
};
/**
* @return {object} data dict
*/
DraftPost.prototype.getData = function() {
throw 'Not Implemented';
};
DraftPost.prototype.backupData = function() {
this._old_data = this.getData();
};
DraftPost.prototype.showNotification = function() {
var note = $('.editor-status span');
note.hide();
note.html(gettext('draft saved...'));
note.fadeIn().delay(3000).fadeOut();
};
DraftPost.prototype.getSaveHandler = function() {
var me = this;
return function(save_synchronously) {
if (me.shouldSave()) {
$.ajax({
type: 'POST',
cache: false,
dataType: 'json',
async: save_synchronously ? false : true,
url: me.getUrl(),
data: me.getData(),
success: function(data) {
if (data['success'] && !save_synchronously) {
me.showNotification();
}
me.backupData();
}
});
}
};
};
DraftPost.prototype.decorate = function(element) {
this._element = element;
this.assignContentElements();
this.backupData();
setInterval(this.getSaveHandler(), 30000);//auto-save twice a minute
var me = this;
window.onbeforeunload = function() {
var saveHandler = me.getSaveHandler();
saveHandler(true);
//var msg = gettext("%s, we've saved your draft, but...");
//return interpolate(msg, [askbot['data']['userName']]);
};
};
/**
* @contstructor
*/
var DraftQuestion = function() {
DraftPost.call(this);
};
inherits(DraftQuestion, DraftPost);
DraftQuestion.prototype.getUrl = function() {
return askbot['urls']['saveDraftQuestion'];
};
DraftQuestion.prototype.shouldSave = function() {
var newd = this.getData();
var oldd = this._old_data;
return (
newd['title'] !== oldd['title'] ||
newd['text'] !== oldd['text'] ||
newd['tagnames'] !== oldd['tagnames']
);
};
DraftQuestion.prototype.getData = function() {
return {
'title': this._title_element.val(),
'text': this._text_element.val(),
'tagnames': this._tagnames_element.val()
};
};
DraftQuestion.prototype.assignContentElements = function() {
this._title_element = $('#id_title');
this._text_element = $('#editor');
this._tagnames_element = $('#id_tags');
};
var DraftAnswer = function() {
DraftPost.call(this);
};
inherits(DraftAnswer, DraftPost);
DraftAnswer.prototype.setThreadId = function(id) {
this._threadId = id;
};
DraftAnswer.prototype.getUrl = function() {
return askbot['urls']['saveDraftAnswer'];
};
DraftAnswer.prototype.shouldSave = function() {
return this.getData()['text'] !== this._old_data['text'];
};
DraftAnswer.prototype.getData = function() {
return {
'text': this._textElement.val(),
'thread_id': this._threadId
};
};
DraftAnswer.prototype.assignContentElements = function() {
this._textElement = $('#editor');
};
/**
* @constructor
* @extends {SimpleControl}
* @param {Comment} comment to upvote
*/
var CommentVoteButton = function(comment){
SimpleControl.call(this);
/**
* @param {Comment}
*/
this._comment = comment;
/**
* @type {boolean}
*/
this._voted = false;
/**
* @type {number}
*/
this._score = 0;
};
inherits(CommentVoteButton, SimpleControl);
/**
* @param {number} score
*/
CommentVoteButton.prototype.setScore = function(score){
this._score = score;
if (this._element){
this._element.html(score);
}
};
/**
* @param {boolean} voted
*/
CommentVoteButton.prototype.setVoted = function(voted){
this._voted = voted;
if (this._element){
this._element.addClass('upvoted');
}
};
CommentVoteButton.prototype.getVoteHandler = function(){
var me = this;
var comment = this._comment;
return function(){
var voted = me._voted;
var post_id = me._comment.getId();
var data = {
cancel_vote: voted ? true:false,
post_id: post_id
};
$.ajax({
type: 'POST',
data: data,
dataType: 'json',
url: askbot['urls']['upvote_comment'],
cache: false,
success: function(data){
if (data['success'] == true){
me.setScore(data['score']);
me.setVoted(true);
} else {
showMessage(comment.getElement(), data['message'], 'after');
}
}
});
};
};
CommentVoteButton.prototype.decorate = function(element){
this._element = element;
this.setHandler(this.getVoteHandler());
var element = this._element;
var comment = this._comment;
/* can't call comment.getElement() here due
* an issue in the getElement() of comment
* so use an "illegal" access to comment._element here
*/
comment._element.mouseenter(function(){
//outside height may not be known
//var height = comment.getElement().height();
//element.height(height);
element.addClass('hover');
});
comment._element.mouseleave(function(){
element.removeClass('hover');
});
};
CommentVoteButton.prototype.createDom = function(){
this._element = this.makeElement('div');
if (this._score > 0){
this._element.html(this._score);
}
this._element.addClass('upvote');
if (this._voted){
this._element.addClass('upvoted');
}
this.decorate(this._element);
};
/**
* legacy Vote class
* handles all sorts of vote-like operations
*/
var Vote = function(){
// All actions are related to a question
var questionId;
//question slug to build redirect urls
var questionSlug;
// The object we operate on actually. It can be a question or an answer.
var postId;
var questionAuthorId;
var currentUserId;
var answerContainerIdPrefix = 'post-id-';
var voteContainerId = 'vote-buttons';
var imgIdPrefixAccept = 'answer-img-accept-';
var classPrefixFollow= 'button follow';
var classPrefixFollowed= 'button followed';
var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
var divIdFavorite = 'favorite-number';
var commentLinkIdPrefix = 'comment-';
var voteNumberClass = "vote-number";
var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
var removeOffensiveIdPrefixQuestionFlag = 'question-offensive-remove-flag-';
var removeAllOffensiveIdPrefixQuestionFlag = 'question-offensive-remove-all-flag-';
var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
var removeOffensiveIdPrefixAnswerFlag = 'answer-offensive-remove-flag-';
var removeAllOffensiveIdPrefixAnswerFlag = 'answer-offensive-remove-all-flag-';
var offensiveClassFlag = 'offensive-flag';
var questionControlsId = 'question-controls';
var removeAnswerLinkIdPrefix = 'answer-delete-link-';
var questionSubscribeUpdates = 'question-subscribe-updates';
var questionSubscribeSidebar= 'question-subscribe-sidebar';
var acceptAnonymousMessage = gettext('insufficient privilege');
var acceptOwnAnswerMessage = gettext('cannot pick own answer as best');
var pleaseLogin = " "
+ gettext('please login') + "";
var favoriteAnonymousMessage = gettext('anonymous users cannot follow questions') + pleaseLogin;
var subscribeAnonymousMessage = gettext('anonymous users cannot subscribe to questions') + pleaseLogin;
var voteAnonymousMessage = gettext('anonymous users cannot vote') + pleaseLogin;
//there were a couple of more messages...
var offensiveConfirmation = gettext('please confirm offensive');
var removeOffensiveConfirmation = gettext('please confirm removal of offensive flag');
var offensiveAnonymousMessage = gettext('anonymous users cannot flag offensive posts') + pleaseLogin;
var removeConfirmation = gettext('confirm delete');
var removeAnonymousMessage = gettext('anonymous users cannot delete/undelete') + pleaseLogin;
var recoveredMessage = gettext('post recovered');
var deletedMessage = gettext('post deleted');
var VoteType = {
acceptAnswer : 0,
questionUpVote : 1,
questionDownVote : 2,
favorite : 4,
answerUpVote: 5,
answerDownVote:6,
offensiveQuestion : 7,
removeOffensiveQuestion : 7.5,
removeAllOffensiveQuestion : 7.6,
offensiveAnswer:8,
removeOffensiveAnswer:8.5,
removeAllOffensiveAnswer:8.6,
removeQuestion: 9,//deprecate
removeAnswer:10,//deprecate
questionSubscribeUpdates:11,
questionUnsubscribeUpdates:12
};
var getFavoriteButton = function(){
var favoriteButton = 'div.'+ voteContainerId +' a[class="'+ classPrefixFollow +'"]';
favoriteButton += ', div.'+ voteContainerId +' a[class="'+ classPrefixFollowed +'"]';
return $(favoriteButton);
};
var getFavoriteNumber = function(){
var favoriteNumber = '#'+ divIdFavorite ;
return $(favoriteNumber);
};
var getQuestionVoteUpButton = function(){
var questionVoteUpButton = 'div.'+ voteContainerId +' div[id^="'+ imgIdPrefixQuestionVoteup +'"]';
return $(questionVoteUpButton);
};
var getQuestionVoteDownButton = function(){
var questionVoteDownButton = 'div.'+ voteContainerId +' div[id^="'+ imgIdPrefixQuestionVotedown +'"]';
return $(questionVoteDownButton);
};
var getAnswerVoteUpButtons = function(){
var answerVoteUpButton = 'div.'+ voteContainerId +' div[id^="'+ imgIdPrefixAnswerVoteup +'"]';
return $(answerVoteUpButton);
};
var getAnswerVoteDownButtons = function(){
var answerVoteDownButton = 'div.'+ voteContainerId +' div[id^="'+ imgIdPrefixAnswerVotedown +'"]';
return $(answerVoteDownButton);
};
var getAnswerVoteUpButton = function(id){
var answerVoteUpButton = 'div.'+ voteContainerId +' div[id="'+ imgIdPrefixAnswerVoteup + id + '"]';
return $(answerVoteUpButton);
};
var getAnswerVoteDownButton = function(id){
var answerVoteDownButton = 'div.'+ voteContainerId +' div[id="'+ imgIdPrefixAnswerVotedown + id + '"]';
return $(answerVoteDownButton);
};
var getOffensiveQuestionFlag = function(){
var offensiveQuestionFlag = '.question-card span[id^="'+ offensiveIdPrefixQuestionFlag +'"]';
return $(offensiveQuestionFlag);
};
var getRemoveOffensiveQuestionFlag = function(){
var removeOffensiveQuestionFlag = '.question-card span[id^="'+ removeOffensiveIdPrefixQuestionFlag +'"]';
return $(removeOffensiveQuestionFlag);
};
var getRemoveAllOffensiveQuestionFlag = function(){
var removeAllOffensiveQuestionFlag = '.question-card span[id^="'+ removeAllOffensiveIdPrefixQuestionFlag +'"]';
return $(removeAllOffensiveQuestionFlag);
};
var getOffensiveAnswerFlags = function(){
var offensiveQuestionFlag = 'div.answer span[id^="'+ offensiveIdPrefixAnswerFlag +'"]';
return $(offensiveQuestionFlag);
};
var getRemoveOffensiveAnswerFlag = function(){
var removeOffensiveAnswerFlag = 'div.answer span[id^="'+ removeOffensiveIdPrefixAnswerFlag +'"]';
return $(removeOffensiveAnswerFlag);
};
var getRemoveAllOffensiveAnswerFlag = function(){
var removeAllOffensiveAnswerFlag = 'div.answer span[id^="'+ removeAllOffensiveIdPrefixAnswerFlag +'"]';
return $(removeAllOffensiveAnswerFlag);
};
var getquestionSubscribeUpdatesCheckbox = function(){
return $('#' + questionSubscribeUpdates);
};
var getquestionSubscribeSidebarCheckbox= function(){
return $('#' + questionSubscribeSidebar);
};
var getremoveAnswersLinks = function(){
var removeAnswerLinks = 'div.answer-controls a[id^="'+ removeAnswerLinkIdPrefix +'"]';
return $(removeAnswerLinks);
};
var setVoteImage = function(voteType, undo, object){
var flag = undo ? false : true;
if (object.hasClass("on")) {
object.removeClass("on");
}else{
object.addClass("on");
}
if(undo){
if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
$(getQuestionVoteUpButton()).removeClass("on");
$(getQuestionVoteDownButton()).removeClass("on");
}
else{
$(getAnswerVoteUpButton(postId)).removeClass("on");
$(getAnswerVoteDownButton(postId)).removeClass("on");
}
}
};
var setVoteNumber = function(object, number){
var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
$(voteNumber).text(number);
};
var bindEvents = function(){
// accept answers
var acceptedButtons = 'div.'+ voteContainerId +' div[id^="'+ imgIdPrefixAccept +'"]';
$(acceptedButtons).unbind('click').click(function(event){
Vote.accept($(event.target));
});
// set favorite question
var favoriteButton = getFavoriteButton();
favoriteButton.unbind('click').click(function(event){
//Vote.favorite($(event.target));
Vote.favorite(favoriteButton);
});
// question vote up
var questionVoteUpButton = getQuestionVoteUpButton();
questionVoteUpButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.questionUpVote);
});
var questionVoteDownButton = getQuestionVoteDownButton();
questionVoteDownButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.questionDownVote);
});
var answerVoteUpButton = getAnswerVoteUpButtons();
answerVoteUpButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.answerUpVote);
});
var answerVoteDownButton = getAnswerVoteDownButtons();
answerVoteDownButton.unbind('click').click(function(event){
Vote.vote($(event.target), VoteType.answerDownVote);
});
getOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveQuestion);
});
getRemoveOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.remove_offensive(this, VoteType.removeOffensiveQuestion);
});
getRemoveAllOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.remove_all_offensive(this, VoteType.removeAllOffensiveQuestion);
});
getOffensiveAnswerFlags().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveAnswer);
});
getRemoveOffensiveAnswerFlag().unbind('click').click(function(event){
Vote.remove_offensive(this, VoteType.removeOffensiveAnswer);
});
getRemoveAllOffensiveAnswerFlag().unbind('click').click(function(event){
Vote.remove_all_offensive(this, VoteType.removeAllOffensiveAnswer);
});
getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){
//despeluchar esto
if (this.checked){
getquestionSubscribeSidebarCheckbox().attr({'checked': true});
Vote.vote($(event.target), VoteType.questionSubscribeUpdates);
}
else {
getquestionSubscribeSidebarCheckbox().attr({'checked': false});
Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates);
}
});
getquestionSubscribeSidebarCheckbox().unbind('click').click(function(event){
if (this.checked){
getquestionSubscribeUpdatesCheckbox().attr({'checked': true});
Vote.vote($(event.target), VoteType.questionSubscribeUpdates);
}
else {
getquestionSubscribeUpdatesCheckbox().attr({'checked': false});
Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates);
}
});
getremoveAnswersLinks().unbind('click').click(function(event){
Vote.remove(this, VoteType.removeAnswer);
});
};
var submit = function(object, voteType, callback) {
//this function submits votes
$.ajax({
type: "POST",
cache: false,
dataType: "json",
url: askbot['urls']['vote_url'],
data: { "type": voteType, "postId": postId },
error: handleFail,
success: function(data) {
callback(object, voteType, data);
}
});
};
var handleFail = function(xhr, msg){
alert("Callback invoke error: " + msg);
};
// callback function for Accept Answer action
var callback_accept = function(object, voteType, data){
if(data.allowed == "0" && data.success == "0"){
showMessage(object, acceptAnonymousMessage);
}
else if(data.allowed == "-1"){
showMessage(object, acceptOwnAnswerMessage);
}
else if(data.status == "1"){
$("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
}
else if(data.success == "1"){
var answers = ('div[id^="'+answerContainerIdPrefix +'"]');
$(answers).removeClass('accepted-answer');
var commentLinks = ('div[id^="'+answerContainerIdPrefix +'"] div[id^="'+ commentLinkIdPrefix +'"]');
$(commentLinks).removeClass("comment-link-accepted");
$("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
}
else{
showMessage(object, data.message);
}
};
var callback_favorite = function(object, voteType, data){
if(data.allowed == "0" && data.success == "0"){
showMessage(
object,
favoriteAnonymousMessage.replace(
'{{QuestionID}}',
questionId).replace(
'{{questionSlug}}',
''
)
);
}
else if(data.status == "1"){
var follow_html = gettext('Follow');
object.attr("class", 'button follow');
object.html(follow_html);
var fav = getFavoriteNumber();
fav.removeClass("my-favorite-number");
if(data.count === 0){
data.count = '';
fav.text('');
}else{
var fmts = ngettext('%s follower', '%s followers', data.count);
fav.text(interpolate(fmts, [data.count]));
}
}
else if(data.success == "1"){
var followed_html = gettext('Following
Unfollow
');
object.html(followed_html);
object.attr("class", 'button followed');
var fav = getFavoriteNumber();
var fmts = ngettext('%s follower', '%s followers', data.count);
fav.text(interpolate(fmts, [data.count]));
fav.addClass("my-favorite-number");
}
else{
showMessage(object, data.message);
}
};
var callback_vote = function(object, voteType, data){
if (data.success == '0'){
showMessage(object, data.message);
return;
}
else {
if (data.status == '1'){
setVoteImage(voteType, true, object);
}
else {
setVoteImage(voteType, false, object);
}
setVoteNumber(object, data.count);
if (data.message && data.message.length > 0){
showMessage(object, data.message);
}
return;
}
//may need to take a look at this again
if (data.status == "1"){
setVoteImage(voteType, true, object);
setVoteNumber(object, data.count);
}
else if (data.success == "1"){
setVoteImage(voteType, false, object);
setVoteNumber(object, data.count);
if (data.message.length > 0){
showMessage(object, data.message);
}
}
};
var callback_offensive = function(object, voteType, data){
//todo: transfer proper translations of these from i18n.js
//to django.po files
//_('anonymous users cannot flag offensive posts') + pleaseLogin;
if (data.success == "1"){
if(data.count > 0)
$(object).children('span[class="darkred"]').text("("+ data.count +")");
else
$(object).children('span[class="darkred"]').text("");
// Change the link text and rebind events
$(object).find("a.question-flag").html(gettext("remove flag"));
var obj_id = $(object).attr("id");
$(object).attr("id", obj_id.replace("flag-", "remove-flag-"));
getRemoveOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.remove_offensive(this, VoteType.removeOffensiveQuestion);
});
getRemoveOffensiveAnswerFlag().unbind('click').click(function(event){
Vote.remove_offensive(this, VoteType.removeOffensiveAnswer);
});
}
else {
object = $(object);
showMessage(object, data.message)
}
};
var callback_remove_offensive = function(object, voteType, data){
//todo: transfer proper translations of these from i18n.js
//to django.po files
//_('anonymous users cannot flag offensive posts') + pleaseLogin;
if (data.success == "1"){
if(data.count > 0){
$(object).children('span[class="darkred"]').text("("+ data.count +")");
}
else{
$(object).children('span[class="darkred"]').text("");
// Remove "remove all flags link" since there are no more flags to remove
var remove_all = $(object).siblings('span.offensive-flag[id*="-offensive-remove-all-flag-"]');
$(remove_all).next("span.sep").remove();
$(remove_all).remove();
}
// Change the link text and rebind events
$(object).find("a.question-flag").html(gettext("flag offensive"));
var obj_id = $(object).attr("id");
$(object).attr("id", obj_id.replace("remove-flag-", "flag-"));
getOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveQuestion);
});
getOffensiveAnswerFlags().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveAnswer);
});
}
else {
object = $(object);
showMessage(object, data.message)
}
};
var callback_remove_all_offensive = function(object, voteType, data){
//todo: transfer proper translations of these from i18n.js
//to django.po files
//_('anonymous users cannot flag offensive posts') + pleaseLogin;
if (data.success == "1"){
if(data.count > 0)
$(object).children('span[class="darkred"]').text("("+ data.count +")");
else
$(object).children('span[class="darkred"]').text("");
// remove the link. All flags are gone
var remove_own = $(object).siblings('span.offensive-flag[id*="-offensive-remove-flag-"]');
$(remove_own).find("a.question-flag").html(gettext("flag offensive"));
$(remove_own).attr("id", $(remove_own).attr("id").replace("remove-flag-", "flag-"));
$(object).next("span.sep").remove();
$(object).remove();
getOffensiveQuestionFlag().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveQuestion);
});
getOffensiveAnswerFlags().unbind('click').click(function(event){
Vote.offensive(this, VoteType.offensiveAnswer);
});
}
else {
object = $(object);
showMessage(object, data.message)
}
};
var callback_remove = function(object, voteType, data){
if (data.success == "1"){
if (removeActionType == 'delete'){
postNode.addClass('deleted');
postRemoveLink.innerHTML = gettext('undelete');
showMessage(object, deletedMessage);
}
else if (removeActionType == 'undelete') {
postNode.removeClass('deleted');
postRemoveLink.innerHTML = gettext('delete');
showMessage(object, recoveredMessage);
}
}
else {
showMessage(object, data.message)
}
};
return {
init : function(qId, qSlug, questionAuthor, userId){
questionId = qId;
questionSlug = qSlug;
questionAuthorId = questionAuthor;
currentUserId = '' + userId;//convert to string
bindEvents();
},
//accept answer
accept: function(object){
postId = object.attr("id").substring(imgIdPrefixAccept.length);
submit(object, VoteType.acceptAnswer, callback_accept);
},
//mark question as favorite
favorite: function(object){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(
object,
favoriteAnonymousMessage.replace(
"{{QuestionID}}",
questionId
).replace(
'{{questionSlug}}',
questionSlug
)
);
return false;
}
postId = questionId;
submit(object, VoteType.favorite, callback_favorite);
},
vote: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE") {
if (voteType == VoteType.questionSubscribeUpdates || voteType == VoteType.questionUnsubscribeUpdates){
getquestionSubscribeSidebarCheckbox().removeAttr('checked');
getquestionSubscribeUpdatesCheckbox().removeAttr('checked');
showMessage(object, subscribeAnonymousMessage);
} else {
showMessage(
$(object),
voteAnonymousMessage.replace(
"{{QuestionID}}",
questionId
).replace(
'{{questionSlug}}',
questionSlug
)
);
}
return false;
}
// up and downvote processor
if (voteType == VoteType.answerUpVote){
postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
} else if (voteType == VoteType.answerDownVote){
postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
} else {
postId = questionId;
}
submit(object, voteType, callback_vote);
},
//flag offensive
offensive: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(
$(object),
offensiveAnonymousMessage.replace(
"{{QuestionID}}",
questionId
).replace(
'{{questionSlug}}',
questionSlug
)
);
return false;
}
if (confirm(offensiveConfirmation)){
postId = object.id.substr(object.id.lastIndexOf('-') + 1);
submit(object, voteType, callback_offensive);
}
},
//remove flag offensive
remove_offensive: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(
$(object),
offensiveAnonymousMessage.replace(
"{{QuestionID}}",
questionId
).replace(
'{{questionSlug}}',
questionSlug
)
);
return false;
}
if (confirm(removeOffensiveConfirmation)){
postId = object.id.substr(object.id.lastIndexOf('-') + 1);
submit(object, voteType, callback_remove_offensive);
}
},
remove_all_offensive: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(
$(object),
offensiveAnonymousMessage.replace(
"{{QuestionID}}",
questionId
).replace(
'{{questionSlug}}',
questionSlug
)
);
return false;
}
if (confirm(removeOffensiveConfirmation)){
postId = object.id.substr(object.id.lastIndexOf('-') + 1);
submit(object, voteType, callback_remove_all_offensive);
}
},
//delete question or answer (comments are deleted separately)
remove: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
showMessage(
$(object),
removeAnonymousMessage.replace(
'{{QuestionID}}',
questionId
).replace(
'{{questionSlug}}',
questionSlug
)
);
return false;
}
bits = object.id.split('-');
postId = bits.pop();/* this seems to be used within submit! */
postType = bits.shift();
var do_proceed = false;
postNode = $('#post-id-' + postId);
postRemoveLink = object;
if (postNode.hasClass('deleted')) {
removeActionType = 'undelete';
do_proceed = true;
} else {
removeActionType = 'delete';
do_proceed = confirm(removeConfirmation);
}
if (do_proceed) {
submit($(object), voteType, callback_remove);
}
}
};
} ();
var questionRetagger = function(){
var oldTagsHTML = '';
var tagInput = null;
var tagsDiv = null;
var retagLink = null;
var restoreEventHandlers = function(){
$(document).unbind('click');
};
var cancelRetag = function(){
tagsDiv.html(oldTagsHTML);
tagsDiv.removeClass('post-retag');
tagsDiv.addClass('post-tags');
restoreEventHandlers();
initRetagger();
};
var drawNewTags = function(new_tags){
tagsDiv.empty();
if (new_tags === ''){
return;
}
new_tags = new_tags.split(/\s+/);
var tags_html = ''
$.each(new_tags, function(index, name){
var tag = new Tag();
tag.setName(name);
tagsDiv.append(tag.getElement());
});
};
var doRetag = function(){
$.ajax({
type: "POST",
url: retagUrl,//todo add this url to askbot['urls']
dataType: "json",
data: { tags: getUniqueWords(tagInput.val()).join(' ') },
success: function(json) {
if (json['success'] === true){
new_tags = getUniqueWords(json['new_tags']);
oldTagsHtml = '';
cancelRetag();
drawNewTags(new_tags.join(' '));
if (json['message']) {
notify.show(json['message']);
}
}
else {
cancelRetag();
showMessage(tagsDiv, json['message']);
}
},
error: function(xhr, textStatus, errorThrown) {
showMessage(tagsDiv, gettext('sorry, something is not right here'));
cancelRetag();
}
});
return false;
}
var setupInputEventHandlers = function(input){
input.keydown(function(e){
if ((e.which && e.which == 27) || (e.keyCode && e.keyCode == 27)){
cancelRetag();
}
});
$(document).unbind('click').click(cancelRetag, false);
input.click(function(){return false});
};
var createRetagForm = function(old_tags_string){
var div = $('');
tagInput = $('');
//var tagLabel = $('');
//populate input
var tagAc = new AutoCompleter({
url: askbot['urls']['get_tag_list'],
minChars: 1,
useCache: true,
matchInside: true,
maxCacheLength: 100,
delay: 10
});
tagAc.decorate(tagInput);
tagInput.val(old_tags_string);
div.append(tagInput);
//div.append(tagLabel);
setupInputEventHandlers(tagInput);
//button = $('');
//button.val(gettext('save tags'));
//div.append(button);
//setupButtonEventHandlers(button);
div.validate({//copy-paste from utils.js
rules: {
tags: {
required: askbot['settings']['tagsAreRequired'],
maxlength: askbot['settings']['maxTagsPerPost'] * askbot['settings']['maxTagLength'],
limit_tag_count: true,
limit_tag_length: true
}
},
messages: {
tags: {
required: gettext('tags cannot be empty'),
maxlength: askbot['messages']['tagLimits'],
limit_tag_count: askbot['messages']['maxTagsPerPost'],
limit_tag_length: askbot['messages']['maxTagLength']
}
},
submitHandler: doRetag,
errorClass: "retag-error"
});
return div;
};
var getTagsAsString = function(tags_div){
var links = tags_div.find('a');
var tags_str = '';
links.each(function(index, element){
if (index === 0){
//this is pretty bad - we should use Tag.getName()
tags_str = $(element).data('tagName');
}
else {
tags_str += ' ' + $(element).data('tagName');
}
});
return tags_str;
};
var noopHandler = function(){
tagInput.focus();
return false;
};
var deactivateRetagLink = function(){
retagLink.unbind('click').click(noopHandler);
retagLink.unbind('keypress').keypress(noopHandler);
};
var startRetag = function(){
tagsDiv = $('#question-tags');
oldTagsHTML = tagsDiv.html();//save to restore on cancel
var old_tags_string = getTagsAsString(tagsDiv);
var retag_form = createRetagForm(old_tags_string);
tagsDiv.html('');
tagsDiv.append(retag_form);
tagsDiv.removeClass('post-tags');
tagsDiv.addClass('post-retag');
tagInput.focus();
deactivateRetagLink();
return false;
};
var setupClickAndEnterHandler = function(element, callback){
element.unbind('click').click(callback);
element.unbind('keypress').keypress(function(e){
if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)){
callback();
}
});
}
var initRetagger = function(){
setupClickAndEnterHandler(retagLink, startRetag);
};
return {
init: function(){
retagLink = $('#retag');
initRetagger();
}
};
}();
/**
* @constructor
* Controls vor voting for a post
*/
var VoteControls = function() {
WrappedElement.call(this);
this._postAuthorId = undefined;
this._postId = undefined;
};
inherits(VoteControls, WrappedElement);
VoteControls.prototype.setPostId = function(postId) {
this._postId = postId;
};
VoteControls.prototype.getPostId = function() {
return this._postId;
};
VoteControls.prototype.setPostAuthorId = function(userId) {
this._postAuthorId = userId;
};
VoteControls.prototype.setSlug = function(slug) {
this._slug = slug;
};
VoteControls.prototype.setPostType = function(postType) {
this._postType = postType;
};
VoteControls.prototype.getPostType = function() {
return this._postType;
};
VoteControls.prototype.clearVotes = function() {
this._upvoteButton.removeClass('on');
this._downvoteButton.removeClass('on');
};
VoteControls.prototype.toggleButton = function(button) {
if (button.hasClass('on')) {
button.removeClass('on');
} else {
button.addClass('on');
}
};
VoteControls.prototype.toggleVote = function(voteType) {
if (voteType === 'upvote') {
this.toggleButton(this._upvoteButton);
} else {
this.toggleButton(this._downvoteButton);
}
};
VoteControls.prototype.setVoteCount = function(count) {
this._voteCount.html(count);
};
VoteControls.prototype.updateDisplay = function(voteType, data) {
if (data['status'] == '1'){
this.clearVotes();
} else {
this.toggleVote(voteType);
}
this.setVoteCount(data['count']);
if (data['message'] && data['message'].length > 0){
showMessage(this._element, data.message);
}
};
VoteControls.prototype.getAnonymousMessage = function(message) {
var pleaseLogin = " "
+ gettext('please login') + "";
message += pleaseLogin;
message = message.replace("{{QuestionID}}", this._postId);
return message.replace('{{questionSlug}}', this._slug);
};
VoteControls.prototype.getVoteHandler = function(voteType) {
var me = this;
return function() {
if (askbot['data']['userIsAuthenticated'] === false) {
var message = me.getAnonymousMessage(gettext('anonymous users cannot vote'));
showMessage(me.getElement(), message);
} else {
//this function submits votes
var voteMap = {
'question': { 'upvote': 1, 'downvote': 2 },
'answer': { 'upvote': 5, 'downvote': 6 }
};
var legacyVoteType = voteMap[me.getPostType()][voteType];
$.ajax({
type: "POST",
cache: false,
dataType: "json",
url: askbot['urls']['vote_url'],
data: {
"type": legacyVoteType,
"postId": me.getPostId()
},
error: function() {
showMessage(me.getElement(), gettext('sorry, something is not right here'));
},
success: function(data) {
if (data['success']) {
me.updateDisplay(voteType, data);
} else {
showMessage(me.getElement(), data['message']);
}
}
});
}
};
};
VoteControls.prototype.decorate = function(element) {
this._element = element;
var upvoteButton = element.find('.upvote');
this._upvoteButton = upvoteButton;
setupButtonEventHandlers(upvoteButton, this.getVoteHandler('upvote'));
var downvoteButton = element.find('.downvote');
this._downvoteButton = downvoteButton;
setupButtonEventHandlers(downvoteButton, this.getVoteHandler('downvote'));
this._voteCount = element.find('.vote-number');
};
var DeletePostLink = function(){
SimpleControl.call(this);
this._post_id = null;
};
inherits(DeletePostLink, SimpleControl);
DeletePostLink.prototype.setPostId = function(id){
this._post_id = id;
};
DeletePostLink.prototype.getPostId = function(){
return this._post_id;
};
DeletePostLink.prototype.getPostElement = function(){
return $('#post-id-' + this.getPostId());
};
DeletePostLink.prototype.isPostDeleted = function(){
return this._post_deleted;
};
DeletePostLink.prototype.setPostDeleted = function(is_deleted){
var post = this.getPostElement();
if (is_deleted === true){
post.addClass('deleted');
this._post_deleted = true;
this.getElement().html(gettext('undelete'));
} else if (is_deleted === false){
post.removeClass('deleted');
this._post_deleted = false;
this.getElement().html(gettext('delete'));
}
};
DeletePostLink.prototype.getDeleteHandler = function(){
var me = this;
var post_id = this.getPostId();
return function(){
var data = {
'post_id': me.getPostId(),
//todo rename cancel_vote -> undo
'cancel_vote': me.isPostDeleted() ? true: false
};
$.ajax({
type: 'POST',
data: data,
dataType: 'json',
url: askbot['urls']['delete_post'],
cache: false,
success: function(data){
if (data['success'] == true){
me.setPostDeleted(data['is_deleted']);
} else {
showMessage(me.getElement(), data['message']);
}
}
});
};
};
DeletePostLink.prototype.decorate = function(element){
this._element = element;
this._post_deleted = this.getPostElement().hasClass('deleted');
this.setHandler(this.getDeleteHandler());
}
/**
* 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._commentsWidget = null;
this._element = null;
this._editorReady = false;
this._text = '';
};
inherits(EditCommentForm, WrappedElement);
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: '70'
};
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);
}
};
EditCommentForm.prototype.getCommentsWidget = function() {
return this._commentsWidget;
};
/**
* attaches comment editor to a particular comment
*/
EditCommentForm.prototype.attachTo = function(comment, mode){
this._comment = comment;
this._type = mode;//action: 'add' or 'edit'
this._commentsWidget = comment.getContainerWidget();
this._text = comment.getText();
comment.getElement().after(this.getElement());
comment.getElement().hide();
this._commentsWidget.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.startEditor();
this._editor.setText(this._text);
var ed = this._editor
var onFocus = function() {
ed.putCursorAtEnd();
};
this._editor.focus(onFocus);
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 length = editor.getText().length;
var length1 = maxCommentLength - 100;
if (length1 < 0){
length1 = Math.round(0.7*maxCommentLength);
}
var length2 = maxCommentLength - 30;
if (length2 < 0){
length2 = Math.round(0.9*maxCommentLength);
}
/* 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('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);
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;
}
if (this._editor === undefined) {
return true;
};
var ctext = this._editor.getText();
if ($.trim(ctext) == $.trim(this._text)){
return true;
} else if (this.confirmAbandon()){
return true;
}
this._editor.focus();
return false;
};
EditCommentForm.prototype.getCancelHandler = function(){
var me = this;
return function(evt){
if (me.canCancel()){
var widget = me.getCommentsWidget();
widget.handleDeletedComment();
me.detach();
evt.preventDefault();
}
return false;
};
};
EditCommentForm.prototype.detach = function(){
if (this._comment === null){
return;
}
this._comment.getContainerWidget().showButton();
if (this._comment.isBlank()){
this._comment.dispose();
} 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(){
this._element = $('');
this._element.attr('class', 'post-comments');
var div = $('');
this._element.append(div);
/** a stub container for the editor */
this._editorBox = div;
/**
* editor itself will live at this._editor
* and will be initialized by the attachTo()
*/
this._controlsBox = this.makeElement('div');
this._controlsBox.addClass('edit-comment-buttons');
div.append(this._controlsBox);
this._text_counter = $('').attr('class', 'counter');
this._controlsBox.append(this._text_counter);
this._submit_btn = $('');
this._controlsBox.append(this._submit_btn);
this._cancel_btn = $('');
this._cancel_btn.html(gettext('cancel'));
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);
}
};
EditCommentForm.prototype.isEnabled = function() {
return (this._submit_btn.attr('disabled') !== 'disabled');//confusing! setters use boolean
};
EditCommentForm.prototype.enableForm = function() {
this._submit_btn.attr('disabled', false);
this._cancel_btn.attr('disabled', false);
};
EditCommentForm.prototype.disableForm = function() {
this._submit_btn.attr('disabled', true);
this._cancel_btn.attr('disabled', true);
};
EditCommentForm.prototype.reset = function(){
this._comment = null;
this._text = '';
this._editor.setText('');
this.enableForm();
};
EditCommentForm.prototype.confirmAbandon = function(){
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.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;
}
me.disableForm();
var text = editor.getText();
if (text.length < askbot['settings']['minCommentBodyLength']){
editor.focus();
me.enableForm();
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
};
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();
post_data['post_id'] = me._comment.getParentId();
post_url = askbot['urls']['postComments'];
}
$.ajax({
type: "POST",
url: post_url,
dataType: "json",
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 {
me._comment.setContent(json);
}
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();
}
});
return false;
};
};
var Comment = function(widget, data){
WrappedElement.call(this);
this._container_widget = widget;
this._data = data || {};
this._blank = true;//set to false by setContent
this._element = null;
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'];
}
else {
this._deletable = false;
}
if (data && data['is_editable']){
this._editable = data['is_deletable'];
}
else {
this._editable = false;
}
};
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};
this._contentBox = this._element.find('.comment-content');
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;
this._delete_icon = new DeleteIcon(this.deletePrompt);
this._delete_icon.setHandler(this.getDeleteHandler());
this._delete_icon.decorate(delete_img);
}
var edit_link = this._element.find('a.edit');
if (edit_link.length > 0){
this._editable = true;
this._edit_link = new EditLink();
this._edit_link.setHandler(this.getEditHandler());
this._edit_link.decorate(edit_link);
}
var convert_link = this._element.find('form.convert-comment');
if (this._is_convertible){
this._convert_link = new CommentConvertLink(comment_id);
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;
};
Comment.prototype.getId = function(){
return this._data['id'];
};
Comment.prototype.hasContent = function(){
return ('id' in this._data);
//shortcut for 'user_url' 'html' 'user_display_name' 'comment_age'
};
Comment.prototype.hasText = function(){
return ('text' in this._data);
}
Comment.prototype.getContainerWidget = function(){
return this._container_widget;
};
Comment.prototype.getParentType = function(){
return this._container_widget.getPostType();
};
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 = $.extend(this._data, data);
this._element.addClass('comment');
this._element.css('display', 'table');//@warning: hardcoded
//display is set to "block" if .show() is called, but we need table.
this._element.attr('id', 'comment-' + this._data['id']);
// 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);
}
vote.setScore(this._data['score']);
var voteElement = vote.getElement();
votesBox.append(vote.getElement());
}
// 2) create the comment content container
if (this._contentBox === undefined) {
var contentBox = this.makeElement('div');
contentBox.addClass('comment-content');
this._contentBox = contentBox;
this._element.append(contentBox);
}
// 2) create the comment deleter if it is not there
if (this._comment_delete === undefined) {
this._comment_delete = $('');
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._contentBox.append(this._comment_delete);
}
// 3) create or replace the comment body
if (this._comment_body === undefined) {
this._comment_body = $('');
this._contentBox.append(this._comment_body);
}
if (EditCommentForm.prototype.getEditorType() === 'tinymce') {
var theComment = $('');
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._comment_body.append(' – ');
// 4) create user link if absent
if (this._user_link !== undefined) {
this._user_link.detach();
this._user_link = undefined;
}
this._user_link = $('').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 = $('');
this._comment_added_at.html(this._data['comment_added_at']);
this._comment_added_at.attr('title', this._data['comment_added_at']);
this._comment_added_at.timeago();
this._comment_body.append(this._comment_added_at);
this._comment_body.append(')');
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._convert_link !== undefined) {
this._convert_link.dispose();
}
this._convert_link = new CommentConvertLink(this._data['id']);
this._comment_body.append(this._convert_link.getElement());
}
this._blank = false;
};
Comment.prototype.dispose = function(){
if (this._comment_body){
this._comment_body.remove();
}
if (this._comment_delete){
this._comment_delete.remove();
}
if (this._user_link){
this._user_link.remove();
}
if (this._comment_added_at){
this._comment_added_at.remove();
}
if (this._delete_icon){
this._delete_icon.dispose();
}
if (this._edit_link){
this._edit_link.dispose();
}
if (this._convert_link){
this._convert_link.dispose();
}
this._data = null;
Comment.superClass_.dispose.call(this);
};
Comment.prototype.getElement = function(){
Comment.superClass_.getElement.call(this);
if (this.isBlank() && this.hasContent()){
this.setContent();
if (askbot['settings']['mathjaxEnabled'] === true){
MathJax.Hub.Queue(['Typeset', MathJax.Hub]);
}
}
return this._element;
};
Comment.prototype.loadText = function(on_load_handler){
var me = this;
$.ajax({
type: "GET",
url: askbot['urls']['getComment'],
data: {id: this._data['id']},
success: function(json){
if (json['success']) {
me._data['text'] = json['text'];
on_load_handler()
} else {
showMessage(me.getElement(), json['message'], 'after');
}
},
error: function(xhr, textStatus, exception) {
showMessage(me.getElement(), xhr.responseText, 'after');
}
});
};
Comment.prototype.getText = function(){
if (!this.isBlank()){
if ('text' in this._data){
return this._data['text'];
}
}
return '';
}
Comment.prototype.getEditHandler = function(){
var me = this;
return function(){
if (me.hasText()){
me.startEditing();
} else {
me.loadText(function(){ me.startEditing() });
}
};
};
Comment.prototype.getDeleteHandler = function(){
var comment = this;
var del_icon = this._delete_icon;
return function(){
if (confirm(gettext('confirm delete comment'))){
comment.getElement().hide();
$.ajax({
type: 'POST',
url: askbot['urls']['deleteComment'],
data: { comment_id: comment.getId() },
success: function(json, textStatus, xhr) {
var widget = comment.getContainerWidget();
comment.dispose();
widget.handleDeletedComment();
},
error: function(xhr, textStatus, exception) {
comment.getElement().show()
showMessage(del_icon.getElement(), xhr.responseText);
},
dataType: "json"
});
}
};
};
var PostCommentsWidget = function(){
WrappedElement.call(this);
this._denied = false;
};
inherits(PostCommentsWidget, WrappedElement);
PostCommentsWidget.prototype.decorate = function(element){
var element = $(element);
this._element = element;
var widget_id = element.attr('id');
var id_bits = widget_id.split('-');
this._post_id = id_bits[3];
this._post_type = id_bits[2];
this._is_truncated = askbot['data'][widget_id]['truncated'];
this._user_can_post = askbot['data'][widget_id]['can_post'];
//see if user can comment here
var controls = element.find('.controls');
this._activate_button = controls.find('.button');
if (this._user_can_post == false){
setupButtonEventHandlers(
this._activate_button,
this.getReadOnlyLoadHandler()
);
}
else {
setupButtonEventHandlers(
this._activate_button,
this.getActivateHandler()
);
}
this._cbox = element.find('.content');
var comments = new Array();
var me = this;
this._cbox.children('.comment').each(function(index, element){
var comment = new Comment(me);
comments.push(comment)
comment.decorate(element);
});
this._comments = comments;
};
PostCommentsWidget.prototype.handleDeletedComment = function() {
/* if the widget does not have any comments, set
the 'empty' class on the widget element */
if (this._cbox.children('.comment').length === 0) {
this._element.siblings('.comment-title').hide();
this._element.addClass('empty');
}
};
PostCommentsWidget.prototype.getPostType = function(){
return this._post_type;
};
PostCommentsWidget.prototype.getPostId = function(){
return this._post_id;
};
PostCommentsWidget.prototype.hideButton = function(){
this._activate_button.hide();
};
PostCommentsWidget.prototype.showButton = function(){
if (this._is_truncated === false){
this._activate_button.html(askbot['messages']['addComment']);
}
this._activate_button.show();
}
PostCommentsWidget.prototype.startNewComment = function(){
var opts = {
'is_deletable': true,
'is_editable': true
};
var comment = new Comment(this, opts);
this._cbox.append(comment.getElement());
this._element.removeClass('empty');
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 (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 (me.needToReload()){
me.reloadAllComments(function(json){
me.reRenderComments(json);
//2) change button text to "post a comment"
button.html(askbot['messages']['addComment']);
});
}
else {
//if user can't post, we tell him something and refuse
if (askbot['data']['userIsAuthenticated']) {
me.startNewComment();
} else {
var message = gettext('please sign in or register to post comments');
showMessage(button, message, 'after');
}
}
};
};
PostCommentsWidget.prototype.getReadOnlyLoadHandler = function(){
var me = this;
return function(){
me.reloadAllComments(function(json){
me.reRenderComments(json);
me._activate_button.remove();
});
};
};
PostCommentsWidget.prototype.reloadAllComments = function(callback){
var post_data = {post_id: this._post_id, post_type: this._post_type};
var me = this;
$.ajax({
type: "GET",
url: askbot['urls']['postComments'],
data: post_data,
success: function(json){
callback(json);
me._is_truncated = false;
},
dataType: "json"
});
};
PostCommentsWidget.prototype.reRenderComments = function(json){
$.each(this._comments, function(i, item){
item.dispose();
});
this._comments = new Array();
var me = this;
$.each(json, function(i, item){
var comment = new Comment(me, item);
me._cbox.append(comment.getElement());
me._comments.push(comment);
});
};
var socialSharing = function(){
var SERVICE_DATA = {
//url - template for the sharing service url, params are for the popup
identica: {
url: "http://identi.ca/notice/new?status_textarea={TEXT}%20{URL}",
params: "width=820, height=526,toolbar=1,status=1,resizable=1,scrollbars=1"
},
twitter: {
url: "http://twitter.com/share?url={URL}&ref=twitbtn&text={TEXT}",
params: "width=820,height=526,toolbar=1,status=1,resizable=1,scrollbars=1"
},
facebook: {
url: "http://www.facebook.com/sharer.php?u={URL}&ref=fbshare&t={TEXT}",
params: "width=630,height=436,toolbar=1,status=1,resizable=1,scrollbars=1"
},
linkedin: {
url: "http://www.linkedin.com/shareArticle?mini=true&url={URL}&title={TEXT}",
params: "width=630,height=436,toolbar=1,status=1,resizable=1,scrollbars=1"
}
};
var URL = "";
var TEXT = "";
var share_page = function(service_name){
if (SERVICE_DATA[service_name]){
var url = SERVICE_DATA[service_name]['url'];
url = url.replace('{TEXT}', TEXT);
url = url.replace('{URL}', URL);
var params = SERVICE_DATA[service_name]['params'];
if(!window.open(url, "sharing", params)){
window.location.href=url;
}
return false;
//@todo: change to some other url shortening service
$.ajax({
url: "http://json-tinyurl.appspot.com/?&callback=?",
dataType: "json",
data: {'url':URL},
success: function(data) {
url = url.replace('{URL}', data.tinyurl);
},
error: function(xhr, opts, error) {
url = url.replace('{URL}', URL);
},
complete: function(data) {
url = url.replace('{TEXT}', TEXT);
var params = SERVICE_DATA[service_name]['params'];
if(!window.open(url, "sharing", params)){
window.location.href=url;
}
}
});
}
}
return {
init: function(){
URL = window.location.href;
var urlBits = URL.split('/');
URL = urlBits.slice(0, -2).join('/') + '/';
TEXT = encodeURIComponent($('h1 > a').html());
var hashtag = encodeURIComponent(
askbot['settings']['sharingSuffixText']
);
TEXT = TEXT.substr(0, 134 - URL.length - hashtag.length);
TEXT = TEXT + '... ' + hashtag;
var fb = $('a.facebook-share')
var tw = $('a.twitter-share');
var ln = $('a.linkedin-share');
var ica = $('a.identica-share');
copyAltToTitle(fb);
copyAltToTitle(tw);
copyAltToTitle(ln);
copyAltToTitle(ica);
setupButtonEventHandlers(fb, function(){ share_page("facebook") });
setupButtonEventHandlers(tw, function(){ share_page("twitter") });
setupButtonEventHandlers(ln, function(){ share_page("linkedin") });
setupButtonEventHandlers(ica, function(){ share_page("identica") });
}
}
}();
/**
* @constructor
* @extends {SimpleControl}
*/
var QASwapper = function(){
SimpleControl.call(this);
this._ans_id = null;
};
inherits(QASwapper, SimpleControl);
QASwapper.prototype.decorate = function(element){
this._element = element;
this._ans_id = parseInt(element.attr('id').split('-').pop());
var me = this;
this.setHandler(function(){
me.startSwapping();
});
};
QASwapper.prototype.startSwapping = function(){
while (true){
var title = prompt(gettext('Please enter question title (>10 characters)'));
if (title.length >= 10){
var data = {new_title: title, answer_id: this._ans_id};
$.ajax({
type: "POST",
cache: false,
dataType: "json",
url: askbot['urls']['swap_question_with_answer'],
data: data,
success: function(data){
window.location.href = data['question_url'];
}
});
break;
}
}
};
/**
* @constructor
* An element that encloses an editor and everything inside it.
* By default editor is hidden and user sees a box with a prompt
* suggesting to make a post.
* When user clicks, editor becomes accessible.
*/
var FoldedEditor = function() {
WrappedElement.call(this);
};
inherits(FoldedEditor, WrappedElement);
FoldedEditor.prototype.getEditor = function() {
return this._editor;
};
FoldedEditor.prototype.getEditorInputId = function() {
return this._element.find('textarea').attr('id');
};
FoldedEditor.prototype.onAfterOpenHandler = function() {
var editor = this.getEditor();
if (editor) {
setTimeout(function() {editor.focus()}, 500);
}
};
FoldedEditor.prototype.getOpenHandler = function() {
var editorBox = this._editorBox;
var promptBox = this._prompt;
var externalTrigger = this._externalTrigger;
var me = this;
return function() {
promptBox.hide();
editorBox.show();
var element = me.getElement();
element.addClass('unfolded');
/* make the editor one shot - once it unfolds it's
* not accepting any events
*/
element.unbind('click');
element.unbind('focus');
/* this function will open the editor
* and focus cursor on the editor
*/
me.onAfterOpenHandler();
/* external trigger is a clickable target
* placed outside of the this._element
* that will cause the editor to unfold
*/
if (externalTrigger) {
var label = me.makeElement('label');
label.html(externalTrigger.html());
//set what the label is for
label.attr('for', me.getEditorInputId());
externalTrigger.replaceWith(label);
}
};
};
FoldedEditor.prototype.setExternalTrigger = function(element) {
this._externalTrigger = element;
};
FoldedEditor.prototype.decorate = function(element) {
this._element = element;
this._prompt = element.find('.prompt');
this._editorBox = element.find('.editor-proper');
var editorType = askbot['settings']['editorType'];
if (editorType === 'tinymce') {
var editor = new TinyMCE();
editor.decorate(element.find('textarea'));
this._editor = editor;
} else if (editorType === 'markdown') {
var editor = new WMD();
editor.decorate(element);
this._editor = editor;
}
var openHandler = this.getOpenHandler();
element.click(openHandler);
element.focus(openHandler);
if (this._externalTrigger) {
this._externalTrigger.click(openHandler);
}
};
/**
* @constructor
* a simple textarea-based editor
*/
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(onFocus) {
this._textarea.focus();
if (onFocus) {
onFocus();
}
};
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 '';
};
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, SimpleEditor);
//@todo: implement getHtml method that runs text through showdown renderer
WMD.prototype.setEnabledButtons = function(buttons){
this._enabled_buttons = buttons;
};
WMD.prototype.setPreviewerEnabled = function(state){
this._is_previewer_enabled = state;
if (this._previewer){
this._previewer.hide();
}
};
WMD.prototype.createDom = function(){
this._element = this.makeElement('div');
var clearfix = this.makeElement('div').addClass('clearfix');
this._element.append(clearfix);
var wmd_container = this.makeElement('div');
wmd_container.addClass('wmd-container');
this._element.append(wmd_container);
var wmd_buttons = this.makeElement('div')
.attr('id', this.makeId('wmd-button-bar'))
.addClass('wmd-panel');
wmd_container.append(wmd_buttons);
var editor = this.makeElement('textarea')
.attr('id', this.makeId('editor'));
wmd_container.append(editor);
this._textarea = editor;
if (this._text){
editor.val(this._text);
}
var previewer = this.makeElement('div')
.attr('id', this.makeId('previewer'))
.addClass('wmd-preview');
wmd_container.append(previewer);
this._previewer = previewer;
if (this._is_previewer_enabled === false) {
previewer.hide();
}
};
WMD.prototype.decorate = function(element) {
this._element = element;
this._textarea = element.find('textarea');
this._previewer = element.find('.wmd-preview');
};
WMD.prototype.start = function(){
Attacklab.Util.startEditor(true, this._enabled_buttons, this.getIdSeed());
};
/**
* @constructor
*/
var TinyMCE = function(config) {
WrappedElement.call(this);
this._config = config || {};
this._id = 'editor';//desired id of the textarea
};
inherits(TinyMCE, WrappedElement);
/*
* not passed onto prototoype on purpose!!!
*/
TinyMCE.onInitHook = function() {
//set initial content
var ed = tinyMCE.activeEditor;
ed.setContent(askbot['data']['editorContent'] || '');
//if we have spellchecker - enable it by default
if (inArray('spellchecker', askbot['settings']['tinyMCEPlugins'])) {
setTimeout(function() {
ed.controlManager.setActive('spellchecker', true);
tinyMCE.execCommand('mceSpellCheck', true);
}, 1);
}
};
/* 3 dummy functions to match WMD api */
TinyMCE.prototype.setEnabledButtons = function() {};
TinyMCE.prototype.start = function() {
//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() {
var ed = tinyMCE.activeEditor;
//add an empty span with a unique id
var endId = tinymce.DOM.uniqueId();
ed.dom.add(ed.getBody(), 'span', {'id': endId}, '');
//select that span
var newNode = ed.dom.select('span#' + endId);
ed.selection.select(newNode[0]);
};
TinyMCE.prototype.focus = function(onFocus) {
var editorId = this._id;
var winH = $(window).height();
var winY = $(window).scrollTop();
var edY = this._element.offset().top;
var edH = this._element.height();
//@todo: the fallacy of this method is timeout - should instead use queue
//because at the time of calling focus() the editor may not be initialized yet
setTimeout(
function() {
tinyMCE.execCommand('mceFocus', false, editorId);
//@todo: make this general to all editors
//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);
}
if (onFocus) {
onFocus();
}
},
100
);
};
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.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);
};
TinyMCE.prototype.decorate = function(element) {
this._element = element;
this._id = element.attr('id');
};
/**
* @constructor
* @todo: change this to generic object description editor
*/
var TagWikiEditor = function(){
WrappedElement.call(this);
this._state = 'display';//'edit' or 'display'
this._content_backup = '';
this._is_editor_loaded = false;
this._enabled_editor_buttons = null;
this._is_previewer_enabled = false;
};
inherits(TagWikiEditor, WrappedElement);
TagWikiEditor.prototype.backupContent = function(){
this._content_backup = this._content_box.contents();
};
TagWikiEditor.prototype.setEnabledEditorButtons = function(buttons){
this._enabled_editor_buttons = buttons;
};
TagWikiEditor.prototype.setPreviewerEnabled = function(state){
this._is_previewer_enabled = state;
if (this.isEditorLoaded()){
this._editor.setPreviewerEnabled(this._is_previewer_enabled);
}
};
TagWikiEditor.prototype.setContent = function(content){
this._content_box.empty();
this._content_box.append(content);
};
TagWikiEditor.prototype.setState = function(state){
if (state === 'edit'){
this._state = state;
this._edit_btn.hide();
this._cancel_btn.show();
this._save_btn.show();
this._cancel_sep.show();
} else if (state === 'display'){
this._state = state;
this._edit_btn.show();
this._cancel_btn.hide();
this._cancel_sep.hide();
this._save_btn.hide();
}
};
TagWikiEditor.prototype.restoreContent = function(){
var content_box = this._content_box;
content_box.empty();
$.each(this._content_backup, function(idx, element){
content_box.append(element);
});
};
TagWikiEditor.prototype.getTagId = function(){
return this._tag_id;
};
TagWikiEditor.prototype.isEditorLoaded = function(){
return this._is_editor_loaded;
};
TagWikiEditor.prototype.setEditorLoaded = function(){
return this._is_editor_loaded = true;
};
/**
* loads initial data for the editor input and activates
* the editor
*/
TagWikiEditor.prototype.startActivatingEditor = function(){
var editor = this._editor;
var me = this;
var data = {
object_id: me.getTagId(),
model_name: 'Group'
};
$.ajax({
type: 'GET',
url: askbot['urls']['load_object_description'],
data: data,
cache: false,
success: function(data){
me.backupContent();
editor.setText(data);
me.setContent(editor.getElement());
me.setState('edit');
if (me.isEditorLoaded() === false){
editor.start();
me.setEditorLoaded();
}
}
});
};
TagWikiEditor.prototype.saveData = function(){
var me = this;
var text = this._editor.getText();
var data = {
object_id: me.getTagId(),
model_name: 'Group',//todo: fixme
text: text
};
$.ajax({
type: 'POST',
dataType: 'json',
url: askbot['urls']['save_object_description'],
data: data,
cache: false,
success: function(data){
if (data['success']){
me.setState('display');
me.setContent(data['html']);
} else {
showMessage(me.getElement(), data['message']);
}
}
});
};
TagWikiEditor.prototype.cancelEdit = function(){
this.restoreContent();
this.setState('display');
};
TagWikiEditor.prototype.decorate = function(element){
//expect
this._element = element;
var edit_btn = element.find('.edit-description');
this._edit_btn = edit_btn;
//adding two buttons...
var save_btn = this.makeElement('a');
save_btn.html(gettext('save'));
edit_btn.after(save_btn);
save_btn.hide();
this._save_btn = save_btn;
var cancel_btn = this.makeElement('a');
cancel_btn.html(gettext('cancel'));
save_btn.after(cancel_btn);
cancel_btn.hide();
this._cancel_btn = cancel_btn;
this._cancel_sep = $(' | ');
cancel_btn.before(this._cancel_sep);
this._cancel_sep.hide();
this._content_box = element.find('.content');
this._tag_id = element.attr('id').split('-').pop();
var me = this;
if (askbot['settings']['editorType'] === 'markdown') {
var editor = new WMD();
} else {
var editor = new TinyMCE({//override defaults
theme_advanced_buttons1: 'bold, italic, |, link, |, numlist, bullist',
theme_advanced_buttons2: '',
theme_advanced_path: false,
plugins: ''
});
}
if (this._enabled_editor_buttons){
editor.setEnabledButtons(this._enabled_editor_buttons);
}
editor.setPreviewerEnabled(this._is_previewer_enabled);
this._editor = editor;
setupButtonEventHandlers(edit_btn, function(){ me.startActivatingEditor() });
setupButtonEventHandlers(cancel_btn, function(){me.cancelEdit()});
setupButtonEventHandlers(save_btn, function(){me.saveData()});
};
var ImageChanger = function(){
WrappedElement.call(this);
this._image_element = undefined;
this._delete_button = undefined;
this._save_url = undefined;
this._delete_url = undefined;
this._messages = undefined;
};
inherits(ImageChanger, WrappedElement);
ImageChanger.prototype.setImageElement = function(image_element){
this._image_element = image_element;
};
ImageChanger.prototype.setMessages = function(messages){
this._messages = messages;
};
ImageChanger.prototype.setDeleteButton = function(delete_button){
this._delete_button = delete_button;
};
ImageChanger.prototype.setSaveUrl = function(url){
this._save_url = url;
};
ImageChanger.prototype.setDeleteUrl = function(url){
this._delete_url = url;
};
ImageChanger.prototype.setAjaxData = function(data){
this._ajax_data = data;
};
ImageChanger.prototype.showImage = function(image_url){
this._image_element.attr('src', image_url);
this._image_element.show();
};
ImageChanger.prototype.deleteImage = function(){
this._image_element.attr('src', '');
this._image_element.css('display', 'none');
var me = this;
var delete_url = this._delete_url;
var data = this._ajax_data;
$.ajax({
type: 'POST',
dataType: 'json',
url: delete_url,
data: data,
cache: false,
success: function(data){
if (data['success'] === true){
showMessage(me.getElement(), data['message'], 'after');
}
}
});
};
ImageChanger.prototype.saveImageUrl = function(image_url){
var me = this;
var data = this._ajax_data;
data['image_url'] = image_url;
var save_url = this._save_url;
$.ajax({
type: 'POST',
dataType: 'json',
url: save_url,
data: data,
cache: false,
success: function(data){
if (!data['success']){
showMessage(me.getElement(), data['message'], 'after');
}
}
});
};
ImageChanger.prototype.startDialog = function(){
//reusing the wmd's file uploader
var me = this;
var change_image_text = this._messages['change_image'];
var change_image_button = this._element;
Attacklab.Util.prompt(
"" + gettext('Enter the logo url or upload an image') + '
',
'http://',
function(image_url){
if (image_url){
me.saveImageUrl(image_url);
me.showImage(image_url);
change_image_button.html(change_image_text);
me.showDeleteButton();
}
},
'image'
);
};
ImageChanger.prototype.showDeleteButton = function(){
this._delete_button.show();
this._delete_button.prev().show();
};
ImageChanger.prototype.hideDeleteButton = function(){
this._delete_button.hide();
this._delete_button.prev().hide();
};
ImageChanger.prototype.startDeleting = function(){
if (confirm(gettext('Do you really want to remove the image?'))){
this.deleteImage();
this._element.html(this._messages['add_image']);
this.hideDeleteButton();
this._delete_button.hide();
var sep = this._delete_button.prev();
sep.hide();
};
};
/**
* decorates an element that will serve as the image changer button
*/
ImageChanger.prototype.decorate = function(element){
this._element = element;
var me = this;
setupButtonEventHandlers(
element,
function(){
me.startDialog();
}
);
setupButtonEventHandlers(
this._delete_button,
function(){
me.startDeleting();
}
)
};
var UserGroupProfileEditor = function(){
TagWikiEditor.call(this);
};
inherits(UserGroupProfileEditor, TagWikiEditor);
UserGroupProfileEditor.prototype.toggleEmailModeration = function(){
var btn = this._moderate_email_btn;
var group_id = this.getTagId();
$.ajax({
type: 'POST',
dataType: 'json',
cache: false,
data: {group_id: group_id},
url: askbot['urls']['toggle_group_email_moderation'],
success: function(data){
if (data['success']){
btn.html(data['new_button_text']);
} else {
showMessage(btn, data['message']);
}
}
});
};
UserGroupProfileEditor.prototype.decorate = function(element){
this.setEnabledEditorButtons('bold italic link ol ul');
this.setPreviewerEnabled(false);
UserGroupProfileEditor.superClass_.decorate.call(this, element);
var change_logo_btn = element.find('.change-logo');
this._change_logo_btn = change_logo_btn;
var moderate_email_toggle = new TwoStateToggle();
moderate_email_toggle.setPostData({
group_id: this.getTagId(),
property_name: 'moderate_email'
});
var moderate_email_btn = element.find('#moderate-email');
this._moderate_email_btn = moderate_email_btn;
moderate_email_toggle.decorate(moderate_email_btn);
var moderate_publishing_replies_toggle = new TwoStateToggle();
moderate_publishing_replies_toggle.setPostData({
group_id: this.getTagId(),
property_name: 'moderate_answers_to_enquirers'
});
var btn = element.find('#moderate-answers-to-enquirers');
moderate_publishing_replies_toggle.decorate(btn);
var vip_toggle = new TwoStateToggle();
vip_toggle.setPostData({
group_id: this.getTagId(),
property_name: 'is_vip'
});
var btn = element.find('#vip-toggle');
vip_toggle.decorate(btn);
var opennessSelector = new DropdownSelect();
var selectorElement = element.find('#group-openness-selector');
opennessSelector.setPostData({
group_id: this.getTagId(),
property_name: 'openness'
});
opennessSelector.decorate(selectorElement);
var email_editor = new TextPropertyEditor();
email_editor.decorate(element.find('#preapproved-emails'));
var domain_editor = new TextPropertyEditor();
domain_editor.decorate(element.find('#preapproved-email-domains'));
var logo_changer = new ImageChanger();
logo_changer.setImageElement(element.find('.group-logo'));
logo_changer.setAjaxData({
group_id: this.getTagId()
});
logo_changer.setSaveUrl(askbot['urls']['save_group_logo_url']);
logo_changer.setDeleteUrl(askbot['urls']['delete_group_logo_url']);
logo_changer.setMessages({
change_image: gettext('change logo'),
add_image: gettext('add logo')
});
var delete_logo_btn = element.find('.delete-logo');
logo_changer.setDeleteButton(delete_logo_btn);
logo_changer.decorate(change_logo_btn);
};
var GroupJoinButton = function(){
TwoStateToggle.call(this);
};
inherits(GroupJoinButton, TwoStateToggle);
GroupJoinButton.prototype.getPostData = function(){
return { group_id: this._group_id };
};
GroupJoinButton.prototype.getHandler = function(){
var me = this;
return function(){
$.ajax({
type: 'POST',
dataType: 'json',
cache: false,
data: me.getPostData(),
url: askbot['urls']['join_or_leave_group'],
success: function(data){
if (data['success']){
var level = data['membership_level'];
var new_state = 'off-state';
if (level == 'full' || level == 'pending') {
new_state = 'on-state';
}
me.setState(new_state);
} else {
showMessage(me.getElement(), data['message']);
}
}
});
};
};
GroupJoinButton.prototype.decorate = function(elem) {
GroupJoinButton.superClass_.decorate.call(this, elem);
this._group_id = this._element.data('groupId');
};
var TagEditor = function() {
WrappedElement.call(this);
this._has_hot_backspace = false;
this._settings = JSON.parse(askbot['settings']['tag_editor']);
};
inherits(TagEditor, WrappedElement);
TagEditor.prototype.getSelectedTags = function() {
return $.trim(this._hidden_tags_input.val()).split(/\s+/);
};
TagEditor.prototype.setSelectedTags = function(tag_names) {
this._hidden_tags_input.val($.trim(tag_names.join(' ')));
};
TagEditor.prototype.addSelectedTag = function(tag_name) {
var tag_names = this._hidden_tags_input.val();
this._hidden_tags_input.val(tag_names + ' ' + tag_name);
$('.acResults').hide();//a hack to hide the autocompleter
};
TagEditor.prototype.isSelectedTagName = function(tag_name) {
var tag_names = this.getSelectedTags();
return $.inArray(tag_name, tag_names) != -1;
};
TagEditor.prototype.removeSelectedTag = function(tag_name) {
var tag_names = this.getSelectedTags();
var idx = $.inArray(tag_name, tag_names);
if (idx !== -1) {
tag_names.splice(idx, 1)
}
this.setSelectedTags(tag_names);
};
TagEditor.prototype.getTagDeleteHandler = function(tag){
var me = this;
return function(){
me.removeSelectedTag(tag.getName());
me.clearErrorMessage();
tag.dispose();
$('.acResults').hide();//a hack to hide the autocompleter
me.fixHeight();
};
};
TagEditor.prototype.cleanTag = function(tag_name, reject_dupe) {
tag_name = $.trim(tag_name);
tag_name = tag_name.replace(/\s+/, ' ');
var force_lowercase = this._settings['force_lowercase_tags'];
if (force_lowercase) {
tag_name = tag_name.toLowerCase();
}
if (reject_dupe && this.isSelectedTagName(tag_name)) {
throw interpolate(
gettext('tag "%s" was already added, no need to repeat (press "escape" to delete)'),
[tag_name]
);
}
var max_tags = this._settings['max_tags_per_post'];
if (this.getSelectedTags().length + 1 > max_tags) {//count current
throw interpolate(
ngettext(
'a maximum of %s tag is allowed',
'a maximum of %s tags are allowed',
max_tags
),
[max_tags]
);
}
//generic cleaning
return cleanTag(tag_name, this._settings);
};
TagEditor.prototype.addTag = function(tag_name) {
var tag = new Tag();
tag.setName(tag_name);
tag.setDeletable(true);
tag.setLinkable(true);
tag.setDeleteHandler(this.getTagDeleteHandler(tag));
this._tags_container.append(tag.getElement());
this.addSelectedTag(tag_name);
};
TagEditor.prototype.immediateClearErrorMessage = function() {
this._error_alert.html('');
this._error_alert.show();
//this._element.css('margin-top', '18px');//todo: the margin thing is a hack
}
TagEditor.prototype.clearErrorMessage = function(fade) {
if (fade) {
var me = this;
this._error_alert.fadeOut(function(){
me.immediateClearErrorMessage();
});
} else {
this.immediateClearErrorMessage();
}
};
TagEditor.prototype.setErrorMessage = function(text) {
var old_text = this._error_alert.html();
this._error_alert.html(text);
if (old_text == '') {
this._error_alert.hide();
this._error_alert.fadeIn(100);
}
//this._element.css('margin-top', '0');//todo: remove this hack
};
TagEditor.prototype.getAddTagHandler = function() {
var me = this;
return function(tag_name) {
if (me.isSelectedTagName(tag_name)) {
return;
}
try {
var clean_tag_name = me.cleanTag($.trim(tag_name));
me.addTag(clean_tag_name);
me.clearNewTagInput();
me.fixHeight();
} catch (error) {
me.setErrorMessage(error);
setTimeout(function(){
me.clearErrorMessage(true);
}, 1000);
}
};
};
TagEditor.prototype.getRawNewTagValue = function() {
return this._visible_tags_input.val();//without trimming
};
TagEditor.prototype.clearNewTagInput = function() {
return this._visible_tags_input.val('');
};
TagEditor.prototype.editLastTag = function() {
//delete rendered tag
var tc = this._tags_container;
tc.find('li:last').remove();
//remove from hidden tags input
var tags = this.getSelectedTags();
var last_tag = tags.pop();
this.setSelectedTags(tags);
//populate the tag editor
this._visible_tags_input.val(last_tag);
putCursorAtEnd(this._visible_tags_input);
};
TagEditor.prototype.setHotBackspace = function(is_hot) {
this._has_hot_backspace = is_hot;
};
TagEditor.prototype.hasHotBackspace = function() {
return this._has_hot_backspace;
};
TagEditor.prototype.completeTagInput = function(reject_dupe) {
var tag_name = $.trim(this._visible_tags_input.val());
try {
tag_name = this.cleanTag(tag_name, reject_dupe);
this.addTag(tag_name);
this.clearNewTagInput();
} catch (error) {
this.setErrorMessage(error);
}
};
TagEditor.prototype.saveHeight = function() {
return;
var elem = this._visible_tags_input;
this._height = elem.offset().top;
};
TagEditor.prototype.fixHeight = function() {
return;
var new_height = this._visible_tags_input.offset().top;
//@todo: replace this by real measurement
var element_height = parseInt(
this._element.css('height').replace('px', '')
);
if (new_height > this._height) {
this._element.css('height', element_height + 28);//magic number!!!
} else if (new_height < this._height) {
this._element.css('height', element_height - 28);//magic number!!!
}
this.saveHeight();
};
TagEditor.prototype.closeAutoCompleter = function() {
this._autocompleter.finish();
};
TagEditor.prototype.getTagInputKeyHandler = function() {
var new_tags = this._visible_tags_input;
var me = this;
return function(e) {
if (e.shiftKey) {
return;
}
me.saveHeight();
var key = e.which || e.keyCode;
var text = me.getRawNewTagValue();
//space 32, enter 13
if (key == 32 || key == 13) {
var tag_name = $.trim(text);
if (tag_name.length > 0) {
me.completeTagInput(true);//true for reject dupes
}
me.fixHeight();
return false;
}
if (text == '') {
me.clearErrorMessage();
me.closeAutoCompleter();
} else {
try {
/* do-nothing validation here
* just to report any errors while
* the user is typing */
me.cleanTag(text);
me.clearErrorMessage();
} catch (error) {
me.setErrorMessage(error);
}
}
//8 is backspace
if (key == 8 && text.length == 0) {
if (me.hasHotBackspace() === true) {
me.editLastTag();
me.setHotBackspace(false);
} else {
me.setHotBackspace(true);
}
}
//27 is escape
if (key == 27) {
me.clearNewTagInput();
me.clearErrorMessage();
}
if (key !== 8) {
me.setHotBackspace(false);
}
me.fixHeight();
return false;
};
}
TagEditor.prototype.decorate = function(element) {
this._element = element;
this._hidden_tags_input = element.find('input[name="tags"]');//this one is hidden
this._tags_container = element.find('ul.tags');
this._error_alert = $('.tag-editor-error-alert > span');
var me = this;
this._tags_container.children().each(function(idx, elem){
var tag = new Tag();
tag.setDeletable(true);
tag.setLinkable(false);
tag.decorate($(elem));
tag.setDeleteHandler(me.getTagDeleteHandler(tag));
});
var visible_tags_input = element.find('.new-tags-input');
this._visible_tags_input = visible_tags_input;
this.saveHeight();
var me = this;
var tagsAc = new AutoCompleter({
url: askbot['urls']['get_tag_list'],
onItemSelect: function(item){
if (me.isSelectedTagName(item['value']) === false) {
me.completeTagInput();
} else {
me.clearNewTagInput();
}
},
minChars: 1,
useCache: true,
matchInside: true,
maxCacheLength: 100,
delay: 10
});
tagsAc.decorate(visible_tags_input);
this._autocompleter = tagsAc;
visible_tags_input.keyup(this.getTagInputKeyHandler());
element.click(function(e) {
visible_tags_input.focus();
return false;
});
};
/**
* @constructor
* Category is a select box item
* that has CategoryEditControls
*/
var Category = function() {
SelectBoxItem.call(this);
this._state = 'display';
this._settings = JSON.parse(askbot['settings']['tag_editor']);
};
inherits(Category, SelectBoxItem);
Category.prototype.setCategoryTree = function(tree) {
this._tree = tree;
};
Category.prototype.getCategoryTree = function() {
return this._tree;
};
Category.prototype.getName = function() {
return this.getContent().getContent();
};
Category.prototype.getPath = function() {
return this._tree.getPathToItem(this);
};
Category.prototype.setState = function(state) {
this._state = state;
if ( !this._element ) {
return;
}
this._input_box.val('');
if (state === 'display') {
this.showContent();
this.hideEditor();
this.hideEditControls();
} else if (state === 'editable') {
this._tree._state = 'editable';//a hack
this.showContent();
this.hideEditor();
this.showEditControls();
} else if (state === 'editing') {
this._prev_tree_state = this._tree.getState();
this._tree._state = 'editing';//a hack
this._input_box.val(this.getName());
this.hideContent();
this.showEditor();
this.hideEditControls();
}
};
Category.prototype.hideEditControls = function() {
this._delete_button.hide();
this._edit_button.hide();
this._element.unbind('mouseenter mouseleave');
};
Category.prototype.showEditControls = function() {
var del = this._delete_button;
var edit = this._edit_button;
this._element.hover(
function(){
del.show();
edit.show();
},
function(){
del.hide();
edit.hide();
}
);
};
Category.prototype.showEditControlsNow = function() {
this._delete_button.show();
this._edit_button.show();
};
Category.prototype.hideContent = function() {
this.getContent().getElement().hide();
};
Category.prototype.showContent = function() {
this.getContent().getElement().show();
};
Category.prototype.showEditor = function() {
this._input_box.show();
this._input_box.focus();
this._save_button.show();
this._cancel_button.show();
};
Category.prototype.hideEditor = function() {
this._input_box.hide();
this._save_button.hide();
this._cancel_button.hide();
};
Category.prototype.getInput = function() {
return this._input_box.val();
};
Category.prototype.getDeleteHandler = function() {
var me = this;
return function() {
if (confirm(gettext('Delete category?'))) {
var tree = me.getCategoryTree();
$.ajax({
type: 'POST',
dataType: 'json',
data: JSON.stringify({
tag_name: me.getName(),
path: me.getPath()
}),
cache: false,
url: askbot['urls']['delete_tag'],
success: function(data) {
if (data['success']) {
//rebuild category tree based on data
tree.setData(data['tree_data']);
//re-open current branch
tree.selectPath(tree.getCurrentPath());
tree.setState('editable');
} else {
alert(data['message']);
}
}
});
}
return false;
};
};
Category.prototype.getSaveHandler = function() {
var me = this;
var settings = this._settings;
//here we need old value and new value
return function(){
var to_name = me.getInput();
try {
to_name = cleanTag(to_name, settings);
var data = {
from_name: me.getOriginalName(),
to_name: to_name,
path: me.getPath()
};
$.ajax({
type: 'POST',
dataType: 'json',
data: JSON.stringify(data),
cache: false,
url: askbot['urls']['rename_tag'],
success: function(data) {
if (data['success']) {
me.setName(to_name);
me.setState('editable');
me.showEditControlsNow();
} else {
alert(data['message']);
}
}
});
} catch (error) {
alert(error);
}
return false;
};
};
Category.prototype.addControls = function() {
var input_box = this.makeElement('input');
this._input_box = input_box;
this._element.append(input_box);
var save_button = this.makeButton(
gettext('save'),
this.getSaveHandler()
);
this._save_button = save_button;
this._element.append(save_button);
var me = this;
var cancel_button = this.makeButton(
'x',
function(){
me.setState('editable');
me.showEditControlsNow();
return false;
}
);
this._cancel_button = cancel_button;
this._element.append(cancel_button);
var edit_button = this.makeButton(
gettext('edit'),
function(){
//todo: I would like to make only one at a time editable
//var tree = me.getCategoryTree();
//tree.closeAllEditors();
//tree.setState('editable');
//calc path, then select it
var tree = me.getCategoryTree();
tree.selectPath(me.getPath());
me.setState('editing');
return false;
}
);
this._edit_button = edit_button;
this._element.append(edit_button);
var delete_button = this.makeButton(
'x', this.getDeleteHandler()
);
this._delete_button = delete_button;
this._element.append(delete_button);
};
Category.prototype.getOriginalName = function() {
return this._original_name;
};
Category.prototype.createDom = function() {
Category.superClass_.createDom.call(this);
this.addControls();
this.setState('display');
this._original_name = this.getName();
};
Category.prototype.decorate = function(element) {
Category.superClass_.decorate.call(this, element);
this.addControls();
this.setState('display');
this._original_name = this.getName();
};
var CategoryAdder = function() {
WrappedElement.call(this);
this._state = 'disabled';//waitedit
this._tree = undefined;//category tree
this._settings = JSON.parse(askbot['settings']['tag_editor']);
};
inherits(CategoryAdder, WrappedElement);
CategoryAdder.prototype.setCategoryTree = function(tree) {
this._tree = tree;
};
CategoryAdder.prototype.setLevel = function(level) {
this._level = level;
};
CategoryAdder.prototype.setState = function(state) {
this._state = state;
if (!this._element) {
return;
}
if (state === 'waiting') {
this._element.show();
this._input.val('');
this._input.hide();
this._save_button.hide();
this._cancel_button.hide();
this._trigger.show();
} else if (state === 'editable') {
this._element.show();
this._input.show();
this._input.val('');
this._input.focus();
this._save_button.show();
this._cancel_button.show();
this._trigger.hide();
} else if (state === 'disabled') {
this.setState('waiting');//a little hack
this._state = 'disabled';
this._element.hide();
}
};
CategoryAdder.prototype.cleanCategoryName = function(name) {
name = $.trim(name);
if (name === '') {
throw gettext('category name cannot be empty');
}
//if ( this._tree.hasCategory(name) ) {
//throw interpolate(
//throw gettext('this category already exists');
// [this._tree.getDisplayPathByName(name)]
//)
//}
return cleanTag(name, this._settings);
};
CategoryAdder.prototype.getPath = function() {
var path = this._tree.getCurrentPath();
if (path.length > this._level + 1) {
return path.slice(0, this._level + 1);
} else {
return path;
}
};
CategoryAdder.prototype.getSelectBox = function() {
return this._tree.getSelectBox(this._level);
};
CategoryAdder.prototype.startAdding = function() {
try {
var name = this._input.val();
name = this.cleanCategoryName(name);
} catch (error) {
alert(error);
return;
}
//don't add dupes to the same level
var existing_names = this.getSelectBox().getNames();
if ($.inArray(name, existing_names) != -1) {
alert(gettext('already exists at the current level!'));
return;
}
var me = this;
var tree = this._tree;
var adder_path = this.getPath();
$.ajax({
type: 'POST',
dataType: 'json',
data: JSON.stringify({
path: adder_path,
new_category_name: name
}),
url: askbot['urls']['add_tag_category'],
cache: false,
success: function(data) {
if (data['success']) {
//rebuild category tree based on data
tree.setData(data['tree_data']);
tree.selectPath(data['new_path']);
tree.setState('editable');
me.setState('waiting');
} else {
alert(data['message']);
}
}
});
};
CategoryAdder.prototype.createDom = function() {
this._element = this.makeElement('li');
//add open adder link
var trigger = this.makeElement('a');
this._trigger = trigger;
trigger.html(gettext('add category'));
this._element.append(trigger);
//add input box and the add button
var input = this.makeElement('input');
this._input = input;
input.addClass('add-category');
input.attr('name', 'add_category');
this._element.append(input);
//add save category button
var save_button = this.makeElement('button');
this._save_button = save_button;
save_button.html(gettext('save'));
this._element.append(save_button);
var cancel_button = this.makeElement('button');
this._cancel_button = cancel_button;
cancel_button.html('x');
this._element.append(cancel_button);
this.setState(this._state);
var me = this;
setupButtonEventHandlers(
trigger,
function(){ me.setState('editable'); }
)
setupButtonEventHandlers(
save_button,
function() {
me.startAdding();
return false;//prevent form submit
}
);
setupButtonEventHandlers(
cancel_button,
function() {
me.setState('waiting');
return false;//prevent submit
}
);
//create input box, button and the "activate" link
};
/**
* @constructor
* SelectBox subclass to create/edit/delete
* categories
*/
var CategorySelectBox = function() {
SelectBox.call(this);
this._item_class = Category;
this._category_adder = undefined;
this._tree = undefined;//cat tree
this._level = undefined;
};
inherits(CategorySelectBox, SelectBox);
CategorySelectBox.prototype.setState = function(state) {
this._state = state;
if (state === 'select') {
if (this._category_adder) {
this._category_adder.setState('disabled');
}
$.each(this._items, function(idx, item){
item.setState('display');
});
} else if (state === 'editable') {
this._category_adder.setState('waiting');
$.each(this._items, function(idx, item){
item.setState('editable');
});
}
};
CategorySelectBox.prototype.setCategoryTree = function(tree) {
this._tree = tree;
};
CategorySelectBox.prototype.getCategoryTree = function() {
};
CategorySelectBox.prototype.setLevel = function(level) {
this._level = level;
};
CategorySelectBox.prototype.getNames = function() {
var names = [];
$.each(this._items, function(idx, item) {
names.push(item.getName());
});
return names;
};
CategorySelectBox.prototype.appendCategoryAdder = function() {
var adder = new CategoryAdder();
adder.setLevel(this._level);
adder.setCategoryTree(this._tree);
this._category_adder = adder;
this._element.append(adder.getElement());
};
CategorySelectBox.prototype.createDom = function() {
CategorySelectBox.superClass_.createDom();
if (askbot['data']['userIsAdmin']) {
this.appendCategoryAdder();
}
};
CategorySelectBox.prototype.decorate = function(element) {
CategorySelectBox.superClass_.decorate.call(this, element);
this.setState(this._state);
if (askbot['data']['userIsAdmin']) {
this.appendCategoryAdder();
}
};
/**
* @constructor
* turns on/off the category editor
*/
var CategoryEditorToggle = function() {
TwoStateToggle.call(this);
};
inherits(CategoryEditorToggle, TwoStateToggle);
CategoryEditorToggle.prototype.setCategorySelector = function(sel) {
this._category_selector = sel;
};
CategoryEditorToggle.prototype.getCategorySelector = function() {
return this._category_selector;
};
CategoryEditorToggle.prototype.decorate = function(element) {
CategoryEditorToggle.superClass_.decorate.call(this, element);
};
CategoryEditorToggle.prototype.getDefaultHandler = function() {
var me = this;
return function() {
var editor = me.getCategorySelector();
if (me.isOn()) {
me.setState('off-state');
editor.setState('select');
} else {
me.setState('on-state');
editor.setState('editable');
}
};
};
var CategorySelector = function() {
Widget.call(this);
this._data = null;
this._select_handler = function(){};//dummy default
this._current_path = [0];//path points to selected item in tree
};
inherits(CategorySelector, Widget);
/**
* propagates state to the individual selectors
*/
CategorySelector.prototype.setState = function(state) {
this._state = state;
if (state === 'editing') {
return;//do not propagate this state
}
$.each(this._selectors, function(idx, selector){
selector.setState(state);
});
};
CategorySelector.prototype.getPathToItem = function(item) {
function findPathToItemInTree(tree, item) {
for (var i = 0; i < tree.length; i++) {
var node = tree[i];
if (node[2] === item) {
return [i];
}
var path = findPathToItemInTree(node[1], item);
if (path.length > 0) {
path.unshift(i);
return path;
}
}
return [];
};
return findPathToItemInTree(this._data, item);
};
CategorySelector.prototype.applyToDataItems = function(func) {
function applyToDataItems(tree) {
$.each(tree, function(idx, item) {
func(item);
applyToDataItems(item[1]);
});
};
if (this._data) {
applyToDataItems(this._data);
}
};
CategorySelector.prototype.setData = function(data) {
this._clearData
this._data = data;
var tree = this;
function attachCategory(item) {
var cat = new Category();
cat.setName(item[0]);
cat.setCategoryTree(tree);
item[2] = cat;
};
this.applyToDataItems(attachCategory);
};
/**
* clears contents of selector boxes starting from
* the given level, in range 0..2
*/
CategorySelector.prototype.clearCategoryLevels = function(level) {
for (var i = level; i < 3; i++) {
this._selectors[i].detachAllItems();
}
};
CategorySelector.prototype.getLeafItems = function(selection_path) {
//traverse the tree down to items pointed to by the path
var data = this._data[0];
for (var i = 1; i < selection_path.length; i++) {
data = data[1][selection_path[i]];
}
return data[1];
}
/**
* called when a sub-level needs to open
*/
CategorySelector.prototype.populateCategoryLevel = function(source_path) {
var level = source_path.length - 1;
if (level >= 3) {
return;
}
//clear all items downstream
this.clearCategoryLevels(level);
//populate current selector
var selector = this._selectors[level];
var items = this.getLeafItems(source_path);
$.each(items, function(idx, item) {
var category_name = item[0];
var category_subtree = item[1];
var category_object = item[2];
selector.addItemObject(category_object);
if (category_subtree.length > 0) {
category_object.addCssClass('tree');
}
});
this.setState(this._state);//update state
selector.clearSelection();
};
CategorySelector.prototype.selectPath = function(path) {
for (var i = 1; i <= path.length; i++) {
this.populateCategoryLevel(path.slice(0, i));
}
for (var i = 1; i < path.length; i++) {
var sel_box = this._selectors[i-1];
var category = sel_box.getItemByIndex(path[i]);
sel_box.selectItem(category);
}
};
CategorySelector.prototype.getSelectBox = function(level) {
return this._selectors[level];
};
CategorySelector.prototype.getSelectedPath = function(selected_level) {
var path = [0];//root, todo: better use names for path???
/*
* upper limit capped by current clicked level
* we ignore all selection above the current level
*/
for (var i = 0; i < selected_level + 1; i++) {
var selector = this._selectors[i];
var item = selector.getSelectedItem();
if (item) {
path.push(selector.getItemIndex(item));
} else {
return path;
}
}
return path;
};
/** getter and setter are not symmetric */
CategorySelector.prototype.setSelectHandler = function(handler) {
this._select_handler = handler;
};
CategorySelector.prototype.getSelectHandlerInternal = function() {
return this._select_handler;
};
CategorySelector.prototype.setCurrentPath = function(path) {
return this._current_path = path;
};
CategorySelector.prototype.getCurrentPath = function() {
return this._current_path;
};
CategorySelector.prototype.getEditorToggle = function() {
return this._editor_toggle;
};
/*CategorySelector.prototype.closeAllEditors = function() {
$.each(this._selectors, function(idx, sel) {
sel._category_adder.setState('wait');
$.each(sel._items, function(idx2, item) {
item.setState('editable');
});
});
};*/
CategorySelector.prototype.getSelectHandler = function(level) {
var me = this;
return function(item_data) {
if (me.getState() === 'editing') {
return;//don't navigate when editing
}
//1) run the assigned select handler
var tag_name = item_data['title']
if (me.getState() === 'select') {
/* this one will actually select the tag
* maybe a bit too implicit
*/
me.getSelectHandlerInternal()(tag_name);
}
//2) if appropriate, populate the higher level
if (level < 2) {
var current_path = me.getSelectedPath(level);
me.setCurrentPath(current_path);
me.populateCategoryLevel(current_path);
}
}
};
CategorySelector.prototype.decorate = function(element) {
this._element = element;
this._selectors = [];
var selector0 = new CategorySelectBox();
selector0.setLevel(0);
selector0.setCategoryTree(this);
selector0.decorate(element.find('.cat-col-0'));
selector0.setSelectHandler(this.getSelectHandler(0));
this._selectors.push(selector0);
var selector1 = new CategorySelectBox();
selector1.setLevel(1);
selector1.setCategoryTree(this);
selector1.decorate(element.find('.cat-col-1'));
selector1.setSelectHandler(this.getSelectHandler(1));
this._selectors.push(selector1)
var selector2 = new CategorySelectBox();
selector2.setLevel(2);
selector2.setCategoryTree(this);
selector2.decorate(element.find('.cat-col-2'));
selector2.setSelectHandler(this.getSelectHandler(2));
this._selectors.push(selector2);
if (askbot['data']['userIsAdminOrMod']) {
var editor_toggle = new CategoryEditorToggle();
editor_toggle.setCategorySelector(this);
var toggle_element = $('.category-editor-toggle');
toggle_element.show();
editor_toggle.decorate(toggle_element);
this._editor_toggle = editor_toggle;
}
this.populateCategoryLevel([0]);
};
/**
* @constructor
* loads html for the category selector from
* the server via ajax and activates the
* CategorySelector on the loaded HTML
*/
var CategorySelectorLoader = function() {
WrappedElement.call(this);
this._is_loaded = false;
};
inherits(CategorySelectorLoader, WrappedElement);
CategorySelectorLoader.prototype.setCategorySelector = function(sel) {
this._category_selector = sel;
};
CategorySelectorLoader.prototype.setLoaded = function(is_loaded) {
this._is_loaded = is_loaded;
};
CategorySelectorLoader.prototype.isLoaded = function() {
return this._is_loaded;
};
CategorySelectorLoader.prototype.setEditor = function(editor) {
this._editor = editor;
};
CategorySelectorLoader.prototype.closeEditor = function() {
this._editor.hide();
this._editor_buttons.hide();
this._display_tags_container.show();
this._question_body.show();
this._question_controls.show();
};
CategorySelectorLoader.prototype.openEditor = function() {
this._editor.show();
this._editor_buttons.show();
this._display_tags_container.hide();
this._question_body.hide();
this._question_controls.hide();
var sel = this._category_selector;
sel.setState('select');
sel.getEditorToggle().setState('off-state');
};
CategorySelectorLoader.prototype.addEditorButtons = function() {
this._editor.after(this._editor_buttons);
};
CategorySelectorLoader.prototype.getOnLoadHandler = function() {
var me = this;
return function(html){
me.setLoaded(true);
//append loaded html to dom
var editor = $('' + html + '
');
me.setEditor(editor);
$('#question-tags').after(editor);
var selector = askbot['functions']['initCategoryTree']();
me.setCategorySelector(selector);
me.addEditorButtons();
me.openEditor();
//add the save button
};
};
CategorySelectorLoader.prototype.startLoadingHTML = function(on_load) {
var me = this;
$.ajax({
type: 'GET',
dataType: 'json',
data: { template_name: 'widgets/tag_category_selector.html' },
url: askbot['urls']['get_html_template'],
cache: true,
success: function(data) {
if (data['success']) {
on_load(data['html']);
} else {
showMessage(me.getElement(), data['message']);
}
}
});
};
CategorySelectorLoader.prototype.getRetagHandler = function() {
var me = this;
return function() {
if (me.isLoaded() === false) {
me.startLoadingHTML(me.getOnLoadHandler());
} else {
me.openEditor();
}
return false;
}
};
CategorySelectorLoader.prototype.drawNewTags = function(new_tags) {
if (new_tags === ''){
this._display_tags_container.html('');
return;
}
new_tags = new_tags.split(/\s+/);
var tags_html = ''
$.each(new_tags, function(index, name){
var tag = new Tag();
tag.setName(name);
tags_html += tag.getElement().outerHTML();
});
this._display_tags_container.html(tags_html);
};
CategorySelectorLoader.prototype.getSaveHandler = function() {
var me = this;
return function() {
var tagInput = $('input[name="tags"]');
$.ajax({
type: "POST",
url: retagUrl,//add to askbot['urls']
dataType: "json",
data: { tags: getUniqueWords(tagInput.val()).join(' ') },
success: function(json) {
if (json['success'] === true){
var new_tags = getUniqueWords(json['new_tags']);
oldTagsHtml = '';
me.closeEditor();
me.drawNewTags(new_tags.join(' '));
}
else {
me.closeEditor();
showMessage(me.getElement(), json['message']);
}
},
error: function(xhr, textStatus, errorThrown) {
showMessage(tagsDiv, 'sorry, something is not right here');
cancelRetag();
}
});
return false;
};
};
CategorySelectorLoader.prototype.getCancelHandler = function() {
var me = this;
return function() {
me.closeEditor();
};
};
CategorySelectorLoader.prototype.decorate = function(element) {
this._element = element;
this._display_tags_container = $('#question-tags');
this._question_body = $('.question .post-body');
this._question_controls = $('#question-controls');
this._editor_buttons = this.makeElement('div');
this._done_button = this.makeElement('button');
this._done_button.html(gettext('save tags'));
this._editor_buttons.append(this._done_button);
this._cancel_button = this.makeElement('button');
this._cancel_button.html(gettext('cancel'));
this._editor_buttons.append(this._cancel_button);
this._editor_buttons.find('button').addClass('submit');
this._editor_buttons.addClass('retagger-buttons');
//done button
setupButtonEventHandlers(
this._done_button,
this.getSaveHandler()
);
//cancel button
setupButtonEventHandlers(
this._cancel_button,
this.getCancelHandler()
);
//retag button
setupButtonEventHandlers(
element,
this.getRetagHandler()
);
};
$(document).ready(function() {
$('[id^="comments-for-"]').each(function(index, element){
var comments = new PostCommentsWidget();
comments.decorate(element);
});
$('[id^="swap-question-with-answer-"]').each(function(idx, element){
var swapper = new QASwapper();
swapper.decorate($(element));
});
$('[id^="post-id-"]').each(function(idx, element){
var deleter = new DeletePostLink();
//confusingly .question-delete matches the answers too need rename
var post_id = element.id.split('-').pop();
deleter.setPostId(post_id);
deleter.decorate($(element).find('.question-delete'));
});
//todo: convert to "control" class
var publishBtns = $('.answer-publish, .answer-unpublish');
publishBtns.each(function(idx, btn) {
setupButtonEventHandlers($(btn), function() {
var answerId = $(btn).data('answerId');
$.ajax({
type: 'POST',
dataType: 'json',
data: {'answer_id': answerId},
url: askbot['urls']['publishAnswer'],
success: function(data) {
if (data['success']) {
window.location.reload(true);
} else {
showMessage($(btn), data['message']);
}
}
});
});
});
if (askbot['settings']['tagSource'] == 'category-tree') {
var catSelectorLoader = new CategorySelectorLoader();
catSelectorLoader.decorate($('#retag'));
} else {
questionRetagger.init();
}
socialSharing.init();
var proxyUserNameInput = $('#id_post_author_username');
var proxyUserEmailInput = $('#id_post_author_email');
if (proxyUserNameInput.length === 1) {
var userSelectHandler = function(data) {
proxyUserEmailInput.val(data['data'][0]);
};
var fakeUserAc = new AutoCompleter({
url: '/get-users-info/',//askbot['urls']['get_users_info'],
promptText: gettext('User name:'),
minChars: 1,
useCache: true,
matchInside: true,
maxCacheLength: 100,
delay: 10,
onItemSelect: userSelectHandler
});
fakeUserAc.decorate(proxyUserNameInput);
if (proxyUserEmailInput.length === 1) {
var tip = new TippedInput();
tip.decorate(proxyUserEmailInput);
}
}
//if groups are enabled - activate share functions
var groupsInput = $('#share_group_name');
if (groupsInput.length === 1) {
var groupsAc = new AutoCompleter({
url: askbot['urls']['getGroupsList'],
promptText: gettext('Group name:'),
minChars: 1,
useCache: false,
matchInside: true,
maxCacheLength: 100,
delay: 10
});
groupsAc.decorate(groupsInput);
}
var usersInput = $('#share_user_name');
if (usersInput.length === 1) {
var usersAc = new AutoCompleter({
url: '/get-users-info/',
promptText: gettext('User name:'),
minChars: 1,
useCache: false,
matchInside: true,
maxCacheLength: 100,
delay: 10
});
usersAc.decorate(usersInput);
}
var showSharedUsers = $('.see-related-users');
if (showSharedUsers.length) {
var usersPopup = new ThreadUsersDialog();
usersPopup.setHeadingText(gettext('Shared with the following users:'));
usersPopup.decorate(showSharedUsers);
}
var showSharedGroups = $('.see-related-groups');
if (showSharedGroups.length) {
var groupsPopup = new ThreadUsersDialog();
groupsPopup.setHeadingText(gettext('Shared with the following groups:'));
groupsPopup.decorate(showSharedGroups);
}
});
/* google prettify.js from google code */
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^