From 88a98ef5480e2ddba639a38cd6d572452b5bfc07 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Thu, 2 Aug 2012 17:03:10 -0400 Subject: [PATCH] refactored discussion.coffee into separate files --- lms/static/coffee/src/discussion.coffee | 589 ------------------ .../coffee/src/discussion/content.coffee | 245 ++++++++ .../coffee/src/discussion/discussion.coffee | 224 +++++++ .../src/discussion/discussion_module.coffee | 42 ++ lms/static/coffee/src/discussion/main.coffee | 18 + .../coffee/src/discussion/templates.coffee | 58 ++ lms/static/coffee/src/discussion/utils.coffee | 52 ++ 7 files changed, 639 insertions(+), 589 deletions(-) delete mode 100644 lms/static/coffee/src/discussion.coffee create mode 100644 lms/static/coffee/src/discussion/content.coffee create mode 100644 lms/static/coffee/src/discussion/discussion.coffee create mode 100644 lms/static/coffee/src/discussion/discussion_module.coffee create mode 100644 lms/static/coffee/src/discussion/main.coffee create mode 100644 lms/static/coffee/src/discussion/templates.coffee create mode 100644 lms/static/coffee/src/discussion/utils.coffee diff --git a/lms/static/coffee/src/discussion.coffee b/lms/static/coffee/src/discussion.coffee deleted file mode 100644 index 126deb7ff4..0000000000 --- a/lms/static/coffee/src/discussion.coffee +++ /dev/null @@ -1,589 +0,0 @@ -$ -> - - 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) - -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)) - -Discussion = - - newPostTemplate: """ -
-
    - -
    - -
    - Cancel - Submit -
    -
    - """ - - replyTemplate: """ -
    - -
    - - - {{#showWatchCheckbox}} - - - {{/showWatchCheckbox}} -
    -
    - Cancel - Submit -
    -
    - """ - - editThreadTemplate: """ -
    - - -
    {{body}}
    - -
    - Cancel - Update -
    -
    - """ - - editCommentTemplate: """ -
    - -
    {{body}}
    - Update - Cancel -
    - """ - - urlFor: (name, param, param1) -> - { - watch_commentable : "/courses/#{$$course_id}/discussion/#{param}/watch" - unwatch_commentable : "/courses/#{$$course_id}/discussion/#{param}/unwatch" - create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" - 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" - watch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/watch" - unwatch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unwatch" - 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" - 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}" - }[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() - - initializeDiscussionModule: (elem) -> - $discussionModule = $(elem) - $local = 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 - method: "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) - - initializeDiscussion: (discussion) -> - - initializeVote = (index, content) -> - $content = $(content) - $local = generateLocal($content.children(".discussion-content")) - id = $content.attr("_id") - if id in $$user_info.upvoted_ids - $local(".discussion-vote-up").addClass("voted") - else if id in $$user_info.downvoted_ids - $local(".discussion-vote-down").addClass("voted") - - initializeWatchDiscussion = (discussion) -> - $discussion = $(discussion) - id = $discussion.attr("_id") - $local = generateLocal($discussion.children(".discussion-non-content")) - - handleWatchDiscussion = (elem) -> - url = Discussion.urlFor('watch_commentable', id) - $.post url, {}, (response, textStatus) -> - if textStatus == "success" - Discussion.handleAnchorAndReload(response) - , 'json' - - handleUnwatchDiscussion = (elem) -> - url = Discussion.urlFor('unwatch_commentable', id) - $.post url, {}, (response, textStatus) -> - if textStatus == "success" - Discussion.handleAnchorAndReload(response) - , 'json' - - if id in $$user_info.subscribed_commentable_ids - unwatchDiscussion = generateDiscussionLink("discussion-unwatch-discussion", "Unwatch", handleUnwatchDiscussion) - $local(".discussion-title-wrapper").append(unwatchDiscussion) - else - watchDiscussion = generateDiscussionLink("discussion-watch-discussion", "Watch", handleWatchDiscussion) - $local(".discussion-title-wrapper").append(watchDiscussion) - - initializeWatchThreads = (index, thread) -> - $thread = $(thread) - id = $thread.attr("_id") - $local = generateLocal($thread.children(".discussion-content")) - - handleWatchThread = (elem) -> - url = Discussion.urlFor('watch_thread', id) - $.post url, {}, (response, textStatus) -> - if textStatus == "success" - Discussion.handleAnchorAndReload(response) - , 'json' - - handleUnwatchThread = (elem) -> - url = Discussion.urlFor('unwatch_thread', id) - $.post url, {}, (response, textStatus) -> - if textStatus == "success" - Discussion.handleAnchorAndReload(response) - , 'json' - - if id in $$user_info.subscribed_thread_ids - unwatchThread = generateDiscussionLink("discussion-unwatch-thread", "Unfollow", handleUnwatchThread) - $local(".info").append(unwatchThread) - else - watchThread = generateDiscussionLink("discussion-watch-thread", "Follow", handleWatchThread) - $local(".info").append(watchThread) - - $local = generateLocal(discussion) - - if $$user_info? - $local(".comment").each(initializeVote) - $local(".thread").each(initializeVote).each(initializeWatchThreads) - #initializeWatchDiscussion(discussion) TODO move this somewhere else - - $local(".new-post-tags").tagsInput - autocomplete_url: Discussion.urlFor('tags_autocomplete') - autocomplete: - remoteDataType: 'json' - interactive: true - defaultText: "Tag your post" - height: "30px" - width: "90%" - removeWithBackspace: true - - bindContentEvents: (content) -> - - $content = $(content) - $discussionContent = $content.children(".discussion-content") - $local = generateLocal($discussionContent) - - id = $content.attr("_id") - - discussionContentHoverIn = -> - status = $discussionContent.attr("status") || "normal" - if status == "normal" - $local(".discussion-link").show() - - discussionContentHoverOut = -> - $local(".discussion-link").hide() - - $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) - - handleReply = (elem) -> - $replyView = $local(".discussion-reply-new") - if $replyView.length - $replyView.show() - else - view = { - id: id - showWatchCheckbox: $discussionContent.parents(".thread").attr("_id") not in $$user_info.subscribed_thread_ids - } - $discussionContent.append Mustache.render Discussion.replyTemplate, view - Markdown.makeWmdEditor $local(".reply-body"), "-reply-body-#{id}", Discussion.urlFor('upload') - $local(".discussion-submit-post").click handleSubmitReply - $local(".discussion-cancel-post").click handleCancelReply - $local(".discussion-link").hide() - $discussionContent.attr("status", "reply") - - handleCancelReply = (elem) -> - $replyView = $local(".discussion-reply-new") - if $replyView.length - $replyView.hide() - reply = generateDiscussionLink("discussion-reply", "Reply", handleReply) - $(elem).replaceWith(reply) - $discussionContent.attr("status", "normal") - - 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 = $local("#wmd-input-reply-body-#{id}").val() - - anonymous = false || $local(".discussion-post-anonymously").is(":checked") - autowatch = false || $local(".discussion-auto-watch").is(":checked") - - $.post url, {body: body, anonymous: anonymous, autowatch: autowatch}, (response, textStatus) -> - if response.errors - errorsField = $local(".discussion-errors").empty() - for error in response.errors - errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) - else - Discussion.handleAnchorAndReload(response) - , 'json' - - handleVote = (elem, value) -> - contentType = if $content.hasClass("thread") then "thread" else "comment" - url = Discussion.urlFor("#{value}vote_#{contentType}", id) - $.post url, {}, (response, textStatus) -> - if textStatus == "success" - Discussion.handleAnchorAndReload(response) - , 'json' - - 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-title").html() - body: $local(".thread-raw-body").html() - tags: $local(".thread-raw-tags").html() - } - $discussionContent.append Mustache.render Discussion.editThreadTemplate, view - Markdown.makeWmdEditor $local(".thread-body-edit"), "-thread-body-edit-#{id}", Discussion.urlFor('update_thread', id) - $local(".thread-tags-edit").tagsInput - autocomplete_url: Discussion.urlFor('tags_autocomplete') - autocomplete: - remoteDataType: 'json' - interactive: true - defaultText: "Tag your post: press enter after each tag" - height: "30px" - width: "100%" - removeWithBackspace: true - $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 = $local("#wmd-input-thread-body-edit-#{id}").val() - tags = $local(".thread-tags-edit").val() - $.post url, {title: title, body: body, tags: tags}, (response, textStatus) -> - if response.errors - errorsField = $local(".discussion-update-errors").empty() - for error in response.errors - errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) - else - Discussion.handleAnchorAndReload(response) - , 'json' - - 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 - Markdown.makeWmdEditor $local(".comment-body-edit"), "-comment-body-edit-#{id}", Discussion.urlFor('update_comment', id) - $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 = $local("#wmd-input-comment-body-edit-#{id}").val() - $.post url, {body: body}, (response, textStatus) -> - if response.errors - errorsField = $local(".discussion-update-errors").empty() - for error in response.errors - errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) - else - Discussion.handleAnchorAndReload(response) - , 'json' - - handleEndorse = (elem) -> - url = Discussion.urlFor('endorse_comment', id) - endorsed = $local(".discussion-endorse").is(":checked") - $.post url, {endorsed: endorsed}, (response, textStatus) -> - # TODO error handling - Discussion.handleAnchorAndReload(response) - , 'json' - - handleHideSingleThread = (elem) -> - $threadTitle = $local(".thread-title") - $showComments = $local(".discussion-show-comments") - $content.children(".comments").hide() - $threadTitle.unbind('click').click handleShowSingleThread - $showComments.unbind('click').click handleShowSingleThread - prevHtml = $showComments.html() - $showComments.html prevHtml.replace "Hide", "Show" - - handleShowSingleThread = -> - $threadTitle = $local(".thread-title") - $showComments = $local(".discussion-show-comments") - - rebindHideEvents = -> - $threadTitle.unbind('click').click handleHideSingleThread - $showComments.unbind('click').click handleHideSingleThread - prevHtml = $showComments.html() - $showComments.html prevHtml.replace "Show", "Hide" - - if $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 - method: "GET" - success: (response, textStatus) -> - if not $$annotated_content_info? - window.$$annotated_content_info = {} - window.$$annotated_content_info = $.extend $$annotated_content_info, response['annotated_content_info'] - $content.append(response['html']) - $content.find(".comment").each (index, comment) -> - Discussion.initializeContent(comment) - Discussion.bindContentEvents(comment) - rebindHideEvents() - dataType: 'json' - - - $local(".thread-title").click handleShowSingleThread - $local(".discussion-show-comments").click handleShowSingleThread - - $local(".discussion-reply-thread").click -> - handleShowSingleThread($local(".thread-title")) - handleReply(this) - - $local(".discussion-reply-comment").click -> - handleReply(this) - - $local(".discussion-cancel-reply").click -> - handleCancelReply(this) - - $local(".discussion-vote-up").click -> - handleVote(this, "up") - - $local(".discussion-vote-down").click -> - handleVote(this, "down") - - $local(".discussion-endorse").click -> - handleEndorse(this) - - $local(".discussion-edit").click -> - if $content.hasClass("thread") - handleEditThread(this) - else - handleEditComment(this) - - initializeContent: (content) -> - $content = $(content) - $local = generateLocal($content.children(".discussion-content")) - $contentBody = $local(".content-body") - raw_text = $contentBody.html() - converter = Markdown.getMathCompatibleConverter() - $contentBody.html(converter.makeHtml(raw_text)) - MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")] - id = $content.attr("_id") - if $$annotated_content_info? - if not ($$annotated_content_info[id] || [])['editable'] - $local(".discussion-edit").remove() - - bindDiscussionEvents: (discussion) -> - $discussion = $(discussion) - $discussionNonContent = $discussion.children(".discussion-non-content") - $local = generateLocal($discussionNonContent)#(selector) -> $discussionNonContent.find(selector) - - id = $discussion.attr("_id") - - handleSearch = (text, isSearchWithinBoard) -> - if text.length - if $local(".discussion-search-within-board").is(":checked") - window.location = window.location.pathname + '?text=' + encodeURI(text) - else - window.location = Discussion.urlFor('search') + '?text=' + encodeURI(text) - - handleSubmitNewPost = (elem) -> - title = $local(".new-post-title").val() - body = $local("#wmd-input-new-post-body-#{id}").val() - tags = $local(".new-post-tags").val() - url = Discussion.urlFor('create_thread', $local(".new-post-form").attr("_id")) - $.post url, {title: title, body: body, tags: tags}, (response, textStatus) -> - if response.errors - errorsField = $local(".discussion-errors").empty() - for error in response.errors - errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) - else - Discussion.handleAnchorAndReload(response) - , 'json' - - handleCancelNewPost = (elem) -> - $local(".new-post-form").hide() - $local(".discussion-new-post").show() - - handleNewPost = (elem) -> - newPostForm = $local(".new-post-form") - if newPostForm.length - newPostForm.show() - $(elem).hide() - else - view = { - discussion_id: id - } - $discussionNonContent.append Mustache.render Discussion.newPostTemplate, view - newPostBody = $(discussion).find(".new-post-body") - if newPostBody.length - Markdown.makeWmdEditor newPostBody, "-new-post-body-#{$(discussion).attr('_id')}", Discussion.urlFor('upload') - $local(".new-post-tags").tagsInput - autocomplete_url: Discussion.urlFor('tags_autocomplete') - autocomplete: - remoteDataType: 'json' - interactive: true - defaultText: "Tag your post: press enter after each tag" - height: "30px" - width: "100%" - removeWithBackspace: true - $local(".discussion-submit-post").click -> - handleSubmitNewPost(this) - $local(".discussion-cancel-post").click -> - handleCancelNewPost(this) - $(elem).hide() - - handleAjaxSearch = (elem) -> - console.log $(elem).attr("action") - $elem = $(elem) - $discussionModule = $elem.parents(".discussion-module") - $discussion = $discussionModule.find(".discussion") - Discussion.safeAjax - $elem: $elem - url: $elem.attr("action") - data: - text: $local(".search-input").val() - method: "GET" - success: (data, textStatus) -> - $discussion.replaceWith(data) - $discussion = $discussionModule.find(".discussion") - Discussion.initializeDiscussion($discussion) - Discussion.bindDiscussionEvents($discussion) - dataType: 'html' - - handleAjaxSort = (elem) -> - $elem = $(elem) - $discussionModule = $elem.parents(".discussion-module") - $discussion = $discussionModule.find(".discussion") - Discussion.safeAjax - $elem: $elem - url: $elem.attr("sort-url") - method: "GET" - success: (data, textStatus) -> - $discussion.replaceWith(data) - $discussion = $discussionModule.find(".discussion") - Discussion.initializeDiscussion($discussion) - Discussion.bindDiscussionEvents($discussion) - dataType: 'html' - - $local(".search-wrapper-forum > .discussion-search-form").submit (event) -> - event.preventDefault() - text = $local(".search-input").val() - isSearchWithinBoard = $local(".discussion-search-within-board").is(":checked") - handleSearch(text, isSearchWithinBoard) - - $local(".discussion-new-post").click -> - handleNewPost(this) - - $local(".discussion-search-link").click -> - handleAjaxSearch(this) - - $local(".search-wrapper-inline > .discussion-search-form").submit (e)-> - e.preventDefault() - handleAjaxSearch(this) - - $local(".discussion-inline-sort-link").click -> - handleAjaxSort(this) - - - $discussion.find(".thread").each (index, thread) -> - Discussion.initializeContent(thread) - Discussion.bindContentEvents(thread) - - $discussion.find(".comment").each (index, comment) -> - Discussion.initializeContent(comment) - Discussion.bindContentEvents(comment) diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee new file mode 100644 index 0000000000..a3e3f8c3a1 --- /dev/null +++ b/lms/static/coffee/src/discussion/content.coffee @@ -0,0 +1,245 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +@Discussion = $.extend @Discussion, + bindContentEvents: (content) -> + + $content = $(content) + $discussionContent = $content.children(".discussion-content") + $local = Discussion.generateLocal($discussionContent) + + id = $content.attr("_id") + + discussionContentHoverIn = -> + status = $discussionContent.attr("status") || "normal" + if status == "normal" + $local(".discussion-link").show() + + discussionContentHoverOut = -> + $local(".discussion-link").hide() + + $discussionContent.hover(discussionContentHoverIn, discussionContentHoverOut) + + handleReply = (elem) -> + $replyView = $local(".discussion-reply-new") + if $replyView.length + $replyView.show() + else + view = { + id: id + showWatchCheckbox: $discussionContent.parents(".thread").attr("_id") not in $$user_info.subscribed_thread_ids + } + $discussionContent.append Mustache.render Discussion.replyTemplate, view + Markdown.makeWmdEditor $local(".reply-body"), "-reply-body-#{id}", Discussion.urlFor('upload') + $local(".discussion-submit-post").click handleSubmitReply + $local(".discussion-cancel-post").click handleCancelReply + $local(".discussion-link").hide() + $discussionContent.attr("status", "reply") + + handleCancelReply = (elem) -> + $replyView = $local(".discussion-reply-new") + if $replyView.length + $replyView.hide() + reply = Discussion.generateDiscussionLink("discussion-reply", "Reply", handleReply) + $(elem).replaceWith(reply) + $discussionContent.attr("status", "normal") + + 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 = $local("#wmd-input-reply-body-#{id}").val() + + anonymous = false || $local(".discussion-post-anonymously").is(":checked") + autowatch = false || $local(".discussion-auto-watch").is(":checked") + + Discussion.safeAjax + url: url + type: "POST" + data: + body: body + anonymous: anonymous + autowatch: autowatch + success: (response, textStatus) -> + if response.errors? and response.errors.length > 0 + errorsField = $local(".discussion-errors").empty() + for error in response.errors + errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) + else + Discussion.handleAnchorAndReload(response) + dataType: 'json' + + handleVote = (elem, value) -> + contentType = if $content.hasClass("thread") then "thread" else "comment" + url = Discussion.urlFor("#{value}vote_#{contentType}", id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + 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-title").html() + body: $local(".thread-raw-body").html() + tags: $local(".thread-raw-tags").html() + } + $discussionContent.append Mustache.render Discussion.editThreadTemplate, view + Markdown.makeWmdEditor $local(".thread-body-edit"), "-thread-body-edit-#{id}", Discussion.urlFor('update_thread', id) + $local(".thread-tags-edit").tagsInput + autocomplete_url: Discussion.urlFor('tags_autocomplete') + autocomplete: + remoteDataType: 'json' + interactive: true + defaultText: "Tag your post: press enter after each tag" + height: "30px" + width: "100%" + removeWithBackspace: true + $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 = $local("#wmd-input-thread-body-edit-#{id}").val() + tags = $local(".thread-tags-edit").val() + $.post url, {title: title, body: body, tags: tags}, (response, textStatus) -> + if response.errors + errorsField = $local(".discussion-update-errors").empty() + for error in response.errors + errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) + else + Discussion.handleAnchorAndReload(response) + , 'json' + + 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 + Markdown.makeWmdEditor $local(".comment-body-edit"), "-comment-body-edit-#{id}", Discussion.urlFor('update_comment', id) + $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 = $local("#wmd-input-comment-body-edit-#{id}").val() + $.post url, {body: body}, (response, textStatus) -> + if response.errors + errorsField = $local(".discussion-update-errors").empty() + for error in response.errors + errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) + else + Discussion.handleAnchorAndReload(response) + , 'json' + + handleEndorse = (elem) -> + url = Discussion.urlFor('endorse_comment', id) + endorsed = $local(".discussion-endorse").is(":checked") + $.post url, {endorsed: endorsed}, (response, textStatus) -> + # TODO error handling + Discussion.handleAnchorAndReload(response) + , 'json' + + handleHideSingleThread = (elem) -> + $threadTitle = $local(".thread-title") + $showComments = $local(".discussion-show-comments") + $content.children(".comments").hide() + $threadTitle.unbind('click').click handleShowSingleThread + $showComments.unbind('click').click handleShowSingleThread + prevHtml = $showComments.html() + $showComments.html prevHtml.replace "Hide", "Show" + + handleShowSingleThread = -> + $threadTitle = $local(".thread-title") + $showComments = $local(".discussion-show-comments") + + rebindHideEvents = -> + $threadTitle.unbind('click').click handleHideSingleThread + $showComments.unbind('click').click handleHideSingleThread + prevHtml = $showComments.html() + $showComments.html prevHtml.replace "Show", "Hide" + + if $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" + success: (response, textStatus) -> + if not $$annotated_content_info? + window.$$annotated_content_info = {} + window.$$annotated_content_info = $.extend $$annotated_content_info, response['annotated_content_info'] + $content.append(response['html']) + $content.find(".comment").each (index, comment) -> + Discussion.initializeContent(comment) + Discussion.bindContentEvents(comment) + rebindHideEvents() + dataType: 'json' + + + $local(".thread-title").click handleShowSingleThread + $local(".discussion-show-comments").click handleShowSingleThread + + $local(".discussion-reply-thread").click -> + handleShowSingleThread($local(".thread-title")) + handleReply(this) + + $local(".discussion-reply-comment").click -> + handleReply(this) + + $local(".discussion-cancel-reply").click -> + handleCancelReply(this) + + $local(".discussion-vote-up").click -> + handleVote(this, "up") + + $local(".discussion-vote-down").click -> + handleVote(this, "down") + + $local(".discussion-endorse").click -> + handleEndorse(this) + + $local(".discussion-edit").click -> + if $content.hasClass("thread") + handleEditThread(this) + else + handleEditComment(this) + + initializeContent: (content) -> + $content = $(content) + $local = Discussion.generateLocal($content.children(".discussion-content")) + $contentBody = $local(".content-body") + raw_text = $contentBody.html() + converter = Markdown.getMathCompatibleConverter() + $contentBody.html(converter.makeHtml(raw_text)) + MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")] + id = $content.attr("_id") + if $$annotated_content_info? + if not ($$annotated_content_info[id] || [])['editable'] + $local(".discussion-edit").remove() diff --git a/lms/static/coffee/src/discussion/discussion.coffee b/lms/static/coffee/src/discussion/discussion.coffee new file mode 100644 index 0000000000..d5f3535130 --- /dev/null +++ b/lms/static/coffee/src/discussion/discussion.coffee @@ -0,0 +1,224 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +@Discussion = $.extend @Discussion, + + initializeDiscussion: (discussion) -> + + initializeVote = (index, content) -> + $content = $(content) + $local = Discussion.generateLocal($content.children(".discussion-content")) + id = $content.attr("_id") + if id in $$user_info.upvoted_ids + $local(".discussion-vote-up").addClass("voted") + else if id in $$user_info.downvoted_ids + $local(".discussion-vote-down").addClass("voted") + + initializeWatchDiscussion = (discussion) -> + $discussion = $(discussion) + id = $discussion.attr("_id") + $local = Discussion.generateLocal($discussion.children(".discussion-non-content")) + + handleWatchDiscussion = (elem) -> + url = Discussion.urlFor('watch_commentable', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + handleUnwatchDiscussion = (elem) -> + url = Discussion.urlFor('unwatch_commentable', id) + $.post url, {}, (response, textStatus) -> + if textStatus == "success" + Discussion.handleAnchorAndReload(response) + , 'json' + + if id in $$user_info.subscribed_commentable_ids + unwatchDiscussion = Discussion.generateDiscussionLink("discussion-unwatch-discussion", "Unwatch", handleUnwatchDiscussion) + $local(".discussion-title-wrapper").append(unwatchDiscussion) + else + watchDiscussion = Discussion.generateDiscussionLink("discussion-watch-discussion", "Watch", handleWatchDiscussion) + $local(".discussion-title-wrapper").append(watchDiscussion) + + initializeWatchThreads = (index, thread) -> + $thread = $(thread) + id = $thread.attr("_id") + $local = Discussion.generateLocal($thread.children(".discussion-content")) + + handleWatchThread = (elem) -> + $elem = $(elem) + url = Discussion.urlFor('watch_thread', id) + Discussion.safeAjax + $elem: $elem + url: url + type: "POST" + success: (response, textStatus) -> + if textStatus == "success" + $elem.removeClass("discussion-watch-thread") + .addClass("discussion-unwatch-thread") + .html("Unfollow") + .unbind('click').click -> + handleUnwatchThread(this) + dataType: 'json' + + handleUnwatchThread = (elem) -> + $elem = $(elem) + url = Discussion.urlFor('unwatch_thread', id) + Discussion.safeAjax + $elem: $elem + url: url + type: "POST" + success: (response, textStatus) -> + if textStatus == "success" + $elem.removeClass("discussion-unwatch-thread") + .addClass("discussion-watch-thread") + .html("Follow") + .unbind('click').click -> + handleWatchThread(this) + dataType: 'json' + + if id in $$user_info.subscribed_thread_ids + unwatchThread = Discussion.generateDiscussionLink("discussion-unwatch-thread", "Unfollow", handleUnwatchThread) + $local(".info").append(unwatchThread) + else + watchThread = Discussion.generateDiscussionLink("discussion-watch-thread", "Follow", handleWatchThread) + $local(".info").append(watchThread) + + $local = Discussion.generateLocal(discussion) + + if $$user_info? + $local(".comment").each(initializeVote) + $local(".thread").each(initializeVote).each(initializeWatchThreads) + #initializeWatchDiscussion(discussion) TODO move this somewhere else + + $local(".new-post-tags").tagsInput + autocomplete_url: Discussion.urlFor('tags_autocomplete') + autocomplete: + remoteDataType: 'json' + interactive: true + defaultText: "Tag your post" + height: "30px" + width: "90%" + removeWithBackspace: true + + bindDiscussionEvents: (discussion) -> + $discussion = $(discussion) + $discussionNonContent = $discussion.children(".discussion-non-content") + $local = Discussion.generateLocal($discussionNonContent)#(selector) -> $discussionNonContent.find(selector) + + id = $discussion.attr("_id") + + handleSearch = (text, isSearchWithinBoard) -> + if text.length + if $local(".discussion-search-within-board").is(":checked") + window.location = window.location.pathname + '?text=' + encodeURI(text) + else + window.location = Discussion.urlFor('search') + '?text=' + encodeURI(text) + + handleSubmitNewPost = (elem) -> + title = $local(".new-post-title").val() + body = $local("#wmd-input-new-post-body-#{id}").val() + tags = $local(".new-post-tags").val() + url = Discussion.urlFor('create_thread', $local(".new-post-form").attr("_id")) + $.post url, {title: title, body: body, tags: tags}, (response, textStatus) -> + if response.errors + errorsField = $local(".discussion-errors").empty() + for error in response.errors + errorsField.append($("
  • ").addClass("new-post-form-error").html(error)) + else + Discussion.handleAnchorAndReload(response) + , 'json' + + handleCancelNewPost = (elem) -> + $local(".new-post-form").hide() + $local(".discussion-new-post").show() + + handleNewPost = (elem) -> + newPostForm = $local(".new-post-form") + if newPostForm.length + newPostForm.show() + $(elem).hide() + else + view = { + discussion_id: id + } + $discussionNonContent.append Mustache.render Discussion.newPostTemplate, view + newPostBody = $(discussion).find(".new-post-body") + if newPostBody.length + Markdown.makeWmdEditor newPostBody, "-new-post-body-#{$(discussion).attr('_id')}", Discussion.urlFor('upload') + $local(".new-post-tags").tagsInput + autocomplete_url: Discussion.urlFor('tags_autocomplete') + autocomplete: + remoteDataType: 'json' + interactive: true + defaultText: "Tag your post: press enter after each tag" + height: "30px" + width: "100%" + removeWithBackspace: true + $local(".discussion-submit-post").click -> + handleSubmitNewPost(this) + $local(".discussion-cancel-post").click -> + handleCancelNewPost(this) + $(elem).hide() + + handleAjaxSearch = (elem) -> + $elem = $(elem) + $discussionModule = $elem.parents(".discussion-module") + $discussion = $discussionModule.find(".discussion") + Discussion.safeAjax + $elem: $elem + url: $elem.attr("action") + data: + text: $local(".search-input").val() + type: "GET" + success: (data, textStatus) -> + $discussion.replaceWith(data) + $discussion = $discussionModule.find(".discussion") + Discussion.initializeDiscussion($discussion) + Discussion.bindDiscussionEvents($discussion) + dataType: 'html' + + handleAjaxSort = (elem) -> + $elem = $(elem) + $discussionModule = $elem.parents(".discussion-module") + $discussion = $discussionModule.find(".discussion") + Discussion.safeAjax + $elem: $elem + url: $elem.attr("sort-url") + type: "GET" + success: (data, textStatus) -> + $discussion.replaceWith(data) + $discussion = $discussionModule.find(".discussion") + Discussion.initializeDiscussion($discussion) + Discussion.bindDiscussionEvents($discussion) + dataType: 'html' + + $local(".search-wrapper-forum > .discussion-search-form").submit (event) -> + event.preventDefault() + text = $local(".search-input").val() + isSearchWithinBoard = $local(".discussion-search-within-board").is(":checked") + handleSearch(text, isSearchWithinBoard) + + $local(".discussion-new-post").click -> + handleNewPost(this) + + $local(".discussion-search-link").click -> + handleAjaxSearch(this) + + $local(".search-wrapper-inline > .discussion-search-form").submit (e)-> + e.preventDefault() + handleAjaxSearch(this) + + $local(".discussion-inline-sort-link").click -> + handleAjaxSort(this) + + + $discussion.find(".thread").each (index, thread) -> + Discussion.initializeContent(thread) + Discussion.bindContentEvents(thread) + + $discussion.find(".comment").each (index, comment) -> + Discussion.initializeContent(comment) + Discussion.bindContentEvents(comment) diff --git a/lms/static/coffee/src/discussion/discussion_module.coffee b/lms/static/coffee/src/discussion/discussion_module.coffee new file mode 100644 index 0000000000..d449533ba9 --- /dev/null +++ b/lms/static/coffee/src/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/discussion/main.coffee b/lms/static/coffee/src/discussion/main.coffee new file mode 100644 index 0000000000..274afe20ea --- /dev/null +++ b/lms/static/coffee/src/discussion/main.coffee @@ -0,0 +1,18 @@ +$ -> + + 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) diff --git a/lms/static/coffee/src/discussion/templates.coffee b/lms/static/coffee/src/discussion/templates.coffee new file mode 100644 index 0000000000..dca1dc0712 --- /dev/null +++ b/lms/static/coffee/src/discussion/templates.coffee @@ -0,0 +1,58 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +@Discussion = $.extend @Discussion, + newPostTemplate: """ +
    +
      + +
      + +
      + Cancel + Submit +
      +
      + """ + + replyTemplate: """ +
      +
        +
        + + + {{#showWatchCheckbox}} + + + {{/showWatchCheckbox}} +
        +
        + Cancel + Submit +
        +
        + """ + + editThreadTemplate: """ +
        +
          + +
          {{body}}
          + +
          + Cancel + Update +
          +
          + """ + + editCommentTemplate: """ +
          +
            +
            {{body}}
            + Update + Cancel +
            + """ diff --git a/lms/static/coffee/src/discussion/utils.coffee b/lms/static/coffee/src/discussion/utils.coffee new file mode 100644 index 0000000000..9988839f12 --- /dev/null +++ b/lms/static/coffee/src/discussion/utils.coffee @@ -0,0 +1,52 @@ +if not @Discussion? + @Discussion = {} + +Discussion = @Discussion + +@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) -> + { + watch_commentable : "/courses/#{$$course_id}/discussion/#{param}/watch" + unwatch_commentable : "/courses/#{$$course_id}/discussion/#{param}/unwatch" + create_thread : "/courses/#{$$course_id}/discussion/#{param}/threads/create" + 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" + watch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/watch" + unwatch_thread : "/courses/#{$$course_id}/discussion/threads/#{param}/unwatch" + 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" + 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}" + }[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()