diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index bf263ffaa1..d76337e8da 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -82,6 +82,7 @@ def render_discussion(request, course_id, threads, *args, **kwargs): 'base_url': base_url, 'query_params': strip_none(extract(query_params, ['page', 'sort_key', 'sort_order', 'tags', 'text'])), 'annotated_content_info': json.dumps(annotated_content_info), + 'discussion_data': json.dumps({ discussion_id: threads }), } context = dict(context.items() + query_params.items()) return render_to_string(template, context) diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee index 9924e540a2..afd7bd6ff0 100644 --- a/lms/static/coffee/src/discussion/content.coffee +++ b/lms/static/coffee/src/discussion/content.coffee @@ -1,409 +1,7 @@ -if not @Discussion? - @Discussion = {} +$ -> + class Content extends Backbone.Model -Discussion = @Discussion + class Thread extends Content -initializeVote = (content) -> - $content = $(content) - $local = Discussion.generateLocal($content.children(".discussion-content")) - id = $content.attr("_id") - if Discussion.isUpvoted id - $local(".discussion-vote-up").addClass("voted") - else if Discussion.isDownvoted id - $local(".discussion-vote-down").addClass("voted") - -initializeFollowThread = (thread) -> - $thread = $(thread) - id = $thread.attr("_id") - $thread.children(".discussion-content") - .find(".follow-wrapper") - .append(Discussion.subscriptionLink('thread', id)) - -@Discussion = $.extend @Discussion, - - bindContentEvents: (content) -> - - $content = $(content) - $discussionContent = $content.children(".discussion-content") - $local = Discussion.generateLocal($discussionContent) - - id = $content.attr("_id") - - handleReply = (elem) -> - $replyView = $local(".discussion-reply-new") - if $replyView.length - $replyView.show() - else - thread_id = $discussionContent.parents(".thread").attr("_id") - view = - id: id - showWatchCheckbox: not Discussion.isSubscribed(thread_id, "thread") - $discussionContent.append Mustache.render Discussion.replyTemplate, view - Discussion.makeWmdEditor $content, $local, "reply-body" - $local(".discussion-submit-post").click -> handleSubmitReply(this) - $local(".discussion-cancel-post").click -> handleCancelReply(this) - $local(".discussion-reply").hide() - $local(".discussion-edit").hide() - - handleCancelReply = (elem) -> - $replyView = $local(".discussion-reply-new") - if $replyView.length - $replyView.hide() - $local(".discussion-reply").show() - $local(".discussion-edit").show() - - handleSubmitReply = (elem) -> - if $content.hasClass("thread") - url = Discussion.urlFor('create_comment', id) - else if $content.hasClass("comment") - url = Discussion.urlFor('create_sub_comment', id) - else - return - - body = Discussion.getWmdContent $content, $local, "reply-body" - - anonymous = false || $local(".discussion-post-anonymously").is(":checked") - autowatch = false || $local(".discussion-auto-watch").is(":checked") - - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: 'json' - data: - body: body - anonymous: anonymous - autowatch: autowatch - error: Discussion.formErrorHandler($local(".discussion-errors")) - success: (response, textStatus) -> - Discussion.clearFormErrors($local(".discussion-errors")) - $comment = $(response.html) - $content.children(".comments").prepend($comment) - Discussion.setWmdContent $content, $local, "reply-body", "" - Discussion.setContentInfo response.content['id'], 'can_reply', true - Discussion.setContentInfo response.content['id'], 'editable', true - Discussion.extendContentInfo response.content['id'], response['annotated_content_info'] - Discussion.initializeContent($comment) - Discussion.bindContentEvents($comment) - $local(".discussion-reply-new").hide() - $local(".discussion-reply").show() - $local(".discussion-edit").show() - $discussionContent.attr("status", "normal") - - handleVote = (elem, value) -> - contentType = if $content.hasClass("thread") then "thread" else "comment" - url = Discussion.urlFor("#{value}vote_#{contentType}", id) - Discussion.safeAjax - $elem: $local(".discussion-vote") - url: url - type: "POST" - dataType: "json" - success: (response, textStatus) -> - if textStatus == "success" - $local(".discussion-vote").removeClass("voted") - $local(".discussion-vote-#{value}").addClass("voted") - $local(".discussion-votes-point").html response.votes.point - - handleUnvote = (elem, value) -> - contentType = if $content.hasClass("thread") then "thread" else "comment" - url = Discussion.urlFor("undo_vote_for_#{contentType}", id) - Discussion.safeAjax - $elem: $local(".discussion-vote") - url: url - type: "POST" - dataType: "json" - success: (response, textStatus) -> - if textStatus == "success" - $local(".discussion-vote").removeClass("voted") - $local(".discussion-votes-point").html response.votes.point - - handleCancelEdit = (elem) -> - $local(".discussion-content-edit").hide() - $local(".discussion-content-wrapper").show() - - handleEditThread = (elem) -> - $local(".discussion-content-wrapper").hide() - $editView = $local(".discussion-content-edit") - if $editView.length - $editView.show() - else - view = { - id: id - title: $local(".thread-raw-title").html() - body: $local(".thread-raw-body").html() - tags: $local(".thread-raw-tags").html() - } - $discussionContent.append Mustache.render Discussion.editThreadTemplate, view - Discussion.makeWmdEditor $content, $local, "thread-body-edit" - $local(".thread-tags-edit").tagsInput Discussion.tagsInputOptions() - $local(".discussion-submit-update").unbind("click").click -> handleSubmitEditThread(this) - $local(".discussion-cancel-update").unbind("click").click -> handleCancelEdit(this) - - handleSubmitEditThread = (elem) -> - url = Discussion.urlFor('update_thread', id) - title = $local(".thread-title-edit").val() - body = Discussion.getWmdContent $content, $local, "thread-body-edit" - tags = $local(".thread-tags-edit").val() - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: 'json' - data: {title: title, body: body, tags: tags}, - error: Discussion.formErrorHandler($local(".discussion-update-errors")) - success: (response, textStatus) -> - Discussion.clearFormErrors($local(".discussion-update-errors")) - $discussionContent.replaceWith(response.html) - Discussion.extendContentInfo response.content['id'], response['annotated_content_info'] - Discussion.initializeContent($content) - Discussion.bindContentEvents($content) - - handleEditComment = (elem) -> - $local(".discussion-content-wrapper").hide() - $editView = $local(".discussion-content-edit") - if $editView.length - $editView.show() - else - view = { id: id, body: $local(".comment-raw-body").html() } - $discussionContent.append Mustache.render Discussion.editCommentTemplate, view - Discussion.makeWmdEditor $content, $local, "comment-body-edit" - $local(".discussion-submit-update").unbind("click").click -> handleSubmitEditComment(this) - $local(".discussion-cancel-update").unbind("click").click -> handleCancelEdit(this) - - handleSubmitEditComment= (elem) -> - url = Discussion.urlFor('update_comment', id) - body = Discussion.getWmdContent $content, $local, "comment-body-edit" - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: "json" - data: {body: body} - error: Discussion.formErrorHandler($local(".discussion-update-errors")) - success: (response, textStatus) -> - Discussion.clearFormErrors($local(".discussion-update-errors")) - $discussionContent.replaceWith(response.html) - Discussion.extendContentInfo response.content['id'], response['annotated_content_info'] - Discussion.initializeContent($content) - Discussion.bindContentEvents($content) - - handleEndorse = (elem, endorsed) -> - url = Discussion.urlFor('endorse_comment', id) - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: "json" - data: {endorsed: endorsed} - success: (response, textStatus) -> - if textStatus == "success" - if endorsed - $(content).addClass("endorsed") - else - $(content).removeClass("endorsed") - - $(elem).unbind('click').click -> - handleEndorse(elem, !endorsed) - - handleOpenClose = (elem, text) -> - url = Discussion.urlFor('openclose_thread', id) - closed = undefined - if text.match(/Close/) - closed = true - else if text.match(/[Oo]pen/) - closed = false - else - console.log "Unexpected text " + text + "for open/close thread." - - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: "json" - data: {closed: closed} - success: (response, textStatus) => - if textStatus == "success" - if closed - $(content).addClass("closed") - $(elem).text "Re-open Thread" - else - $(content).removeClass("closed") - $(elem).text "Close Thread" - error: (response, textStatus, e) -> - console.log e - - handleDelete = (elem) -> - if $content.hasClass("thread") - url = Discussion.urlFor('delete_thread', id) - c = confirm "Are you sure to delete thread \"" + $content.find("a.thread-title").text() + "\"?" - else - url = Discussion.urlFor('delete_comment', id) - c = confirm "Are you sure to delete this comment? " - if c != true - return - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: "json" - data: {} - success: (response, textStatus) => - if textStatus == "success" - $(content).remove() - error: (response, textStatus, e) -> - console.log e - - handleHideSingleThread = (elem) -> - $threadTitle = $local(".thread-title") - $hideComments = $local(".discussion-hide-comments") - $hideComments.removeClass("discussion-hide-comments") - .addClass("discussion-show-comments") - $content.children(".comments").hide() - $threadTitle.unbind('click').click handleShowSingleThread - $hideComments.unbind('click').click handleShowSingleThread - prevHtml = $hideComments.html() - $hideComments.html prevHtml.replace "Hide", "Show" - - handleShowSingleThread = -> - $threadTitle = $local(".thread-title") - $showComments = $local(".discussion-show-comments") - - if not $showComments.hasClass("first-time") and (not $showComments.length or not $threadTitle.length) - return - - rebindHideEvents = -> - $threadTitle.unbind('click').click handleHideSingleThread - $showComments.unbind('click').click handleHideSingleThread - $showComments.removeClass("discussion-show-comments") - .addClass("discussion-hide-comments") - prevHtml = $showComments.html() - $showComments.html prevHtml.replace "Show", "Hide" - - - if not $showComments.hasClass("first-time") and $content.children(".comments").length - $content.children(".comments").show() - rebindHideEvents() - else - discussion_id = $threadTitle.parents(".discussion").attr("_id") - url = Discussion.urlFor('retrieve_single_thread', discussion_id, id) - Discussion.safeAjax - $elem: $.merge($threadTitle, $showComments) - url: url - type: "GET" - dataType: 'json' - success: (response, textStatus) -> - Discussion.bulkExtendContentInfo response['annotated_content_info'] - $content.append(response['html']) - $content.find(".comment").each (index, comment) -> - Discussion.initializeContent(comment) - Discussion.bindContentEvents(comment) - $showComments.removeClass("first-time") - rebindHideEvents() - - Discussion.bindLocalEvents $local, - - "click .thread-title": -> - handleShowSingleThread(this) - - "click .discussion-show-comments": -> - handleShowSingleThread(this) - - "click .discussion-hide-comments": -> - handleHideSingleThread(this) - - "click .discussion-reply-thread": -> - handleShowSingleThread($local(".thread-title")) - handleReply(this) - - "click .discussion-reply-comment": -> - handleReply(this) - - "click .discussion-cancel-reply": -> - handleCancelReply(this) - - "click .discussion-vote-up": -> - $elem = $(this) - if $elem.hasClass("voted") - handleUnvote($elem) - else - handleVote($elem, "up") - - "click .discussion-vote-down": -> - $elem = $(this) - if $elem.hasClass("voted") - handleUnvote($elem) - else - handleVote($elem, "down") - - "click .admin-endorse": -> - handleEndorse(this, not $content.hasClass("endorsed")) - - "click .admin-openclose": -> - handleOpenClose(this, $(this).text()) - - "click .admin-edit": -> - if $content.hasClass("thread") - handleEditThread(this) - else - handleEditComment(this) - - "click .admin-delete": -> - handleDelete(this) - - initializeContent: (content) -> - - unescapeHighlightTag = (text) -> - text.replace(/\<\;highlight\>\;/g, "") - .replace(/\<\;\/highlight\>\;/g, "") - - stripHighlight = (text, type) -> - text.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "") - .replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "") - - - stripLatexHighlight = (text) -> - Discussion.processEachMathAndCode text, stripHighlight - - markdownWithHighlight = (text) -> - converter = Markdown.getMathCompatibleConverter() - unescapeHighlightTag stripLatexHighlight converter.makeHtml text - - $content = $(content) - initializeVote $content - if $content.hasClass("thread") - initializeFollowThread $content - $local = Discussion.generateLocal($content.children(".discussion-content")) - - $contentTitle = $local(".thread-title") - - if $contentTitle.length - $contentTitle.html unescapeHighlightTag stripLatexHighlight $contentTitle.html() - - $contentBody = $local(".content-body") - - $contentBody.html Discussion.postMathJaxProcessor markdownWithHighlight $contentBody.html() - - MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")] - id = $content.attr("_id") - - if $content.hasClass("thread") - discussion_id = $content.attr("_discussion_id") - permalink = Discussion.urlFor("permanent_link_thread", discussion_id, id) - else - thread_id = $content.parents(".thread").attr("_id") - discussion_id = $content.parents(".thread").attr("_discussion_id") - permalink = Discussion.urlFor("permanent_link_comment", discussion_id, thread_id, id) - $local(".discussion-permanent-link").attr "href", permalink - - if not Discussion.getContentInfo id, 'editable' - $local(".admin-edit").remove() - if not Discussion.getContentInfo id, 'can_reply' - $local(".discussion-reply").remove() - if not Discussion.getContentInfo id, 'can_endorse' - $local(".admin-endorse").remove() - if not Discussion.getContentInfo id, 'can_delete' - $local(".admin-delete").remove() - if not Discussion.getContentInfo id, 'can_openclose' - $local(".admin-openclose").remove() - #if not Discussion.getContentInfo id, 'can_vote' - # $local(".discussion-vote").css "visibility", "hidden" + window.Content = Content + window.Thread = Thread diff --git a/lms/static/coffee/src/discussion/discussion.coffee b/lms/static/coffee/src/discussion/discussion.coffee index 8e98ac3110..d67da04e0e 100644 --- a/lms/static/coffee/src/discussion/discussion.coffee +++ b/lms/static/coffee/src/discussion/discussion.coffee @@ -1,190 +1,31 @@ -if not @Discussion? - @Discussion = {} +$ -> + + class Discussion extends Backbone.Collection + model: Thread + initialize: -> + this.bind "add", (item) => + item.collection = this -Discussion = @Discussion + class DiscussionModuleView extends Backbone.View -initializeFollowDiscussion = (discussion) -> - $discussion = $(discussion) - id = $following.attr("_id") - $local = Discussion.generateLocal() - $discussion.children(".discussion-non-content") - .find(".discussion-title-wrapper") - .append(Discussion.subscriptionLink('discussion', id)) + class DiscussionView extends Backbone.View -@Discussion = $.extend @Discussion, + $: (selector) -> + @$local.find(selector) - initializeDiscussion: (discussion) -> - $discussion = $(discussion) - $discussion.find(".thread").each (index, thread) -> - Discussion.initializeContent(thread) - Discussion.bindContentEvents(thread) - $discussion.find(".comment").each (index, comment) -> - Discussion.initializeContent(comment) - Discussion.bindContentEvents(comment) + initialize: -> + @$local = @$el.children(".local") - #initializeFollowDiscussion(discussion) TODO move this somewhere else + events: + "submit .search-wrapper>.discussion-search-form": "search" + "click .discussion-search-link": "search" + "click .discussion-sort-link": "sort" + "click .discussion-paginator>.discussion-page-link": "page" + + $(".discussion-module").each (index, elem) -> + view = new DiscussionModuleView(el: elem) - bindDiscussionEvents: (discussion) -> - - $discussion = $(discussion) - $discussionNonContent = $discussion.children(".discussion-non-content") - $local = Discussion.generateLocal($discussion.children(".discussion-local")) - - id = $discussion.attr("_id") - - handleSubmitNewPost = (elem) -> - title = $local(".new-post-title").val() - body = Discussion.getWmdContent $discussion, $local, "new-post-body" - tags = $local(".new-post-tags").val() - url = Discussion.urlFor('create_thread', id) - Discussion.safeAjax - $elem: $(elem) - url: url - type: "POST" - dataType: 'json' - data: - title: title - body: body - tags: tags - error: Discussion.formErrorHandler($local(".new-post-form-errors")) - success: (response, textStatus) -> - Discussion.clearFormErrors($local(".new-post-form-errors")) - $thread = $(response.html) - $discussion.children(".threads").prepend($thread) - $local(".new-post-title").val("") - Discussion.setWmdContent $discussion, $local, "new-post-body", "" - $local(".new-post-tags").val("") - if $discussion.hasClass("inline-discussion") - $local(".new-post-form").addClass("collapsed") - else if $discussion.hasClass("forum-discussion") - $local(".new-post-form").hide() - - handleCancelNewPost = (elem) -> - if $discussion.hasClass("inline-discussion") - $local(".new-post-form").addClass("collapsed") - else if $discussion.hasClass("forum-discussion") - $local(".new-post-form").hide() - - handleSimilarPost = (elem) -> - $title = $local(".new-post-title") - $wrapper = $local(".new-post-similar-posts-wrapper") - $similarPosts = $local(".new-post-similar-posts") - prevText = $title.attr("prev-text") - text = $title.val() - if text == prevText - if $local(".similar-post").length - $wrapper.show() - else if $.trim(text).length - Discussion.safeAjax - $elem: $(elem) - url: Discussion.urlFor 'search_similar_threads', id - type: "GET" - dateType: 'json' - data: - text: $local(".new-post-title").val() - success: (response, textStatus) -> - $similarPosts.empty() - console.log response - if $.type(response) == "array" and response.length - $wrapper.show() - for thread in response - #singleThreadUrl = Discussion.urlFor 'retrieve_single_thread - $similarPost = $("").addClass("similar-post") - .html(thread["title"]) - .attr("href", "javascript:void(0)") #TODO - .appendTo($similarPosts) - else - $wrapper.hide() - else - $wrapper.hide() - $title.attr("prev-text", text) - - initializeNewPost = -> - view = { discussion_id: id } - $discussionNonContent = $discussion.children(".discussion-non-content") - - if not $local(".wmd-panel").length - $discussionNonContent.append Mustache.render Discussion.newPostTemplate, view - $newPostBody = $local(".new-post-body") - Discussion.makeWmdEditor $discussion, $local, "new-post-body" - - $input = Discussion.getWmdInput($discussion, $local, "new-post-body") - $input.attr("placeholder", "post a new topic...") - if $discussion.hasClass("inline-discussion") - $input.bind 'focus', (e) -> - $local(".new-post-form").removeClass('collapsed') - else if $discussion.hasClass("forum-discussion") - $local(".new-post-form").removeClass('collapsed') - - $local(".new-post-tags").tagsInput Discussion.tagsInputOptions() - - $local(".new-post-title").blur -> - handleSimilarPost(this) - - $local(".hide-similar-posts").click -> - $local(".new-post-similar-posts-wrapper").hide() - - $local(".discussion-submit-post").click -> - handleSubmitNewPost(this) - $local(".discussion-cancel-post").click -> - handleCancelNewPost(this) - - $local(".new-post-form").show() - - handleAjaxReloadDiscussion = (elem, url) -> - if not url then return - $elem = $(elem) - $discussion = $elem.parents("section.discussion") - Discussion.safeAjax - $elem: $elem - url: url - type: "GET" - dataType: 'html' - success: (data, textStatus) -> - $data = $(data) - $parent = $discussion.parent() - $discussion.replaceWith($data) - $discussion = $parent.children(".discussion") - Discussion.initializeDiscussion($discussion) - Discussion.bindDiscussionEvents($discussion) - - handleAjaxSearch = (elem) -> - $elem = $(elem) - url = URI($elem.attr("action")).addSearch({text: $local(".search-input").val()}) - handleAjaxReloadDiscussion($elem, url) - - handleAjaxSort = (elem) -> - $elem = $(elem) - url = $elem.attr("sort-url") - handleAjaxReloadDiscussion($elem, url) - - handleAjaxPage = (elem) -> - $elem = $(elem) - url = $elem.attr("page-url") - handleAjaxReloadDiscussion($elem, url) - - if $discussion.hasClass("inline-discussion") - initializeNewPost() - - if $discussion.hasClass("forum-discussion") - $discussionSidebar = $(".discussion-sidebar") - if $discussionSidebar.length - $sidebarLocal = Discussion.generateLocal($discussionSidebar) - Discussion.bindLocalEvents $sidebarLocal, - "click .sidebar-new-post-button": (event) -> - initializeNewPost() - - Discussion.bindLocalEvents $local, - - "submit .search-wrapper>.discussion-search-form": (event) -> - event.preventDefault() - handleAjaxSearch(this) - - "click .discussion-search-link": -> - handleAjaxSearch($local(".search-wrapper>.discussion-search-form")) - - "click .discussion-sort-link": -> - handleAjaxSort(this) - - $discussion.children(".discussion-paginator").find(".discussion-page-link").unbind('click').click -> - handleAjaxPage(this) + $("section.discussion").each (index, elem) -> + discussionData = DiscussionUtil.getDiscussionData(elem) + discussion = new Discussion(discussionData) + view = new DiscussionView(el: elem, model: discussion) diff --git a/lms/static/coffee/src/discussion/utils.coffee b/lms/static/coffee/src/discussion/utils.coffee index c034b79675..09bac7a335 100644 --- a/lms/static/coffee/src/discussion/utils.coffee +++ b/lms/static/coffee/src/discussion/utils.coffee @@ -1,244 +1,7 @@ -if not @Discussion? - @Discussion = {} - -Discussion = @Discussion - -wmdEditors = {} - -@Discussion = $.extend @Discussion, - - generateLocal: (elem) -> - (selector) -> $(elem).find(selector) - - generateDiscussionLink: (cls, txt, handler) -> - $("").addClass("discussion-link") - .attr("href", "javascript:void(0)") - .addClass(cls).html(txt) - .click -> handler(this) - - urlFor: (name, param, param1, param2) -> - { - follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow" - unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow" - create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" - search_similar_threads : "/courses/#{$$course_id}/discussion/#{param}/threads/search_similar" - update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update" - create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply" - delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete" - upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote" - downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote" - undo_vote_for_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unvote" - follow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/follow" - unfollow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unfollow" - update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update" - endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse" - create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply" - delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete" - upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote" - downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote" - undo_vote_for_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/unvote" - upload : "/courses/#{$$course_id}/discussion/upload" - search : "/courses/#{$$course_id}/discussion/forum/search" - tags_autocomplete : "/courses/#{$$course_id}/discussion/threads/tags/autocomplete" - retrieve_discussion : "/courses/#{$$course_id}/discussion/forum/#{param}/inline" - retrieve_single_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}" - update_moderator_status : "/courses/#{$$course_id}/discussion/users/#{param}/update_moderator_status" - openclose_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/close" - permanent_link_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}" - permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}" - }[name] - - safeAjax: (params) -> - $elem = params.$elem - if $elem.attr("disabled") - return - $elem.attr("disabled", "disabled") - $.ajax(params).always -> - $elem.removeAttr("disabled") - - handleAnchorAndReload: (response) -> - #window.location = window.location.pathname + "#" + response['id'] - window.location.reload() - - bindLocalEvents: ($local, eventsHandler) -> - for eventSelector, handler of eventsHandler - [event, selector] = eventSelector.split(' ') - $local(selector).unbind(event)[event] handler - - tagsInputOptions: -> - autocomplete_url: Discussion.urlFor('tags_autocomplete') - autocomplete: - remoteDataType: 'json' - interactive: true - height: '30px' - width: '100%' - defaultText: "Tag your post: press enter after each tag" - removeWithBackspace: true - - isSubscribed: (id, type) -> - $$user_info? and ( - if type == "thread" - id in $$user_info.subscribed_thread_ids - else if type == "commentable" or type == "discussion" - id in $$user_info.subscribed_commentable_ids - else - id in $$user_info.subscribed_user_ids - ) - - isUpvoted: (id) -> - $$user_info? and (id in $$user_info.upvoted_ids) - - isDownvoted: (id) -> - $$user_info? and (id in $$user_info.downvoted_ids) - - formErrorHandler: (errorsField) -> - (xhr, textStatus, error) -> - response = JSON.parse(xhr.responseText) - if response.errors? and response.errors.length > 0 - errorsField.empty() - for error in response.errors - errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) - - clearFormErrors: (errorsField) -> - errorsField.empty() - - postMathJaxProcessor: (text) -> - RE_INLINEMATH = /^\$([^\$]*)\$/g - RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g - Discussion.processEachMathAndCode text, (s, type) -> - if type == 'display' - s.replace RE_DISPLAYMATH, ($0, $1) -> - "\\[" + $1 + "\\]" - else if type == 'inline' - s.replace RE_INLINEMATH, ($0, $1) -> - "\\(" + $1 + "\\)" - else - s - - makeWmdEditor: ($content, $local, cls_identifier) -> - elem = $local(".#{cls_identifier}") - id = $content.attr("_id") - appended_id = "-#{cls_identifier}-#{id}" - imageUploadUrl = Discussion.urlFor('upload') - editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor - wmdEditors["#{cls_identifier}-#{id}"] = editor - editor - - getWmdEditor: ($content, $local, cls_identifier) -> - id = $content.attr("_id") - wmdEditors["#{cls_identifier}-#{id}"] - - getWmdInput: ($content, $local, cls_identifier) -> - id = $content.attr("_id") - $local("#wmd-input-#{cls_identifier}-#{id}") - - getWmdContent: ($content, $local, cls_identifier) -> - Discussion.getWmdInput($content, $local, cls_identifier).val() - - setWmdContent: ($content, $local, cls_identifier, text) -> - Discussion.getWmdInput($content, $local, cls_identifier).val(text) - Discussion.getWmdEditor($content, $local, cls_identifier).refreshPreview() - - getContentInfo: (id, attr) -> - if not window.$$annotated_content_info? - window.$$annotated_content_info = {} - (window.$$annotated_content_info[id] || {})[attr] - - setContentInfo: (id, attr, value) -> - if not window.$$annotated_content_info? - window.$$annotated_content_info = {} - window.$$annotated_content_info[id] ||= {} - window.$$annotated_content_info[id][attr] = value - - extendContentInfo: (id, newInfo) -> - if not window.$$annotated_content_info? - window.$$annotated_content_info = {} - window.$$annotated_content_info[id] = newInfo - bulkExtendContentInfo: (newInfos) -> - if not window.$$annotated_content_info? - window.$$annotated_content_info = {} - window.$$annotated_content_info = $.extend window.$$annotated_content_info, newInfos - - subscriptionLink: (type, id) -> - followLink = -> - Discussion.generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow) - - unfollowLink = -> - Discussion.generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow) - - handleFollow = (elem) -> - Discussion.safeAjax - $elem: $(elem) - url: Discussion.urlFor("follow_#{type}", id) - type: "POST" - success: (response, textStatus) -> - if textStatus == "success" - $(elem).replaceWith unfollowLink() - dataType: 'json' - - handleUnfollow = (elem) -> - Discussion.safeAjax - $elem: $(elem) - url: Discussion.urlFor("unfollow_#{type}", id) - type: "POST" - success: (response, textStatus) -> - if textStatus == "success" - $(elem).replaceWith followLink() - dataType: 'json' - - if Discussion.isSubscribed(id, type) - unfollowLink() - else - followLink() - - processEachMathAndCode: (text, processor) -> - - codeArchive = [] - - RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m - RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m - - ESCAPED_DOLLAR = '@@ESCAPED_D@@' - ESCAPED_BACKSLASH = '@@ESCAPED_B@@' - - processedText = "" - - $div = $("
    ").html(text) - - $div.find("code").each (index, code) -> - codeArchive.push $(code).html() - $(code).html(codeArchive.length - 1) - - text = $div.html() - text = text.replace /\\\$/g, ESCAPED_DOLLAR - - while true - if RE_INLINEMATH.test(text) - text = text.replace RE_INLINEMATH, ($0, $1, $2, $3) -> - processedText += $1 + processor("$" + $2 + "$", 'inline') - $3 - else if RE_DISPLAYMATH.test(text) - text = text.replace RE_DISPLAYMATH, ($0, $1, $2, $3) -> - processedText += $1 + processor("$$" + $2 + "$$", 'display') - $3 - else - processedText += text - break - - text = processedText - text = text.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$') - - text = text.replace /\\\\\\\\/g, ESCAPED_BACKSLASH - text = text.replace /\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, ($0, $1, $2) -> - processor("\\begin{#{$1}}" + $2 + "\\end{#{$1}}") - text = text.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\') - - $div = $("
    ").html(text) - cnt = 0 - $div.find("code").each (index, code) -> - $(code).html(processor(codeArchive[cnt], 'code')) - cnt += 1 - - text = $div.html() - - text +class @DiscussionUtil + @getDiscussionData: (id) -> + if id instanceof $ + id = id.attr("_id") + else if typeof id == "object" + id = $(id).attr("_id") + return $$discussion_data[id] diff --git a/lms/static/coffee/src/old_discussion/content.coffee b/lms/static/coffee/src/old_discussion/content.coffee new file mode 100644 index 0000000000..9924e540a2 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/content.coffee @@ -0,0 +1,409 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +initializeVote = (content) -> + $content = $(content) + $local = Discussion.generateLocal($content.children(".discussion-content")) + id = $content.attr("_id") + if Discussion.isUpvoted id + $local(".discussion-vote-up").addClass("voted") + else if Discussion.isDownvoted id + $local(".discussion-vote-down").addClass("voted") + +initializeFollowThread = (thread) -> + $thread = $(thread) + id = $thread.attr("_id") + $thread.children(".discussion-content") + .find(".follow-wrapper") + .append(Discussion.subscriptionLink('thread', id)) + +@Discussion = $.extend @Discussion, + + bindContentEvents: (content) -> + + $content = $(content) + $discussionContent = $content.children(".discussion-content") + $local = Discussion.generateLocal($discussionContent) + + id = $content.attr("_id") + + handleReply = (elem) -> + $replyView = $local(".discussion-reply-new") + if $replyView.length + $replyView.show() + else + thread_id = $discussionContent.parents(".thread").attr("_id") + view = + id: id + showWatchCheckbox: not Discussion.isSubscribed(thread_id, "thread") + $discussionContent.append Mustache.render Discussion.replyTemplate, view + Discussion.makeWmdEditor $content, $local, "reply-body" + $local(".discussion-submit-post").click -> handleSubmitReply(this) + $local(".discussion-cancel-post").click -> handleCancelReply(this) + $local(".discussion-reply").hide() + $local(".discussion-edit").hide() + + handleCancelReply = (elem) -> + $replyView = $local(".discussion-reply-new") + if $replyView.length + $replyView.hide() + $local(".discussion-reply").show() + $local(".discussion-edit").show() + + handleSubmitReply = (elem) -> + if $content.hasClass("thread") + url = Discussion.urlFor('create_comment', id) + else if $content.hasClass("comment") + url = Discussion.urlFor('create_sub_comment', id) + else + return + + body = Discussion.getWmdContent $content, $local, "reply-body" + + anonymous = false || $local(".discussion-post-anonymously").is(":checked") + autowatch = false || $local(".discussion-auto-watch").is(":checked") + + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: 'json' + data: + body: body + anonymous: anonymous + autowatch: autowatch + error: Discussion.formErrorHandler($local(".discussion-errors")) + success: (response, textStatus) -> + Discussion.clearFormErrors($local(".discussion-errors")) + $comment = $(response.html) + $content.children(".comments").prepend($comment) + Discussion.setWmdContent $content, $local, "reply-body", "" + Discussion.setContentInfo response.content['id'], 'can_reply', true + Discussion.setContentInfo response.content['id'], 'editable', true + Discussion.extendContentInfo response.content['id'], response['annotated_content_info'] + Discussion.initializeContent($comment) + Discussion.bindContentEvents($comment) + $local(".discussion-reply-new").hide() + $local(".discussion-reply").show() + $local(".discussion-edit").show() + $discussionContent.attr("status", "normal") + + handleVote = (elem, value) -> + contentType = if $content.hasClass("thread") then "thread" else "comment" + url = Discussion.urlFor("#{value}vote_#{contentType}", id) + Discussion.safeAjax + $elem: $local(".discussion-vote") + url: url + type: "POST" + dataType: "json" + success: (response, textStatus) -> + if textStatus == "success" + $local(".discussion-vote").removeClass("voted") + $local(".discussion-vote-#{value}").addClass("voted") + $local(".discussion-votes-point").html response.votes.point + + handleUnvote = (elem, value) -> + contentType = if $content.hasClass("thread") then "thread" else "comment" + url = Discussion.urlFor("undo_vote_for_#{contentType}", id) + Discussion.safeAjax + $elem: $local(".discussion-vote") + url: url + type: "POST" + dataType: "json" + success: (response, textStatus) -> + if textStatus == "success" + $local(".discussion-vote").removeClass("voted") + $local(".discussion-votes-point").html response.votes.point + + handleCancelEdit = (elem) -> + $local(".discussion-content-edit").hide() + $local(".discussion-content-wrapper").show() + + handleEditThread = (elem) -> + $local(".discussion-content-wrapper").hide() + $editView = $local(".discussion-content-edit") + if $editView.length + $editView.show() + else + view = { + id: id + title: $local(".thread-raw-title").html() + body: $local(".thread-raw-body").html() + tags: $local(".thread-raw-tags").html() + } + $discussionContent.append Mustache.render Discussion.editThreadTemplate, view + Discussion.makeWmdEditor $content, $local, "thread-body-edit" + $local(".thread-tags-edit").tagsInput Discussion.tagsInputOptions() + $local(".discussion-submit-update").unbind("click").click -> handleSubmitEditThread(this) + $local(".discussion-cancel-update").unbind("click").click -> handleCancelEdit(this) + + handleSubmitEditThread = (elem) -> + url = Discussion.urlFor('update_thread', id) + title = $local(".thread-title-edit").val() + body = Discussion.getWmdContent $content, $local, "thread-body-edit" + tags = $local(".thread-tags-edit").val() + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: 'json' + data: {title: title, body: body, tags: tags}, + error: Discussion.formErrorHandler($local(".discussion-update-errors")) + success: (response, textStatus) -> + Discussion.clearFormErrors($local(".discussion-update-errors")) + $discussionContent.replaceWith(response.html) + Discussion.extendContentInfo response.content['id'], response['annotated_content_info'] + Discussion.initializeContent($content) + Discussion.bindContentEvents($content) + + handleEditComment = (elem) -> + $local(".discussion-content-wrapper").hide() + $editView = $local(".discussion-content-edit") + if $editView.length + $editView.show() + else + view = { id: id, body: $local(".comment-raw-body").html() } + $discussionContent.append Mustache.render Discussion.editCommentTemplate, view + Discussion.makeWmdEditor $content, $local, "comment-body-edit" + $local(".discussion-submit-update").unbind("click").click -> handleSubmitEditComment(this) + $local(".discussion-cancel-update").unbind("click").click -> handleCancelEdit(this) + + handleSubmitEditComment= (elem) -> + url = Discussion.urlFor('update_comment', id) + body = Discussion.getWmdContent $content, $local, "comment-body-edit" + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: "json" + data: {body: body} + error: Discussion.formErrorHandler($local(".discussion-update-errors")) + success: (response, textStatus) -> + Discussion.clearFormErrors($local(".discussion-update-errors")) + $discussionContent.replaceWith(response.html) + Discussion.extendContentInfo response.content['id'], response['annotated_content_info'] + Discussion.initializeContent($content) + Discussion.bindContentEvents($content) + + handleEndorse = (elem, endorsed) -> + url = Discussion.urlFor('endorse_comment', id) + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: "json" + data: {endorsed: endorsed} + success: (response, textStatus) -> + if textStatus == "success" + if endorsed + $(content).addClass("endorsed") + else + $(content).removeClass("endorsed") + + $(elem).unbind('click').click -> + handleEndorse(elem, !endorsed) + + handleOpenClose = (elem, text) -> + url = Discussion.urlFor('openclose_thread', id) + closed = undefined + if text.match(/Close/) + closed = true + else if text.match(/[Oo]pen/) + closed = false + else + console.log "Unexpected text " + text + "for open/close thread." + + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: "json" + data: {closed: closed} + success: (response, textStatus) => + if textStatus == "success" + if closed + $(content).addClass("closed") + $(elem).text "Re-open Thread" + else + $(content).removeClass("closed") + $(elem).text "Close Thread" + error: (response, textStatus, e) -> + console.log e + + handleDelete = (elem) -> + if $content.hasClass("thread") + url = Discussion.urlFor('delete_thread', id) + c = confirm "Are you sure to delete thread \"" + $content.find("a.thread-title").text() + "\"?" + else + url = Discussion.urlFor('delete_comment', id) + c = confirm "Are you sure to delete this comment? " + if c != true + return + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: "json" + data: {} + success: (response, textStatus) => + if textStatus == "success" + $(content).remove() + error: (response, textStatus, e) -> + console.log e + + handleHideSingleThread = (elem) -> + $threadTitle = $local(".thread-title") + $hideComments = $local(".discussion-hide-comments") + $hideComments.removeClass("discussion-hide-comments") + .addClass("discussion-show-comments") + $content.children(".comments").hide() + $threadTitle.unbind('click').click handleShowSingleThread + $hideComments.unbind('click').click handleShowSingleThread + prevHtml = $hideComments.html() + $hideComments.html prevHtml.replace "Hide", "Show" + + handleShowSingleThread = -> + $threadTitle = $local(".thread-title") + $showComments = $local(".discussion-show-comments") + + if not $showComments.hasClass("first-time") and (not $showComments.length or not $threadTitle.length) + return + + rebindHideEvents = -> + $threadTitle.unbind('click').click handleHideSingleThread + $showComments.unbind('click').click handleHideSingleThread + $showComments.removeClass("discussion-show-comments") + .addClass("discussion-hide-comments") + prevHtml = $showComments.html() + $showComments.html prevHtml.replace "Show", "Hide" + + + if not $showComments.hasClass("first-time") and $content.children(".comments").length + $content.children(".comments").show() + rebindHideEvents() + else + discussion_id = $threadTitle.parents(".discussion").attr("_id") + url = Discussion.urlFor('retrieve_single_thread', discussion_id, id) + Discussion.safeAjax + $elem: $.merge($threadTitle, $showComments) + url: url + type: "GET" + dataType: 'json' + success: (response, textStatus) -> + Discussion.bulkExtendContentInfo response['annotated_content_info'] + $content.append(response['html']) + $content.find(".comment").each (index, comment) -> + Discussion.initializeContent(comment) + Discussion.bindContentEvents(comment) + $showComments.removeClass("first-time") + rebindHideEvents() + + Discussion.bindLocalEvents $local, + + "click .thread-title": -> + handleShowSingleThread(this) + + "click .discussion-show-comments": -> + handleShowSingleThread(this) + + "click .discussion-hide-comments": -> + handleHideSingleThread(this) + + "click .discussion-reply-thread": -> + handleShowSingleThread($local(".thread-title")) + handleReply(this) + + "click .discussion-reply-comment": -> + handleReply(this) + + "click .discussion-cancel-reply": -> + handleCancelReply(this) + + "click .discussion-vote-up": -> + $elem = $(this) + if $elem.hasClass("voted") + handleUnvote($elem) + else + handleVote($elem, "up") + + "click .discussion-vote-down": -> + $elem = $(this) + if $elem.hasClass("voted") + handleUnvote($elem) + else + handleVote($elem, "down") + + "click .admin-endorse": -> + handleEndorse(this, not $content.hasClass("endorsed")) + + "click .admin-openclose": -> + handleOpenClose(this, $(this).text()) + + "click .admin-edit": -> + if $content.hasClass("thread") + handleEditThread(this) + else + handleEditComment(this) + + "click .admin-delete": -> + handleDelete(this) + + initializeContent: (content) -> + + unescapeHighlightTag = (text) -> + text.replace(/\<\;highlight\>\;/g, "") + .replace(/\<\;\/highlight\>\;/g, "") + + stripHighlight = (text, type) -> + text.replace(/\&(amp\;)?lt\;highlight\&(amp\;)?gt\;/g, "") + .replace(/\&(amp\;)?lt\;\/highlight\&(amp\;)?gt\;/g, "") + + + stripLatexHighlight = (text) -> + Discussion.processEachMathAndCode text, stripHighlight + + markdownWithHighlight = (text) -> + converter = Markdown.getMathCompatibleConverter() + unescapeHighlightTag stripLatexHighlight converter.makeHtml text + + $content = $(content) + initializeVote $content + if $content.hasClass("thread") + initializeFollowThread $content + $local = Discussion.generateLocal($content.children(".discussion-content")) + + $contentTitle = $local(".thread-title") + + if $contentTitle.length + $contentTitle.html unescapeHighlightTag stripLatexHighlight $contentTitle.html() + + $contentBody = $local(".content-body") + + $contentBody.html Discussion.postMathJaxProcessor markdownWithHighlight $contentBody.html() + + MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")] + id = $content.attr("_id") + + if $content.hasClass("thread") + discussion_id = $content.attr("_discussion_id") + permalink = Discussion.urlFor("permanent_link_thread", discussion_id, id) + else + thread_id = $content.parents(".thread").attr("_id") + discussion_id = $content.parents(".thread").attr("_discussion_id") + permalink = Discussion.urlFor("permanent_link_comment", discussion_id, thread_id, id) + $local(".discussion-permanent-link").attr "href", permalink + + if not Discussion.getContentInfo id, 'editable' + $local(".admin-edit").remove() + if not Discussion.getContentInfo id, 'can_reply' + $local(".discussion-reply").remove() + if not Discussion.getContentInfo id, 'can_endorse' + $local(".admin-endorse").remove() + if not Discussion.getContentInfo id, 'can_delete' + $local(".admin-delete").remove() + if not Discussion.getContentInfo id, 'can_openclose' + $local(".admin-openclose").remove() + #if not Discussion.getContentInfo id, 'can_vote' + # $local(".discussion-vote").css "visibility", "hidden" diff --git a/lms/static/coffee/src/old_discussion/discussion.coffee b/lms/static/coffee/src/old_discussion/discussion.coffee new file mode 100644 index 0000000000..8e98ac3110 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/discussion.coffee @@ -0,0 +1,190 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +initializeFollowDiscussion = (discussion) -> + $discussion = $(discussion) + id = $following.attr("_id") + $local = Discussion.generateLocal() + $discussion.children(".discussion-non-content") + .find(".discussion-title-wrapper") + .append(Discussion.subscriptionLink('discussion', id)) + +@Discussion = $.extend @Discussion, + + initializeDiscussion: (discussion) -> + $discussion = $(discussion) + $discussion.find(".thread").each (index, thread) -> + Discussion.initializeContent(thread) + Discussion.bindContentEvents(thread) + $discussion.find(".comment").each (index, comment) -> + Discussion.initializeContent(comment) + Discussion.bindContentEvents(comment) + + #initializeFollowDiscussion(discussion) TODO move this somewhere else + + bindDiscussionEvents: (discussion) -> + + $discussion = $(discussion) + $discussionNonContent = $discussion.children(".discussion-non-content") + $local = Discussion.generateLocal($discussion.children(".discussion-local")) + + id = $discussion.attr("_id") + + handleSubmitNewPost = (elem) -> + title = $local(".new-post-title").val() + body = Discussion.getWmdContent $discussion, $local, "new-post-body" + tags = $local(".new-post-tags").val() + url = Discussion.urlFor('create_thread', id) + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: 'json' + data: + title: title + body: body + tags: tags + error: Discussion.formErrorHandler($local(".new-post-form-errors")) + success: (response, textStatus) -> + Discussion.clearFormErrors($local(".new-post-form-errors")) + $thread = $(response.html) + $discussion.children(".threads").prepend($thread) + $local(".new-post-title").val("") + Discussion.setWmdContent $discussion, $local, "new-post-body", "" + $local(".new-post-tags").val("") + if $discussion.hasClass("inline-discussion") + $local(".new-post-form").addClass("collapsed") + else if $discussion.hasClass("forum-discussion") + $local(".new-post-form").hide() + + handleCancelNewPost = (elem) -> + if $discussion.hasClass("inline-discussion") + $local(".new-post-form").addClass("collapsed") + else if $discussion.hasClass("forum-discussion") + $local(".new-post-form").hide() + + handleSimilarPost = (elem) -> + $title = $local(".new-post-title") + $wrapper = $local(".new-post-similar-posts-wrapper") + $similarPosts = $local(".new-post-similar-posts") + prevText = $title.attr("prev-text") + text = $title.val() + if text == prevText + if $local(".similar-post").length + $wrapper.show() + else if $.trim(text).length + Discussion.safeAjax + $elem: $(elem) + url: Discussion.urlFor 'search_similar_threads', id + type: "GET" + dateType: 'json' + data: + text: $local(".new-post-title").val() + success: (response, textStatus) -> + $similarPosts.empty() + console.log response + if $.type(response) == "array" and response.length + $wrapper.show() + for thread in response + #singleThreadUrl = Discussion.urlFor 'retrieve_single_thread + $similarPost = $("").addClass("similar-post") + .html(thread["title"]) + .attr("href", "javascript:void(0)") #TODO + .appendTo($similarPosts) + else + $wrapper.hide() + else + $wrapper.hide() + $title.attr("prev-text", text) + + initializeNewPost = -> + view = { discussion_id: id } + $discussionNonContent = $discussion.children(".discussion-non-content") + + if not $local(".wmd-panel").length + $discussionNonContent.append Mustache.render Discussion.newPostTemplate, view + $newPostBody = $local(".new-post-body") + Discussion.makeWmdEditor $discussion, $local, "new-post-body" + + $input = Discussion.getWmdInput($discussion, $local, "new-post-body") + $input.attr("placeholder", "post a new topic...") + if $discussion.hasClass("inline-discussion") + $input.bind 'focus', (e) -> + $local(".new-post-form").removeClass('collapsed') + else if $discussion.hasClass("forum-discussion") + $local(".new-post-form").removeClass('collapsed') + + $local(".new-post-tags").tagsInput Discussion.tagsInputOptions() + + $local(".new-post-title").blur -> + handleSimilarPost(this) + + $local(".hide-similar-posts").click -> + $local(".new-post-similar-posts-wrapper").hide() + + $local(".discussion-submit-post").click -> + handleSubmitNewPost(this) + $local(".discussion-cancel-post").click -> + handleCancelNewPost(this) + + $local(".new-post-form").show() + + handleAjaxReloadDiscussion = (elem, url) -> + if not url then return + $elem = $(elem) + $discussion = $elem.parents("section.discussion") + Discussion.safeAjax + $elem: $elem + url: url + type: "GET" + dataType: 'html' + success: (data, textStatus) -> + $data = $(data) + $parent = $discussion.parent() + $discussion.replaceWith($data) + $discussion = $parent.children(".discussion") + Discussion.initializeDiscussion($discussion) + Discussion.bindDiscussionEvents($discussion) + + handleAjaxSearch = (elem) -> + $elem = $(elem) + url = URI($elem.attr("action")).addSearch({text: $local(".search-input").val()}) + handleAjaxReloadDiscussion($elem, url) + + handleAjaxSort = (elem) -> + $elem = $(elem) + url = $elem.attr("sort-url") + handleAjaxReloadDiscussion($elem, url) + + handleAjaxPage = (elem) -> + $elem = $(elem) + url = $elem.attr("page-url") + handleAjaxReloadDiscussion($elem, url) + + if $discussion.hasClass("inline-discussion") + initializeNewPost() + + if $discussion.hasClass("forum-discussion") + $discussionSidebar = $(".discussion-sidebar") + if $discussionSidebar.length + $sidebarLocal = Discussion.generateLocal($discussionSidebar) + Discussion.bindLocalEvents $sidebarLocal, + "click .sidebar-new-post-button": (event) -> + initializeNewPost() + + Discussion.bindLocalEvents $local, + + "submit .search-wrapper>.discussion-search-form": (event) -> + event.preventDefault() + handleAjaxSearch(this) + + "click .discussion-search-link": -> + handleAjaxSearch($local(".search-wrapper>.discussion-search-form")) + + "click .discussion-sort-link": -> + handleAjaxSort(this) + + $discussion.children(".discussion-paginator").find(".discussion-page-link").unbind('click').click -> + handleAjaxPage(this) diff --git a/lms/static/coffee/src/old_discussion/discussion_module.coffee b/lms/static/coffee/src/old_discussion/discussion_module.coffee new file mode 100644 index 0000000000..d449533ba9 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/discussion_module.coffee @@ -0,0 +1,42 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +@Discussion = $.extend @Discussion, + initializeDiscussionModule: (elem) -> + $discussionModule = $(elem) + $local = Discussion.generateLocal($discussionModule) + handleShowDiscussion = (elem) -> + $elem = $(elem) + if not $local("section.discussion").length + discussion_id = $elem.attr("discussion_id") + url = Discussion.urlFor 'retrieve_discussion', discussion_id + Discussion.safeAjax + $elem: $elem + url: url + type: "GET" + success: (data, textStatus, xhr) -> + $discussionModule.append(data) + discussion = $local("section.discussion") + Discussion.initializeDiscussion(discussion) + Discussion.bindDiscussionEvents(discussion) + $elem.html("Hide Discussion") + $elem.unbind('click').click -> + handleHideDiscussion(this) + dataType: 'html' + else + $local("section.discussion").show() + $elem.html("Hide Discussion") + $elem.unbind('click').click -> + handleHideDiscussion(this) + + handleHideDiscussion = (elem) -> + $local("section.discussion").hide() + $elem = $(elem) + $elem.html("Show Discussion") + $elem.unbind('click').click -> + handleShowDiscussion(this) + + $local(".discussion-show").click -> + handleShowDiscussion(this) diff --git a/lms/static/coffee/src/old_discussion/main.coffee b/lms/static/coffee/src/old_discussion/main.coffee new file mode 100644 index 0000000000..f9b11b72e3 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/main.coffee @@ -0,0 +1,23 @@ +$ -> + + #toggle = -> + # $('.course-wrapper').toggleClass('closed') + + #Discussion = window.Discussion + #if $('#accordion').length + # active = $('#accordion ul:has(li.active)').index('#accordion ul') + # $('#accordion').bind('accordionchange', @log).accordion + # active: if active >= 0 then active else 1 + # header: 'h3' + # autoHeight: false + # $('#open_close_accordion a').click toggle + # $('#accordion').show() + + #$(".discussion-module").each (index, elem) -> + # Discussion.initializeDiscussionModule(elem) + + #$("section.discussion").each (index, discussion) -> + # Discussion.initializeDiscussion(discussion) + # Discussion.bindDiscussionEvents(discussion) + + #Discussion.initializeUserProfile($(".discussion-sidebar>.user-profile")) diff --git a/lms/static/coffee/src/old_discussion/templates.coffee b/lms/static/coffee/src/old_discussion/templates.coffee new file mode 100644 index 0000000000..4d43442a16 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/templates.coffee @@ -0,0 +1,73 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + + +@Discussion = $.extend @Discussion, + + newPostTemplate: """ + + """ + + replyTemplate: """ +
    +
      +
      + + + {{#showWatchCheckbox}} + + + {{/showWatchCheckbox}} +
      +
      + Cancel + Submit +
      +
      + """ + + editThreadTemplate: """ +
      +
        + +
        {{body}}
        + +
        + Cancel + Update +
        +
        + """ + + editCommentTemplate: """ +
        +
          +
          {{body}}
          +
          + Cancel + Update +
          +
          + """ diff --git a/lms/static/coffee/src/old_discussion/user_profile.coffee b/lms/static/coffee/src/old_discussion/user_profile.coffee new file mode 100644 index 0000000000..0cff708ae6 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/user_profile.coffee @@ -0,0 +1,34 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +@Discussion = $.extend @Discussion, + initializeUserProfile: ($userProfile) -> + $local = Discussion.generateLocal $userProfile + + handleUpdateModeratorStatus = (elem, isModerator) -> + confirmValue = confirm("Are you sure?") + if not confirmValue then return + url = Discussion.urlFor('update_moderator_status', $$profiled_user_id) + Discussion.safeAjax + $elem: $(elem) + url: url + type: "POST" + dataType: 'json' + data: + is_moderator: isModerator + error: (response, textStatus, e) -> + console.log e + success: (response, textStatus) -> + parent = $userProfile.parent() + $userProfile.replaceWith(response.html) + Discussion.initializeUserProfile parent.children(".user-profile") + + Discussion.bindLocalEvents $local, + "click .sidebar-revoke-moderator-button": (event) -> + handleUpdateModeratorStatus(this, false) + "click .sidebar-promote-moderator-button": (event) -> + handleUpdateModeratorStatus(this, true) + + initializeUserActiveDiscussion: ($discussion) -> diff --git a/lms/static/coffee/src/old_discussion/utils.coffee b/lms/static/coffee/src/old_discussion/utils.coffee new file mode 100644 index 0000000000..c034b79675 --- /dev/null +++ b/lms/static/coffee/src/old_discussion/utils.coffee @@ -0,0 +1,244 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +wmdEditors = {} + +@Discussion = $.extend @Discussion, + + generateLocal: (elem) -> + (selector) -> $(elem).find(selector) + + generateDiscussionLink: (cls, txt, handler) -> + $("").addClass("discussion-link") + .attr("href", "javascript:void(0)") + .addClass(cls).html(txt) + .click -> handler(this) + + urlFor: (name, param, param1, param2) -> + { + follow_discussion : "/courses/#{$$course_id}/discussion/#{param}/follow" + unfollow_discussion : "/courses/#{$$course_id}/discussion/#{param}/unfollow" + create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" + search_similar_threads : "/courses/#{$$course_id}/discussion/#{param}/threads/search_similar" + update_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/update" + create_comment : "/courses/#{$$course_id}/discussion/threads/#{param}/reply" + delete_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/delete" + upvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/upvote" + downvote_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/downvote" + undo_vote_for_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unvote" + follow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/follow" + unfollow_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unfollow" + update_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/update" + endorse_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/endorse" + create_sub_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/reply" + delete_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/delete" + upvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/upvote" + downvote_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/downvote" + undo_vote_for_comment : "/courses/#{$$course_id}/discussion/comments/#{param}/unvote" + upload : "/courses/#{$$course_id}/discussion/upload" + search : "/courses/#{$$course_id}/discussion/forum/search" + tags_autocomplete : "/courses/#{$$course_id}/discussion/threads/tags/autocomplete" + retrieve_discussion : "/courses/#{$$course_id}/discussion/forum/#{param}/inline" + retrieve_single_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}" + update_moderator_status : "/courses/#{$$course_id}/discussion/users/#{param}/update_moderator_status" + openclose_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/close" + permanent_link_thread : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}" + permanent_link_comment : "/courses/#{$$course_id}/discussion/forum/#{param}/threads/#{param1}##{param2}" + }[name] + + safeAjax: (params) -> + $elem = params.$elem + if $elem.attr("disabled") + return + $elem.attr("disabled", "disabled") + $.ajax(params).always -> + $elem.removeAttr("disabled") + + handleAnchorAndReload: (response) -> + #window.location = window.location.pathname + "#" + response['id'] + window.location.reload() + + bindLocalEvents: ($local, eventsHandler) -> + for eventSelector, handler of eventsHandler + [event, selector] = eventSelector.split(' ') + $local(selector).unbind(event)[event] handler + + tagsInputOptions: -> + autocomplete_url: Discussion.urlFor('tags_autocomplete') + autocomplete: + remoteDataType: 'json' + interactive: true + height: '30px' + width: '100%' + defaultText: "Tag your post: press enter after each tag" + removeWithBackspace: true + + isSubscribed: (id, type) -> + $$user_info? and ( + if type == "thread" + id in $$user_info.subscribed_thread_ids + else if type == "commentable" or type == "discussion" + id in $$user_info.subscribed_commentable_ids + else + id in $$user_info.subscribed_user_ids + ) + + isUpvoted: (id) -> + $$user_info? and (id in $$user_info.upvoted_ids) + + isDownvoted: (id) -> + $$user_info? and (id in $$user_info.downvoted_ids) + + formErrorHandler: (errorsField) -> + (xhr, textStatus, error) -> + response = JSON.parse(xhr.responseText) + if response.errors? and response.errors.length > 0 + errorsField.empty() + for error in response.errors + errorsField.append($("
        • ").addClass("new-post-form-error").html(error)) + + clearFormErrors: (errorsField) -> + errorsField.empty() + + postMathJaxProcessor: (text) -> + RE_INLINEMATH = /^\$([^\$]*)\$/g + RE_DISPLAYMATH = /^\$\$([^\$]*)\$\$/g + Discussion.processEachMathAndCode text, (s, type) -> + if type == 'display' + s.replace RE_DISPLAYMATH, ($0, $1) -> + "\\[" + $1 + "\\]" + else if type == 'inline' + s.replace RE_INLINEMATH, ($0, $1) -> + "\\(" + $1 + "\\)" + else + s + + makeWmdEditor: ($content, $local, cls_identifier) -> + elem = $local(".#{cls_identifier}") + id = $content.attr("_id") + appended_id = "-#{cls_identifier}-#{id}" + imageUploadUrl = Discussion.urlFor('upload') + editor = Markdown.makeWmdEditor elem, appended_id, imageUploadUrl, Discussion.postMathJaxProcessor + wmdEditors["#{cls_identifier}-#{id}"] = editor + editor + + getWmdEditor: ($content, $local, cls_identifier) -> + id = $content.attr("_id") + wmdEditors["#{cls_identifier}-#{id}"] + + getWmdInput: ($content, $local, cls_identifier) -> + id = $content.attr("_id") + $local("#wmd-input-#{cls_identifier}-#{id}") + + getWmdContent: ($content, $local, cls_identifier) -> + Discussion.getWmdInput($content, $local, cls_identifier).val() + + setWmdContent: ($content, $local, cls_identifier, text) -> + Discussion.getWmdInput($content, $local, cls_identifier).val(text) + Discussion.getWmdEditor($content, $local, cls_identifier).refreshPreview() + + getContentInfo: (id, attr) -> + if not window.$$annotated_content_info? + window.$$annotated_content_info = {} + (window.$$annotated_content_info[id] || {})[attr] + + setContentInfo: (id, attr, value) -> + if not window.$$annotated_content_info? + window.$$annotated_content_info = {} + window.$$annotated_content_info[id] ||= {} + window.$$annotated_content_info[id][attr] = value + + extendContentInfo: (id, newInfo) -> + if not window.$$annotated_content_info? + window.$$annotated_content_info = {} + window.$$annotated_content_info[id] = newInfo + bulkExtendContentInfo: (newInfos) -> + if not window.$$annotated_content_info? + window.$$annotated_content_info = {} + window.$$annotated_content_info = $.extend window.$$annotated_content_info, newInfos + + subscriptionLink: (type, id) -> + followLink = -> + Discussion.generateDiscussionLink("discussion-follow-#{type}", "Follow", handleFollow) + + unfollowLink = -> + Discussion.generateDiscussionLink("discussion-unfollow-#{type}", "Unfollow", handleUnfollow) + + handleFollow = (elem) -> + Discussion.safeAjax + $elem: $(elem) + url: Discussion.urlFor("follow_#{type}", id) + type: "POST" + success: (response, textStatus) -> + if textStatus == "success" + $(elem).replaceWith unfollowLink() + dataType: 'json' + + handleUnfollow = (elem) -> + Discussion.safeAjax + $elem: $(elem) + url: Discussion.urlFor("unfollow_#{type}", id) + type: "POST" + success: (response, textStatus) -> + if textStatus == "success" + $(elem).replaceWith followLink() + dataType: 'json' + + if Discussion.isSubscribed(id, type) + unfollowLink() + else + followLink() + + processEachMathAndCode: (text, processor) -> + + codeArchive = [] + + RE_DISPLAYMATH = /^([^\$]*?)\$\$([^\$]*?)\$\$(.*)$/m + RE_INLINEMATH = /^([^\$]*?)\$([^\$]+?)\$(.*)$/m + + ESCAPED_DOLLAR = '@@ESCAPED_D@@' + ESCAPED_BACKSLASH = '@@ESCAPED_B@@' + + processedText = "" + + $div = $("
          ").html(text) + + $div.find("code").each (index, code) -> + codeArchive.push $(code).html() + $(code).html(codeArchive.length - 1) + + text = $div.html() + text = text.replace /\\\$/g, ESCAPED_DOLLAR + + while true + if RE_INLINEMATH.test(text) + text = text.replace RE_INLINEMATH, ($0, $1, $2, $3) -> + processedText += $1 + processor("$" + $2 + "$", 'inline') + $3 + else if RE_DISPLAYMATH.test(text) + text = text.replace RE_DISPLAYMATH, ($0, $1, $2, $3) -> + processedText += $1 + processor("$$" + $2 + "$$", 'display') + $3 + else + processedText += text + break + + text = processedText + text = text.replace(new RegExp(ESCAPED_DOLLAR, 'g'), '\\$') + + text = text.replace /\\\\\\\\/g, ESCAPED_BACKSLASH + text = text.replace /\\begin\{([a-z]*\*?)\}([\s\S]*?)\\end\{\1\}/img, ($0, $1, $2) -> + processor("\\begin{#{$1}}" + $2 + "\\end{#{$1}}") + text = text.replace(new RegExp(ESCAPED_BACKSLASH, 'g'), '\\\\\\\\') + + $div = $("
          ").html(text) + cnt = 0 + $div.find("code").each (index, code) -> + $(code).html(processor(codeArchive[cnt], 'code')) + cnt += 1 + + text = $div.html() + + text diff --git a/lms/templates/discussion/_forum.html b/lms/templates/discussion/_forum.html index 21dd182002..3248d1e3c9 100644 --- a/lms/templates/discussion/_forum.html +++ b/lms/templates/discussion/_forum.html @@ -2,7 +2,7 @@
          -
          +
          <%include file="_search_bar.html" />
          @@ -13,13 +13,13 @@
          % else: - <%include file="_sort.html" /> +
          <%include file="_sort.html" />
          % for thread in threads: ${renderer.render_content_with_comments(thread)} % endfor
          - <%include file="_paginator.html" /> +
          <%include file="_paginator.html" />
          % endif
          diff --git a/lms/templates/discussion/_inline.html b/lms/templates/discussion/_inline.html index 1737e2e558..d49a25a7ff 100644 --- a/lms/templates/discussion/_inline.html +++ b/lms/templates/discussion/_inline.html @@ -2,7 +2,7 @@
          -
          +
          % for thread in threads: @@ -10,7 +10,7 @@ % endfor
          - <%include file="_paginator.html" /> +
          <%include file="_paginator.html" />
          <%include file="_js_data.html" /> diff --git a/lms/templates/discussion/_js_data.html b/lms/templates/discussion/_js_data.html index 3e1dc4c280..2dc3759114 100644 --- a/lms/templates/discussion/_js_data.html +++ b/lms/templates/discussion/_js_data.html @@ -3,8 +3,12 @@ diff --git a/lms/templates/discussion/_js_dependencies.html b/lms/templates/discussion/_js_dependencies.html index 474f1abfe3..7ee4549028 100644 --- a/lms/templates/discussion/_js_dependencies.html +++ b/lms/templates/discussion/_js_dependencies.html @@ -26,5 +26,8 @@ + + +