Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
9f3b8ddf
Commit
9f3b8ddf
authored
Jul 18, 2016
by
Eugeny Kolpakov
Committed by
GitHub
Jul 18, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12914 from open-craft/ekolpakov/discussion_fixes_from_solutions
Discussion fixes from solutions
parents
e1dbfe3a
e0c7228d
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
555 additions
and
360 deletions
+555
-360
common/static/common/js/discussion/discussion_router.js
+55
-20
common/static/common/js/discussion/main.js
+5
-4
common/static/common/js/discussion/utils.js
+155
-103
common/static/common/js/discussion/views/discussion_content_view.js
+21
-19
common/static/common/js/discussion/views/discussion_thread_edit_view.js
+12
-11
common/static/common/js/discussion/views/discussion_thread_list_view.js
+113
-103
common/static/common/js/discussion/views/discussion_thread_profile_view.js
+9
-11
common/static/common/js/discussion/views/discussion_thread_show_view.js
+8
-17
common/static/common/js/discussion/views/discussion_thread_view.js
+21
-18
common/static/common/js/discussion/views/response_comment_show_view.js
+19
-14
common/static/common/js/discussion/views/thread_response_show_view.js
+10
-16
common/static/common/js/spec/discussion/utils_spec.js
+30
-5
common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js
+31
-1
common/static/common/js/spec/discussion/view/new_post_view_spec.js
+2
-1
common/static/common/js/spec/discussion/view/response_comment_view_spec.js
+13
-4
common/static/common/js/spec_helpers/discussion_spec_helper.js
+3
-3
common/static/common/templates/discussion/alert-popup.underscore
+14
-0
common/static/common/templates/discussion/nav-load-more-link.underscore
+5
-0
common/static/common/templates/discussion/nav-loading.underscore
+4
-0
common/static/common/templates/discussion/search-alert.underscore
+2
-2
common/static/karma_common.conf.js
+2
-0
lms/templates/discussion/_underscore_templates.html
+18
-6
lms/templates/discussion/user_profile.html
+3
-2
No files found.
common/static/common/js/discussion/discussion_router.js
View file @
9f3b8ddf
/* globals DiscussionThreadListView, DiscussionThreadView, DiscussionUtil, NewPostView */
/* globals DiscussionThreadListView, DiscussionThreadView, DiscussionUtil, NewPostView
, Thread
*/
(
function
()
{
'use strict'
;
var
__hasProp
=
{}.
hasOwnProperty
,
...
...
@@ -18,9 +18,20 @@
return
child
;
};
function
getSingleThreadRoute
(
commentable_id
,
thread_id
)
{
return
commentable_id
+
"/threads/"
+
thread_id
;
}
if
(
typeof
Backbone
!==
"undefined"
&&
Backbone
!==
null
)
{
this
.
DiscussionRouter
=
(
function
(
_super
)
{
var
allThreadsRoute
=
""
,
singleThreadRoute
=
getSingleThreadRoute
(
":forum_name"
,
":thread_id"
),
// :forum_name/threads/:thread_id
routes
=
{};
routes
[
allThreadsRoute
]
=
"allThreads"
;
routes
[
singleThreadRoute
]
=
"showThread"
;
__extends
(
DiscussionRouter
,
_super
);
function
DiscussionRouter
()
{
...
...
@@ -40,16 +51,16 @@
this
.
showMain
=
function
()
{
return
DiscussionRouter
.
prototype
.
showMain
.
apply
(
self
,
arguments
);
};
this
.
renderThreadView
=
function
()
{
return
DiscussionRouter
.
prototype
.
renderThreadView
.
apply
(
self
,
arguments
);
};
this
.
setActiveThread
=
function
()
{
return
DiscussionRouter
.
prototype
.
setActiveThread
.
apply
(
self
,
arguments
);
};
return
DiscussionRouter
.
__super__
.
constructor
.
apply
(
this
,
arguments
);
}
DiscussionRouter
.
prototype
.
routes
=
{
""
:
"allThreads"
,
":forum_name/threads/:thread_id"
:
"showThread"
};
DiscussionRouter
.
prototype
.
routes
=
routes
;
DiscussionRouter
.
prototype
.
initialize
=
function
(
options
)
{
var
self
=
this
;
...
...
@@ -82,23 +93,50 @@
DiscussionRouter
.
prototype
.
allThreads
=
function
()
{
this
.
nav
.
updateSidebar
();
return
this
.
nav
.
goHome
();
this
.
nav
.
goHome
();
};
DiscussionRouter
.
prototype
.
setActiveThread
=
function
()
{
if
(
this
.
thread
)
{
return
this
.
nav
.
setActiveThread
(
this
.
thread
.
get
(
"id"
));
}
else
{
return
this
.
nav
.
goHome
;
this
.
nav
.
setActiveThread
(
this
.
thread
.
get
(
"id"
));
}
};
DiscussionRouter
.
prototype
.
showThread
=
function
(
forum_name
,
thread_id
)
{
var
self
=
this
;
this
.
thread
=
this
.
discussion
.
get
(
thread_id
);
if
(
this
.
thread
)
{
this
.
renderThreadView
();
return
;
}
// if thread is not loaded yet for some reason - try loading it
DiscussionUtil
.
safeAjax
({
url
:
DiscussionUtil
.
urlFor
(
'retrieve_single_thread'
,
forum_name
,
thread_id
)
}).
done
(
function
(
data
)
{
// if succeded - proceed normally
self
.
thread
=
new
Thread
(
data
.
content
);
self
.
discussion
.
add
(
self
.
thread
);
self
.
renderThreadView
();
}).
fail
(
function
(
xhr
)
{
// otherwise display error message and navigate to all threads view
var
errorMsg
;
if
(
xhr
.
status
===
404
)
{
errorMsg
=
gettext
(
"The thread you selected has been deleted. Please select another thread."
);
}
else
{
errorMsg
=
gettext
(
"We had some trouble loading more responses. Please try again."
);
}
DiscussionUtil
.
discussionAlert
(
gettext
(
"Sorry"
),
errorMsg
);
this
.
allThreads
();
});
};
DiscussionRouter
.
prototype
.
renderThreadView
=
function
()
{
this
.
thread
.
set
(
"unread_comments_count"
,
0
);
this
.
thread
.
set
(
"read"
,
true
);
this
.
setActiveThread
();
return
this
.
showMain
();
this
.
showMain
();
};
DiscussionRouter
.
prototype
.
showMain
=
function
()
{
...
...
@@ -123,26 +161,23 @@
this
.
main
.
on
(
"thread:responses:rendered"
,
function
()
{
return
self
.
nav
.
updateSidebar
();
});
return
this
.
thread
.
on
(
"thread:thread_type_updated"
,
this
.
showMain
);
this
.
thread
.
on
(
"thread:thread_type_updated"
,
this
.
showMain
);
};
DiscussionRouter
.
prototype
.
navigateToThread
=
function
(
thread_id
)
{
var
thread
;
var
thread
,
targetThreadRoute
;
thread
=
this
.
discussion
.
get
(
thread_id
);
return
this
.
navigate
(
""
+
(
thread
.
get
(
"commentable_id"
))
+
"/threads/"
+
thread_id
,
{
trigger
:
true
});
targetThreadRoute
=
getSingleThreadRoute
(
thread
.
get
(
"commentable_id"
),
thread_id
);
this
.
navigate
(
targetThreadRoute
,
{
trigger
:
true
});
};
DiscussionRouter
.
prototype
.
navigateToAllThreads
=
function
()
{
return
this
.
navigate
(
""
,
{
trigger
:
true
});
this
.
navigate
(
allThreadsRoute
,
{
trigger
:
true
});
};
DiscussionRouter
.
prototype
.
showNewPost
=
function
()
{
var
self
=
this
;
return
$
(
'.forum-content'
).
fadeOut
({
$
(
'.forum-content'
).
fadeOut
({
duration
:
200
,
complete
:
function
()
{
return
self
.
newPost
.
fadeIn
(
200
).
focus
();
...
...
@@ -151,7 +186,7 @@
};
DiscussionRouter
.
prototype
.
hideNewPost
=
function
()
{
return
this
.
newPost
.
fadeOut
({
this
.
newPost
.
fadeOut
({
duration
:
200
,
complete
:
function
()
{
return
$
(
'.forum-content'
).
fadeIn
(
200
).
find
(
'.thread-wrapper'
).
focus
();
...
...
common/static/common/js/discussion/main.js
View file @
9f3b8ddf
...
...
@@ -34,10 +34,11 @@
course_settings
:
course_settings
});
/* jshint +W031*/
return
Backbone
.
history
.
start
({
pushState
:
true
,
root
:
"/courses/"
+
$$course_id
+
"/discussion/forum/"
});
if
(
!
Backbone
.
History
.
started
)
{
Backbone
.
history
.
start
({
pushState
:
true
,
root
:
"/courses/"
+
$$course_id
+
"/discussion/forum/"
});
}
else
{
Backbone
.
history
.
loadUrl
(
window
.
location
.
pathname
);
}
}
};
DiscussionProfileApp
=
{
...
...
common/static/common/js/discussion/utils.js
View file @
9f3b8ddf
/* globals $$course_id, Content, Markdown, URI */
/* globals $$course_id, Content, Markdown,
MathJax,
URI */
(
function
()
{
'use strict'
;
this
.
DiscussionUtil
=
(
function
()
{
...
...
@@ -65,7 +65,7 @@
DiscussionUtil
.
generateDiscussionLink
=
function
(
cls
,
txt
,
handler
)
{
return
$
(
"<a>"
)
.
addClass
(
"discussion-link"
).
attr
(
"href"
,
"#"
)
.
addClass
(
cls
).
html
(
txt
).
click
(
function
()
{
return
handler
(
this
);});
.
addClass
(
cls
).
text
(
txt
).
click
(
function
()
{
return
handler
(
this
);});
};
DiscussionUtil
.
urlFor
=
function
(
name
,
param
,
param1
,
param2
)
{
...
...
@@ -134,15 +134,17 @@
};
DiscussionUtil
.
showLoadingIndicator
=
function
(
element
,
takeFocus
)
{
this
.
$_loading
=
$
(
"<div class='loading-animation' tabindex='0'><span class='sr'>"
+
gettext
(
"Loading content"
)
+
"</span></div>"
var
animElem
=
edx
.
HtmlUtils
.
joinHtml
(
edx
.
HtmlUtils
.
HTML
(
"<div class='loading-animation' tabindex='0'><span class='sr'>"
),
gettext
(
"Loading content"
)
,
edx
.
HtmlUtils
.
HTML
(
"</span></div>"
)
);
element
.
after
(
this
.
$_loading
);
var
$animElem
=
$
(
animElem
.
toString
());
element
.
after
(
$animElem
);
this
.
$_loading
=
$animElem
;
if
(
takeFocus
)
{
this
.
makeFocusTrap
(
this
.
$_loading
);
return
this
.
$_loading
.
focus
();
this
.
$_loading
.
focus
();
}
};
...
...
@@ -151,42 +153,35 @@
};
DiscussionUtil
.
discussionAlert
=
function
(
header
,
body
)
{
var
alertDiv
,
alertTrigger
;
var
$alertDiv
,
$alertTrigger
;
// Prevents "text" is undefined in underscore.js in tests - looks like some tests use
// discussions somehow, but never append discussion fixtures or reset them; this causes
// entire test suite (lms, cms, common) to fail due to unhandled JS exception
var
popupTemplate
=
$
(
"#alert-popup"
).
html
()
||
""
;
if
(
$
(
"#discussion-alert"
).
length
===
0
)
{
alertDiv
=
$
(
"<div class='modal' role='alertdialog' id='discussion-alert' "
+
"aria-describedby='discussion-alert-message'/>"
).
css
(
"display"
,
"none"
);
alertDiv
.
html
(
"<div class='inner-wrapper discussion-alert-wrapper'>"
+
" <button class='close-modal dismiss' title='"
+
gettext
(
"Close"
)
+
"'>"
+
" <span class='icon fa fa-times' aria-hidden='true'></span>"
+
" </button>"
+
" <header><h2/><hr/></header>"
+
" <p id='discussion-alert-message'/><hr/>"
+
" <button class='dismiss'>"
+
gettext
(
"OK"
)
+
"</button>"
+
"</div>"
$alertDiv
=
$
(
edx
.
HtmlUtils
.
template
(
popupTemplate
)({}).
toString
()
);
this
.
makeFocusTrap
(
alertDiv
.
find
(
"button"
));
alertTrigger
=
$
(
"<a href='#discussion-alert' id='discussion-alert-trigger'/>"
).
css
(
"display"
,
"none"
);
alertTrigger
.
leanModal
({
this
.
makeFocusTrap
(
$
alertDiv
.
find
(
"button"
));
$
alertTrigger
=
$
(
"<a href='#discussion-alert' id='discussion-alert-trigger'/>"
).
css
(
"display"
,
"none"
);
$
alertTrigger
.
leanModal
({
closeButton
:
"#discussion-alert .dismiss"
,
overlay
:
1
,
top
:
200
});
$
(
"body"
).
append
(
alertDiv
).
append
(
alertTrigger
);
$
(
"body"
).
append
(
$alertDiv
).
append
(
$
alertTrigger
);
}
$
(
"#discussion-alert header h2"
).
html
(
header
);
$
(
"#discussion-alert p"
).
html
(
body
);
$
(
"#discussion-alert header h2"
).
text
(
header
);
$
(
"#discussion-alert p"
).
text
(
body
);
$
(
"#discussion-alert-trigger"
).
click
();
return
$
(
"#discussion-alert button"
).
focus
();
$
(
"#discussion-alert button"
).
focus
();
};
DiscussionUtil
.
safeAjax
=
function
(
params
)
{
var
$elem
,
deferred
,
request
,
self
=
this
;
$elem
=
params
.
$elem
;
if
(
$elem
&&
$elem
.
attr
(
"disabled"
))
{
if
(
$elem
&&
$elem
.
prop
(
"disabled"
))
{
deferred
=
$
.
Deferred
();
deferred
.
reject
();
return
deferred
.
promise
();
...
...
@@ -194,18 +189,6 @@
params
.
url
=
URI
(
params
.
url
).
addSearch
({
ajax
:
1
});
params
.
beforeSend
=
function
()
{
if
(
$elem
)
{
$elem
.
attr
(
"disabled"
,
"disabled"
);
}
if
(
params
.
$loading
)
{
if
(
params
.
loadingCallback
)
{
return
params
.
loadingCallback
.
apply
(
params
.
$loading
);
}
else
{
return
self
.
showLoadingIndicator
(
$
(
params
.
$loading
),
params
.
takeFocus
);
}
}
};
if
(
!
params
.
error
)
{
params
.
error
=
function
()
{
self
.
discussionAlert
(
...
...
@@ -216,9 +199,21 @@
);
};
}
if
(
$elem
)
{
$elem
.
prop
(
"disabled"
,
true
);
}
if
(
params
.
$loading
)
{
if
(
params
.
loadingCallback
)
{
params
.
loadingCallback
.
apply
(
params
.
$loading
);
}
else
{
self
.
showLoadingIndicator
(
params
.
$loading
,
params
.
takeFocus
);
}
}
request
=
$
.
ajax
(
params
).
always
(
function
()
{
if
(
$elem
)
{
$elem
.
removeAttr
(
"disabled"
);
$elem
.
prop
(
"disabled"
,
false
);
}
if
(
params
.
$loading
)
{
if
(
params
.
loadedCallback
)
{
...
...
@@ -231,7 +226,7 @@
return
request
;
};
DiscussionUtil
.
updateWithUndo
=
function
(
model
,
updates
,
safeAjaxParams
,
errorMsg
)
{
DiscussionUtil
.
updateWithUndo
=
function
(
model
,
updates
,
safeAjaxParams
,
errorMsg
,
beforeSend
)
{
var
undo
,
self
=
this
;
if
(
errorMsg
)
{
...
...
@@ -241,6 +236,9 @@
}
undo
=
_
.
pick
(
model
.
attributes
,
_
.
keys
(
updates
));
model
.
set
(
updates
);
if
(
typeof
beforeSend
===
'function'
)
{
beforeSend
();
}
return
this
.
safeAjax
(
safeAjaxParams
).
fail
(
function
()
{
return
model
.
set
(
undo
);
});
...
...
@@ -263,9 +261,12 @@
DiscussionUtil
.
formErrorHandler
=
function
(
errorsField
)
{
return
function
(
xhr
,
textStatus
,
error
)
{
var
makeErrorElem
,
response
,
_i
,
_len
,
_ref
,
_results
;
var
makeErrorElem
,
response
,
_i
,
_len
,
_ref
,
_results
,
$errorItem
;
makeErrorElem
=
function
(
message
)
{
return
$
(
"<li>"
).
addClass
(
"post-error"
).
html
(
message
);
return
edx
.
HtmlUtils
.
setHtml
(
$
(
"<li>"
).
addClass
(
"post-error"
),
message
);
};
errorsField
.
empty
().
show
();
if
(
xhr
.
status
===
400
)
{
...
...
@@ -275,14 +276,16 @@
_results
=
[];
for
(
_i
=
0
,
_len
=
_ref
.
length
;
_i
<
_len
;
_i
++
)
{
error
=
_ref
[
_i
];
_results
.
push
(
errorsField
.
append
(
makeErrorElem
(
error
)));
$errorItem
=
makeErrorElem
(
error
);
_results
.
push
(
errorsField
.
append
(
$errorItem
));
}
return
_results
;
}
}
else
{
return
errorsField
.
append
(
makeErrorElem
(
gettext
(
"We had some trouble processing your request. Please try again."
)
)
$errorItem
=
makeErrorElem
(
gettext
(
"We had some trouble processing your request. Please try again."
)
);
return
errorsField
.
append
(
$errorItem
);
}
};
};
...
...
@@ -291,11 +294,11 @@
return
errorsField
.
empty
();
};
DiscussionUtil
.
postMathJaxProcessor
=
function
(
tex
t
)
{
DiscussionUtil
.
postMathJaxProcessor
=
function
(
htmlSnippe
t
)
{
var
RE_DISPLAYMATH
,
RE_INLINEMATH
;
RE_INLINEMATH
=
/^
\$([^\$]
*
)\$
/g
;
RE_DISPLAYMATH
=
/^
\$\$([^\$]
*
)\$\$
/g
;
return
this
.
processEachMathAndCode
(
tex
t
,
function
(
s
,
type
)
{
return
this
.
processEachMathAndCode
(
htmlSnippe
t
,
function
(
s
,
type
)
{
if
(
type
===
'display'
)
{
return
s
.
replace
(
RE_DISPLAYMATH
,
function
(
$0
,
$1
)
{
return
"
\\
["
+
$1
+
"
\\
]"
;
...
...
@@ -353,84 +356,104 @@
return
this
.
getWmdEditor
(
$content
,
$local
,
cls_identifier
).
refreshPreview
();
};
DiscussionUtil
.
processEachMathAndCode
=
function
(
text
,
processor
)
{
var
$div
,
ESCAPED_BACKSLASH
,
ESCAPED_DOLLAR
,
RE_DISPLAYMATH
,
RE_INLINEMATH
,
cnt
,
codeArchive
,
processedText
;
codeArchive
=
[];
RE_DISPLAYMATH
=
/^
([^\$]
*
?)\$\$([^\$]
*
?)\$\$(
.*
)
$/m
;
RE_INLINEMATH
=
/^
([^\$]
*
?)\$([^\$]
+
?)\$(
.*
)
$/m
;
ESCAPED_DOLLAR
=
'@@ESCAPED_D@@'
;
var
RE_DISPLAYMATH
=
/^
([^\$]
*
?)\$\$([^\$]
*
?)\$\$(
.*
)
$/m
,
RE_INLINEMATH
=
/^
([^\$]
*
?)\$([^\$]
+
?)\$(
.*
)
$/m
,
ESCAPED_DOLLAR
=
'@@ESCAPED_D@@'
,
ESCAPED_BACKSLASH
=
'@@ESCAPED_B@@'
;
processedText
=
""
;
$div
=
$
(
"<div>"
).
html
(
text
);
/**
* Formats math and code chunks
* @param htmlSnippet - post contents in form of safe (escaped and/or stripped) HTML
* @param processor - callback to post-process math and code chunks. Should return HtmlUtils.HTML or "subclass"
* @returns {*}
*/
DiscussionUtil
.
processEachMathAndCode
=
function
(
htmlSnippet
,
processor
)
{
var
$div
,
codeArchive
,
processedHtmlString
,
htmlString
;
codeArchive
=
{};
processedHtmlString
=
""
;
$div
=
edx
.
HtmlUtils
.
setHtml
(
$
(
"<div>"
),
edx
.
HtmlUtils
.
ensureHtml
(
htmlSnippet
));
$div
.
find
(
"code"
).
each
(
function
(
index
,
code
)
{
codeArchive
.
push
(
$
(
code
).
html
()
);
return
$
(
code
).
html
(
codeArchive
.
length
-
1
);
codeArchive
[
index
]
=
$
(
code
).
html
(
);
return
$
(
code
).
text
(
index
);
});
text
=
$div
.
html
();
text
=
text
.
replace
(
/
\\\$
/g
,
ESCAPED_DOLLAR
);
htmlString
=
$div
.
html
();
htmlString
=
htmlString
.
replace
(
/
\\\$
/g
,
ESCAPED_DOLLAR
);
// suppressing Don't make functions within a loop.
/* jshint -W083 */
while
(
true
)
{
if
(
RE_INLINEMATH
.
test
(
text
))
{
text
=
text
.
replace
(
RE_INLINEMATH
,
function
(
$0
,
$1
,
$2
,
$3
)
{
processed
Text
+=
$1
+
processor
(
"$"
+
$2
+
"$"
,
'inline'
);
if
(
RE_INLINEMATH
.
test
(
htmlString
))
{
htmlString
=
htmlString
.
replace
(
RE_INLINEMATH
,
function
(
$0
,
$1
,
$2
,
$3
)
{
processed
HtmlString
+=
$1
+
processor
(
"$"
+
$2
+
"$"
,
'inline'
);
return
$3
;
});
}
else
if
(
RE_DISPLAYMATH
.
test
(
text
))
{
text
=
text
.
replace
(
RE_DISPLAYMATH
,
function
(
$0
,
$1
,
$2
,
$3
)
{
}
else
if
(
RE_DISPLAYMATH
.
test
(
htmlString
))
{
htmlString
=
htmlString
.
replace
(
RE_DISPLAYMATH
,
function
(
$0
,
$1
,
$2
,
$3
)
{
/*
bug fix, ordering is off
*/
processed
Text
=
processor
(
"$$"
+
$2
+
"$$"
,
'display'
)
+
processedText
;
processed
Text
=
$1
+
processedText
;
processed
HtmlString
=
processor
(
"$$"
+
$2
+
"$$"
,
'display'
)
+
processedHtmlString
;
processed
HtmlString
=
$1
+
processedHtmlString
;
return
$3
;
});
}
else
{
processed
Text
+=
text
;
processed
HtmlString
+=
htmlString
;
break
;
}
}
/* jshint +W083 */
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
,
function
(
$0
,
$1
,
$2
)
{
htmlString
=
processedHtmlString
;
htmlString
=
htmlString
.
replace
(
new
RegExp
(
ESCAPED_DOLLAR
,
'g'
),
'
\\
$'
);
htmlString
=
htmlString
.
replace
(
/
\\\\\\\\
/g
,
ESCAPED_BACKSLASH
);
htmlString
=
htmlString
.
replace
(
/
\\
begin
\{([
a-z
]
*
\*?)\}([\s\S]
*
?)\\
end
\{\1\}
/img
,
function
(
$0
,
$1
,
$2
)
{
return
processor
((
"
\\
begin{"
+
$1
+
"}"
)
+
$2
+
(
"
\\
end{"
+
$1
+
"}"
));
});
text
=
text
.
replace
(
new
RegExp
(
ESCAPED_BACKSLASH
,
'g'
),
'
\\\\\\\
\'
);
$div = $("<div>").html(text);
cnt = 0;
htmlString
=
htmlString
.
replace
(
new
RegExp
(
ESCAPED_BACKSLASH
,
'g'
),
'
\\\\\\\
\'
);
$div = edx.HtmlUtils.setHtml($("<div>"), edx.HtmlUtils.HTML(htmlString));
$div.find("code").each(function(index, code) {
$(code).html(processor(codeArchive[cnt], '
code
'));
return cnt += 1;
edx.HtmlUtils.setHtml(
$(code),
edx.HtmlUtils.HTML(processor(codeArchive[index], '
code
'))
);
});
text = $div.html();
return text;
return edx.HtmlUtils.HTML($div.html());
};
DiscussionUtil.unescapeHighlightTag = function(text) {
return text.replace(
/
\
<
\
;highlight
\
>
\
;/g,
"<span class='
search
-
highlight
'>").replace(/
\
<
\
;
\
/highlight
\
>
\
;/g, "</span>"
DiscussionUtil.unescapeHighlightTag = function(htmlSnippet) {
return edx.HtmlUtils.HTML(
htmlSnippet.toString().replace(
/
\
<
\
;highlight
\
>
\
;/g,
"<span class='
search
-
highlight
'>").replace(/
\
<
\
;
\
/highlight
\
>
\
;/g, "</span>"
)
);
};
DiscussionUtil.stripHighlight = function(
text
) {
return
text.replace(
/
\
&(amp
\
;)?lt
\
;highlight
\
&(amp
\
;)?gt
\
;/g, "").replace(/
\
&(amp
\
;)?lt
\
;
\
/highlight
\
&(amp
\
;)?gt
\
;/g, ""
);
DiscussionUtil.stripHighlight = function(
htmlString
) {
return
htmlString
.replace(/
\
&(amp
\
;)?lt
\
;highlight
\
&(amp
\
;)?gt
\
;/g, "")
.replace(/
\
&(amp
\
;)?lt
\
;
\
/highlight
\
&(amp
\
;)?gt
\
;/g, ""
);
};
DiscussionUtil.stripLatexHighlight = function(
tex
t) {
return this.processEachMathAndCode(
tex
t, this.stripHighlight);
DiscussionUtil.stripLatexHighlight = function(
htmlSnippe
t) {
return this.processEachMathAndCode(
htmlSnippe
t, this.stripHighlight);
};
DiscussionUtil.markdownWithHighlight = function(text) {
/**
* Processes markdown into formatted text and handles highlighting.
* @param unsafeText - raw markdown text, with all HTML entitites being *unescaped*.
* @returns HtmlSnippet
*/
DiscussionUtil.markdownWithHighlight = function(unsafeText) {
var converter;
text = t
ext.replace(/^
\
>
\
;/gm, ">");
unsafeText = unsafeT
ext.replace(/^
\
>
\
;/gm, ">");
converter = Markdown.getMathCompatibleConverter();
text = this.unescapeHighlightTag(this.stripLatexHighlight(converter.makeHtml(text)));
return text.replace(/^>/gm, ">");
/*
* converter.makeHtml and HTML escaping:
* - converter.makeHtml is not HtmlSnippet aware, so we must pass unescaped raw text
* - converter.makeHtml strips html tags in post body and escapes in code blocks by design.
* HTML tags are not supported. Only markdown is supported.
*/
var htmlSnippet = edx.HtmlUtils.HTML(converter.makeHtml(unsafeText));
return this.unescapeHighlightTag(this.stripLatexHighlight(htmlSnippet));
};
DiscussionUtil.abbreviateString = function(text, minLength) {
...
...
@@ -444,19 +467,48 @@
}
};
DiscussionUtil.abbreviateHTML = function(html, minLength) {
DiscussionUtil.convertMath = function(element) {
edx.HtmlUtils.setHtml(
element,
this.postMathJaxProcessor(this.markdownWithHighlight(element.text()))
);
this.typesetMathJax(element);
};
DiscussionUtil.typesetMathJax = function(element) {
if (typeof MathJax !== "undefined" && MathJax !== null) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, element[0]]);
}
};
DiscussionUtil.abbreviateHTML = function(htmlSnippet, maxLength) {
var $result, imagesToReplace, truncated_text;
truncated_text =
jQuery.truncate(html
, {
length: m
in
Length,
truncated_text =
edx.HtmlUtils.HTML(jQuery.truncate(htmlSnippet.toString()
, {
length: m
ax
Length,
noBreaks: true,
ellipsis: gettext('
…
')
});
$result = $("<div>" + truncated_text + "</div>");
}));
$result = $(edx.HtmlUtils.joinHtml(
edx.HtmlUtils.HTML("<div>"),
truncated_text,
edx.HtmlUtils.HTML("</div>")
).toString());
imagesToReplace = $result.find("img:not(:first)");
if (imagesToReplace.length > 0) {
$result.append("<p><em>Some images in this post have been omitted</em></p>");
edx.HtmlUtils.append(
$result,
edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML("<p><em>{text}</em></p>"),
{text: gettext("Some images in this post have been omitted")}
)
);
}
imagesToReplace.replaceWith("<em>image omitted</em>");
// See TNL-4983 for an explanation of why the linter requires ensureHtml()
var afterMessage = edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML("<em>{text}</em>"), {text: gettext("image omitted")}
);
imagesToReplace.after(edx.HtmlUtils.ensureHtml(afterMessage).toString()).remove();
return $result.html();
};
...
...
common/static/common/js/discussion/views/discussion_content_view.js
View file @
9f3b8ddf
...
...
@@ -271,20 +271,22 @@
return
$button
.
toggleClass
(
"is-checked"
,
endorsed
);
},
votes
:
function
(
votes
)
{
var
button
,
numVotes
,
selector
,
votes
Html
,
votesCountMsg
;
var
button
,
numVotes
,
selector
,
votes
Text
,
votesCountMsg
;
selector
=
".action-vote"
;
this
.
updateButtonState
(
selector
,
window
.
user
.
voted
(
this
.
model
));
button
=
this
.
$el
.
find
(
selector
);
numVotes
=
votes
.
up_count
;
votesCountMsg
=
ngettext
(
"there is currently
%(numVotes)s vote"
,
"there are currently %(numVotes)s
votes"
,
numVotes
"there is currently
{numVotes} vote"
,
"there are currently {numVotes}
votes"
,
numVotes
);
button
.
find
(
".js-sr-vote-count"
).
html
(
interpolate
(
votesCountMsg
,
{
numVotes
:
numVotes
},
true
));
votesHtml
=
interpolate
(
ngettext
(
"%(numVotes)s Vote"
,
"%(numVotes)s Votes"
,
numVotes
),
{
numVotes
:
numVotes
},
true
);
button
.
find
(
".vote-count"
).
html
(
votesHtml
);
return
this
.
$el
.
find
(
'.display-vote .vote-count'
).
html
(
votesHtml
);
button
.
find
(
".js-sr-vote-count"
).
empty
().
text
(
edx
.
StringUtils
.
interpolate
(
votesCountMsg
,
{
numVotes
:
numVotes
})
);
votesText
=
edx
.
StringUtils
.
interpolate
(
ngettext
(
"{numVotes} Vote"
,
"{numVotes} Votes"
,
numVotes
),
{
numVotes
:
numVotes
});
button
.
find
(
".vote-count"
).
empty
().
text
(
votesText
);
this
.
$el
.
find
(
'.display-vote .vote-count'
).
empty
().
text
(
votesText
);
},
pinned
:
function
(
pinned
)
{
this
.
updateButtonState
(
".action-pin"
,
pinned
);
...
...
@@ -389,18 +391,18 @@
msg
=
gettext
(
"We had some trouble removing this endorsement. Please try again."
);
}
}
beforeFunc
=
function
()
{
return
self
.
trigger
(
"comment:endorse"
);
};
return
DiscussionUtil
.
updateWithUndo
(
this
.
model
,
updates
,
{
url
:
url
,
type
:
"POST"
,
data
:
{
endorsed
:
is_endorsing
return
DiscussionUtil
.
updateWithUndo
(
this
.
model
,
updates
,
{
url
:
url
,
type
:
"POST"
,
data
:
{
endorsed
:
is_endorsing
},
$elem
:
$
(
event
.
currentTarget
)
},
beforeSend
:
beforeFunc
,
$elem
:
$
(
event
.
currentTarget
)
},
msg
).
always
(
this
.
trigger
(
"comment:endorse"
));
msg
,
function
()
{
return
self
.
trigger
(
"comment:endorse"
);
}
).
always
(
this
.
trigger
(
"comment:endorse"
));
};
DiscussionContentShowView
.
prototype
.
toggleVote
=
function
(
event
)
{
...
...
common/static/common/js/discussion/views/discussion_thread_edit_view.js
View file @
9f3b8ddf
...
...
@@ -25,13 +25,14 @@
},
render
:
function
()
{
var
threadTypeTemplate
,
formId
=
_
.
uniqueId
(
"form-"
);
this
.
template
=
_
.
template
(
$
(
'#thread-edit-template'
).
html
());
this
.
$el
.
html
(
this
.
template
(
this
.
model
.
toJSON
())).
appendTo
(
this
.
container
);
this
.
submitBtn
=
this
.
$
(
'.post-update'
);
threadTypeTemplate
=
_
.
template
(
$
(
"#thread-type-template"
).
html
());
this
.
addField
(
threadTypeTemplate
({
form_id
:
formId
}));
var
formId
=
_
.
uniqueId
(
"form-"
),
threadTypeTemplate
=
edx
.
HtmlUtils
.
template
(
$
(
"#thread-type-template"
).
html
()),
$threadTypeSelector
=
$
(
threadTypeTemplate
({
form_id
:
formId
}).
toString
()),
mainTemplate
=
edx
.
HtmlUtils
.
template
(
$
(
'#thread-edit-template'
).
html
());
edx
.
HtmlUtils
.
setHtml
(
this
.
$el
,
mainTemplate
(
this
.
model
.
toJSON
()));
this
.
container
.
append
(
this
.
$el
);
this
.
$submitBtn
=
this
.
$
(
'.post-update'
);
this
.
addField
(
$threadTypeSelector
);
this
.
$
(
"#"
+
formId
+
"-post-type-"
+
this
.
threadType
).
attr
(
'checked'
,
true
);
// Only allow the topic field for course threads, as standalone threads
// cannot be moved.
...
...
@@ -46,8 +47,8 @@
return
this
;
},
addField
:
function
(
fieldView
)
{
this
.
$
(
'.forum-edit-post-form-wrapper'
).
append
(
fieldView
);
addField
:
function
(
$
fieldView
)
{
this
.
$
(
'.forum-edit-post-form-wrapper'
).
append
(
$
fieldView
);
return
this
;
},
...
...
@@ -69,8 +70,8 @@
}
return
DiscussionUtil
.
safeAjax
({
$elem
:
this
.
submitBtn
,
$loading
:
this
.
submitBtn
,
$elem
:
this
.
$
submitBtn
,
$loading
:
this
.
$
submitBtn
,
url
:
DiscussionUtil
.
urlFor
(
'update_thread'
,
this
.
model
.
id
),
type
:
'POST'
,
dataType
:
'json'
,
...
...
common/static/common/js/discussion/views/discussion_thread_list_view.js
View file @
9f3b8ddf
...
...
@@ -123,6 +123,7 @@
return
self
.
displayedCollection
.
reset
(
discussion
.
models
);
});
this
.
collection
.
on
(
"add"
,
this
.
addAndSelectThread
);
this
.
collection
.
on
(
"thread:remove"
,
this
.
threadRemoved
);
this
.
sidebar_padding
=
10
;
this
.
boardName
=
null
;
this
.
template
=
_
.
template
(
$
(
"#thread-list-template"
).
html
());
...
...
@@ -133,11 +134,12 @@
});
this
.
searchAlertCollection
.
on
(
"add"
,
function
(
searchAlert
)
{
var
content
;
content
=
_
.
template
(
$
(
"#search-alert-template"
).
html
())({
'message'
:
searchAlert
.
attributes
.
message
,
'cid'
:
searchAlert
.
cid
content
=
edx
.
HtmlUtils
.
template
(
$
(
"#search-alert-template"
).
html
())({
'messageHtml'
:
searchAlert
.
attributes
.
message
,
'cid'
:
searchAlert
.
cid
,
'css_class'
:
searchAlert
.
attributes
.
css_class
});
self
.
$
(
".search-alerts"
).
append
(
content
);
edx
.
HtmlUtils
.
append
(
self
.
$
(
".search-alerts"
),
content
);
return
self
.
$
(
"#search-alert-"
+
searchAlert
.
cid
+
" a.dismiss"
)
.
bind
(
"click"
,
searchAlert
,
function
(
event
)
{
return
self
.
removeSearchAlert
(
event
.
data
.
cid
);
...
...
@@ -151,11 +153,19 @@
});
};
DiscussionThreadListView
.
prototype
.
addSearchAlert
=
function
(
message
)
{
/**
* Creates search alert model and adds it to collection
* @param message - alert message
* @param css_class - Allows setting custom css class for a message. This can be used to style messages
* of different types differently (i.e. other background, completely hide, etc.)
* @returns {Backbone.Model}
*/
DiscussionThreadListView
.
prototype
.
addSearchAlert
=
function
(
message
,
css_class
)
{
var
m
;
m
=
new
Backbone
.
Model
({
"message"
:
message
});
if
(
typeof
css_class
===
'undefined'
||
css_class
===
null
)
{
css_class
=
""
;
}
m
=
new
Backbone
.
Model
({
"message"
:
message
,
"css_class"
:
css_class
});
this
.
searchAlertCollection
.
add
(
m
);
return
m
;
};
...
...
@@ -169,13 +179,13 @@
};
DiscussionThreadListView
.
prototype
.
reloadDisplayedCollection
=
function
(
thread
)
{
var
active
,
content
,
current_el
,
thread_id
;
var
active
,
$
content
,
current_el
,
thread_id
;
this
.
clearSearchAlerts
();
thread_id
=
thread
.
get
(
'id'
);
content
=
this
.
renderThread
(
thread
);
$
content
=
this
.
renderThread
(
thread
);
current_el
=
this
.
$
(
".forum-nav-thread[data-id="
+
thread_id
+
"]"
);
active
=
current_el
.
has
(
".forum-nav-thread-link.is-active"
).
length
!==
0
;
current_el
.
replaceWith
(
content
);
current_el
.
replaceWith
(
$
content
);
this
.
showMetadataAccordingToSort
();
if
(
active
)
{
return
this
.
setActiveThread
(
thread_id
);
...
...
@@ -236,12 +246,14 @@
};
DiscussionThreadListView
.
prototype
.
render
=
function
()
{
var
self
=
this
;
var
self
=
this
,
$elem
=
this
.
template
({
isCohorted
:
this
.
courseSettings
.
get
(
"is_cohorted"
),
isPrivilegedUser
:
DiscussionUtil
.
isPrivilegedUser
()
});
this
.
timer
=
0
;
this
.
$el
.
html
(
this
.
template
({
isCohorted
:
this
.
courseSettings
.
get
(
"is_cohorted"
),
isPrivilegedUser
:
DiscussionUtil
.
isPrivilegedUser
()
}));
this
.
$el
.
empty
();
this
.
$el
.
append
(
$elem
);
this
.
$
(
".forum-nav-sort-control option"
).
removeProp
(
"selected"
);
this
.
$
(
".forum-nav-sort-control option[value="
+
this
.
collection
.
sort_preference
+
"]"
)
.
prop
(
"selected"
,
true
);
...
...
@@ -258,20 +270,17 @@
};
DiscussionThreadListView
.
prototype
.
renderThreads
=
function
()
{
var
content
,
rendered
,
thread
,
_i
,
_len
,
_ref
;
this
.
$
(
".forum-nav-thread-list"
).
html
(
""
);
rendered
=
$
(
"<div></div>"
);
_ref
=
this
.
displayedCollection
.
models
;
for
(
_i
=
0
,
_len
=
_ref
.
length
;
_i
<
_len
;
_i
++
)
{
thread
=
_ref
[
_i
];
content
=
this
.
renderThread
(
thread
);
rendered
.
append
(
content
);
var
$content
,
thread
,
i
,
len
;
this
.
$
(
".forum-nav-thread-list"
).
empty
();
for
(
i
=
0
,
len
=
this
.
displayedCollection
.
models
.
length
;
i
<
len
;
i
++
)
{
thread
=
this
.
displayedCollection
.
models
[
i
];
$content
=
this
.
renderThread
(
thread
);
this
.
$
(
".forum-nav-thread-list"
).
append
(
$content
);
}
this
.
$
(
".forum-nav-thread-list"
).
html
(
rendered
.
html
());
this
.
showMetadataAccordingToSort
();
this
.
renderMorePages
();
this
.
updateSidebar
();
return
this
.
trigger
(
"threads:rendered"
);
this
.
trigger
(
"threads:rendered"
);
};
DiscussionThreadListView
.
prototype
.
showMetadataAccordingToSort
=
function
()
{
...
...
@@ -291,19 +300,15 @@
DiscussionThreadListView
.
prototype
.
renderMorePages
=
function
()
{
if
(
this
.
displayedCollection
.
hasMorePages
())
{
return
this
.
$
(
".forum-nav-thread-list"
)
.
append
(
"<li class='forum-nav-load-more'>"
+
" <a href='#' class='forum-nav-load-more-link'>"
+
gettext
(
"Load more"
)
+
"</a>"
+
"</li>"
);
edx
.
HtmlUtils
.
append
(
this
.
$
(
".forum-nav-thread-list"
),
edx
.
HtmlUtils
.
template
(
$
(
"#nav-load-more-link"
).
html
())({})
);
}
};
DiscussionThreadListView
.
prototype
.
getLoadingContent
=
function
(
srText
)
{
return
'<div class="forum-nav-loading" tabindex="0">'
+
' <span class="icon fa fa-spinner fa-spin"/><span class="sr" role="alert">'
+
srText
+
'</span>'
+
'</div>'
;
return
edx
.
HtmlUtils
.
template
(
$
(
"#nav-loading-template"
).
html
())({
srText
:
srText
});
};
DiscussionThreadListView
.
prototype
.
loadMorePages
=
function
(
event
)
{
...
...
@@ -313,7 +318,8 @@
event
.
preventDefault
();
}
loadMoreElem
=
this
.
$
(
".forum-nav-load-more"
);
loadMoreElem
.
html
(
this
.
getLoadingContent
(
gettext
(
"Loading more threads"
)));
loadMoreElem
.
empty
();
edx
.
HtmlUtils
.
append
(
loadMoreElem
,
this
.
getLoadingContent
(
gettext
(
"Loading more threads"
)));
loadingElem
=
loadMoreElem
.
find
(
".forum-nav-loading"
);
DiscussionUtil
.
makeFocusTrap
(
loadingElem
);
loadingElem
.
focus
();
...
...
@@ -374,8 +380,8 @@
if
(
unreadCount
>
0
)
{
content
.
find
(
'.forum-nav-thread-comments-count'
).
attr
(
"data-tooltip"
,
interpolate
(
ngettext
(
'
%(unread_count)s new comment'
,
'%(unread_count)s
new comments'
,
unreadCount
),
edx
.
StringUtils
.
interpolate
(
ngettext
(
'
{unread_count} new comment'
,
'{unread_count}
new comments'
,
unreadCount
),
{
unread_count
:
unreadCount
},
true
)
...
...
@@ -392,23 +398,30 @@
return
false
;
};
DiscussionThreadListView
.
prototype
.
threadRemoved
=
function
(
thread
_id
)
{
return
this
.
trigger
(
"thread:removed"
,
thread_i
d
);
DiscussionThreadListView
.
prototype
.
threadRemoved
=
function
(
thread
)
{
this
.
trigger
(
"thread:removed"
,
threa
d
);
};
DiscussionThreadListView
.
prototype
.
setActiveThread
=
function
(
thread_id
)
{
var
$srElem
;
this
.
$
(
".forum-nav-thread-link"
).
find
(
".sr"
).
remove
();
this
.
$
(
".forum-nav-thread[data-id!='"
+
thread_id
+
"'] .forum-nav-thread-link"
)
.
removeClass
(
"is-active"
);
$srElem
=
edx
.
HtmlUtils
.
joinHtml
(
edx
.
HtmlUtils
.
HTML
(
'<span class="sr">'
),
edx
.
HtmlUtils
.
ensureHtml
(
gettext
(
"Current conversation"
)),
edx
.
HtmlUtils
.
HTML
(
'</span>'
)
).
toString
();
this
.
$
(
".forum-nav-thread[data-id='"
+
thread_id
+
"'] .forum-nav-thread-link"
)
.
addClass
(
"is-active"
).
find
(
".forum-nav-thread-wrapper-1"
)
.
prepend
(
'<span class="sr">'
+
gettext
(
"Current conversation"
)
+
'</span>'
);
.
prepend
(
$srElem
);
};
DiscussionThreadListView
.
prototype
.
goHome
=
function
()
{
var
thread_id
,
url
;
var
url
,
$tpl_content
;
this
.
template
=
_
.
template
(
$
(
"#discussion-home-template"
).
html
());
$
(
".forum-content"
).
html
(
this
.
template
);
$tpl_content
=
$
(
this
.
template
());
$
(
".forum-content"
).
empty
().
append
(
$tpl_content
);
$
(
".forum-nav-thread-list a"
).
removeClass
(
"is-active"
).
find
(
".sr"
).
remove
();
$
(
"input.email-setting"
).
bind
(
"click"
,
this
.
updateEmailNotifications
);
url
=
DiscussionUtil
.
urlFor
(
"notifications_status"
,
window
.
user
.
get
(
"id"
));
...
...
@@ -416,19 +429,9 @@
url
:
url
,
type
:
"GET"
,
success
:
function
(
response
)
{
if
(
response
.
status
)
{
return
$
(
'input.email-setting'
).
attr
(
'checked'
,
'checked'
);
}
else
{
return
$
(
'input.email-setting'
).
removeAttr
(
'checked'
);
}
$
(
'input.email-setting'
).
prop
(
'checked'
,
response
.
status
);
}
});
thread_id
=
null
;
return
this
.
trigger
(
"thread:removed"
);
/*
select all threads
*/
};
DiscussionThreadListView
.
prototype
.
isBrowseMenuVisible
=
function
()
{
...
...
@@ -505,19 +508,19 @@
};
DiscussionThreadListView
.
prototype
.
getNameWidth
=
function
(
name
)
{
var
test
,
width
;
test
=
$
(
"<div>"
);
test
.
css
({
var
$
test
,
width
;
$
test
=
$
(
"<div>"
);
$
test
.
css
({
"font-size"
:
this
.
$
(
".forum-nav-browse-current"
).
css
(
'font-size'
),
opacity
:
0
,
position
:
'absolute'
,
left
:
-
1000
,
top
:
-
1000
});
$
(
"body"
).
append
(
test
);
test
.
html
(
name
);
width
=
test
.
width
();
test
.
remove
();
$
(
"body"
).
append
(
$
test
);
$test
.
text
(
name
);
width
=
$
test
.
width
();
$
test
.
remove
();
return
width
;
};
...
...
@@ -653,8 +656,7 @@
};
DiscussionThreadListView
.
prototype
.
searchFor
=
function
(
text
)
{
var
url
,
self
=
this
;
var
url
,
self
=
this
;
this
.
clearSearchAlerts
();
this
.
clearFilters
();
this
.
mode
=
'search'
;
...
...
@@ -677,12 +679,16 @@
dataType
:
'json'
,
$loading
:
$
,
loadingCallback
:
function
()
{
return
self
.
$
(
".forum-nav-thread-list"
)
.
html
(
"<li class='forum-nav-load-more'>"
+
self
.
getLoadingContent
(
gettext
(
"Loading thread list"
))
+
"</li>"
);
var
element
=
self
.
$
(
".forum-nav-thread-list"
);
element
.
empty
();
edx
.
HtmlUtils
.
append
(
element
,
edx
.
HtmlUtils
.
joinHtml
(
edx
.
HtmlUtils
.
HTML
(
"<li class='forum-nav-load-more'>"
),
self
.
getLoadingContent
(
gettext
(
"Loading thread list"
)),
edx
.
HtmlUtils
.
HTML
(
"</li>"
)
)
);
},
loadedCallback
:
function
()
{
return
self
.
$
(
".forum-nav-thread-list .forum-nav-load-more"
).
remove
();
...
...
@@ -697,17 +703,22 @@
if
(
!
_
.
isNull
(
response
.
corrected_text
))
{
noResponseMsg
=
_
.
escape
(
gettext
(
'No results found for
%(original_query)s
. '
+
'Showing results for
%(suggested_query)s
.'
'No results found for
{original_query}
. '
+
'Showing results for
{suggested_query}
.'
)
);
message
=
interpolate
(
message
=
edx
.
HtmlUtils
.
interpolateHtml
(
noResponseMsg
,
{
"original_query"
:
"<em>"
+
_
.
escape
(
text
)
+
"</em>"
,
"suggested_query"
:
"<em>"
+
response
.
corrected_text
+
"</em>"
},
true
"original_query"
:
edx
.
HtmlUtils
.
joinHtml
(
edx
.
HtmlUtils
.
HTML
(
"<em>"
),
text
,
edx
.
HtmlUtils
.
HTML
(
"</em>"
)
),
"suggested_query"
:
edx
.
HtmlUtils
.
joinHtml
(
edx
.
HtmlUtils
.
HTML
(
"<em>"
),
response
.
corrected_text
,
edx
.
HtmlUtils
.
HTML
(
"</em>"
)
)
}
);
self
.
addSearchAlert
(
message
);
}
else
if
(
response
.
discussion_data
.
length
===
0
)
{
...
...
@@ -731,18 +742,23 @@
url
:
DiscussionUtil
.
urlFor
(
"users"
),
type
:
"GET"
,
dataType
:
'json'
,
error
:
function
()
{
},
error
:
function
()
{},
success
:
function
(
response
)
{
var
message
;
var
message
,
username
;
if
(
response
.
users
.
length
>
0
)
{
message
=
interpolate
(
_
.
escape
(
gettext
(
'Show posts by %(username)s.'
)),
{
"username"
:
_
.
template
(
'<a class="link-jump" href="<%= url %>"><%- username %></a>'
)({
url
:
DiscussionUtil
.
urlFor
(
"user_profile"
,
response
.
users
[
0
].
id
),
username
:
response
.
users
[
0
].
username
})
},
true
);
return
self
.
addSearchAlert
(
message
);
username
=
edx
.
HtmlUtils
.
joinHtml
(
edx
.
HtmlUtils
.
interpolateHtml
(
edx
.
HtmlUtils
.
HTML
(
'<a class="link-jump" href="{url}">'
),
{
url
:
DiscussionUtil
.
urlFor
(
"user_profile"
,
response
.
users
[
0
].
id
)}
),
response
.
users
[
0
].
username
,
edx
.
HtmlUtils
.
HTML
(
"</a>"
)
);
message
=
edx
.
HtmlUtils
.
interpolateHtml
(
gettext
(
'Show posts by {username}.'
),
{
"username"
:
username
}
);
return
self
.
addSearchAlert
(
message
,
'search-by-user'
);
}
}
});
...
...
@@ -765,23 +781,17 @@
};
DiscussionThreadListView
.
prototype
.
updateEmailNotifications
=
function
()
{
if
(
$
(
'input.email-setting'
).
attr
(
'checked'
))
{
return
DiscussionUtil
.
safeAjax
({
url
:
DiscussionUtil
.
urlFor
(
"enable_notifications"
),
type
:
"POST"
,
error
:
function
()
{
return
$
(
'input.email-setting'
).
removeAttr
(
'checked'
);
}
});
}
else
{
return
DiscussionUtil
.
safeAjax
({
url
:
DiscussionUtil
.
urlFor
(
"disable_notifications"
),
type
:
"POST"
,
error
:
function
()
{
return
$
(
'input.email-setting'
).
attr
(
'checked'
,
'checked'
);
}
});
}
var
$checkbox
,
checked
,
urlName
;
$checkbox
=
$
(
'input.email-setting'
);
checked
=
$checkbox
.
prop
(
'checked'
);
urlName
=
(
checked
)
?
"enable_notifications"
:
"disable_notifications"
;
DiscussionUtil
.
safeAjax
({
url
:
DiscussionUtil
.
urlFor
(
urlName
),
type
:
"POST"
,
error
:
function
()
{
$checkbox
.
prop
(
'checked'
,
!
checked
);
}
});
};
return
DiscussionThreadListView
;
...
...
common/static/common/js/discussion/views/discussion_thread_profile_view.js
View file @
9f3b8ddf
...
...
@@ -28,7 +28,7 @@
}
DiscussionThreadProfileView
.
prototype
.
render
=
function
()
{
var
element
,
params
;
var
params
;
this
.
convertMath
();
this
.
abbreviateBody
();
params
=
$
.
extend
(
this
.
model
.
toJSON
(),
{
...
...
@@ -42,26 +42,24 @@
}
});
}
this
.
$el
.
html
(
_
.
template
(
$
(
"#profile-thread-template"
).
html
())(
params
));
edx
.
HtmlUtils
.
setHtml
(
this
.
$el
,
edx
.
HtmlUtils
.
template
(
$
(
"#profile-thread-template"
).
html
())(
params
)
);
this
.
$
(
"span.timeago"
).
timeago
();
element
=
this
.
$
(
".post-body"
);
if
(
typeof
MathJax
!==
"undefined"
&&
MathJax
!==
null
)
{
MathJax
.
Hub
.
Queue
([
"Typeset"
,
MathJax
.
Hub
,
element
[
0
]]);
}
DiscussionUtil
.
typesetMathJax
(
this
.
$
(
".post-body"
));
return
this
;
};
DiscussionThreadProfileView
.
prototype
.
convertMath
=
function
()
{
return
this
.
model
.
set
(
'markdownBody'
,
DiscussionUtil
.
postMathJaxProcessor
(
DiscussionUtil
.
markdownWithHighlight
(
this
.
model
.
get
(
'body'
)))
);
var
htmlSnippet
=
DiscussionUtil
.
markdownWithHighlight
(
this
.
model
.
get
(
'body'
));
this
.
model
.
set
(
'markdownBody'
,
htmlSnippet
);
};
DiscussionThreadProfileView
.
prototype
.
abbreviateBody
=
function
()
{
var
abbreviated
;
abbreviated
=
DiscussionUtil
.
abbreviateHTML
(
this
.
model
.
get
(
'markdownBody'
),
140
);
return
this
.
model
.
set
(
'abbreviatedBody'
,
abbreviated
);
this
.
model
.
set
(
'abbreviatedBody'
,
abbreviated
);
};
return
DiscussionThreadProfileView
;
...
...
common/static/common/js/discussion/views/discussion_thread_show_view.js
View file @
9f3b8ddf
...
...
@@ -38,7 +38,6 @@
DiscussionThreadShowView
.
prototype
.
renderTemplate
=
function
()
{
var
context
;
this
.
template
=
_
.
template
(
$
(
"#thread-show-template"
).
html
());
context
=
$
.
extend
({
mode
:
this
.
mode
,
flagged
:
this
.
model
.
isFlagged
(),
...
...
@@ -46,27 +45,25 @@
cid
:
this
.
model
.
cid
,
readOnly
:
$
(
'.discussion-module'
).
data
(
'read-only'
)
},
this
.
model
.
attributes
);
return
this
.
template
(
context
);
return
edx
.
HtmlUtils
.
template
(
$
(
"#thread-show-template"
).
html
())
(
context
);
};
DiscussionThreadShowView
.
prototype
.
render
=
function
()
{
this
.
$el
.
html
(
this
.
renderTemplate
());
edx
.
HtmlUtils
.
setHtml
(
this
.
$el
,
this
.
renderTemplate
()
);
this
.
delegateEvents
();
this
.
renderAttrs
();
this
.
$
(
"span.timeago"
).
timeago
();
this
.
convertMath
();
this
.
highlight
(
this
.
$
(
".post-body"
)
);
this
.
highlight
(
this
.
$
(
"h1,h3"
)
);
this
.
$
(
".post-body"
);
this
.
$
(
"h1,h3"
);
return
this
;
};
DiscussionThreadShowView
.
prototype
.
convertMath
=
function
()
{
var
element
;
element
=
this
.
$
(
".post-body"
);
element
.
html
(
DiscussionUtil
.
postMathJaxProcessor
(
DiscussionUtil
.
markdownWithHighlight
(
element
.
text
())));
if
(
typeof
MathJax
!==
"undefined"
&&
MathJax
!==
null
)
{
return
MathJax
.
Hub
.
Queue
([
"Typeset"
,
MathJax
.
Hub
,
element
[
0
]]);
}
DiscussionUtil
.
convertMath
(
this
.
$
(
".post-body"
));
};
DiscussionThreadShowView
.
prototype
.
edit
=
function
(
event
)
{
...
...
@@ -77,12 +74,6 @@
return
this
.
trigger
(
"thread:_delete"
,
event
);
};
DiscussionThreadShowView
.
prototype
.
highlight
=
function
(
el
)
{
if
(
el
.
html
())
{
return
el
.
html
(
el
.
html
().
replace
(
/<mark>/g
,
"<mark>"
).
replace
(
/<
\/
mark>/g
,
"</mark>"
));
}
};
return
DiscussionThreadShowView
;
})(
DiscussionContentShowView
);
...
...
common/static/common/js/discussion/views/discussion_thread_view.js
View file @
9f3b8ddf
...
...
@@ -91,6 +91,7 @@
id
=
self
.
model
.
get
(
"id"
);
if
(
collection
.
get
(
id
))
{
self
.
model
=
collection
.
get
(
id
);
self
.
rerender
();
}
});
this
.
createShowView
();
...
...
@@ -133,7 +134,9 @@
DiscussionThreadView
.
prototype
.
render
=
function
()
{
var
self
=
this
;
this
.
$el
.
html
(
this
.
renderTemplate
());
var
$element
=
$
(
this
.
renderTemplate
());
this
.
$el
.
empty
();
this
.
$el
.
append
(
$element
);
this
.
delegateEvents
();
this
.
renderShowView
();
this
.
renderAttrs
();
...
...
@@ -215,7 +218,7 @@
}
};
DiscussionThreadView
.
prototype
.
loadResponses
=
function
(
responseLimit
,
elem
,
firstLoad
)
{
DiscussionThreadView
.
prototype
.
loadResponses
=
function
(
responseLimit
,
$
elem
,
firstLoad
)
{
var
takeFocus
,
self
=
this
;
takeFocus
=
this
.
mode
===
"tab"
?
false
:
true
;
...
...
@@ -227,8 +230,8 @@
resp_skip
:
this
.
responses
.
size
(),
resp_limit
:
responseLimit
?
responseLimit
:
void
0
},
$elem
:
elem
,
$loading
:
elem
,
$elem
:
$
elem
,
$loading
:
$
elem
,
takeFocus
:
takeFocus
,
complete
:
function
()
{
self
.
responsesRequest
=
null
;
...
...
@@ -279,20 +282,20 @@
};
DiscussionThreadView
.
prototype
.
renderResponseCountAndPagination
=
function
(
responseTotal
)
{
var
buttonText
,
loadMoreButton
,
responseCountFormat
,
responseLimit
,
responsePagination
,
var
buttonText
,
$
loadMoreButton
,
responseCountFormat
,
responseLimit
,
responsePagination
,
responsesRemaining
,
showingResponsesText
,
self
=
this
;
if
(
this
.
isQuestion
()
&&
this
.
markedAnswers
.
length
!==
0
)
{
responseCountFormat
=
ngettext
(
"
%(numResponses)s other response"
,
"%(numResponses)s
other responses"
,
responseTotal
"
{numResponses} other response"
,
"{numResponses}
other responses"
,
responseTotal
);
}
else
{
responseCountFormat
=
ngettext
(
"
%(numResponses)s response"
,
"%(numResponses)s
responses"
,
responseTotal
"
{numResponses} response"
,
"{numResponses}
responses"
,
responseTotal
);
}
this
.
$el
.
find
(
".response-count"
).
html
(
interpolate
(
responseCountFormat
,
{
numResponses
:
responseTotal
},
true
)
);
this
.
$el
.
find
(
".response-count"
).
text
(
edx
.
StringUtils
.
interpolate
(
responseCountFormat
,
{
numResponses
:
responseTotal
},
true
)
);
responsePagination
=
this
.
$el
.
find
(
".response-pagination"
);
responsePagination
.
empty
();
if
(
responseTotal
>
0
)
{
...
...
@@ -301,9 +304,9 @@
showingResponsesText
=
gettext
(
"Showing all responses"
);
}
else
{
showingResponsesText
=
interpolate
(
showingResponsesText
=
edx
.
StringUtils
.
interpolate
(
ngettext
(
"Showing first response"
,
"Showing first
%(numResponses)s
responses"
,
"Showing first response"
,
"Showing first
{numResponses}
responses"
,
this
.
responses
.
size
()
),
{
numResponses
:
this
.
responses
.
size
()
},
...
...
@@ -312,22 +315,22 @@
}
responsePagination
.
append
(
$
(
"<span>"
)
.
addClass
(
"response-display-count"
).
html
(
_
.
escape
(
showingResponsesText
)
));
.
addClass
(
"response-display-count"
).
text
(
showingResponsesText
));
if
(
responsesRemaining
>
0
)
{
if
(
responsesRemaining
<
SUBSEQUENT_RESPONSE_PAGE_SIZE
)
{
responseLimit
=
null
;
buttonText
=
gettext
(
"Load all responses"
);
}
else
{
responseLimit
=
SUBSEQUENT_RESPONSE_PAGE_SIZE
;
buttonText
=
interpolate
(
gettext
(
"Load next %(numResponses)s
responses"
),
{
buttonText
=
edx
.
StringUtils
.
interpolate
(
gettext
(
"Load next {numResponses}
responses"
),
{
numResponses
:
responseLimit
},
true
);
}
loadMoreButton
=
$
(
"<button>"
).
addClass
(
"load-response-button"
).
html
(
_
.
escape
(
buttonText
)
);
loadMoreButton
.
click
(
function
()
{
return
self
.
loadResponses
(
responseLimit
,
loadMoreButton
);
$loadMoreButton
=
$
(
"<button>"
).
addClass
(
"load-response-button"
).
text
(
buttonText
);
$
loadMoreButton
.
click
(
function
()
{
return
self
.
loadResponses
(
responseLimit
,
$
loadMoreButton
);
});
return
responsePagination
.
append
(
loadMoreButton
);
return
responsePagination
.
append
(
$
loadMoreButton
);
}
}
};
...
...
common/static/common/js/discussion/views/response_comment_show_view.js
View file @
9f3b8ddf
...
...
@@ -37,12 +37,14 @@
ResponseCommentShowView
.
prototype
.
tagName
=
"li"
;
ResponseCommentShowView
.
prototype
.
render
=
function
()
{
this
.
template
=
_
.
template
(
$
(
"#response-comment-show-template"
).
html
());
this
.
$el
.
html
(
this
.
template
(
_
.
extend
({
var
template
=
edx
.
HtmlUtils
.
template
(
$
(
"#response-comment-show-template"
).
html
());
var
context
=
_
.
extend
({
cid
:
this
.
model
.
cid
,
author_display
:
this
.
getAuthorDisplay
(),
readOnly
:
$
(
'.discussion-module'
).
data
(
'read-only'
)
},
this
.
model
.
attributes
)));
},
this
.
model
.
attributes
);
edx
.
HtmlUtils
.
setHtml
(
this
.
$el
,
template
(
context
));
this
.
delegateEvents
();
this
.
renderAttrs
();
this
.
$el
.
find
(
".timeago"
).
timeago
();
...
...
@@ -52,22 +54,25 @@
};
ResponseCommentShowView
.
prototype
.
addReplyLink
=
function
()
{
var
html
,
name
,
p
,
_ref
;
var
html
,
name
;
if
(
this
.
model
.
hasOwnProperty
(
'parent'
))
{
name
=
(
_ref
=
this
.
model
.
parent
.
get
(
'username'
))
!==
null
?
_ref
:
gettext
(
"anonymous"
);
html
=
"<a href='#comment_"
+
this
.
model
.
parent
.
id
+
"'>@"
+
name
+
"</a>: "
;
p
=
this
.
$
(
'.response-body p:first'
);
return
p
.
prepend
(
html
);
name
=
this
.
model
.
parent
.
get
(
'username'
)
||
gettext
(
"anonymous"
);
html
=
edx
.
HtmlUtils
.
interpolateHtml
(
edx
.
HtmlUtils
.
HTML
(
"<a href='#comment_{parent_id}'>@{name}</a>: "
),
{
parent_id
:
this
.
model
.
parent
.
id
,
name
:
name
}
);
return
edx
.
HtmlUtils
.
prepend
(
this
.
$
(
'.response-body p:first'
),
html
);
}
};
ResponseCommentShowView
.
prototype
.
convertMath
=
function
()
{
var
body
;
body
=
this
.
$el
.
find
(
".response-body"
);
body
.
html
(
DiscussionUtil
.
postMathJaxProcessor
(
DiscussionUtil
.
markdownWithHighlight
(
body
.
text
())));
if
(
typeof
MathJax
!==
"undefined"
&&
MathJax
!==
null
)
{
return
MathJax
.
Hub
.
Queue
([
"Typeset"
,
MathJax
.
Hub
,
body
[
0
]]);
}
DiscussionUtil
.
convertMath
(
this
.
$el
.
find
(
".response-body"
));
};
ResponseCommentShowView
.
prototype
.
_delete
=
function
(
event
)
{
...
...
common/static/common/js/discussion/views/thread_response_show_view.js
View file @
9f3b8ddf
...
...
@@ -33,19 +33,18 @@
};
ThreadResponseShowView
.
prototype
.
renderTemplate
=
function
()
{
var
context
;
this
.
template
=
_
.
template
(
$
(
"#thread-response-show-template"
).
html
());
context
=
_
.
extend
({
cid
:
this
.
model
.
cid
,
author_display
:
this
.
getAuthorDisplay
(),
endorser_display
:
this
.
getEndorserDisplay
(),
readOnly
:
$
(
'.discussion-module'
).
data
(
'read-only'
)
},
this
.
model
.
attributes
);
return
this
.
template
(
context
);
var
template
=
edx
.
HtmlUtils
.
template
(
$
(
"#thread-response-show-template"
).
html
()),
context
=
_
.
extend
({
cid
:
this
.
model
.
cid
,
author_display
:
this
.
getAuthorDisplay
(),
endorser_display
:
this
.
getEndorserDisplay
(),
readOnly
:
$
(
'.discussion-module'
).
data
(
'read-only'
)
},
this
.
model
.
attributes
);
return
template
(
context
);
};
ThreadResponseShowView
.
prototype
.
render
=
function
()
{
this
.
$el
.
html
(
this
.
renderTemplate
());
edx
.
HtmlUtils
.
setHtml
(
this
.
$el
,
this
.
renderTemplate
());
this
.
delegateEvents
();
this
.
renderAttrs
();
this
.
$el
.
find
(
".posted-details .timeago"
).
timeago
();
...
...
@@ -54,12 +53,7 @@
};
ThreadResponseShowView
.
prototype
.
convertMath
=
function
()
{
var
element
;
element
=
this
.
$
(
".response-body"
);
element
.
html
(
DiscussionUtil
.
postMathJaxProcessor
(
DiscussionUtil
.
markdownWithHighlight
(
element
.
text
())));
if
(
typeof
MathJax
!==
"undefined"
&&
MathJax
!==
null
)
{
return
MathJax
.
Hub
.
Queue
([
"Typeset"
,
MathJax
.
Hub
,
element
[
0
]]);
}
DiscussionUtil
.
convertMath
(
this
.
$
(
".response-body"
));
};
ThreadResponseShowView
.
prototype
.
edit
=
function
(
event
)
{
...
...
common/static/common/js/spec/discussion/utils_spec.js
View file @
9f3b8ddf
...
...
@@ -3,9 +3,10 @@
'use strict'
;
describe
(
'DiscussionUtil'
,
function
()
{
beforeEach
(
function
()
{
return
DiscussionSpecHelper
.
setUpGlobals
();
DiscussionSpecHelper
.
setUpGlobals
();
});
return
describe
(
"updateWithUndo"
,
function
()
{
describe
(
"updateWithUndo"
,
function
()
{
it
(
"calls through to safeAjax with correct params, and reverts the model in case of failure"
,
function
()
{
var
deferred
,
model
,
res
,
updates
;
deferred
=
$
.
Deferred
();
...
...
@@ -45,13 +46,13 @@
updates
=
{
hello
:
"world"
};
$elem
=
jasmine
.
createSpyObj
(
'$elem'
,
[
'
attr
'
]);
$elem
.
attr
.
and
.
returnValue
(
true
);
$elem
=
jasmine
.
createSpyObj
(
'$elem'
,
[
'
prop
'
]);
$elem
.
prop
.
and
.
returnValue
(
true
);
res
=
DiscussionUtil
.
updateWithUndo
(
model
,
updates
,
{
foo
:
"bar"
,
$elem
:
$elem
},
"error message"
);
expect
(
$elem
.
attr
).
toHaveBeenCalledWith
(
"disabled"
);
expect
(
$elem
.
prop
).
toHaveBeenCalledWith
(
"disabled"
);
expect
(
DiscussionUtil
.
safeAjax
).
toHaveBeenCalled
();
expect
(
model
.
attributes
).
toEqual
({
hello
:
false
,
...
...
@@ -64,6 +65,30 @@
return
expect
(
failed
).
toBe
(
true
);
});
});
describe
(
'safeAjax'
,
function
()
{
function
dismissAlert
()
{
$
(
".modal#discussion-alert"
).
remove
();
}
it
(
'respects global beforeSend'
,
function
()
{
var
beforeSendSpy
=
jasmine
.
createSpy
();
$
.
ajaxSetup
({
beforeSend
:
beforeSendSpy
});
var
$elem
=
jasmine
.
createSpyObj
(
'$elem'
,
[
'prop'
]);
DiscussionUtil
.
safeAjax
({
$elem
:
$elem
,
url
:
"/"
,
type
:
"GET"
,
dataType
:
"json"
}).
always
(
function
()
{
dismissAlert
();
});
expect
(
$elem
.
prop
).
toHaveBeenCalledWith
(
"disabled"
,
true
);
expect
(
beforeSendSpy
).
toHaveBeenCalled
();
});
});
});
}).
call
(
this
);
common/static/common/js/spec/discussion/view/discussion_thread_list_view_spec.js
View file @
9f3b8ddf
...
...
@@ -261,7 +261,7 @@
});
});
describe
(
"search alerts"
,
function
()
{
var
testAlertMessages
;
var
testAlertMessages
,
getAlertMessagesAndClasses
;
testAlertMessages
=
function
(
expectedMessages
)
{
return
expect
(
$
(
".search-alert .message"
).
map
(
function
()
{
...
...
@@ -269,6 +269,15 @@
}).
get
()).
toEqual
(
expectedMessages
);
};
getAlertMessagesAndClasses
=
function
()
{
return
$
(
".search-alert"
).
map
(
function
()
{
return
{
text
:
$
(
'.message'
,
this
).
html
(),
css_class
:
$
(
this
).
attr
(
'class'
)
};
}).
get
();
};
it
(
"renders and removes search alerts"
,
function
()
{
var
bar
,
foo
;
testAlertMessages
([]);
...
...
@@ -282,6 +291,27 @@
return
testAlertMessages
([]);
});
it
(
"renders search alert with custom class"
,
function
()
{
var
foo
,
messages
;
testAlertMessages
([]);
this
.
view
.
addSearchAlert
(
"foo"
,
"custom-class"
);
messages
=
getAlertMessagesAndClasses
();
expect
(
messages
.
length
).
toEqual
(
1
);
expect
(
messages
[
0
].
text
).
toEqual
(
"foo"
);
expect
(
messages
[
0
].
css_class
).
toEqual
(
"search-alert custom-class"
);
foo
=
this
.
view
.
addSearchAlert
(
"bar"
,
"other-class"
);
messages
=
getAlertMessagesAndClasses
();
expect
(
messages
.
length
).
toEqual
(
2
);
expect
(
messages
[
0
].
text
).
toEqual
(
"foo"
);
expect
(
messages
[
0
].
css_class
).
toEqual
(
"search-alert custom-class"
);
expect
(
messages
[
1
].
text
).
toEqual
(
"bar"
);
expect
(
messages
[
1
].
css_class
).
toEqual
(
"search-alert other-class"
);
});
it
(
"clears all search alerts"
,
function
()
{
this
.
view
.
addSearchAlert
(
"foo"
);
this
.
view
.
addSearchAlert
(
"bar"
);
...
...
common/static/common/js/spec/discussion/view/new_post_view_spec.js
View file @
9f3b8ddf
...
...
@@ -94,7 +94,7 @@
this
.
view
.
render
();
expectedGroupId
=
null
;
DiscussionSpecHelper
.
makeAjaxSpy
(
function
(
params
)
{
return
expect
(
params
.
data
.
group_id
).
toEqual
(
expectedGroupId
);
expect
(
params
.
data
.
group_id
).
toEqual
(
expectedGroupId
);
});
return
_
.
each
([
"1"
,
"2"
,
""
],
function
(
groupIdStr
)
{
expectedGroupId
=
groupIdStr
;
...
...
@@ -103,6 +103,7 @@
self
.
view
.
$
(
".js-post-body textarea"
).
val
(
"dummy body"
);
self
.
view
.
$
(
".forum-new-post-form"
).
submit
();
expect
(
$
.
ajax
).
toHaveBeenCalled
();
self
.
view
.
$
(
".forum-new-post-form"
).
prop
(
"disabled"
,
false
);
return
$
.
ajax
.
calls
.
reset
();
});
});
...
...
common/static/common/js/spec/discussion/view/response_comment_view_spec.js
View file @
9f3b8ddf
...
...
@@ -33,8 +33,10 @@
}
});
this
.
event
=
DiscussionSpecHelper
.
makeEventSpy
();
this
.
event
.
target
=
$
(
"body"
);
spyOn
(
this
.
comment
,
"remove"
);
return
spyOn
(
this
.
view
.
$el
,
"remove"
);
spyOn
(
this
.
view
.
$el
,
"remove"
);
$
(
this
.
event
.
target
).
prop
(
"disabled"
,
false
);
});
setAjaxResult
=
function
(
isSuccess
)
{
return
spyOn
(
$
,
"ajax"
).
and
.
callFake
(
function
(
params
)
{
...
...
@@ -151,7 +153,7 @@
this
.
view
.
$el
.
find
(
".edit-comment-body"
).
html
(
$
(
"<textarea></textarea>"
));
this
.
view
.
$el
.
find
(
".edit-comment-body textarea"
).
val
(
this
.
updatedBody
);
spyOn
(
this
.
view
,
'cancelEdit'
);
return
spyOn
(
$
,
"ajax"
).
and
.
callFake
(
function
(
params
)
{
spyOn
(
$
,
"ajax"
).
and
.
callFake
(
function
(
params
)
{
if
(
self
.
ajaxSucceed
)
{
params
.
success
();
}
else
{
...
...
@@ -164,10 +166,17 @@
}
};
});
this
.
event
=
DiscussionSpecHelper
.
makeEventSpy
();
// All the way down in discussion/utils.js there's this line
// element.after(...);
// element is event.target in this case. This causes a JS exception, so we override the target
this
.
event
.
target
=
$
(
"body"
);
$
(
this
.
event
.
target
).
prop
(
"disabled"
,
false
);
});
it
(
'calls the update endpoint correctly and displays the show view on success'
,
function
()
{
this
.
ajaxSucceed
=
true
;
this
.
view
.
update
(
DiscussionSpecHelper
.
makeEventSpy
()
);
this
.
view
.
update
(
this
.
event
);
expect
(
$
.
ajax
).
toHaveBeenCalled
();
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
url
.
_parts
.
path
)
.
toEqual
(
'/courses/edX/999/test/discussion/comments/01234567/update'
);
...
...
@@ -179,7 +188,7 @@
var
originalBody
;
originalBody
=
this
.
comment
.
get
(
"body"
);
this
.
ajaxSucceed
=
false
;
this
.
view
.
update
(
DiscussionSpecHelper
.
makeEventSpy
()
);
this
.
view
.
update
(
this
.
event
);
expect
(
$
.
ajax
).
toHaveBeenCalled
();
expect
(
$
.
ajax
.
calls
.
mostRecent
().
args
[
0
].
url
.
_parts
.
path
)
.
toEqual
(
'/courses/edX/999/test/discussion/comments/01234567/update'
);
...
...
common/static/common/js/spec_helpers/discussion_spec_helper.js
View file @
9f3b8ddf
...
...
@@ -72,12 +72,12 @@
'thread-response-edit'
,
'response-comment-show'
,
'response-comment-edit'
,
'thread-list-item'
,
'discussion-home'
,
'search-alert'
,
'new-post'
,
'thread-type'
,
'new-post-menu-entry'
,
'new-post-menu-category'
,
'topic'
,
'post-user-display'
,
'inline-discussion'
,
'pagination'
,
'user-profile'
,
'profile-thread'
'user-profile'
,
'profile-thread'
,
'customwmd-prompt'
,
'nav-loading'
];
templateNamesNoTrailingTemplate
=
[
'forum-action-endorse'
,
'forum-action-answer'
,
'forum-action-follow'
,
'forum-action-vote'
,
'forum-action-report'
,
'forum-action-pin'
,
'forum-action-close'
,
'forum-action-edit'
,
'forum-action-delete'
,
'forum-actions'
'forum-action-report'
,
'forum-action-pin'
,
'forum-action-close'
,
'forum-action-edit'
,
'forum-action-delete'
,
'forum-actions'
,
'alert-popup'
,
'nav-load-more-link'
];
for
(
_i
=
0
,
_len
=
templateNames
.
length
;
_i
<
_len
;
_i
++
)
{
templateName
=
templateNames
[
_i
];
...
...
common/static/common/templates/discussion/alert-popup.underscore
0 → 100644
View file @
9f3b8ddf
<div class='modal' role='alertdialog' id='discussion-alert' aria-describedby='discussion-alert-message'>
<div class='inner-wrapper discussion-alert-wrapper'>
<button class='close-modal dismiss' title='<%- gettext("Close") %>''>
<span class='icon fa fa-times' aria-hidden='true'></span>
</button>
<header>
<h2/></h2>
<hr/>
</header>
<p id='discussion-alert-message'></p>
<hr/>
<button class='dismiss'><%- gettext("OK") %></button>
</div>
</div>
common/static/common/templates/discussion/nav-load-more-link.underscore
0 → 100644
View file @
9f3b8ddf
<li class='forum-nav-load-more'>
<a href='#' class='forum-nav-load-more-link'>
<%- gettext("Load more") %>
</a>
</li>
common/static/common/templates/discussion/nav-loading.underscore
0 → 100644
View file @
9f3b8ddf
<div class="forum-nav-loading" tabindex="0">
<span class="icon fa fa-spinner fa-spin"/>
<span class="sr" role="alert"><%- srText %></span>
</div>
common/static/common/templates/discussion/search-alert.underscore
View file @
9f3b8ddf
<div class="search-alert" id="search-alert-<%- cid %>">
<div class="search-alert
<%- css_class %>
" id="search-alert-<%- cid %>">
<div class="search-alert-content">
<p class="message"><%=
message
%></p>
<p class="message"><%=
HtmlUtils.ensureHtml(messageHtml)
%></p>
</div>
<div class="search-alert-controls">
...
...
common/static/karma_common.conf.js
View file @
9f3b8ddf
...
...
@@ -35,6 +35,8 @@ var options = {
{
pattern
:
'common/js/vendor/backbone.js'
,
included
:
true
},
{
pattern
:
'edx-ui-toolkit/js/utils/global-loader.js'
,
included
:
true
},
{
pattern
:
'edx-ui-toolkit/js/utils/string-utils.js'
,
included
:
true
},
{
pattern
:
'edx-ui-toolkit/js/utils/html-utils.js'
,
included
:
true
},
{
pattern
:
'js/vendor/jasmine-imagediff.js'
,
included
:
true
},
{
pattern
:
'common/js/spec_helpers/jasmine-extensions.js'
,
included
:
true
},
...
...
lms/templates/discussion/_underscore_templates.html
View file @
9f3b8ddf
<
%
page
expression_filter=
"h"
/>
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%!
import
json
%
>
<
%!
from
openedx
.
core
.
djangolib
.
js_utils
import
js_escaped_string
%
>
<script
type=
"text/javascript"
>
window
.
PLATFORM_NAME
=
$
{
json
.
dumps
(
settings
.
PLATFORM_NAME
)};
window
.
ENABLE_DISCUSSION_HOME_PANEL
=
$
{
json
.
dumps
(
settings
.
FEATURES
.
get
(
'ENABLE_DISCUSSION_HOME_PANEL'
,
False
))};
window
.
PLATFORM_NAME
=
"${settings.PLATFORM_NAME | n, js_escaped_string}"
;
%
if
settings
.
FEATURES
.
get
(
'ENABLE_DISCUSSION_HOME_PANEL'
,
False
):
window
.
ENABLE_DISCUSSION_HOME_PANEL
=
true
;
%
else
:
window
.
ENABLE_DISCUSSION_HOME_PANEL
=
false
;
%
endif
</script>
<
%
...
...
@@ -11,7 +16,14 @@ template_names = [
'
thread
',
'
thread-show
',
'
thread-edit
',
'
thread-response
',
'
thread-response-show
',
'
thread-response-edit
',
'
response-comment-show
',
'
response-comment-edit
',
'
thread-list-item
',
'
discussion-home
',
'
search-alert
',
'
new-post
',
'
thread-type
',
'
new-post-menu-entry
',
'
new-post-menu-category
',
'
topic
',
'
post-user-display
',
'
inline-discussion
',
'
pagination
',
'
user-profile
',
'
profile-thread
',
'
customwmd-prompt
'
'
inline-discussion
',
'
pagination
',
'
user-profile
',
'
profile-thread
',
'
customwmd-prompt
',
'
nav-loading
'
]
##
same
,
but
without
trailing
"
-template
"
in
script
ID
-
these
templates
does
not
contain
any
free
variables
template_names_no_suffix =
[
'
forum-action-endorse
',
'
forum-action-answer
',
'
forum-action-follow
',
'
forum-action-vote
',
'
forum-action-report
',
'
forum-action-pin
',
'
forum-action-close
',
'
forum-action-edit
',
'
forum-action-delete
',
'
forum-actions
',
'
alert-popup
',
'
nav-load-more-link
'
]
%
>
...
...
@@ -21,8 +33,8 @@ template_names = [
</script>
% endfor
## same, but without trailing "-template" in script ID
% for template_name in
['forum-action-endorse', 'forum-action-answer', 'forum-action-follow', 'forum-action-vote', 'forum-action-report', 'forum-action-pin', 'forum-action-close', 'forum-action-edit', 'forum-action-delete', 'forum-actions']
:
% for template_name in
template_names_no_suffix
:
<script
aria-hidden=
"true"
type=
"text/template"
id=
"${template_name}"
>
<%
static
:
include
path
=
"common/templates/discussion/${template_name}.underscore"
/>
</script>
...
...
lms/templates/discussion/user_profile.html
View file @
9f3b8ddf
<
%
inherit
file=
"../main.html"
/>
<
%
page
expression_filter=
"h"
/>
<
%
namespace
name=
'static'
file=
'../static_content.html'
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
...
...
@@ -6,7 +7,7 @@ from django.template.defaultfilters import escapejs
%
>
<
%
block
name=
"bodyclass"
>
discussion
</
%
block>
<
%
block
name=
"pagetitle"
>
${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)
| h
}
</
%
block>
<
%
block
name=
"pagetitle"
>
${_("Discussion - {course_number}").format(course_number=course.display_number_with_default)}
</
%
block>
<
%
block
name=
"headextra"
>
<
%
static:css
group=
'style-course-vendor'
/>
...
...
@@ -33,7 +34,7 @@ from django.template.defaultfilters import escapejs
</nav>
</section>
<section
class=
"course-content container discussion-user-threads"
data-course-id=
"${course.id
| h}"
data-course-name=
"${course.display_name_with_default_escaped | h}"
data-threads=
"${threads | h}"
data-user-info=
"${user_info | h}"
data-page=
"${page | h}"
data-num-pages=
"${num_pages | h
}"
/>
<section
class=
"course-content container discussion-user-threads"
data-course-id=
"${course.id
}"
data-course-name=
"${course.display_name_with_default}"
data-threads=
"${threads}"
data-user-info=
"${user_info}"
data-page=
"${page}"
data-num-pages=
"${num_pages
}"
/>
</div>
</section>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment