From c44e52761518d468d9e08358695b4677916b26d8 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Fri, 20 Dec 2013 11:49:01 -0500 Subject: [PATCH 1/3] Remove unused forums-related cruft --- .../django_comment_client/base/views.py | 12 ++-- .../django_comment_client/forum/views.py | 3 - .../django_comment_client/helpers.py | 19 ----- .../discussion/_ajax_single_thread.html | 2 - .../discussion/_content_renderer.html | 20 ------ lms/templates/discussion/_forum.html | 26 ------- lms/templates/discussion/_inline.html | 16 ----- lms/templates/discussion/_single_thread.html | 39 ---------- .../discussion/_user_active_threads.html | 16 ----- .../discussion/ajax_create_comment.html | 3 - .../discussion/ajax_create_thread.html | 3 - .../discussion/ajax_update_comment.html | 3 - .../discussion/ajax_update_thread.html | 3 - .../discussion/mustache/_content.mustache | 72 ------------------- .../mustache/_edit_comment.mustache | 8 --- .../discussion/mustache/_edit_thread.mustache | 10 --- .../discussion/mustache/_new_post.mustache | 18 ----- .../discussion/mustache/_reply.mustache | 15 ---- 18 files changed, 5 insertions(+), 283 deletions(-) delete mode 100644 lms/templates/discussion/_ajax_single_thread.html delete mode 100644 lms/templates/discussion/_content_renderer.html delete mode 100644 lms/templates/discussion/_forum.html delete mode 100644 lms/templates/discussion/_inline.html delete mode 100644 lms/templates/discussion/_single_thread.html delete mode 100644 lms/templates/discussion/_user_active_threads.html delete mode 100644 lms/templates/discussion/ajax_create_comment.html delete mode 100644 lms/templates/discussion/ajax_create_thread.html delete mode 100644 lms/templates/discussion/ajax_update_comment.html delete mode 100644 lms/templates/discussion/ajax_update_thread.html delete mode 100644 lms/templates/discussion/mustache/_content.mustache delete mode 100644 lms/templates/discussion/mustache/_edit_comment.mustache delete mode 100644 lms/templates/discussion/mustache/_edit_thread.mustache delete mode 100644 lms/templates/discussion/mustache/_new_post.mustache delete mode 100644 lms/templates/discussion/mustache/_reply.mustache diff --git a/lms/djangoapps/django_comment_client/base/views.py b/lms/djangoapps/django_comment_client/base/views.py index 4e3331e592..4d8bffdd73 100644 --- a/lms/djangoapps/django_comment_client/base/views.py +++ b/lms/djangoapps/django_comment_client/base/views.py @@ -50,16 +50,14 @@ def permitted(fn): return wrapper -def ajax_content_response(request, course_id, content, template_name): +def ajax_content_response(request, course_id, content): context = { 'course_id': course_id, 'content': content, } - html = render_to_string(template_name, context) user_info = cc.User.from_django_user(request.user).to_dict() annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info) return JsonResponse({ - 'html': html, 'content': utils.safe_content(content), 'annotated_content_info': annotated_content_info, }) @@ -131,7 +129,7 @@ def create_thread(request, course_id, commentable_id): data = thread.to_dict() add_courseware_context([data], course) if request.is_ajax(): - return ajax_content_response(request, course_id, data, 'discussion/ajax_create_thread.html') + return ajax_content_response(request, course_id, data) else: return JsonResponse(utils.safe_content(data)) @@ -147,7 +145,7 @@ def update_thread(request, course_id, thread_id): thread.update_attributes(**extract(request.POST, ['body', 'title', 'tags'])) thread.save() if request.is_ajax(): - return ajax_content_response(request, course_id, thread.to_dict(), 'discussion/ajax_update_thread.html') + return ajax_content_response(request, course_id, thread.to_dict()) else: return JsonResponse(utils.safe_content(thread.to_dict())) @@ -184,7 +182,7 @@ def _create_comment(request, course_id, thread_id=None, parent_id=None): user = cc.User.from_django_user(request.user) user.follow(comment.thread) if request.is_ajax(): - return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_create_comment.html') + return ajax_content_response(request, course_id, comment.to_dict()) else: return JsonResponse(utils.safe_content(comment.to_dict())) @@ -228,7 +226,7 @@ def update_comment(request, course_id, comment_id): comment.update_attributes(**extract(request.POST, ['body'])) comment.save() if request.is_ajax(): - return ajax_content_response(request, course_id, comment.to_dict(), 'discussion/ajax_update_comment.html') + return ajax_content_response(request, course_id, comment.to_dict()) else: return JsonResponse(utils.safe_content(comment.to_dict())) diff --git a/lms/djangoapps/django_comment_client/forum/views.py b/lms/djangoapps/django_comment_client/forum/views.py index 83516fc3ac..4bbcd5acd4 100644 --- a/lms/djangoapps/django_comment_client/forum/views.py +++ b/lms/djangoapps/django_comment_client/forum/views.py @@ -248,13 +248,10 @@ def single_thread(request, course_id, discussion_id, thread_id): with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"): annotated_content_info = utils.get_annotated_content_infos(course_id, thread, request.user, user_info=user_info) context = {'thread': thread.to_dict(), 'course_id': course_id} - # TODO: Remove completely or switch back to server side rendering - # html = render_to_string('discussion/_ajax_single_thread.html', context) content = utils.safe_content(thread.to_dict()) with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): add_courseware_context([content], course) return utils.JsonResponse({ - #'html': html, 'content': content, 'annotated_content_info': annotated_content_info, }) diff --git a/lms/djangoapps/django_comment_client/helpers.py b/lms/djangoapps/django_comment_client/helpers.py index 1310c4e0c1..b9f144e76c 100644 --- a/lms/djangoapps/django_comment_client/helpers.py +++ b/lms/djangoapps/django_comment_client/helpers.py @@ -31,22 +31,3 @@ def include_mustache_templates(): file_contents = map(read_file, filter(valid_file_name, os.listdir(mustache_dir))) return '\n'.join(map(wrap_in_tag, map(strip_file_name, file_contents))) - - -def render_content(content, additional_context={}): - - context = { - 'content': extend_content(content), - content['type']: True, - } - if cc_settings.MAX_COMMENT_DEPTH is not None: - if content['type'] == 'thread': - if cc_settings.MAX_COMMENT_DEPTH < 0: - context['max_depth'] = True - elif content['type'] == 'comment': - if cc_settings.MAX_COMMENT_DEPTH <= content['depth']: - context['max_depth'] = True - context = merge_dict(context, additional_context) - partial_mustache_helpers = {k: partial(v, content) for k, v in mustache_helpers.items()} - context = merge_dict(context, partial_mustache_helpers) - return render_mustache('discussion/mustache/_content.mustache', context) diff --git a/lms/templates/discussion/_ajax_single_thread.html b/lms/templates/discussion/_ajax_single_thread.html deleted file mode 100644 index ab52a31d94..0000000000 --- a/lms/templates/discussion/_ajax_single_thread.html +++ /dev/null @@ -1,2 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> -${renderer.render_comments(thread.get('children'))} diff --git a/lms/templates/discussion/_content_renderer.html b/lms/templates/discussion/_content_renderer.html deleted file mode 100644 index 1de687cb19..0000000000 --- a/lms/templates/discussion/_content_renderer.html +++ /dev/null @@ -1,20 +0,0 @@ -<%! import django_comment_client.helpers as helpers %> - -<%def name="render_content(content, *args, **kwargs)"> - ${helpers.render_content(content, *args, **kwargs)} - - -<%def name="render_content_with_comments(content, *args, **kwargs)"> -
- ${render_content(content, *args, **kwargs)} - ${render_comments(content.get('children', []), *args, **kwargs)} -
- - -<%def name="render_comments(comments, *args, **kwargs)"> -
- % for comment in comments: - ${render_content_with_comments(comment, *args, **kwargs)} - % endfor -
- diff --git a/lms/templates/discussion/_forum.html b/lms/templates/discussion/_forum.html deleted file mode 100644 index b43efae666..0000000000 --- a/lms/templates/discussion/_forum.html +++ /dev/null @@ -1,26 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -
- -
-
- <%include file="_search_bar.html" /> -
-
- % if len(threads) == 0: -
- <%include file="_blank_slate.html" /> -
-
- % else: - <%include file="_sort.html" /> -
- % for thread in threads: - ${renderer.render_content_with_comments(thread)} - % endfor -
- <%include file="_paginator.html" /> - % endif -
- -<%include file="_js_data.html" /> diff --git a/lms/templates/discussion/_inline.html b/lms/templates/discussion/_inline.html deleted file mode 100644 index abef7f39e8..0000000000 --- a/lms/templates/discussion/_inline.html +++ /dev/null @@ -1,16 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -
- -
- -
- % for thread in threads: - ${renderer.render_content_with_comments(thread)} - % endfor -
- - <%include file="_paginator.html" /> -
- -<%include file="_js_data.html" /> diff --git a/lms/templates/discussion/_single_thread.html b/lms/templates/discussion/_single_thread.html deleted file mode 100644 index a14e03d10f..0000000000 --- a/lms/templates/discussion/_single_thread.html +++ /dev/null @@ -1,39 +0,0 @@ -<%! from django.utils.translation import ugettext as _ %> -<%namespace name="renderer" file="_content_renderer.html"/> -<%! from django_comment_client.mustache_helpers import url_for_user %> - -
- -
- -
- %if thread['group_id']: -
${_("This post visible only to group {group}.").format(group=cohort_dictionary[thread['group_id']])}
- %endif - - + ${thread['votes']['up_count']}votes (click to vote) -

${thread['title']}

-

- sometime by - ${thread['username']} -

-
-
- ${thread['body']} -
-
-
    - % for reply in thread.get("children", []): -
  1. -
    ${reply['body']}
    -
      - % for comment in reply.get("children", []): -
    1. ${comment['body']}
    2. - % endfor -
    -
  2. - % endfor -
-
- -<%include file="_js_data.html" /> diff --git a/lms/templates/discussion/_user_active_threads.html b/lms/templates/discussion/_user_active_threads.html deleted file mode 100644 index 1844009466..0000000000 --- a/lms/templates/discussion/_user_active_threads.html +++ /dev/null @@ -1,16 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -
- -
- -
- % for thread in threads: - ${renderer.render_content_with_comments(thread, {'partial_comments': True})} - % endfor -
- - <%include file="_paginator.html" /> -
- -<%include file="_js_data.html" /> diff --git a/lms/templates/discussion/ajax_create_comment.html b/lms/templates/discussion/ajax_create_comment.html deleted file mode 100644 index 92f78f9e52..0000000000 --- a/lms/templates/discussion/ajax_create_comment.html +++ /dev/null @@ -1,3 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -${renderer.render_content_with_comments(content)} diff --git a/lms/templates/discussion/ajax_create_thread.html b/lms/templates/discussion/ajax_create_thread.html deleted file mode 100644 index 92f78f9e52..0000000000 --- a/lms/templates/discussion/ajax_create_thread.html +++ /dev/null @@ -1,3 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -${renderer.render_content_with_comments(content)} diff --git a/lms/templates/discussion/ajax_update_comment.html b/lms/templates/discussion/ajax_update_comment.html deleted file mode 100644 index 2a451bb34c..0000000000 --- a/lms/templates/discussion/ajax_update_comment.html +++ /dev/null @@ -1,3 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -${renderer.render_content(content)} diff --git a/lms/templates/discussion/ajax_update_thread.html b/lms/templates/discussion/ajax_update_thread.html deleted file mode 100644 index 2a451bb34c..0000000000 --- a/lms/templates/discussion/ajax_update_thread.html +++ /dev/null @@ -1,3 +0,0 @@ -<%namespace name="renderer" file="_content_renderer.html"/> - -${renderer.render_content(content)} diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache deleted file mode 100644 index f1e387c89e..0000000000 --- a/lms/templates/discussion/mustache/_content.mustache +++ /dev/null @@ -1,72 +0,0 @@ -
-CONTENT MUSTACHE -
-
- -
{{content.votes.point}}votes (click to vote)
- -
-
- - {{#thread}} - {{content.displayed_title}} - {{/thread}} -
- -
{{content.displayed_body}}
- {{#thread}} -
- {{#content.tags}} - {{.}} - {{/content.tags}} -
- {{/thread}} -
- {{#content.courseware_location}} - (this post is about {{content.courseware_title}}) - {{/content.courseware_location}} -
-
-
- {{#content.updated}} - updated - {{/content.updated}} - {{content.created_at}} by - {{#content.anonymous}} - anonymous - {{/content.anonymous}} - {{^content.anonymous}} - {{content.username}} - {{/content.anonymous}} -
-
- {{#thread}} - {{#partial_comments}} - Show all comments ({{content.comments_count}} total) - {{/partial_comments}} - {{^partial_comments}} - Show {{content.comments_count}} {{##pluralize}}{{content.comments_count}} comment{{/pluralize}} - {{/partial_comments}} - {{/thread}} -
- -
-
-
-
-
diff --git a/lms/templates/discussion/mustache/_edit_comment.mustache b/lms/templates/discussion/mustache/_edit_comment.mustache deleted file mode 100644 index 72dc7b90a1..0000000000 --- a/lms/templates/discussion/mustache/_edit_comment.mustache +++ /dev/null @@ -1,8 +0,0 @@ -
- -
{{body}}
-
- Cancel - Update -
-
diff --git a/lms/templates/discussion/mustache/_edit_thread.mustache b/lms/templates/discussion/mustache/_edit_thread.mustache deleted file mode 100644 index a8860ed70d..0000000000 --- a/lms/templates/discussion/mustache/_edit_thread.mustache +++ /dev/null @@ -1,10 +0,0 @@ -
- - -
{{body}}
- -
- Cancel - Update -
-
diff --git a/lms/templates/discussion/mustache/_new_post.mustache b/lms/templates/discussion/mustache/_new_post.mustache deleted file mode 100644 index 21b5a05c7f..0000000000 --- a/lms/templates/discussion/mustache/_new_post.mustache +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/lms/templates/discussion/mustache/_reply.mustache b/lms/templates/discussion/mustache/_reply.mustache deleted file mode 100644 index 9affe0d8ce..0000000000 --- a/lms/templates/discussion/mustache/_reply.mustache +++ /dev/null @@ -1,15 +0,0 @@ -
- -
- - - {{#showWatchCheckbox}} - - - {{/showWatchCheckbox}} -
-
- Cancel - Submit -
-
From d4f7cd68d8403710da0b642c07845b462aa4573b Mon Sep 17 00:00:00 2001 From: Greg Price Date: Fri, 20 Dec 2013 18:56:13 -0500 Subject: [PATCH 2/3] Remove redundant URI.min.js fron lms/ A newer version was added to common/, so that is used instead. It is also added to the set of JS files used in unit tests to allow forums code in common/ to be tested. --- common/static/js_test.yml | 1 + lms/static/js/URI.min.js | 58 ------------------- .../discussion/_js_dependencies.html | 2 +- .../discussion/_js_head_dependencies.html | 2 +- 4 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 lms/static/js/URI.min.js diff --git a/common/static/js_test.yml b/common/static/js_test.yml index 31929401f0..c56e3f4a90 100644 --- a/common/static/js_test.yml +++ b/common/static/js_test.yml @@ -34,6 +34,7 @@ lib_paths: - js/vendor/underscore-min.js - js/vendor/backbone-min.js - js/vendor/jquery.timeago.js + - js/vendor/URI.min.js - coffee/src/ajax_prefix.js - js/test/add_ajax_prefix.js - coffee/src/jquery.immediateDescendents.js diff --git a/lms/static/js/URI.min.js b/lms/static/js/URI.min.js deleted file mode 100644 index 2663ddccd6..0000000000 --- a/lms/static/js/URI.min.js +++ /dev/null @@ -1,58 +0,0 @@ -/*! URI.js v1.6.3 http://medialize.github.com/URI.js/ */ -(function(){("undefined"!==typeof module&&module.exports?module.exports:window).IPv6={best:function(e){var e=e.toLowerCase().split(":"),h=e.length,j=8;""===e[0]&&""===e[1]&&""===e[2]?(e.shift(),e.shift()):""===e[0]&&""===e[1]?e.shift():""===e[h-1]&&""===e[h-2]&&e.pop();h=e.length;-1!==e[h-1].indexOf(".")&&(j=7);var f;for(f=0;fl;l++)if("0"===h[0]&&1l&&(h=p,l=r)):"0"==e[f]&&(d=!0,p=f,r=1);r>l&&(h=p,l=r);1>>10&1023|55296),a=56320|a&1023);return b+=x(a)}).join("")}function r(g, -d,e){for(var f=0,g=e?t(g/c):g>>1,g=g+t(g/d);g>B*a>>1;f+=s)g=t(g/B);return t(f+(B+1)*g/(g+b))}function p(b){var c=[],d=b.length,e,f=0,j=C,k=g,m,u,n,o,i;m=b.lastIndexOf(D);0>m&&(m=0);for(u=0;u=d&&h("invalid-input");o=b.charCodeAt(m++);o=10>o-48?o-22:26>o-65?o-65:26>o-97?o-97:s;(o>=s||o>t((v-f)/e))&&h("overflow");f+=o*e;i=n<=k?y:n>=k+a?a:n-k;if(ot(v/o)&&h("overflow");e*= -o}e=c.length+1;k=r(f-u,e,0==u);t(f/e)>v-j&&h("overflow");j+=t(f/e);f%=e;c.splice(f++,0,j)}return l(c)}function d(b){var c,d,e,j,k,i,m,l,n,o=[],w,p,q,b=f(b);w=b.length;c=C;d=0;k=g;for(i=0;in&&o.push(x(n));for((e=j=o.length)&&o.push(D);e=c&&nt((v-d)/p)&&h("overflow");d+=(m-c)*p;c=m;for(i=0;iv&&h("overflow"),n==c){l=d;for(m=s;;m+=s){n=m<=k?y:m>=k+a?a:m-k;if(l -n+q%l)-0));l=t(q/l)}o.push(x(l+22+75*(26>l)-0));k=r(d,p,e==j);d=0;++e}++d;++c}return o.join("")}var k,i="function"==typeof define&&"object"==typeof define.amd&&define.amd&&define,q="object"==typeof exports&&exports,z="object"==typeof module&&module,v=2147483647,s=36,y=1,a=26,b=38,c=700,g=72,C=128,D="-",w=/[^ -~]/,F=/^xn--/,E={overflow:"Overflow: input needs wider integers to process.",ucs2decode:"UCS-2(decode): illegal sequence",ucs2encode:"UCS-2(encode): illegal value","not-basic":"Illegal input >= 0x80 (not a basic code point)", -"invalid-input":"Invalid input"},B=s-y,t=Math.floor,x=String.fromCharCode,A;k={version:"0.3.0",ucs2:{decode:f,encode:l},decode:p,encode:d,toASCII:function(a){return j(a.split("."),function(a){return w.test(a)?"xn--"+d(a):a}).join(".")},toUnicode:function(a){return j(a.split("."),function(a){return F.test(a)?p(a.slice(4).toLowerCase()):a}).join(".")}};if(q)if(z&&z.exports==q)z.exports=k;else for(A in k)k.hasOwnProperty(A)&&(q[A]=k[A]);else i?define("punycode",k):e.punycode=k})(this); -(function(){var e={list:{ac:"com|gov|mil|net|org",ae:"ac|co|gov|mil|name|net|org|pro|sch",af:"com|edu|gov|net|org",al:"com|edu|gov|mil|net|org",ao:"co|ed|gv|it|og|pb",ar:"com|edu|gob|gov|int|mil|net|org|tur",at:"ac|co|gv|or",au:"asn|com|csiro|edu|gov|id|net|org",ba:"co|com|edu|gov|mil|net|org|rs|unbi|unmo|unsa|untz|unze",bb:"biz|co|com|edu|gov|info|net|org|store|tv",bh:"biz|cc|com|edu|gov|info|net|org",bn:"com|edu|gov|net|org",bo:"com|edu|gob|gov|int|mil|net|org|tv",br:"adm|adv|agr|am|arq|art|ato|b|bio|blog|bmd|cim|cng|cnt|com|coop|ecn|edu|eng|esp|etc|eti|far|flog|fm|fnd|fot|fst|g12|ggf|gov|imb|ind|inf|jor|jus|lel|mat|med|mil|mus|net|nom|not|ntr|odo|org|ppg|pro|psc|psi|qsl|rec|slg|srv|tmp|trd|tur|tv|vet|vlog|wiki|zlg", -bs:"com|edu|gov|net|org",bz:"du|et|om|ov|rg",ca:"ab|bc|mb|nb|nf|nl|ns|nt|nu|on|pe|qc|sk|yk",ck:"biz|co|edu|gen|gov|info|net|org",cn:"ac|ah|bj|com|cq|edu|fj|gd|gov|gs|gx|gz|ha|hb|he|hi|hl|hn|jl|js|jx|ln|mil|net|nm|nx|org|qh|sc|sd|sh|sn|sx|tj|tw|xj|xz|yn|zj",co:"com|edu|gov|mil|net|nom|org",cr:"ac|c|co|ed|fi|go|or|sa",cy:"ac|biz|com|ekloges|gov|ltd|name|net|org|parliament|press|pro|tm","do":"art|com|edu|gob|gov|mil|net|org|sld|web",dz:"art|asso|com|edu|gov|net|org|pol",ec:"com|edu|fin|gov|info|med|mil|net|org|pro", -eg:"com|edu|eun|gov|mil|name|net|org|sci",er:"com|edu|gov|ind|mil|net|org|rochest|w",es:"com|edu|gob|nom|org",et:"biz|com|edu|gov|info|name|net|org",fj:"ac|biz|com|info|mil|name|net|org|pro",fk:"ac|co|gov|net|nom|org",fr:"asso|com|f|gouv|nom|prd|presse|tm",gg:"co|net|org",gh:"com|edu|gov|mil|org",gn:"ac|com|gov|net|org",gr:"com|edu|gov|mil|net|org",gt:"com|edu|gob|ind|mil|net|org",gu:"com|edu|gov|net|org",hk:"com|edu|gov|idv|net|org",id:"ac|co|go|mil|net|or|sch|web",il:"ac|co|gov|idf|k12|muni|net|org", -"in":"ac|co|edu|ernet|firm|gen|gov|i|ind|mil|net|nic|org|res",iq:"com|edu|gov|i|mil|net|org",ir:"ac|co|dnssec|gov|i|id|net|org|sch",it:"edu|gov",je:"co|net|org",jo:"com|edu|gov|mil|name|net|org|sch",jp:"ac|ad|co|ed|go|gr|lg|ne|or",ke:"ac|co|go|info|me|mobi|ne|or|sc",kh:"com|edu|gov|mil|net|org|per",ki:"biz|com|de|edu|gov|info|mob|net|org|tel",km:"asso|com|coop|edu|gouv|k|medecin|mil|nom|notaires|pharmaciens|presse|tm|veterinaire",kn:"edu|gov|net|org",kr:"ac|busan|chungbuk|chungnam|co|daegu|daejeon|es|gangwon|go|gwangju|gyeongbuk|gyeonggi|gyeongnam|hs|incheon|jeju|jeonbuk|jeonnam|k|kg|mil|ms|ne|or|pe|re|sc|seoul|ulsan", -kw:"com|edu|gov|net|org",ky:"com|edu|gov|net|org",kz:"com|edu|gov|mil|net|org",lb:"com|edu|gov|net|org",lk:"assn|com|edu|gov|grp|hotel|int|ltd|net|ngo|org|sch|soc|web",lr:"com|edu|gov|net|org",lv:"asn|com|conf|edu|gov|id|mil|net|org",ly:"com|edu|gov|id|med|net|org|plc|sch",ma:"ac|co|gov|m|net|org|press",mc:"asso|tm",me:"ac|co|edu|gov|its|net|org|priv",mg:"com|edu|gov|mil|nom|org|prd|tm",mk:"com|edu|gov|inf|name|net|org|pro",ml:"com|edu|gov|net|org|presse",mn:"edu|gov|org",mo:"com|edu|gov|net|org", -mt:"com|edu|gov|net|org",mv:"aero|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro",mw:"ac|co|com|coop|edu|gov|int|museum|net|org",mx:"com|edu|gob|net|org",my:"com|edu|gov|mil|name|net|org|sch",nf:"arts|com|firm|info|net|other|per|rec|store|web",ng:"biz|com|edu|gov|mil|mobi|name|net|org|sch",ni:"ac|co|com|edu|gob|mil|net|nom|org",np:"com|edu|gov|mil|net|org",nr:"biz|com|edu|gov|info|net|org",om:"ac|biz|co|com|edu|gov|med|mil|museum|net|org|pro|sch",pe:"com|edu|gob|mil|net|nom|org|sld",ph:"com|edu|gov|i|mil|net|ngo|org", -pk:"biz|com|edu|fam|gob|gok|gon|gop|gos|gov|net|org|web",pl:"art|bialystok|biz|com|edu|gda|gdansk|gorzow|gov|info|katowice|krakow|lodz|lublin|mil|net|ngo|olsztyn|org|poznan|pwr|radom|slupsk|szczecin|torun|warszawa|waw|wroc|wroclaw|zgora",pr:"ac|biz|com|edu|est|gov|info|isla|name|net|org|pro|prof",ps:"com|edu|gov|net|org|plo|sec",pw:"belau|co|ed|go|ne|or",ro:"arts|com|firm|info|nom|nt|org|rec|store|tm|www",rs:"ac|co|edu|gov|in|org",sb:"com|edu|gov|net|org",sc:"com|edu|gov|net|org",sh:"co|com|edu|gov|net|nom|org", -sl:"com|edu|gov|net|org",st:"co|com|consulado|edu|embaixada|gov|mil|net|org|principe|saotome|store",sv:"com|edu|gob|org|red",sz:"ac|co|org",tr:"av|bbs|bel|biz|com|dr|edu|gen|gov|info|k12|name|net|org|pol|tel|tsk|tv|web",tt:"aero|biz|cat|co|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel",tw:"club|com|ebiz|edu|game|gov|idv|mil|net|org",mu:"ac|co|com|gov|net|or|org",mz:"ac|co|edu|gov|org",na:"co|com",nz:"ac|co|cri|geek|gen|govt|health|iwi|maori|mil|net|org|parliament|school", -pa:"abo|ac|com|edu|gob|ing|med|net|nom|org|sld",pt:"com|edu|gov|int|net|nome|org|publ",py:"com|edu|gov|mil|net|org",qa:"com|edu|gov|mil|net|org",re:"asso|com|nom",ru:"ac|adygeya|altai|amur|arkhangelsk|astrakhan|bashkiria|belgorod|bir|bryansk|buryatia|cbg|chel|chelyabinsk|chita|chukotka|chuvashia|com|dagestan|e-burg|edu|gov|grozny|int|irkutsk|ivanovo|izhevsk|jar|joshkar-ola|kalmykia|kaluga|kamchatka|karelia|kazan|kchr|kemerovo|khabarovsk|khakassia|khv|kirov|koenig|komi|kostroma|kranoyarsk|kuban|kurgan|kursk|lipetsk|magadan|mari|mari-el|marine|mil|mordovia|mosreg|msk|murmansk|nalchik|net|nnov|nov|novosibirsk|nsk|omsk|orenburg|org|oryol|penza|perm|pp|pskov|ptz|rnd|ryazan|sakhalin|samara|saratov|simbirsk|smolensk|spb|stavropol|stv|surgut|tambov|tatarstan|tom|tomsk|tsaritsyn|tsk|tula|tuva|tver|tyumen|udm|udmurtia|ulan-ude|vladikavkaz|vladimir|vladivostok|volgograd|vologda|voronezh|vrn|vyatka|yakutia|yamal|yekaterinburg|yuzhno-sakhalinsk", -rw:"ac|co|com|edu|gouv|gov|int|mil|net",sa:"com|edu|gov|med|net|org|pub|sch",sd:"com|edu|gov|info|med|net|org|tv",se:"a|ac|b|bd|c|d|e|f|g|h|i|k|l|m|n|o|org|p|parti|pp|press|r|s|t|tm|u|w|x|y|z",sg:"com|edu|gov|idn|net|org|per",sn:"art|com|edu|gouv|org|perso|univ",sy:"com|edu|gov|mil|net|news|org",th:"ac|co|go|in|mi|net|or",tj:"ac|biz|co|com|edu|go|gov|info|int|mil|name|net|nic|org|test|web",tn:"agrinet|com|defense|edunet|ens|fin|gov|ind|info|intl|mincom|nat|net|org|perso|rnrt|rns|rnu|tourism",tz:"ac|co|go|ne|or", -ua:"biz|cherkassy|chernigov|chernovtsy|ck|cn|co|com|crimea|cv|dn|dnepropetrovsk|donetsk|dp|edu|gov|if|in|ivano-frankivsk|kh|kharkov|kherson|khmelnitskiy|kiev|kirovograd|km|kr|ks|kv|lg|lugansk|lutsk|lviv|me|mk|net|nikolaev|od|odessa|org|pl|poltava|pp|rovno|rv|sebastopol|sumy|te|ternopil|uzhgorod|vinnica|vn|zaporizhzhe|zhitomir|zp|zt",ug:"ac|co|go|ne|or|org|sc",uk:"ac|bl|british-library|co|cym|gov|govt|icnet|jet|lea|ltd|me|mil|mod|national-library-scotland|nel|net|nhs|nic|nls|org|orgn|parliament|plc|police|sch|scot|soc", -us:"dni|fed|isa|kids|nsn",uy:"com|edu|gub|mil|net|org",ve:"co|com|edu|gob|info|mil|net|org|web",vi:"co|com|k12|net|org",vn:"ac|biz|com|edu|gov|health|info|int|name|net|org|pro",ye:"co|com|gov|ltd|me|net|org|plc",yu:"ac|co|edu|gov|org",za:"ac|agric|alt|bourse|city|co|cybernet|db|edu|gov|grondar|iaccess|imt|inca|landesign|law|mil|net|ngo|nis|nom|olivetti|org|pix|school|tm|web",zm:"ac|co|com|edu|gov|net|org|sch"},has_expression:null,is_expression:null,has:function(h){return!!h.match(e.has_expression)}, -is:function(h){return!!h.match(e.is_expression)},get:function(h){return(h=h.match(e.has_expression))&&h[1]||null},init:function(){var h="",j;for(j in e.list)Object.prototype.hasOwnProperty.call(e.list,j)&&(h+="|("+("("+e.list[j]+")."+j)+")");e.has_expression=RegExp(".("+h.substr(1)+")$","i");e.is_expression=RegExp("^("+h.substr(1)+")$","i")}};e.init();"undefined"!==typeof module&&module.exports?module.exports=e:window.SecondLevelDomains=e})(); -(function(e){function h(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function j(a){return"[object Array]"===""+Object.prototype.toString.call(a)}var f="undefined"!==typeof module&&module.exports,l=f?require("./punycode"):window.punycode,r=f?require("./IPv6"):window.IPv6,p=f?require("./SecondLevelDomains"):window.SecondLevelDomains,d=function(a,b){if(!(this instanceof d))return new d(a);a===e&&(a=location.href+"");this.href(a);return b!==e?this.absoluteTo(b):this},f=d.prototype;d.idn_expression= -/[^a-z0-9\.-]/i;d.punycode_expression=/(xn--)/i;d.ip4_expression=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;d.ip6_expression=/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; -d.find_uri_expression=/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;d.defaultPorts={http:"80",https:"443",ftp:"21"};d.invalid_hostname_characters=/[^a-zA-Z0-9\.-]/;d.encode=encodeURIComponent;d.decode=decodeURIComponent;d.iso8859=function(){d.encode=escape;d.decode=unescape};d.unicode=function(){d.encode=encodeURIComponent; -d.decode=decodeURIComponent};d.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}}};d.encodeQuery=function(a){return d.encode(a+"").replace(/%20/g,"+")};d.decodeQuery=function(a){return d.decode((a+"").replace(/\+/g,"%20"))};d.recodePath=function(a){for(var a=(a+"").split("/"),b=0,c=a.length;bd)return a[0]===b[0]&&"/"===a[0]?"/":"";"/"!==a[d]&&(d=a.substring(0,d).lastIndexOf("/")); -return a.substring(0,d+1)};d.withinString=function(a,b){return a.replace(d.find_uri_expression,b)};d.ensureValidHostname=function(a){if(a.match(d.invalid_hostname_characters)){if(!l)throw new TypeError("Hostname '"+a+"' contains characters other than [A-Z0-9.-] and Punycode.js is not available");if(l.toASCII(a).match(d.invalid_hostname_characters))throw new TypeError("Hostname '"+a+"' contains characters other than [A-Z0-9.-]");}};f.build=function(a){if(!0===a)this._deferred_build=!0;else if(a=== -e||this._deferred_build)this._string=d.build(this._parts),this._deferred_build=!1;return this};f.clone=function(){return new d(this)};f.toString=function(){return this.build(!1)._string};f.valueOf=function(){return this.toString()};k={protocol:"protocol",username:"username",password:"password",hostname:"hostname",port:"port"};q=function(a){return function(b,c){if(b===e)return this._parts[a]||"";this._parts[a]=b;this.build(!c);return this}};for(i in k)f[i]=q(k[i]);k={query:"?",fragment:"#"};q=function(a, -b){return function(c,d){if(c===e)return this._parts[a]||"";null!==c&&(c+="",c[0]===b&&(c=c.substring(1)));this._parts[a]=c;this.build(!d);return this}};for(i in k)f[i]=q(i,k[i]);k={search:["?","query"],hash:["#","fragment"]};q=function(a,b){return function(c,d){var e=this[a](c,d);return"string"===typeof e&&e.length?b+e:e}};for(i in k)f[i]=q(k[i][1],k[i][0]);f.pathname=function(a,b){if(a===e||!0===a){var c=this._parts.path||(this._parts.urn?"":"/");return a?d.decodePath(c):c}this._parts.path=a?d.recodePath(a): -"/";this.build(!b);return this};f.path=f.pathname;f.href=function(a,b){if(a===e)return this.toString();this._string="";this._parts={protocol:null,username:null,password:null,hostname:null,urn:null,port:null,path:null,query:null,fragment:null};var c=a instanceof d,g="object"===typeof a&&(a.hostname||a.path),f;if("string"===typeof a)this._parts=d.parse(a);else if(c||g)for(f in c=c?a._parts:a,c)Object.hasOwnProperty.call(this._parts,f)&&(this._parts[f]=c[f]);else throw new TypeError("invalid input"); -this.build(!b);return this};f.is=function(a){var b=!1,c=!1,g=!1,e=!1,f=!1,h=!1,i=!1,j=!this._parts.urn;this._parts.hostname&&(j=!1,c=d.ip4_expression.test(this._parts.hostname),g=d.ip6_expression.test(this._parts.hostname),b=c||g,f=(e=!b)&&p&&p.has(this._parts.hostname),h=e&&d.idn_expression.test(this._parts.hostname),i=e&&d.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return j;case "absolute":return!j;case "domain":case "name":return e;case "sld":return f; -case "ip":return b;case "ip4":case "ipv4":case "inet4":return c;case "ip6":case "ipv6":case "inet6":return g;case "idn":return h;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return i}return null};var z=f.protocol,v=f.port,s=f.hostname;f.protocol=function(a,b){if(a!==e&&a&&(a=a.replace(/:(\/\/)?$/,""),a.match(/[^a-zA-z0-9\.+-]/)))throw new TypeError("Protocol '"+a+"' contains characters other than [A-Z0-9.+-]");return z.call(this,a,b)};f.scheme=f.protocol;f.port= -function(a,b){if(this._parts.urn)return a===e?"":this;if(a!==e&&(0===a&&(a=null),a&&(a+="",":"===a[0]&&(a=a.substring(1)),a.match(/[^0-9]/))))throw new TypeError("Port '"+a+"' contains characters other than [0-9]");return v.call(this,a,b)};f.hostname=function(a,b){if(this._parts.urn)return a===e?"":this;if(a!==e){var c={};d.parseHost(a,c);a=c.hostname}return s.call(this,a,b)};f.host=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e)return this._parts.hostname?d.buildHost(this._parts): -"";d.parseHost(a,this._parts);this.build(!b);return this};f.authority=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e)return this._parts.hostname?d.buildAuthority(this._parts):"";d.parseAuthority(a,this._parts);this.build(!b);return this};f.userinfo=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e){if(!this._parts.username)return"";var c=d.buildUserinfo(this._parts);return c.substring(0,c.length-1)}"@"!==a[a.length-1]&&(a+="@");d.parseUserinfo(a,this._parts);this.build(!b); -return this};f.subdomain=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e)return!this._parts.hostname||this.is("IP")?"":this._parts.hostname.substring(0,this._parts.hostname.length-this.domain().length-1)||"";var c=this._parts.hostname.substring(0,this._parts.hostname.length-this.domain().length),c=RegExp("^"+h(c));a&&"."!==a[a.length-1]&&(a+=".");a&&d.ensureValidHostname(a);this._parts.hostname=this._parts.hostname.replace(c,a);this.build(!b);return this};f.domain=function(a,b){if(this._parts.urn)return a=== -e?"":this;"boolean"==typeof a&&(b=a,a=e);if(a===e){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.match(/\./g);if(c&&2>c.length)return this._parts.hostname;c=this._parts.hostname.length-this.tld(b).length-1;c=this._parts.hostname.lastIndexOf(".",c-1)+1;return this._parts.hostname.substring(c)||""}if(!a)throw new TypeError("cannot set domain empty");d.ensureValidHostname(a);this._parts.hostname=!this._parts.hostname||this.is("IP")?a:this._parts.hostname.replace(RegExp(h(this.domain())+ -"$"),a);this.build(!b);return this};f.tld=function(a,b){if(this._parts.urn)return a===e?"":this;"boolean"==typeof a&&(b=a,a=e);if(a===e){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.substring(this._parts.hostname.lastIndexOf(".")+1);return!0!==b&&p&&p.list[c.toLowerCase()]?p.get(this._parts.hostname)||c:c}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(p&&p.is(a))c=RegExp(h(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(c,a);else throw new TypeError("TLD '"+ -a+"' contains characters other than [A-Z0-9]");else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");c=RegExp(h(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(c,a)}else throw new TypeError("cannot set TLD empty");this.build(!b);return this};f.directory=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var c=this._parts.path.substring(0, -this._parts.path.length-this.filename().length-1)||(this._parts.hostname?"/":"");return a?d.decodePath(c):c}c=this._parts.path.substring(0,this._parts.path.length-this.filename().length);c=RegExp("^"+h(c));this.is("relative")||(a||(a="/"),"/"!==a[0]&&(a="/"+a));a&&"/"!==a[a.length-1]&&(a+="/");a=d.recodePath(a);this._parts.path=this._parts.path.replace(c,a);this.build(!b);return this};f.filename=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e||!0===a){if(!this._parts.path||"/"===this._parts.path)return""; -var c=this._parts.path.substring(this._parts.path.lastIndexOf("/")+1);return a?d.decodePathSegment(c):c}c=!1;"/"===a[0]&&(a=a.substring(1));a.match(/\.?\//)&&(c=!0);var g=RegExp(h(this.filename())+"$"),a=d.recodePath(a);this._parts.path=this._parts.path.replace(g,a);c?this.normalizePath(b):this.build(!b);return this};f.suffix=function(a,b){if(this._parts.urn)return a===e?"":this;if(a===e||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this.filename(),g=c.lastIndexOf(".");if(-1=== -g)return"";c=c.substring(g+1);c=/^[a-z0-9%]+$/i.test(c)?c:"";return a?d.decodePathSegment(c):c}"."===a[0]&&(a=a.substring(1));if(c=this.suffix())g=a?RegExp(h(c)+"$"):RegExp(h("."+c)+"$");else{if(!a)return this;this._parts.path+="."+d.recodePath(a)}g&&(a=d.recodePath(a),this._parts.path=this._parts.path.replace(g,a));this.build(!b);return this};var y=f.query;f.query=function(a,b){return!0===a?d.parseQuery(this._parts.query):a!==e&&"string"!==typeof a?(this._parts.query=d.buildQuery(a),this.build(!b), -this):y.call(this,a,b)};f.addQuery=function(a,b,c){var g=d.parseQuery(this._parts.query);d.addQuery(g,a,b);this._parts.query=d.buildQuery(g);"string"!==typeof a&&(c=b);this.build(!c);return this};f.removeQuery=function(a,b,c){var g=d.parseQuery(this._parts.query);d.removeQuery(g,a,b);this._parts.query=d.buildQuery(g);"string"!==typeof a&&(c=b);this.build(!c);return this};f.addSearch=f.addQuery;f.removeSearch=f.removeQuery;f.normalize=function(){return this._parts.urn?this.normalizeProtocol(!1).normalizeQuery(!1).normalizeFragment(!1).build(): -this.normalizeProtocol(!1).normalizeHostname(!1).normalizePort(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build()};f.normalizeProtocol=function(a){"string"===typeof this._parts.protocol&&(this._parts.protocol=this._parts.protocol.toLowerCase(),this.build(!a));return this};f.normalizeHostname=function(a){this._parts.hostname&&(this.is("IDN")&&l?this._parts.hostname=l.toASCII(this._parts.hostname):this.is("IPv6")&&r&&(this._parts.hostname=r.best(this._parts.hostname)),this._parts.hostname= -this._parts.hostname.toLowerCase(),this.build(!a));return this};f.normalizePort=function(a){"string"===typeof this._parts.protocol&&this._parts.port===d.defaultPorts[this._parts.protocol]&&(this._parts.port=null,this.build(!a));return this};f.normalizePath=function(a){if(this._parts.urn||!this._parts.path||"/"===this._parts.path)return this;var b,c,g=this._parts.path,e,f;"/"!==g[0]&&("."===g[0]&&(c=g.substring(0,g.indexOf("/"))),b=!0,g="/"+g);for(g=g.replace(/(\/(\.\/)+)|\/{2,}/g,"/");;){e=g.indexOf("/../"); -if(-1===e)break;else if(0===e){g=g.substring(3);break}f=g.substring(0,e).lastIndexOf("/");-1===f&&(f=e);g=g.substring(0,f)+g.substring(e+3)}b&&this.is("relative")&&(g=c?c+g:g.substring(1));g=d.recodePath(g);this._parts.path=g;this.build(!a);return this};f.normalizePathname=f.normalizePath;f.normalizeQuery=function(a){"string"===typeof this._parts.query&&(this._parts.query.length?this.query(d.parseQuery(this._parts.query)):this._parts.query=null,this.build(!a));return this};f.normalizeFragment=function(a){this._parts.fragment|| -(this._parts.fragment=null,this.build(!a));return this};f.normalizeSearch=f.normalizeQuery;f.normalizeHash=f.normalizeFragment;f.iso8859=function(){var a=d.encode,b=d.decode;d.encode=escape;d.decode=decodeURIComponent;this.normalize();d.encode=a;d.decode=b;return this};f.unicode=function(){var a=d.encode,b=d.decode;d.encode=encodeURIComponent;d.decode=unescape;this.normalize();d.encode=a;d.decode=b;return this};f.readable=function(){var a=this.clone();a.username("").password("").normalize();var b= -"";a._parts.protocol&&(b+=a._parts.protocol+"://");a._parts.hostname&&(a.is("punycode")&&l?(b+=l.toUnicode(a._parts.hostname),a._parts.port&&(b+=":"+a._parts.port)):b+=a.host());a._parts.hostname&&(a._parts.path&&"/"!==a._parts.path[0])&&(b+="/");b+=a.path(!0);if(a._parts.query){for(var c="",g=0,f=a._parts.query.split("&"),h=f.length;g - + diff --git a/lms/templates/discussion/_js_head_dependencies.html b/lms/templates/discussion/_js_head_dependencies.html index bde873fee1..961f254efe 100644 --- a/lms/templates/discussion/_js_head_dependencies.html +++ b/lms/templates/discussion/_js_head_dependencies.html @@ -9,7 +9,7 @@ - + From b7d7751dc268d9e538bd16ea82cf89ffa334b4c0 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Mon, 6 Jan 2014 16:10:48 -0500 Subject: [PATCH 3/3] Improve accessibility of forum vote buttons This change involves substantial refactoring of the relevant code to reduce duplication. It also makes the templates for the vote buttons more consistent and cleaner. JIRA: FOR-64 --- .../view/discussion_content_view_spec.coffee | 40 +++++-- ...discussion_thread_profile_view_spec.coffee | 40 +++++++ .../discussion_thread_show_view_spec.coffee | 40 +++++++ .../view/discussion_view_spec_helper.coffee | 113 ++++++++++++++++++ .../thread_response_show_view_spec.coffee | 40 +++++++ .../coffee/src/discussion/content.coffee | 15 ++- .../static/coffee/src/discussion/utils.coffee | 2 +- .../views/discussion_content_view.coffee | 39 ++++++ .../discussion_thread_profile_view.coffee | 45 +------ .../views/discussion_thread_show_view.coffee | 54 +-------- .../views/thread_response_show_view.coffee | 43 +------ .../discussion/_underscore_templates.html | 6 +- .../mustache/_inline_thread_show.mustache | 2 +- .../mustache/_profile_thread.mustache | 2 +- 14 files changed, 331 insertions(+), 150 deletions(-) create mode 100644 common/static/coffee/spec/discussion/view/discussion_thread_profile_view_spec.coffee create mode 100644 common/static/coffee/spec/discussion/view/discussion_thread_show_view_spec.coffee create mode 100644 common/static/coffee/spec/discussion/view/discussion_view_spec_helper.coffee create mode 100644 common/static/coffee/spec/discussion/view/thread_response_show_view_spec.coffee diff --git a/common/static/coffee/spec/discussion/view/discussion_content_view_spec.coffee b/common/static/coffee/spec/discussion/view/discussion_content_view_spec.coffee index 85ab5ec254..71495ad9c6 100644 --- a/common/static/coffee/spec/discussion/view/discussion_content_view_spec.coffee +++ b/common/static/coffee/spec/discussion/view/discussion_content_view_spec.coffee @@ -1,13 +1,12 @@ describe "DiscussionContentView", -> beforeEach -> - setFixtures - ( + setFixtures( """
- - + 0 + + 0 votes (click to vote)

Post Title

robot @@ -23,16 +22,21 @@ describe "DiscussionContentView", -> """ ) - @thread = new Thread { - id: '01234567', - user_id: '567', - course_id: 'mitX/999/test', - body: 'this is a thread', - created_at: '2013-04-03T20:08:39Z', - abuse_flaggers: ['123'] - roles: [] + @threadData = { + id: '01234567', + user_id: '567', + course_id: 'mitX/999/test', + body: 'this is a thread', + created_at: '2013-04-03T20:08:39Z', + abuse_flaggers: ['123'], + votes: {up_count: '42'}, + type: "thread", + roles: [] } + @thread = new Thread(@threadData) @view = new DiscussionContentView({ model: @thread }) + @view.setElement($('.discussion-post')) + window.user = new DiscussionUser({id: '567', upvoted_ids: []}) it 'defines the tag', -> expect($('#jasmine-fixtures')).toExist @@ -56,3 +60,15 @@ describe "DiscussionContentView", -> @thread.set("abuse_flaggers",temp_array) @thread.unflagAbuse() expect(@thread.get 'abuse_flaggers').toEqual [] + + it 'renders the vote button properly', -> + DiscussionViewSpecHelper.checkRenderVote(@view, @thread) + + it 'votes correctly', -> + DiscussionViewSpecHelper.checkVote(@view, @thread, @threadData, false) + + it 'unvotes correctly', -> + DiscussionViewSpecHelper.checkUnvote(@view, @thread, @threadData, false) + + it 'toggles the vote correctly', -> + DiscussionViewSpecHelper.checkToggleVote(@view, @thread) diff --git a/common/static/coffee/spec/discussion/view/discussion_thread_profile_view_spec.coffee b/common/static/coffee/spec/discussion/view/discussion_thread_profile_view_spec.coffee new file mode 100644 index 0000000000..f10d30d0af --- /dev/null +++ b/common/static/coffee/spec/discussion/view/discussion_thread_profile_view_spec.coffee @@ -0,0 +1,40 @@ +describe "DiscussionThreadProfileView", -> + beforeEach -> + setFixtures( + """ +

+ + 0 votes (click to vote) + +
+ """ + ) + + @threadData = { + id: "dummy", + user_id: "567", + course_id: "TestOrg/TestCourse/TestRun", + body: "this is a thread", + created_at: "2013-04-03T20:08:39Z", + abuse_flaggers: [], + votes: {up_count: "42"} + } + @thread = new Thread(@threadData) + @view = new DiscussionThreadProfileView({ model: @thread }) + @view.setElement($(".discussion-post")) + window.user = new DiscussionUser({id: "567", upvoted_ids: []}) + + it "renders the vote correctly", -> + DiscussionViewSpecHelper.checkRenderVote(@view, @thread) + + it "votes correctly", -> + DiscussionViewSpecHelper.checkVote(@view, @thread, @threadData, true) + + it "unvotes correctly", -> + DiscussionViewSpecHelper.checkUnvote(@view, @thread, @threadData, true) + + it "toggles the vote correctly", -> + DiscussionViewSpecHelper.checkToggleVote(@view, @thread) + + it "vote button activates on appropriate events", -> + DiscussionViewSpecHelper.checkVoteButtonEvents(@view) diff --git a/common/static/coffee/spec/discussion/view/discussion_thread_show_view_spec.coffee b/common/static/coffee/spec/discussion/view/discussion_thread_show_view_spec.coffee new file mode 100644 index 0000000000..69e4b231a2 --- /dev/null +++ b/common/static/coffee/spec/discussion/view/discussion_thread_show_view_spec.coffee @@ -0,0 +1,40 @@ +describe "DiscussionThreadShowView", -> + beforeEach -> + setFixtures( + """ +
+ + 0 votes (click to vote) + +
+ """ + ) + + @threadData = { + id: "dummy", + user_id: "567", + course_id: "TestOrg/TestCourse/TestRun", + body: "this is a thread", + created_at: "2013-04-03T20:08:39Z", + abuse_flaggers: [], + votes: {up_count: "42"} + } + @thread = new Thread(@threadData) + @view = new DiscussionThreadShowView({ model: @thread }) + @view.setElement($(".discussion-post")) + window.user = new DiscussionUser({id: "567", upvoted_ids: []}) + + it "renders the vote correctly", -> + DiscussionViewSpecHelper.checkRenderVote(@view, @thread) + + it "votes correctly", -> + DiscussionViewSpecHelper.checkVote(@view, @thread, @threadData, true) + + it "unvotes correctly", -> + DiscussionViewSpecHelper.checkUnvote(@view, @thread, @threadData, true) + + it 'toggles the vote correctly', -> + DiscussionViewSpecHelper.checkToggleVote(@view, @thread) + + it "vote button activates on appropriate events", -> + DiscussionViewSpecHelper.checkVoteButtonEvents(@view) diff --git a/common/static/coffee/spec/discussion/view/discussion_view_spec_helper.coffee b/common/static/coffee/spec/discussion/view/discussion_view_spec_helper.coffee new file mode 100644 index 0000000000..d5c25aa5e2 --- /dev/null +++ b/common/static/coffee/spec/discussion/view/discussion_view_spec_helper.coffee @@ -0,0 +1,113 @@ +class @DiscussionViewSpecHelper + @expectVoteRendered = (view, voted) -> + button = view.$el.find(".vote-btn") + if voted + expect(button.hasClass("is-cast")).toBe(true) + expect(button.attr("aria-pressed")).toEqual("true") + expect(button.attr("data-tooltip")).toEqual("remove vote") + expect(button.find(".votes-count-number").html()).toEqual("43") + expect(button.find(".sr").html()).toEqual("votes (click to remove your vote)") + else + expect(button.hasClass("is-cast")).toBe(false) + expect(button.attr("aria-pressed")).toEqual("false") + expect(button.attr("data-tooltip")).toEqual("vote") + expect(button.find(".votes-count-number").html()).toEqual("42") + expect(button.find(".sr").html()).toEqual("votes (click to vote)") + + @checkRenderVote = (view, model) -> + view.renderVote() + DiscussionViewSpecHelper.expectVoteRendered(view, false) + window.user.vote(model) + view.renderVote() + DiscussionViewSpecHelper.expectVoteRendered(view, true) + window.user.unvote(model) + view.renderVote() + DiscussionViewSpecHelper.expectVoteRendered(view, false) + + @checkVote = (view, model, modelData, checkRendering) -> + view.renderVote() + if checkRendering + DiscussionViewSpecHelper.expectVoteRendered(view, false) + + spyOn($, "ajax").andCallFake((params) => + newModelData = {} + $.extend(newModelData, modelData, {votes: {up_count: "43"}}) + params.success(newModelData, "success") + # Caller invokes always function on return value but it doesn't matter here + {always: ->} + ) + + view.vote() + expect(window.user.voted(model)).toBe(true) + if checkRendering + DiscussionViewSpecHelper.expectVoteRendered(view, true) + expect($.ajax).toHaveBeenCalled() + $.ajax.reset() + + # Check idempotence + view.vote() + expect(window.user.voted(model)).toBe(true) + if checkRendering + DiscussionViewSpecHelper.expectVoteRendered(view, true) + expect($.ajax).toHaveBeenCalled() + + @checkUnvote = (view, model, modelData, checkRendering) -> + window.user.vote(model) + expect(window.user.voted(model)).toBe(true) + if checkRendering + DiscussionViewSpecHelper.expectVoteRendered(view, true) + + spyOn($, "ajax").andCallFake((params) => + newModelData = {} + $.extend(newModelData, modelData, {votes: {up_count: "42"}}) + params.success(newModelData, "success") + # Caller invokes always function on return value but it doesn't matter here + {always: ->} + ) + + view.unvote() + expect(window.user.voted(model)).toBe(false) + if checkRendering + DiscussionViewSpecHelper.expectVoteRendered(view, false) + expect($.ajax).toHaveBeenCalled() + $.ajax.reset() + + # Check idempotence + view.unvote() + expect(window.user.voted(model)).toBe(false) + if checkRendering + DiscussionViewSpecHelper.expectVoteRendered(view, false) + expect($.ajax).toHaveBeenCalled() + + @checkToggleVote = (view, model) -> + event = {preventDefault: ->} + spyOn(event, "preventDefault") + spyOn(view, "vote").andCallFake(() -> window.user.vote(model)) + spyOn(view, "unvote").andCallFake(() -> window.user.unvote(model)) + + expect(window.user.voted(model)).toBe(false) + view.toggleVote(event) + expect(view.vote).toHaveBeenCalled() + expect(view.unvote).not.toHaveBeenCalled() + expect(event.preventDefault.callCount).toEqual(1) + + view.vote.reset() + view.unvote.reset() + expect(window.user.voted(model)).toBe(true) + view.toggleVote(event) + expect(view.vote).not.toHaveBeenCalled() + expect(view.unvote).toHaveBeenCalled() + expect(event.preventDefault.callCount).toEqual(2) + + @checkVoteButtonEvents = (view) -> + spyOn(view, "toggleVote") + button = view.$el.find(".vote-btn") + + button.click() + expect(view.toggleVote).toHaveBeenCalled() + view.toggleVote.reset() + button.trigger($.Event("keydown", {which: 13})) + expect(view.toggleVote).toHaveBeenCalled() + view.toggleVote.reset() + button.trigger($.Event("keydown", {which: 32})) + expect(view.toggleVote).not.toHaveBeenCalled() diff --git a/common/static/coffee/spec/discussion/view/thread_response_show_view_spec.coffee b/common/static/coffee/spec/discussion/view/thread_response_show_view_spec.coffee new file mode 100644 index 0000000000..7ba00c66d1 --- /dev/null +++ b/common/static/coffee/spec/discussion/view/thread_response_show_view_spec.coffee @@ -0,0 +1,40 @@ +describe "ThreadResponseShowView", -> + beforeEach -> + setFixtures( + """ +
+ + 0 votes (click to vote) + +
+ """ + ) + + @commentData = { + id: "dummy", + user_id: "567", + course_id: "TestOrg/TestCourse/TestRun", + body: "this is a comment", + created_at: "2013-04-03T20:08:39Z", + abuse_flaggers: [], + votes: {up_count: "42"} + } + @comment = new Comment(@commentData) + @view = new ThreadResponseShowView({ model: @comment }) + @view.setElement($(".discussion-post")) + window.user = new DiscussionUser({id: "567", upvoted_ids: []}) + + it "renders the vote correctly", -> + DiscussionViewSpecHelper.checkRenderVote(@view, @comment) + + it "votes correctly", -> + DiscussionViewSpecHelper.checkVote(@view, @comment, @commentData, true) + + it "unvotes correctly", -> + DiscussionViewSpecHelper.checkUnvote(@view, @comment, @commentData, true) + + it 'toggles the vote correctly', -> + DiscussionViewSpecHelper.checkToggleVote(@view, @comment) + + it "vote button activates on appropriate events", -> + DiscussionViewSpecHelper.checkVoteButtonEvents(@view) diff --git a/common/static/coffee/src/discussion/content.coffee b/common/static/coffee/src/discussion/content.coffee index 23a31ae7e6..5e3d4ce20b 100644 --- a/common/static/coffee/src/discussion/content.coffee +++ b/common/static/coffee/src/discussion/content.coffee @@ -99,6 +99,13 @@ if Backbone? @get("abuse_flaggers").pop(window.user.get('id')) @trigger "change", @ + vote: -> + @get("votes")["up_count"] = parseInt(@get("votes")["up_count"]) + 1 + @trigger "change", @ + + unvote: -> + @get("votes")["up_count"] = parseInt(@get("votes")["up_count"]) - 1 + @trigger "change", @ class @Thread extends @Content urlMappers: @@ -130,14 +137,6 @@ if Backbone? unfollow: -> @set('subscribed', false) - vote: -> - @get("votes")["up_count"] = parseInt(@get("votes")["up_count"]) + 1 - @trigger "change", @ - - unvote: -> - @get("votes")["up_count"] = parseInt(@get("votes")["up_count"]) - 1 - @trigger "change", @ - display_body: -> if @has("highlighted_body") String(@get("highlighted_body")).replace(//g, '').replace(/<\/highlight>/g, '') diff --git a/common/static/coffee/src/discussion/utils.coffee b/common/static/coffee/src/discussion/utils.coffee index a85e4f0eaa..0e8362472a 100644 --- a/common/static/coffee/src/discussion/utils.coffee +++ b/common/static/coffee/src/discussion/utils.coffee @@ -91,7 +91,7 @@ class @DiscussionUtil @activateOnEnter: (event, func) -> if event.which == 13 - e.preventDefault() + event.preventDefault() func(event) @makeFocusTrap: (elem) -> diff --git a/common/static/coffee/src/discussion/views/discussion_content_view.coffee b/common/static/coffee/src/discussion/views/discussion_content_view.coffee index 9c3c4a01f5..96d74df4ef 100644 --- a/common/static/coffee/src/discussion/views/discussion_content_view.coffee +++ b/common/static/coffee/src/discussion/views/discussion_content_view.coffee @@ -159,3 +159,42 @@ if Backbone? temp_array = [] @model.set('abuse_flaggers', temp_array) + + renderVote: => + button = @$el.find(".vote-btn") + voted = window.user.voted(@model) + voteNum = @model.get("votes")["up_count"] + button.toggleClass("is-cast", voted) + button.attr("aria-pressed", voted) + button.attr("data-tooltip", if voted then "remove vote" else "vote") + button.find(".votes-count-number").html(voteNum) + button.find(".sr").html(if voted then "votes (click to remove your vote)" else "votes (click to vote)") + + toggleVote: (event) => + event.preventDefault() + if window.user.voted(@model) + @unvote() + else + @vote() + + vote: => + window.user.vote(@model) + url = @model.urlFor("upvote") + DiscussionUtil.safeAjax + $elem: @$el.find(".vote-btn") + url: url + type: "POST" + success: (response, textStatus) => + if textStatus == 'success' + @model.set(response) + + unvote: => + window.user.unvote(@model) + url = @model.urlFor("unvote") + DiscussionUtil.safeAjax + $elem: @$el.find(".vote-btn") + url: url + type: "POST" + success: (response, textStatus) => + if textStatus == 'success' + @model.set(response) diff --git a/common/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee b/common/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee index 7130ac555c..f6a6ea8eb6 100644 --- a/common/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee +++ b/common/static/coffee/src/discussion/views/discussion_thread_profile_view.coffee @@ -2,7 +2,10 @@ if Backbone? class @DiscussionThreadProfileView extends DiscussionContentView expanded = false events: - "click .discussion-vote": "toggleVote" + "click .vote-btn": + (event) -> @toggleVote(event) + "keydown .vote-btn": + (event) -> DiscussionUtil.activateOnEnter(event, @toggleVote) "click .action-follow": "toggleFollowing" "keypress .action-follow": (event) -> DiscussionUtil.activateOnEnter(event, toggleFollowing) @@ -27,7 +30,7 @@ if Backbone? @$el.html(Mustache.render(@template, params)) @initLocal() @delegateEvents() - @renderVoted() + @renderVote() @renderAttrs() @$("span.timeago").timeago() @convertMath() @@ -35,15 +38,8 @@ if Backbone? @renderResponses() @ - renderVoted: => - if window.user.voted(@model) - @$("[data-role=discussion-vote]").addClass("is-cast") - else - @$("[data-role=discussion-vote]").removeClass("is-cast") - updateModelDetails: => - @renderVoted() - @$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"]) + @renderVote() convertMath: -> element = @$(".post-body") @@ -71,35 +67,6 @@ if Backbone? addComment: => @model.comment() - toggleVote: (event) -> - event.preventDefault() - if window.user.voted(@model) - @unvote() - else - @vote() - - vote: -> - window.user.vote(@model) - url = @model.urlFor("upvote") - DiscussionUtil.safeAjax - $elem: @$(".discussion-vote") - url: url - type: "POST" - success: (response, textStatus) => - if textStatus == 'success' - @model.set(response) - - unvote: -> - window.user.unvote(@model) - url = @model.urlFor("unvote") - DiscussionUtil.safeAjax - $elem: @$(".discussion-vote") - url: url - type: "POST" - success: (response, textStatus) => - if textStatus == 'success' - @model.set(response) - edit: -> abbreviateBody: -> diff --git a/common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee b/common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee index 1a3f8929e1..14dd01e3fa 100644 --- a/common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee +++ b/common/static/coffee/src/discussion/views/discussion_thread_show_view.coffee @@ -2,7 +2,10 @@ if Backbone? class @DiscussionThreadShowView extends DiscussionContentView events: - "click .discussion-vote": "toggleVote" + "click .vote-btn": + (event) -> @toggleVote(event) + "keydown .vote-btn": + (event) -> DiscussionUtil.activateOnEnter(event, @toggleVote) "click .discussion-flag-abuse": "toggleFlagAbuse" "keypress .discussion-flag-abuse": (event) -> DiscussionUtil.activateOnEnter(event, toggleFlagAbuse) @@ -28,7 +31,7 @@ if Backbone? render: -> @$el.html(@renderTemplate()) @delegateEvents() - @renderVoted() + @renderVote() @renderFlagged() @renderPinned() @renderAttrs() @@ -38,14 +41,6 @@ if Backbone? @highlight @$("h1,h3") @ - renderVoted: => - if window.user.voted(@model) - @$("[data-role=discussion-vote]").addClass("is-cast") - @$("[data-role=discussion-vote] span.sr").html("votes (click to remove your vote)") - else - @$("[data-role=discussion-vote]").removeClass("is-cast") - @$("[data-role=discussion-vote] span.sr").html("votes (click to vote)") - renderFlagged: => if window.user.id in @model.get("abuse_flaggers") or (DiscussionUtil.isFlagModerator and @model.get("abuse_flaggers").length > 0) @$("[data-role=thread-flag]").addClass("flagged") @@ -70,52 +65,15 @@ if Backbone? updateModelDetails: => - @renderVoted() + @renderVote() @renderFlagged() @renderPinned() - @$("[data-role=discussion-vote] .votes-count-number").html(@model.get("votes")["up_count"] + '') - if window.user.voted(@model) - @$("[data-role=discussion-vote] .votes-count-number span.sr").html("votes (click to remove your vote)") - else - @$("[data-role=discussion-vote] .votes-count-number span.sr").html("votes (click to vote)") - convertMath: -> element = @$(".post-body") element.html DiscussionUtil.postMathJaxProcessor DiscussionUtil.markdownWithHighlight element.text() MathJax.Hub.Queue ["Typeset", MathJax.Hub, element[0]] - toggleVote: (event) -> - event.preventDefault() - if window.user.voted(@model) - @unvote() - else - @vote() - - vote: -> - window.user.vote(@model) - url = @model.urlFor("upvote") - DiscussionUtil.safeAjax - $elem: @$(".discussion-vote") - url: url - type: "POST" - success: (response, textStatus) => - if textStatus == 'success' - @model.set(response, {silent: true}) - - - unvote: -> - window.user.unvote(@model) - url = @model.urlFor("unvote") - DiscussionUtil.safeAjax - $elem: @$(".discussion-vote") - url: url - type: "POST" - success: (response, textStatus) => - if textStatus == 'success' - @model.set(response, {silent: true}) - - edit: (event) -> @trigger "thread:edit", event diff --git a/common/static/coffee/src/discussion/views/thread_response_show_view.coffee b/common/static/coffee/src/discussion/views/thread_response_show_view.coffee index eaed0568c2..57736e789d 100644 --- a/common/static/coffee/src/discussion/views/thread_response_show_view.coffee +++ b/common/static/coffee/src/discussion/views/thread_response_show_view.coffee @@ -1,7 +1,10 @@ if Backbone? class @ThreadResponseShowView extends DiscussionContentView events: - "click .vote-btn": "toggleVote" + "click .vote-btn": + (event) -> @toggleVote(event) + "keydown .vote-btn": + (event) -> DiscussionUtil.activateOnEnter(event, @toggleVote) "click .action-endorse": "toggleEndorse" "click .action-delete": "_delete" "click .action-edit": "edit" @@ -23,9 +26,7 @@ if Backbone? render: -> @$el.html(@renderTemplate()) @delegateEvents() - if window.user.voted(@model) - @$(".vote-btn").addClass("is-cast") - @$(".vote-btn span.sr").html("votes (click to remove your vote)") + @renderVote() @renderAttrs() @renderFlagged() @$el.find(".posted-details").timeago() @@ -46,39 +47,6 @@ if Backbone? @$el.addClass("community-ta") @$el.prepend('
Community TA
') - toggleVote: (event) -> - event.preventDefault() - @$(".vote-btn").toggleClass("is-cast") - if @$(".vote-btn").hasClass("is-cast") - @vote() - @$(".vote-btn span.sr").html("votes (click to remove your vote)") - else - @unvote() - @$(".vote-btn span.sr").html("votes (click to vote)") - - vote: -> - url = @model.urlFor("upvote") - @$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) + 1) + '') - DiscussionUtil.safeAjax - $elem: @$(".discussion-vote") - url: url - type: "POST" - success: (response, textStatus) => - if textStatus == 'success' - @model.set(response) - - unvote: -> - url = @model.urlFor("unvote") - @$(".votes-count-number").html((parseInt(@$(".votes-count-number").html()) - 1)+'') - DiscussionUtil.safeAjax - $elem: @$(".discussion-vote") - url: url - type: "POST" - success: (response, textStatus) => - if textStatus == 'success' - @model.set(response) - - edit: (event) -> @trigger "response:edit", event @@ -115,4 +83,5 @@ if Backbone? @$(".discussion-flag-abuse .flag-label").html("Report Misuse") updateModelDetails: => + @renderVote() @renderFlagged() diff --git a/lms/templates/discussion/_underscore_templates.html b/lms/templates/discussion/_underscore_templates.html index e331a779a5..2ebd1465e1 100644 --- a/lms/templates/discussion/_underscore_templates.html +++ b/lms/templates/discussion/_underscore_templates.html @@ -31,8 +31,8 @@
${"<%- obj.group_string%>"}
${"<% } %>"} - - + ${'<%- votes["up_count"] %>'}votes (click to vote) + + ${'<%- votes["up_count"] %>'} votes (click to vote)

${'<%- title %>'}

${"<% if (obj.username) { %>"} @@ -123,7 +123,7 @@