Commit 0790a3ad by Rocky Duan

image uploading & render markdown

parent 7a53bc52
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
// The text that appears on the upper part of the dialog box when // The text that appears on the upper part of the dialog box when
// entering links. // entering links.
var linkDialogText = "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>"; var linkDialogText = "<p><b>Insert Hyperlink</b></p><p>http://example.com/ \"optional title\"</p>";
var imageDialogText = "<p><b>Insert Image</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br>Need <a href='http://www.google.com/search?q=free+image+hosting' target='_blank'>free image hosting?</a></p>"; var imageDialogText = "<p><b>Insert Image (upload file or type url)</b></p><p>http://example.com/images/diagram.jpg \"optional title\"<br><br></p>";
// The default text that appears in the dialog input box when entering // The default text that appears in the dialog input box when entering
// links. // links.
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
// - getConverter() returns the markdown converter object that was passed to the constructor // - getConverter() returns the markdown converter object that was passed to the constructor
// - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op. // - run() actually starts the editor; should be called after all necessary plugins are registered. Calling this more than once is a no-op.
// - refreshPreview() forces the preview to be updated. This method is only available after run() was called. // - refreshPreview() forces the preview to be updated. This method is only available after run() was called.
Markdown.Editor = function (markdownConverter, idPostfix, help) { Markdown.Editor = function (markdownConverter, idPostfix, help, imageUploadHandler) {
idPostfix = idPostfix || ""; idPostfix = idPostfix || "";
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
} }
} }
uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help); uiManager = new UIManager(idPostfix, panels, undoManager, previewManager, commandManager, help, imageUploadHandler);
uiManager.setUndoRedoButtonStates(); uiManager.setUndoRedoButtonStates();
var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); }; var forceRefresh = that.refreshPreview = function () { previewManager.refresh(true); };
...@@ -1010,7 +1010,7 @@ ...@@ -1010,7 +1010,7 @@
// callback: The function which is executed when the prompt is dismissed, either via OK or Cancel. // callback: The function which is executed when the prompt is dismissed, either via OK or Cancel.
// It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel // It receives a single argument; either the entered text (if OK was chosen) or null (if Cancel
// was chosen). // was chosen).
ui.prompt = function (text, defaultInputText, callback) { ui.prompt = function (text, defaultInputText, callback, imageUploadHandler) {
// These variables need to be declared at this level since they are used // These variables need to be declared at this level since they are used
// in multiple functions. // in multiple functions.
...@@ -1044,8 +1044,10 @@ ...@@ -1044,8 +1044,10 @@
else { else {
// Fixes common pasting errors. // Fixes common pasting errors.
text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://'); text = text.replace(/^http:\/\/(https?|ftp):\/\//, '$1://');
if (!/^(?:https?|ftp):\/\//.test(text)) // doesn't change url if started with '/' (local)
if (!/^(?:https?|ftp):\/\//.test(text) && text.charAt(0) != '/') {
text = 'http://' + text; text = 'http://' + text;
}
} }
dialog.parentNode.removeChild(dialog); dialog.parentNode.removeChild(dialog);
...@@ -1095,6 +1097,21 @@ ...@@ -1095,6 +1097,21 @@
style.marginLeft = style.marginRight = "auto"; style.marginLeft = style.marginRight = "auto";
form.appendChild(input); form.appendChild(input);
// The choose file button if prompt type is 'image'
if (imageUploadHandler) {
var chooseFile = doc.createElement("input");
chooseFile.type = "file";
chooseFile.name = "file-upload";
chooseFile.id = "file-upload";
chooseFile.onchange = function() {
imageUploadHandler(this, input);
};
form.appendChild(doc.createElement("br"));
form.appendChild(chooseFile);
}
// The ok button // The ok button
var okButton = doc.createElement("input"); var okButton = doc.createElement("input");
okButton.type = "button"; okButton.type = "button";
...@@ -1160,7 +1177,7 @@ ...@@ -1160,7 +1177,7 @@
}, 0); }, 0);
}; };
function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions) { function UIManager(postfix, panels, undoManager, previewManager, commandManager, helpOptions, imageUploadHandler) {
var inputBox = panels.input, var inputBox = panels.input,
buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements. buttons = {}; // buttons.undo, buttons.link, etc. The actual DOM elements.
...@@ -1419,7 +1436,7 @@ ...@@ -1419,7 +1436,7 @@
buttons.quote = makeButton("wmd-quote-button", "Blockquote <blockquote> Ctrl+Q", "-60px", bindCommand("doBlockquote")); buttons.quote = makeButton("wmd-quote-button", "Blockquote <blockquote> Ctrl+Q", "-60px", bindCommand("doBlockquote"));
buttons.code = makeButton("wmd-code-button", "Code Sample <pre><code> Ctrl+K", "-80px", bindCommand("doCode")); buttons.code = makeButton("wmd-code-button", "Code Sample <pre><code> Ctrl+K", "-80px", bindCommand("doCode"));
buttons.image = makeButton("wmd-image-button", "Image <img> Ctrl+G", "-100px", bindCommand(function (chunk, postProcessing) { buttons.image = makeButton("wmd-image-button", "Image <img> Ctrl+G", "-100px", bindCommand(function (chunk, postProcessing) {
return this.doLinkOrImage(chunk, postProcessing, true); return this.doLinkOrImage(chunk, postProcessing, true, imageUploadHandler);
})); }));
makeSpacer(2); makeSpacer(2);
buttons.olist = makeButton("wmd-olist-button", "Numbered List <ol> Ctrl+O", "-120px", bindCommand(function (chunk, postProcessing) { buttons.olist = makeButton("wmd-olist-button", "Numbered List <ol> Ctrl+O", "-120px", bindCommand(function (chunk, postProcessing) {
...@@ -1649,7 +1666,7 @@ ...@@ -1649,7 +1666,7 @@
}); });
} }
commandProto.doLinkOrImage = function (chunk, postProcessing, isImage) { commandProto.doLinkOrImage = function (chunk, postProcessing, isImage, imageUploadHandler) {
chunk.trimWhitespace(); chunk.trimWhitespace();
chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/); chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
...@@ -1724,7 +1741,7 @@ ...@@ -1724,7 +1741,7 @@
if (isImage) { if (isImage) {
if (!this.hooks.insertImageDialog(linkEnteredCallback)) if (!this.hooks.insertImageDialog(linkEnteredCallback))
ui.prompt(imageDialogText, imageDefaultText, linkEnteredCallback); ui.prompt(imageDialogText, imageDefaultText, linkEnteredCallback, imageUploadHandler);
} }
else { else {
ui.prompt(linkDialogText, linkDefaultText, linkEnteredCallback); ui.prompt(linkDialogText, linkDefaultText, linkEnteredCallback);
......
jQuery.extend({
handleError: function( s, xhr, status, e ) {
// If a local callback was specified, fire it
if ( s.error ) {
s.error.call( s.context || s, xhr, status, e );
}
// Fire the global callback
if ( s.global ) {
(s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] );
}
},
createUploadIframe: function(id, uri){
//create frame
var frameId = 'jUploadFrame' + id;
if(window.ActiveXObject) {
var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
if(typeof uri== 'boolean'){
io.src = 'javascript:false';
}
else if(typeof uri== 'string'){
io.src = uri;
}
}
else {
var io = document.createElement('iframe');
io.id = frameId;
io.name = frameId;
}
io.style.position = 'absolute';
io.style.top = '-1000px';
io.style.left = '-1000px';
document.body.appendChild(io);
return io;
},
createUploadForm: function(id, fileElementId)
{
//create form
var formId = 'jUploadForm' + id;
var fileId = 'jUploadFile' + id;
var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId
+ '" enctype="multipart/form-data"></form>');
var oldElement = $('#' + fileElementId);
var newElement = $(oldElement).clone();
$(oldElement).attr('id', fileId);
$(oldElement).before(newElement);
$(oldElement).appendTo(form);
//set attributes
$(form).css('position', 'absolute');
$(form).css('top', '-1200px');
$(form).css('left', '-1200px');
$(form).appendTo('body');
return form;
},
ajaxFileUpload: function(s) {
// TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
s = jQuery.extend({}, jQuery.ajaxSettings, s);
var id = new Date().getTime()
var form = jQuery.createUploadForm(id, s.fileElementId);
var io = jQuery.createUploadIframe(id, s.secureuri);
var frameId = 'jUploadFrame' + id;
var formId = 'jUploadForm' + id;
// Watch for a new set of requests
if ( s.global && ! jQuery.active++ )
{
jQuery.event.trigger( "ajaxStart" );
}
var requestDone = false;
// Create the request object
var xml = {}
if ( s.global )
jQuery.event.trigger("ajaxSend", [xml, s]);
// Wait for a response to come back
var uploadCallback = function(isTimeout)
{
var io = document.getElementById(frameId);
try {
if(io.contentWindow){
xml.responseText = io.contentWindow.document.body ?
io.contentWindow.document.body.innerText : null;
xml.responseXML = io.contentWindow.document.XMLDocument ?
io.contentWindow.document.XMLDocument : io.contentWindow.document;
}
else if(io.contentDocument)
{
xml.responseText = io.contentDocument.document.body ?
io.contentDocument.document.body.textContent || document.body.innerText : null;
xml.responseXML = io.contentDocument.document.XMLDocument ?
io.contentDocument.document.XMLDocument : io.contentDocument.document;
}
}
catch(e)
{
jQuery.handleError(s, xml, null, e);
}
if ( xml || isTimeout == "timeout")
{
requestDone = true;
var status;
try {
status = isTimeout != "timeout" ? "success" : "error";
// Make sure that the request was successful or notmodified
if ( status != "error" )
{
// process the data (runs the xml through httpData regardless of callback)
var data = jQuery.uploadHttpData( xml, s.dataType );
// If a local callback was specified, fire it and pass it the data
if ( s.success )
s.success( data, status );
// Fire the global callback
if( s.global )
jQuery.event.trigger( "ajaxSuccess", [xml, s] );
} else
jQuery.handleError(s, xml, status);
} catch(e)
{
status = "error";
jQuery.handleError(s, xml, status, e);
}
// The request was completed
if( s.global )
jQuery.event.trigger( "ajaxComplete", [xml, s] );
// Handle the global AJAX counter
if ( s.global && ! --jQuery.active )
jQuery.event.trigger( "ajaxStop" );
// Process result
if ( s.complete )
s.complete(xml, status);
jQuery(io).unbind();
setTimeout(function()
{ try
{
$(io).remove();
$(form).remove();
} catch(e) {
jQuery.handleError(s, xml, null, e);
}
}, 100)
xml = null;
}
}
// Timeout checker
if ( s.timeout > 0 ) {
setTimeout(function(){
// Check to see if the request is still happening
if( !requestDone ) uploadCallback( "timeout" );
}, s.timeout);
}
try
{
// var io = $('#' + frameId);
var form = $('#' + formId);
$(form).attr('action', s.url);
$(form).attr('method', 'POST');
$(form).attr('target', frameId);
if(form.encoding)
{
form.encoding = 'multipart/form-data';
}
else
{
form.enctype = 'multipart/form-data';
}
$(form).submit();
} catch(e)
{
jQuery.handleError(s, xml, null, e);
}
if(window.attachEvent){
document.getElementById(frameId).attachEvent('onload', uploadCallback);
}
else{
document.getElementById(frameId).addEventListener('load', uploadCallback, false);
}
return {abort: function () {}};
},
uploadHttpData: function( r, type ) {
var data = !type;
data = type == "xml" || data ? r.responseXML : r.responseText;
// If the type is "script", eval it in global context
if ( type == "script" )
jQuery.globalEval( data );
// Get the JavaScript object, if JSON is used.
if ( type == "json" )
eval( "data = " + data );
// evaluate scripts within html
if ( type == "html" )
jQuery("<div>").html(data).evalScripts();
//alert($('param', data).each(function(){alert($(this).attr('value'));}));
return data;
}
})
...@@ -3,6 +3,7 @@ import django_comment_client.base.views ...@@ -3,6 +3,7 @@ import django_comment_client.base.views
urlpatterns = patterns('django_comment_client.base.views', urlpatterns = patterns('django_comment_client.base.views',
url(r'upload$', 'upload', name='upload'),
url(r'threads/(?P<thread_id>[\w\-]+)/update$', 'update_thread', name='update_thread'), url(r'threads/(?P<thread_id>[\w\-]+)/update$', 'update_thread', name='update_thread'),
url(r'threads/(?P<thread_id>[\w\-]+)/reply$', 'create_comment', name='create_comment'), url(r'threads/(?P<thread_id>[\w\-]+)/reply$', 'create_comment', name='create_comment'),
url(r'threads/(?P<thread_id>[\w\-]+)/delete', 'delete_thread', name='delete_thread'), url(r'threads/(?P<thread_id>[\w\-]+)/delete', 'delete_thread', name='delete_thread'),
......
import time
import random
import os
import os.path
import logging
import urlparse
import comment_client
from django.core import exceptions
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET from django.views.decorators.http import require_POST, require_GET
from django.http import HttpResponse from django.http import HttpResponse
from django.utils import simplejson from django.utils import simplejson
from django.views.decorators import csrf
import comment_client from django.core.files.storage import get_storage_class
from django.utils.translation import ugettext as _
from django.conf import settings
class JsonResponse(HttpResponse): class JsonResponse(HttpResponse):
def __init__(self, data=None): def __init__(self, data=None):
...@@ -183,3 +195,82 @@ def search(request, course_id): ...@@ -183,3 +195,82 @@ def search(request, course_id):
commentable_id = request.GET.get('commentable_id', None) commentable_id = request.GET.get('commentable_id', None)
response = comment_client.search(text, commentable_id) response = comment_client.search(text, commentable_id)
return JsonResponse(response) return JsonResponse(response)
@csrf.csrf_exempt
@login_required
@require_POST
def upload(request, course_id):#ajax upload file to a question or answer
"""view that handles file upload via Ajax
"""
# check upload permission
result = ''
error = ''
new_file_name = ''
try:
# TODO authorization
#may raise exceptions.PermissionDenied
#if request.user.is_anonymous():
# msg = _('Sorry, anonymous users cannot upload files')
# raise exceptions.PermissionDenied(msg)
#request.user.assert_can_upload_file()
# check file type
f = request.FILES['file-upload']
file_extension = os.path.splitext(f.name)[1].lower()
if not file_extension in settings.DISCUSSION_ALLOWED_UPLOAD_FILE_TYPES:
file_types = "', '".join(settings.DISCUSSION_ALLOWED_UPLOAD_FILE_TYPES)
msg = _("allowed file types are '%(file_types)s'") % \
{'file_types': file_types}
raise exceptions.PermissionDenied(msg)
# generate new file name
new_file_name = str(
time.time()
).replace(
'.',
str(random.randint(0,100000))
) + file_extension
file_storage = get_storage_class()()
# use default storage to store file
file_storage.save(new_file_name, f)
# check file size
# byte
size = file_storage.size(new_file_name)
if size > settings.ASKBOT_MAX_UPLOAD_FILE_SIZE:
file_storage.delete(new_file_name)
msg = _("maximum upload file size is %(file_size)sK") % \
{'file_size': settings.ASKBOT_MAX_UPLOAD_FILE_SIZE}
raise exceptions.PermissionDenied(msg)
except exceptions.PermissionDenied, e:
error = unicode(e)
except Exception, e:
logging.critical(unicode(e))
error = _('Error uploading file. Please contact the site administrator. Thank you.')
if error == '':
result = 'Good'
file_url = file_storage.url(new_file_name)
parsed_url = urlparse.urlparse(file_url)
file_url = urlparse.urlunparse(
urlparse.ParseResult(
parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
'', '', ''
)
)
else:
result = ''
file_url = ''
return JsonResponse({
'result': {
'msg': result,
'error': error,
'file_url': file_url,
}
})
...@@ -30,6 +30,7 @@ import djcelery ...@@ -30,6 +30,7 @@ import djcelery
from path import path from path import path
from .askbotsettings import * # this is where LIVESETTINGS_OPTIONS comes from from .askbotsettings import * # this is where LIVESETTINGS_OPTIONS comes from
from .discussionsettings import *
################################### FEATURES ################################### ################################### FEATURES ###################################
COURSEWARE_ENABLED = True COURSEWARE_ENABLED = True
......
DISCUSSION_ALLOWED_UPLOAD_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
...@@ -102,6 +102,7 @@ $ -> ...@@ -102,6 +102,7 @@ $ ->
text text
if Markdown? if Markdown?
Markdown.getMathCompatibleConverter = -> Markdown.getMathCompatibleConverter = ->
converter = Markdown.getSanitizingConverter() converter = Markdown.getSanitizingConverter()
processor = new MathJaxProcessor() processor = new MathJaxProcessor()
...@@ -109,11 +110,13 @@ $ -> ...@@ -109,11 +110,13 @@ $ ->
converter.hooks.chain "postConversion", processor.replaceMath converter.hooks.chain "postConversion", processor.replaceMath
converter converter
Markdown.makeWmdEditor = (elem, appended_id) -> Markdown.makeWmdEditor = (elem, appended_id, imageUploadUrl) ->
$elem = $(elem) $elem = $(elem)
if not $elem.length if not $elem.length
console.log "warning: elem for makeWmdEditor doesn't exist" console.log "warning: elem for makeWmdEditor doesn't exist"
return return
if not $elem.find(".wmd-panel").length if not $elem.find(".wmd-panel").length
_append = appended_id || "" _append = appended_id || ""
$wmdPanel = $("<div>").addClass("wmd-panel") $wmdPanel = $("<div>").addClass("wmd-panel")
...@@ -121,8 +124,42 @@ $ -> ...@@ -121,8 +124,42 @@ $ ->
.append($("<textarea>").addClass("wmd-input").attr("id", "wmd-input#{_append}")) .append($("<textarea>").addClass("wmd-input").attr("id", "wmd-input#{_append}"))
.append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview")) .append($("<div>").attr("id", "wmd-preview#{_append}").addClass("wmd-panel wmd-preview"))
$elem.append($wmdPanel) $elem.append($wmdPanel)
converter = Markdown.getMathCompatibleConverter() converter = Markdown.getMathCompatibleConverter()
editor = new Markdown.Editor(converter, appended_id)
ajaxFileUpload = (imageUploadUrl, input, startUploadHandler) ->
$("#loading").ajaxStart(-> $(this).show()).ajaxComplete(-> $(this).hide())
$("#upload").ajaxStart(-> $(this).hide()).ajaxComplete(-> $(this).show())
$.ajaxFileUpload
url: imageUploadUrl
secureuri: false
fileElementId: 'file-upload'
dataType: 'json'
success: (data, status) ->
fileURL = data['result']['file_url']
error = data['result']['error']
if error != ''
alert error
if startUploadHandler
$('#file-upload').unbind('change').change(startUploadHandler)
console.log error
else
$(input).attr('value', fileURL)
error: (data, status, e) ->
alert(e)
if startUploadHandler
$('#file-upload').unbind('change').change(startUploadHandler)
imageUploadHandler = (elem, input) ->
console.log "here"
ajaxFileUpload(imageUploadUrl, input, imageUploadHandler)
editor = new Markdown.Editor(
converter,
appended_id, # idPostfix
null, # help handler
imageUploadHandler
)
delayRenderer = new MathJaxDelayRenderer() delayRenderer = new MathJaxDelayRenderer()
editor.hooks.chain "onPreviewPush", (text, previewSet) -> editor.hooks.chain "onPreviewPush", (text, previewSet) ->
delayRenderer.render delayRenderer.render
......
...@@ -43,6 +43,7 @@ Discussion = ...@@ -43,6 +43,7 @@ Discussion =
delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete" delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete"
upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote" upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote"
downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote" downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote"
upload : "/courses/#{$$course_id}/discussion/upload"
search : "/courses/#{$$course_id}/discussion/forum/search" search : "/courses/#{$$course_id}/discussion/forum/search"
}[name] }[name]
...@@ -89,7 +90,7 @@ Discussion = ...@@ -89,7 +90,7 @@ Discussion =
newPostBody = $(discussion).find(".new-post-body") newPostBody = $(discussion).find(".new-post-body")
if newPostBody.length if newPostBody.length
Markdown.makeWmdEditor newPostBody, "-new-post-body-#{$(discussion).attr('_id')}" Markdown.makeWmdEditor newPostBody, "-new-post-body-#{$(discussion).attr('_id')}", Discussion.urlFor('upload')
initializeWatchThreads = (index, thread) -> initializeWatchThreads = (index, thread) ->
$thread = $(thread) $thread = $(thread)
...@@ -175,7 +176,7 @@ Discussion = ...@@ -175,7 +176,7 @@ Discussion =
$discussionContent.append(editView) $discussionContent.append(editView)
Markdown.makeWmdEditor $local(".comment-edit"), "-comment-edit-#{id}" Markdown.makeWmdEditor $local(".comment-edit"), "-comment-edit-#{id}", Discussion.urlFor('upload')
cancelReply = generateDiscussionLink("discussion-cancel-reply", "Cancel", handleCancelReply) cancelReply = generateDiscussionLink("discussion-cancel-reply", "Cancel", handleCancelReply)
submitReply = generateDiscussionLink("discussion-submit-reply", "Submit", handleSubmitReply) submitReply = generateDiscussionLink("discussion-submit-reply", "Submit", handleSubmitReply)
$local(".discussion-link").hide() $local(".discussion-link").hide()
...@@ -230,10 +231,17 @@ Discussion = ...@@ -230,10 +231,17 @@ Discussion =
$local(".discussion-vote-down").click -> $local(".discussion-vote-down").click ->
handleVote(this, "down") handleVote(this, "down")
initializeContent: (content) ->
$content = $(content)
$local = generateLocal($content.children(".discussion-content"))
raw_text = $local(".content-body").html()
converter = Markdown.getMathCompatibleConverter()
$local(".content-body").html(converter.makeHtml(raw_text))
bindDiscussionEvents: (discussion) -> bindDiscussionEvents: (discussion) ->
$discussion = $(discussion) $discussion = $(discussion)
$discussionNonContent = $discussion.children(".discussion-non-content") $discussionNonContent = $discussion.children(".discussion-non-content")
$local = (selector) -> $discussionNonContent.find(selector) $local = generateLocal($discussionNonContent)#(selector) -> $discussionNonContent.find(selector)
id = $discussion.attr("_id") id = $discussion.attr("_id")
...@@ -266,7 +274,9 @@ Discussion = ...@@ -266,7 +274,9 @@ Discussion =
$local(".new-post-form").submit() $local(".new-post-form").submit()
$discussion.find(".thread").each (index, thread) -> $discussion.find(".thread").each (index, thread) ->
Discussion.initializeContent(thread)
Discussion.bindContentEvents(thread) Discussion.bindContentEvents(thread)
$discussion.find(".comment").each (index, comment) -> $discussion.find(".comment").each (index, comment) ->
Discussion.initializeContent(comment)
Discussion.bindContentEvents(comment) Discussion.bindContentEvents(comment)
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
MathJax extension libraries --> MathJax extension libraries -->
<script type="text/javascript" src="/static/js/vendor/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"></script> <script type="text/javascript" src="/static/js/vendor/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"></script>
<script type="text/javascript" src="${static.url('js/vendor/split.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/split.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/jquery.ajaxfileupload.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/Markdown.Converter.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/Markdown.Converter.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/Markdown.Sanitizer.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/Markdown.Sanitizer.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/Markdown.Editor.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/Markdown.Editor.js')}"></script>
......
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
<div class="discussion-upper-wrapper clearfix"> <div class="discussion-upper-wrapper clearfix">
${render_vote(thread)} ${render_vote(thread)}
<div class="discussion-right-wrapper clearfix"> <div class="discussion-right-wrapper clearfix">
<a class="thread-title" name="${thread['id']}" href="${url_for_thread}">${thread['title']}</a> <a class="thread-title" name="${thread['id']}" href="${url_for_thread}">${thread['title'] | h}</a>
<div class="discussion-content-view"> <div class="discussion-content-view">
<div class="thread-body">${thread['body']}</div> <div class="content-body thread-body">${thread['body'] | h}</div>
<div class="info"> <div class="info">
${render_info(thread)} ${render_info(thread)}
% if edit_thread: % if edit_thread:
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
${render_vote(comment)} ${render_vote(comment)}
<div class="discussion-right-wrapper"> <div class="discussion-right-wrapper">
<div class="discussion-content-view"> <div class="discussion-content-view">
<a class="comment-body" name="${comment['id']}">${comment['body']}</a> <a class="content-body comment-body" name="${comment['id']}">${comment['body'] | h}</a>
<div class="info"> <div class="info">
${render_info(comment)} ${render_info(comment)}
${render_reply()} ${render_reply()}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment