Commit 13b14e0a by muhammad-ammar Committed by muzaffaryousaf

Paginate edxnotes frontend

TNL-3908
parent 840f5017
...@@ -114,7 +114,7 @@ class EdxNotesPageItem(NoteChild): ...@@ -114,7 +114,7 @@ class EdxNotesPageItem(NoteChild):
""" """
BODY_SELECTOR = ".note" BODY_SELECTOR = ".note"
UNIT_LINK_SELECTOR = "a.reference-unit-link" UNIT_LINK_SELECTOR = "a.reference-unit-link"
TAG_SELECTOR = "a.reference-tags" TAG_SELECTOR = "span.reference-tags"
def go_to_unit(self, unit_page=None): def go_to_unit(self, unit_page=None):
self.q(css=self._bounded_selector(self.UNIT_LINK_SELECTOR)).click() self.q(css=self._bounded_selector(self.UNIT_LINK_SELECTOR)).click()
......
""" """
Test LMS Notes Test LMS Notes
""" """
from unittest import skip
from uuid import uuid4 from uuid import uuid4
from datetime import datetime from datetime import datetime
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
...@@ -850,6 +851,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin): ...@@ -850,6 +851,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
self.assert_viewed_event('Search Results') self.assert_viewed_event('Search Results')
self.assert_search_event('note', 4) self.assert_search_event('note', 4)
@skip("scroll to tag functionality is removed")
def test_scroll_to_tag_recent_activity(self): def test_scroll_to_tag_recent_activity(self):
""" """
Scenario: Can scroll to a tag group from the Recent Activity view (default view) Scenario: Can scroll to a tag group from the Recent Activity view (default view)
...@@ -861,6 +863,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin): ...@@ -861,6 +863,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
self.notes_page.visit() self.notes_page.visit()
self._scroll_to_tag_and_verify("pear", 3) self._scroll_to_tag_and_verify("pear", 3)
@skip("scroll to tag functionality is removed")
def test_scroll_to_tag_course_structure(self): def test_scroll_to_tag_course_structure(self):
""" """
Scenario: Can scroll to a tag group from the Course Structure view Scenario: Can scroll to a tag group from the Course Structure view
...@@ -872,6 +875,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin): ...@@ -872,6 +875,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
self.notes_page.visit().switch_to_tab("structure") self.notes_page.visit().switch_to_tab("structure")
self._scroll_to_tag_and_verify("squash", 5) self._scroll_to_tag_and_verify("squash", 5)
@skip("scroll to tag functionality is removed")
def test_scroll_to_tag_search(self): def test_scroll_to_tag_search(self):
""" """
Scenario: Can scroll to a tag group from the Search Results view Scenario: Can scroll to a tag group from the Search Results view
...@@ -884,6 +888,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin): ...@@ -884,6 +888,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
self.notes_page.visit().search("note") self.notes_page.visit().search("note")
self._scroll_to_tag_and_verify("pumpkin", 4) self._scroll_to_tag_and_verify("pumpkin", 4)
@skip("scroll to tag functionality is removed")
def test_scroll_to_tag_from_tag_view(self): def test_scroll_to_tag_from_tag_view(self):
""" """
Scenario: Can scroll to a tag group from the Tags view Scenario: Can scroll to a tag group from the Tags view
......
...@@ -106,6 +106,7 @@ def send_request(user, course_id, page, page_size, path="", text=None): ...@@ -106,6 +106,7 @@ def send_request(user, course_id, page, page_size, path="", text=None):
params=params params=params
) )
except RequestException: except RequestException:
log.error("Failed to connect to edx-notes-api: url=%s, params=%s", url, str(params))
raise EdxNotesServiceUnavailable(_("EdxNotes Service is unavailable. Please try again in a few minutes.")) raise EdxNotesServiceUnavailable(_("EdxNotes Service is unavailable. Please try again in a few minutes."))
return response return response
...@@ -141,6 +142,7 @@ def preprocess_collection(user, course, collection): ...@@ -141,6 +142,7 @@ def preprocess_collection(user, course, collection):
store = modulestore() store = modulestore()
filtered_collection = list() filtered_collection = list()
cache = {} cache = {}
include_extra_info = settings.NOTES_DISABLED_TABS == []
with store.bulk_operations(course.id): with store.bulk_operations(course.id):
for model in collection: for model in collection:
update = { update = {
...@@ -176,6 +178,7 @@ def preprocess_collection(user, course, collection): ...@@ -176,6 +178,7 @@ def preprocess_collection(user, course, collection):
log.debug("Unit not found: %s", usage_key) log.debug("Unit not found: %s", usage_key)
continue continue
if include_extra_info:
section = unit.get_parent() section = unit.get_parent()
if not section: if not section:
log.debug("Section not found: %s", usage_key) log.debug("Section not found: %s", usage_key)
...@@ -207,11 +210,14 @@ def preprocess_collection(user, course, collection): ...@@ -207,11 +210,14 @@ def preprocess_collection(user, course, collection):
usage_context = { usage_context = {
"unit": get_module_context(course, unit), "unit": get_module_context(course, unit),
"section": get_module_context(course, section), "section": get_module_context(course, section) if include_extra_info else {},
"chapter": get_module_context(course, chapter), "chapter": get_module_context(course, chapter) if include_extra_info else {},
} }
model.update(usage_context) model.update(usage_context)
cache[usage_id] = cache[unit] = cache[section] = cache[chapter] = usage_context if include_extra_info:
cache[section] = cache[chapter] = usage_context
cache[usage_id] = cache[unit] = usage_context
filtered_collection.append(model) filtered_collection.append(model)
return filtered_collection return filtered_collection
...@@ -319,16 +325,24 @@ def get_notes(request, course, page=DEFAULT_PAGE, page_size=DEFAULT_PAGE_SIZE, t ...@@ -319,16 +325,24 @@ def get_notes(request, course, page=DEFAULT_PAGE, page_size=DEFAULT_PAGE_SIZE, t
try: try:
collection = json.loads(response.content) collection = json.loads(response.content)
except ValueError: except ValueError:
raise EdxNotesParseError(_("Invalid response received from notes api.")) log.error("Invalid JSON response received from notes api: response_content=%s", response.content)
raise EdxNotesParseError(_("Invalid JSON response received from notes api."))
# Verify response dict structure # Verify response dict structure
expected_keys = ['count', 'results', 'num_pages', 'start', 'next', 'previous', 'current_page'] expected_keys = ['total', 'rows', 'num_pages', 'start', 'next', 'previous', 'current_page']
keys = collection.keys() keys = collection.keys()
if not keys or not all(key in expected_keys for key in keys): if not keys or not all(key in expected_keys for key in keys):
raise EdxNotesParseError(_("Invalid response received from notes api.")) log.error("Incorrect data received from notes api: collection_data=%s", str(collection))
raise EdxNotesParseError(_("Incorrect data received from notes api."))
filtered_results = preprocess_collection(request.user, course, collection['results'])
filtered_results = preprocess_collection(request.user, course, collection['rows'])
# Notes API is called from:
# 1. The annotatorjs in courseware. It expects these attributes to be named "total" and "rows".
# 2. The Notes tab Javascript proxied through LMS. It expects these attributes to be called "count" and "results".
collection['count'] = collection['total']
del collection['total']
collection['results'] = filtered_results collection['results'] = filtered_results
del collection['rows']
collection['next'], collection['previous'] = construct_pagination_urls( collection['next'], collection['previous'] = construct_pagination_urls(
request, request,
......
...@@ -33,6 +33,16 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory ...@@ -33,6 +33,16 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
NOTES_API_EMPTY_RESPONSE = { NOTES_API_EMPTY_RESPONSE = {
"total": 0,
"rows": [],
"current_page": 1,
"start": 0,
"next": None,
"previous": None,
"num_pages": 0,
}
NOTES_VIEW_EMPTY_RESPONSE = {
"count": 0, "count": 0,
"results": [], "results": [],
"current_page": 1, "current_page": 1,
...@@ -297,13 +307,13 @@ class EdxNotesHelpersTest(ModuleStoreTestCase): ...@@ -297,13 +307,13 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
""" """
mock_get.return_value.content = json.dumps( mock_get.return_value.content = json.dumps(
{ {
"count": 2, "total": 2,
"current_page": 1, "current_page": 1,
"start": 0, "start": 0,
"next": None, "next": None,
"previous": None, "previous": None,
"num_pages": 1, "num_pages": 1,
"results": [ "rows": [
{ {
u"quote": u"quote text", u"quote": u"quote text",
u"text": u"text", u"text": u"text",
...@@ -404,13 +414,13 @@ class EdxNotesHelpersTest(ModuleStoreTestCase): ...@@ -404,13 +414,13 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
Tests the result if correct data is received. Tests the result if correct data is received.
""" """
mock_get.return_value.content = json.dumps({ mock_get.return_value.content = json.dumps({
"count": 2, "total": 2,
"current_page": 1, "current_page": 1,
"start": 0, "start": 0,
"next": None, "next": None,
"previous": None, "previous": None,
"num_pages": 1, "num_pages": 1,
"results": [ "rows": [
{ {
u"quote": u"quote text", u"quote": u"quote text",
u"text": u"text", u"text": u"text",
...@@ -511,7 +521,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase): ...@@ -511,7 +521,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
""" """
mock_get.return_value.content = json.dumps(NOTES_API_EMPTY_RESPONSE) mock_get.return_value.content = json.dumps(NOTES_API_EMPTY_RESPONSE)
self.assertItemsEqual( self.assertItemsEqual(
NOTES_API_EMPTY_RESPONSE, NOTES_VIEW_EMPTY_RESPONSE,
json.loads(helpers.get_notes(self.request, self.course)) json.loads(helpers.get_notes(self.request, self.course))
) )
...@@ -1006,19 +1016,17 @@ class EdxNotesViewsTest(ModuleStoreTestCase): ...@@ -1006,19 +1016,17 @@ class EdxNotesViewsTest(ModuleStoreTestCase):
""" """
Tests that search notes successfully respond if EdxNotes feature is enabled. Tests that search notes successfully respond if EdxNotes feature is enabled.
""" """
mock_search.return_value = json.dumps(NOTES_API_EMPTY_RESPONSE) mock_search.return_value = json.dumps(NOTES_VIEW_EMPTY_RESPONSE)
enable_edxnotes_for_the_course(self.course, self.user.id) enable_edxnotes_for_the_course(self.course, self.user.id)
response = self.client.get(self.notes_url, {"text": "test"}) response = self.client.get(self.notes_url, {"text": "test"})
self.assertEqual(json.loads(response.content), NOTES_API_EMPTY_RESPONSE) self.assertEqual(json.loads(response.content), NOTES_VIEW_EMPTY_RESPONSE)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_EDXNOTES": False}) @patch.dict("django.conf.settings.FEATURES", {"ENABLE_EDXNOTES": False})
@patch("edxnotes.views.get_notes", autospec=True) def test_search_notes_is_disabled(self):
def test_search_notes_is_disabled(self, mock_search):
""" """
Tests that 404 status code is received if EdxNotes feature is disabled. Tests that 404 status code is received if EdxNotes feature is disabled.
""" """
mock_search.return_value = json.dumps(NOTES_API_EMPTY_RESPONSE)
response = self.client.get(self.notes_url, {"text": "test"}) response = self.client.get(self.notes_url, {"text": "test"})
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
......
...@@ -55,8 +55,10 @@ def edxnotes(request, course_id): ...@@ -55,8 +55,10 @@ def edxnotes(request, course_id):
"course": course, "course": course,
"notes_endpoint": reverse("notes", kwargs={"course_id": course_id}), "notes_endpoint": reverse("notes", kwargs={"course_id": course_id}),
"notes": notes_info, "notes": notes_info,
"page_size": DEFAULT_PAGE_SIZE,
"debug": json.dumps(settings.DEBUG), "debug": json.dumps(settings.DEBUG),
'position': None, 'position': None,
'disabled_tabs': settings.NOTES_DISABLED_TABS,
} }
if len(json.loads(notes_info)['results']) == 0: if len(json.loads(notes_info)['results']) == 0:
......
...@@ -91,6 +91,8 @@ XQUEUE_INTERFACE['url'] = 'http://localhost:8040' ...@@ -91,6 +91,8 @@ XQUEUE_INTERFACE['url'] = 'http://localhost:8040'
EDXNOTES_PUBLIC_API = 'http://localhost:8042/api/v1' EDXNOTES_PUBLIC_API = 'http://localhost:8042/api/v1'
EDXNOTES_INTERNAL_API = 'http://localhost:8042/api/v1' EDXNOTES_INTERNAL_API = 'http://localhost:8042/api/v1'
NOTES_DISABLED_TABS = []
# Silence noisy logs # Silence noisy logs
import logging import logging
LOG_OVERRIDES = [ LOG_OVERRIDES = [
......
...@@ -2521,6 +2521,9 @@ ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = 60 ...@@ -2521,6 +2521,9 @@ ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = 60
if FEATURES['ENABLE_EDXNOTES']: if FEATURES['ENABLE_EDXNOTES']:
OAUTH_ID_TOKEN_EXPIRATION = 60 * 60 OAUTH_ID_TOKEN_EXPIRATION = 60 * 60
# These tabs are currently disabled
NOTES_DISABLED_TABS = ['course_structure', 'tags']
# Configuration used for generating PDF Receipts/Invoices # Configuration used for generating PDF Receipts/Invoices
PDF_RECEIPT_TAX_ID = 'add here' PDF_RECEIPT_TAX_ID = 'add here'
PDF_RECEIPT_FOOTER_TEXT = 'add your own specific footer text here' PDF_RECEIPT_FOOTER_TEXT = 'add your own specific footer text here'
......
...@@ -509,6 +509,8 @@ MONGODB_LOG = { ...@@ -509,6 +509,8 @@ MONGODB_LOG = {
'db': 'xlog', 'db': 'xlog',
} }
NOTES_DISABLED_TABS = []
# Enable EdxNotes for tests. # Enable EdxNotes for tests.
FEATURES['ENABLE_EDXNOTES'] = True FEATURES['ENABLE_EDXNOTES'] = True
......
;(function (define, undefined) { ;(function (define) {
'use strict'; 'use strict';
define([ define([
'backbone', 'js/edxnotes/models/note' 'underscore', 'common/js/components/collections/paging_collection', 'js/edxnotes/models/note'
], function (Backbone, NoteModel) { ], function (_, PagingCollection, NoteModel) {
var NotesCollection = Backbone.Collection.extend({ return PagingCollection.extend({
model: NoteModel, model: NoteModel,
initialize: function(models, options) {
PagingCollection.prototype.initialize.call(this);
this.perPage = options.perPage;
this.server_api = _.pick(PagingCollection.prototype.server_api, "page", "page_size");
if (options.text) {
this.server_api.text = options.text;
}
},
/** /**
* Returns course structure from the list of notes. * Returns course structure from the list of notes.
* @return {Object} * @return {Object}
...@@ -17,7 +27,6 @@ define([ ...@@ -17,7 +27,6 @@ define([
sections = {}, sections = {},
units = {}; units = {};
if (!courseStructure) {
this.each(function (note) { this.each(function (note) {
var chapter = note.get('chapter'), var chapter = note.get('chapter'),
section = note.get('section'), section = note.get('section'),
...@@ -34,13 +43,10 @@ define([ ...@@ -34,13 +43,10 @@ define([
sections: sections, sections: sections,
units: units units: units
}; };
}
return courseStructure; return courseStructure;
}; };
}()) }())
}); });
return NotesCollection;
}); });
}).call(this, define || RequireJS.define); }).call(this, define || RequireJS.define);
...@@ -15,6 +15,7 @@ define([ ...@@ -15,6 +15,7 @@ define([
this.options = options; this.options = options;
this.tabsCollection = new TabsCollection(); this.tabsCollection = new TabsCollection();
if (!_.contains(this.options.disabledTabs, 'tags')) {
// Must create the Tags view first to get the "scrollToTag" method. // Must create the Tags view first to get the "scrollToTag" method.
this.tagsView = new TagsView({ this.tagsView = new TagsView({
el: this.el, el: this.el,
...@@ -26,6 +27,7 @@ define([ ...@@ -26,6 +27,7 @@ define([
// Remove the Tags model from the tabs collection because it should not appear first. // Remove the Tags model from the tabs collection because it should not appear first.
tagsModel = this.tabsCollection.shift(); tagsModel = this.tabsCollection.shift();
}
this.recentActivityView = new RecentActivityView({ this.recentActivityView = new RecentActivityView({
el: this.el, el: this.el,
...@@ -34,6 +36,7 @@ define([ ...@@ -34,6 +36,7 @@ define([
scrollToTag: scrollToTag scrollToTag: scrollToTag
}); });
if (!_.contains(this.options.disabledTabs, 'course_structure')) {
this.courseStructureView = new CourseStructureView({ this.courseStructureView = new CourseStructureView({
el: this.el, el: this.el,
collection: this.collection, collection: this.collection,
...@@ -43,11 +46,13 @@ define([ ...@@ -43,11 +46,13 @@ define([
// Add the Tags model after the Course Structure model. // Add the Tags model after the Course Structure model.
this.tabsCollection.push(tagsModel); this.tabsCollection.push(tagsModel);
}
this.searchResultsView = new SearchResultsView({ this.searchResultsView = new SearchResultsView({
el: this.el, el: this.el,
tabsCollection: this.tabsCollection, tabsCollection: this.tabsCollection,
debug: this.options.debug, debug: this.options.debug,
perPage: this.options.perPage,
createTabOnInitialization: false, createTabOnInitialization: false,
scrollToTag: scrollToTag scrollToTag: scrollToTag
}); });
......
...@@ -6,19 +6,30 @@ define([ ...@@ -6,19 +6,30 @@ define([
/** /**
* Factory method for the Notes page. * Factory method for the Notes page.
* @param {Object} params Params for the Notes page. * @param {Object} params Params for the Notes page.
* @param {Array} params.notesList A list of note models. * @param {List} params.disabledTabs Names of disabled tabs, these tabs will not be shown.
* @param {Object} params.notes Paginated notes info.
* @param {Number} params.pageSize Number of notes per page.
* @param {Boolean} params.debugMode Enable the flag to see debug information. * @param {Boolean} params.debugMode Enable the flag to see debug information.
* @param {String} params.endpoint The endpoint of the store. * @param {String} params.endpoint The endpoint of the store.
* @return {Object} An instance of NotesPageView. * @return {Object} An instance of NotesPageView.
*/ */
return function (params) { return function (params) {
var collection = new NotesCollection(params.notesList); var collection = new NotesCollection(
params.notes,
{
url: params.notesEndpoint,
perPage: params.pageSize,
parse: true
}
);
return new NotesPageView({ return new NotesPageView({
el: $('.wrapper-student-notes').get(0), el: $('.wrapper-student-notes').get(0),
collection: collection, collection: collection,
debug: params.debugMode, debug: params.debugMode,
endpoint: params.endpoint endpoint: params.endpoint,
perPage: params.pageSize,
disabledTabs: params.disabledTabs
}); });
}; };
}); });
......
...@@ -43,15 +43,12 @@ define([ ...@@ -43,15 +43,12 @@ define([
* @return {Array} * @return {Array}
*/ */
prepareData: function (data) { prepareData: function (data) {
var collection; if (!(data && _.has(data, 'count') && _.has(data, 'results'))) {
if (!(data && _.has(data, 'total') && _.has(data, 'rows'))) {
this.logger.log('Wrong data', data, this.searchQuery); this.logger.log('Wrong data', data, this.searchQuery);
return null; return null;
} }
collection = new NotesCollection(data.rows); return [this.collection, this.searchQuery];
return [collection, data.total, this.searchQuery];
}, },
/** /**
...@@ -99,8 +96,8 @@ define([ ...@@ -99,8 +96,8 @@ define([
if (args) { if (args) {
this.options.search.apply(this, args); this.options.search.apply(this, args);
this.logger.emit('edx.course.student_notes.searched', { this.logger.emit('edx.course.student_notes.searched', {
'number_of_results': args[1], 'number_of_results': args[0].totalCount,
'search_string': args[2] 'search_string': args[1]
}); });
} else { } else {
this.options.error(this.errorMessage, this.searchQuery); this.options.error(this.errorMessage, this.searchQuery);
...@@ -144,15 +141,15 @@ define([ ...@@ -144,15 +141,15 @@ define([
* @return {jQuery.Deferred} * @return {jQuery.Deferred}
*/ */
sendRequest: function (text) { sendRequest: function (text) {
var settings = { this.collection = new NotesCollection(
url: this.el.action, [],
type: this.el.method, {
dataType: 'json', text: text,
data: {text: text} perPage: this.options.perPage,
}; url: this.el.action
}
this.logger.log(settings); );
return $.ajax(settings); return this.collection.goTo(1);
} }
}); });
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define(['gettext', 'underscore', 'backbone', 'js/edxnotes/views/note_item'], define(['gettext', 'underscore', 'backbone', 'js/edxnotes/views/note_item',
function (gettext, _, Backbone, NoteItemView) { 'common/js/components/views/paging_header', 'common/js/components/views/paging_footer'],
function (gettext, _, Backbone, NoteItemView, PagingHeaderView, PagingFooterView) {
var TabPanelView = Backbone.View.extend({ var TabPanelView = Backbone.View.extend({
tagName: 'section', tagName: 'section',
className: 'tab-panel', className: 'tab-panel',
...@@ -13,14 +14,30 @@ function (gettext, _, Backbone, NoteItemView) { ...@@ -13,14 +14,30 @@ function (gettext, _, Backbone, NoteItemView) {
initialize: function () { initialize: function () {
this.children = []; this.children = [];
if (this.options.createHeaderFooter) {
this.pagingHeaderView = new PagingHeaderView({collection: this.collection});
this.pagingFooterView = new PagingFooterView({collection: this.collection});
}
if (this.hasOwnProperty('collection')) {
this.listenTo(this.collection, 'page_changed', this.render);
}
}, },
render: function () { render: function () {
this.$el.html(this.getTitle()); this.$el.html(this.getTitle());
this.renderView(this.pagingHeaderView);
this.renderContent(); this.renderContent();
this.renderView(this.pagingFooterView);
return this; return this;
}, },
renderView: function(view) {
if (this.options.createHeaderFooter && this.collection.models.length) {
this.$el.append(view.render().el);
view.delegateEvents();
}
},
renderContent: function () { renderContent: function () {
return this; return this;
}, },
......
...@@ -14,7 +14,8 @@ define([ ...@@ -14,7 +14,8 @@ define([
initialize: function (options) { initialize: function (options) {
_.bindAll(this, 'showLoadingIndicator', 'hideLoadingIndicator'); _.bindAll(this, 'showLoadingIndicator', 'hideLoadingIndicator');
this.options = _.defaults(options || {}, { this.options = _.defaults(options || {}, {
createTabOnInitialization: true createTabOnInitialization: true,
createHeaderFooter: true
}); });
if (this.options.createTabOnInitialization) { if (this.options.createTabOnInitialization) {
...@@ -64,7 +65,13 @@ define([ ...@@ -64,7 +65,13 @@ define([
getSubView: function () { getSubView: function () {
var collection = this.getCollection(); var collection = this.getCollection();
return new this.PanelConstructor({collection: collection, scrollToTag: this.options.scrollToTag}); return new this.PanelConstructor(
{
collection: collection,
scrollToTag: this.options.scrollToTag,
createHeaderFooter: this.options.createHeaderFooter
}
);
}, },
destroySubView: function () { destroySubView: function () {
......
...@@ -58,6 +58,7 @@ define([ ...@@ -58,6 +58,7 @@ define([
this.searchBox = new SearchBoxView({ this.searchBox = new SearchBoxView({
el: document.getElementById('search-notes-form'), el: document.getElementById('search-notes-form'),
debug: this.options.debug, debug: this.options.debug,
perPage: this.options.perPage,
beforeSearchStart: this.onBeforeSearchStart, beforeSearchStart: this.onBeforeSearchStart,
search: this.onSearch, search: this.onSearch,
error: this.onSearchError error: this.onSearchError
...@@ -81,7 +82,8 @@ define([ ...@@ -81,7 +82,8 @@ define([
return new this.PanelConstructor({ return new this.PanelConstructor({
collection: collection, collection: collection,
searchQuery: this.searchResults.searchQuery, searchQuery: this.searchResults.searchQuery,
scrollToTag: this.options.scrollToTag scrollToTag: this.options.scrollToTag,
createHeaderFooter: this.options.createHeaderFooter
}); });
} else { } else {
return new this.NoResultsViewConstructor({ return new this.NoResultsViewConstructor({
...@@ -122,10 +124,9 @@ define([ ...@@ -122,10 +124,9 @@ define([
} }
}, },
onSearch: function (collection, total, searchQuery) { onSearch: function (collection, searchQuery) {
this.searchResults = { this.searchResults = {
collection: collection, collection: collection,
total: total,
searchQuery: searchQuery searchQuery: searchQuery
}; };
......
...@@ -6,7 +6,7 @@ define([ ...@@ -6,7 +6,7 @@ define([
var notes = Helpers.getDefaultNotes(); var notes = Helpers.getDefaultNotes();
beforeEach(function () { beforeEach(function () {
this.collection = new NotesCollection(notes); this.collection = new NotesCollection(notes, {perPage: 10, parse: true});
}); });
it('can return correct course structure', function () { it('can return correct course structure', function () {
...@@ -23,11 +23,22 @@ define([ ...@@ -23,11 +23,22 @@ define([
'i4x://section/2': Helpers.getSection('First Section', 2, [3]) 'i4x://section/2': Helpers.getSection('First Section', 2, [3])
}); });
expect(structure.units).toEqual({ var compareUnits = function (structureUnits, collectionUnits) {
expect(structureUnits.length === collectionUnits.length).toBeTruthy();
for(var i = 0; i < structureUnits.length; i++) {
expect(structureUnits[i].attributes).toEqual(collectionUnits[i].attributes);
}
};
var units = {
'i4x://unit/0': [this.collection.at(0), this.collection.at(1)], 'i4x://unit/0': [this.collection.at(0), this.collection.at(1)],
'i4x://unit/1': [this.collection.at(2)], 'i4x://unit/1': [this.collection.at(2)],
'i4x://unit/2': [this.collection.at(3)], 'i4x://unit/2': [this.collection.at(3)],
'i4x://unit/3': [this.collection.at(4)] 'i4x://unit/3': [this.collection.at(4)]
};
_.each(units, function(value, key){
compareUnits(structure.units[key], value);
}); });
}); });
}); });
......
define(['underscore'], function(_) { define(['underscore', 'URI', 'common/js/spec_helpers/ajax_helpers'], function(_, URI, AjaxHelpers) {
'use strict'; 'use strict';
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
LONG_TEXT, PRUNED_TEXT, TRUNCATED_TEXT, SHORT_TEXT, LONG_TEXT, PRUNED_TEXT, TRUNCATED_TEXT, SHORT_TEXT,
base64Encode, makeToken, getChapter, getSection, getUnit, getDefaultNotes; base64Encode, makeToken, getChapter, getSection, getUnit, getDefaultNotes,
verifyUrl, verifyRequestParams, createNotesData, respondToRequest,
verifyPaginationInfo, verifyPageData;
LONG_TEXT = [ LONG_TEXT = [
'Adipisicing elit, sed do eiusmod tempor incididunt ', 'Adipisicing elit, sed do eiusmod tempor incididunt ',
...@@ -106,7 +108,14 @@ define(['underscore'], function(_) { ...@@ -106,7 +108,14 @@ define(['underscore'], function(_) {
getDefaultNotes = function () { getDefaultNotes = function () {
// Note that the server returns notes in reverse chronological order (newest first). // Note that the server returns notes in reverse chronological order (newest first).
return [ return {
'count': 5,
'current_page': 1,
'num_pages': 1,
'start': 0,
'next': null,
'previous': null,
'results': [
{ {
chapter: getChapter('Second Chapter', 0, 1, [1, 'w_n', 0]), chapter: getChapter('Second Chapter', 0, 1, [1, 'w_n', 0]),
section: getSection('Third Section', 0, ['w_n', 1, 0]), section: getSection('Third Section', 0, ['w_n', 1, 0]),
...@@ -156,7 +165,74 @@ define(['underscore'], function(_) { ...@@ -156,7 +165,74 @@ define(['underscore'], function(_) {
quote: 'Note 1', quote: 'Note 1',
tags: ['pie', 'pumpkin'] tags: ['pie', 'pumpkin']
} }
]; ]
};
};
verifyUrl = function (requestUrl, expectedUrl, expectedParams) {
expect(requestUrl.slice(0, expectedUrl.length) === expectedUrl).toBeTruthy();
verifyRequestParams(requestUrl, expectedParams);
};
verifyRequestParams = function (requestUrl, expectedParams) {
var urlParams = (new URI(requestUrl)).query(true);
_.each(expectedParams, function (value, key) {
expect(urlParams[key]).toBe(value);
});
};
createNotesData = function (options) {
var data = {
count: options.count || 0,
num_pages: options.num_pages || 1,
current_page: options.current_page || 1,
start: options.start || 0,
results: []
};
for(var i = 0; i < options.numNotesToCreate; i++) {
var notesInfo = {
chapter: getChapter('First Chapter__' + i, 1, 0, [2]),
section: getSection('First Section__' + i, 2, [3]),
unit: getUnit('First Unit__' + i, 3),
created: new Date().toISOString(),
updated: new Date().toISOString(),
text: 'text__' + i,
quote: 'Note__' + i,
tags: ['tag__' + i, 'tag__' + i+1]
};
data.results.push(notesInfo);
}
return data;
};
respondToRequest = function(requests, responseJson, respondToEvent) {
// Respond to the analytics event
if (respondToEvent) {
AjaxHelpers.respondWithNoContent(requests);
}
// Now process the actual request
AjaxHelpers.respondWithJson(requests, responseJson);
};
verifyPaginationInfo = function (view, headerMessage, currentPage, totalPages) {
expect(view.$('.search-count.listing-count').text().trim()).toBe(headerMessage);
expect(parseInt(view.$('.pagination span.current-page').text().trim())).toBe(currentPage);
expect(parseInt(view.$('.pagination span.total-pages').text().trim())).toBe(totalPages);
};
verifyPageData = function (view, tabsCollection, tabInfo, tabId, notes) {
expect(tabsCollection).toHaveLength(1);
expect(tabsCollection.at(0).toJSON()).toEqual(tabInfo);
expect(view.$(tabId)).toExist();
expect(view.$('.note')).toHaveLength(notes.results.length);
_.each(view.$('.note'), function(element, index) {
expect($('.note-comments', element)).toContainText(notes.results[index].text);
expect($('.note-excerpt', element)).toContainText(notes.results[index].quote);
});
}; };
return { return {
...@@ -169,6 +245,12 @@ define(['underscore'], function(_) { ...@@ -169,6 +245,12 @@ define(['underscore'], function(_) {
getChapter: getChapter, getChapter: getChapter,
getSection: getSection, getSection: getSection,
getUnit: getUnit, getUnit: getUnit,
getDefaultNotes: getDefaultNotes getDefaultNotes: getDefaultNotes,
verifyUrl: verifyUrl,
verifyRequestParams: verifyRequestParams,
createNotesData: createNotesData,
respondToRequest: respondToRequest,
verifyPaginationInfo: verifyPaginationInfo,
verifyPageData: verifyPageData
}; };
}); });
...@@ -4,10 +4,23 @@ define([ ...@@ -4,10 +4,23 @@ define([
'use strict'; 'use strict';
describe('EdxNotes NoteModel', function() { describe('EdxNotes NoteModel', function() {
beforeEach(function () { beforeEach(function () {
this.collection = new NotesCollection([ this.collection = new NotesCollection(
{
'count': 2,
'current_page': 1,
'num_pages': 1,
'start': 0,
'next': null,
'previous': null,
'results': [
{quote: Helpers.LONG_TEXT, text: 'text\n with\r\nline\n\rbreaks \r'}, {quote: Helpers.LONG_TEXT, text: 'text\n with\r\nline\n\rbreaks \r'},
{quote: Helpers.SHORT_TEXT, text: 'text\n with\r\nline\n\rbreaks \r'} {quote: Helpers.SHORT_TEXT, text: 'text\n with\r\nline\n\rbreaks \r'}
]); ]
},
{
perPage: 10, parse: true
}
);
}); });
it('has correct values on initialization', function () { it('has correct values on initialization', function () {
......
...@@ -67,12 +67,12 @@ define([ ...@@ -67,12 +67,12 @@ define([
var view = getView({tags: ["First", "Second"]}); var view = getView({tags: ["First", "Second"]});
expect(view.$('.reference-title').length).toBe(3); expect(view.$('.reference-title').length).toBe(3);
expect(view.$('.reference-title')[2]).toContainText('Tags:'); expect(view.$('.reference-title')[2]).toContainText('Tags:');
expect(view.$('a.reference-tags').length).toBe(2); expect(view.$('span.reference-tags').length).toBe(2);
expect(view.$('a.reference-tags')[0]).toContainText('First'); expect(view.$('span.reference-tags')[0]).toContainText('First');
expect(view.$('a.reference-tags')[1]).toContainText('Second'); expect(view.$('span.reference-tags')[1]).toContainText('Second');
}); });
it('should handle a click event on the tag', function() { xit('should handle a click event on the tag', function() {
var scrollToTagSpy = { var scrollToTagSpy = {
scrollToTag: function (tagName){} scrollToTag: function (tagName){}
}; };
......
...@@ -13,7 +13,7 @@ define([ ...@@ -13,7 +13,7 @@ define([
TemplateHelpers.installTemplates([ TemplateHelpers.installTemplates([
'templates/edxnotes/note-item', 'templates/edxnotes/tab-item' 'templates/edxnotes/note-item', 'templates/edxnotes/tab-item'
]); ]);
this.view = new NotesFactory({notesList: notes}); this.view = new NotesFactory({notes: notes, pageSize: 10});
}); });
...@@ -35,8 +35,13 @@ define([ ...@@ -35,8 +35,13 @@ define([
this.view.$('.search-notes-input').val('test_query'); this.view.$('.search-notes-input').val('test_query');
this.view.$('.search-notes-submit').click(); this.view.$('.search-notes-submit').click();
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, {
total: 0, 'count': 0,
rows: [] 'current_page': 1,
'num_pages': 1,
'start': 0,
'next': null,
'previous': null,
'results': []
}); });
expect(this.view.$('#view-search-results')).toHaveClass('is-active'); expect(this.view.$('#view-search-results')).toHaveClass('is-active');
expect(this.view.$('#view-recent-activity')).toExist(); expect(this.view.$('#view-recent-activity')).toExist();
......
define([ define([
'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'js/edxnotes/views/search_box', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers', 'js/edxnotes/views/search_box',
'js/edxnotes/collections/notes', 'js/spec/edxnotes/custom_matchers', 'jasmine-jquery' 'js/edxnotes/collections/notes', 'js/spec/edxnotes/custom_matchers', 'js/spec/edxnotes/helpers', 'jasmine-jquery'
], function($, _, AjaxHelpers, SearchBoxView, NotesCollection, customMatchers) { ], function($, _, AjaxHelpers, SearchBoxView, NotesCollection, customMatchers, Helpers) {
'use strict'; 'use strict';
describe('EdxNotes SearchBoxView', function() { describe('EdxNotes SearchBoxView', function() {
var getSearchBox, submitForm, assertBoxIsEnabled, assertBoxIsDisabled; var getSearchBox, submitForm, assertBoxIsEnabled, assertBoxIsDisabled, searchResponse;
searchResponse = {
'count': 2,
'current_page': 1,
'num_pages': 1,
'start': 0,
'next': null,
'previous': null,
'results': [null, null]
};
getSearchBox = function (options) { getSearchBox = function (options) {
options = _.defaults(options || {}, { options = _.defaults(options || {}, {
el: $('#search-notes-form').get(0), el: $('#search-notes-form').get(0),
perPage: 10,
beforeSearchStart: jasmine.createSpy(), beforeSearchStart: jasmine.createSpy(),
search: jasmine.createSpy(), search: jasmine.createSpy(),
error: jasmine.createSpy(), error: jasmine.createSpy(),
...@@ -50,7 +61,11 @@ define([ ...@@ -50,7 +61,11 @@ define([
submitForm(this.searchBox, 'test_text'); submitForm(this.searchBox, 'test_text');
request = requests[0]; request = requests[0];
expect(request.method).toBe(form.method.toUpperCase()); expect(request.method).toBe(form.method.toUpperCase());
expect(request.url).toBe(form.action + '?' + $.param({text: 'test_text'})); Helpers.verifyUrl(
request.url,
form.action,
{text: 'test_text', page: '1', page_size: '10'}
);
}); });
it('returns success result', function () { it('returns success result', function () {
...@@ -60,13 +75,10 @@ define([ ...@@ -60,13 +75,10 @@ define([
'test_text' 'test_text'
); );
assertBoxIsDisabled(this.searchBox); assertBoxIsDisabled(this.searchBox);
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, searchResponse);
total: 2,
rows: [null, null]
});
assertBoxIsEnabled(this.searchBox); assertBoxIsEnabled(this.searchBox);
expect(this.searchBox.options.search).toHaveBeenCalledWith( expect(this.searchBox.options.search).toHaveBeenCalledWith(
jasmine.any(NotesCollection), 2, 'test_text' jasmine.any(NotesCollection), 'test_text'
); );
expect(this.searchBox.options.complete).toHaveBeenCalledWith( expect(this.searchBox.options.complete).toHaveBeenCalledWith(
'test_text' 'test_text'
...@@ -76,10 +88,7 @@ define([ ...@@ -76,10 +88,7 @@ define([
it('should log the edx.course.student_notes.searched event properly', function () { it('should log the edx.course.student_notes.searched event properly', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
submitForm(this.searchBox, 'test_text'); submitForm(this.searchBox, 'test_text');
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, searchResponse);
total: 2,
rows: [null, null]
});
expect(Logger.log).toHaveBeenCalledWith('edx.course.student_notes.searched', { expect(Logger.log).toHaveBeenCalledWith('edx.course.student_notes.searched', {
'number_of_results': 2, 'number_of_results': 2,
...@@ -140,10 +149,7 @@ define([ ...@@ -140,10 +149,7 @@ define([
submitForm(this.searchBox, 'test_text'); submitForm(this.searchBox, 'test_text');
assertBoxIsDisabled(this.searchBox); assertBoxIsDisabled(this.searchBox);
submitForm(this.searchBox, 'another_text'); submitForm(this.searchBox, 'another_text');
AjaxHelpers.respondWithJson(requests, { AjaxHelpers.respondWithJson(requests, searchResponse);
total: 2,
rows: [null, null]
});
assertBoxIsEnabled(this.searchBox); assertBoxIsEnabled(this.searchBox);
expect(requests).toHaveLength(1); expect(requests).toHaveLength(1);
}); });
......
...@@ -40,7 +40,7 @@ define([ ...@@ -40,7 +40,7 @@ define([
'templates/edxnotes/note-item', 'templates/edxnotes/tab-item' 'templates/edxnotes/note-item', 'templates/edxnotes/tab-item'
]); ]);
this.collection = new NotesCollection(notes); this.collection = new NotesCollection(notes, {perPage: 10, parse: true});
this.tabsCollection = new TabsCollection(); this.tabsCollection = new TabsCollection();
}); });
......
define([ define([
'jquery', 'common/js/spec_helpers/template_helpers', 'js/edxnotes/collections/notes', 'jquery', 'common/js/spec_helpers/template_helpers', 'common/js/spec_helpers/ajax_helpers',
'js/edxnotes/collections/tabs', 'js/edxnotes/views/tabs/recent_activity', 'js/edxnotes/collections/notes', 'js/edxnotes/collections/tabs', 'js/edxnotes/views/tabs/recent_activity',
'js/spec/edxnotes/custom_matchers', 'jasmine-jquery' 'js/spec/edxnotes/custom_matchers', 'js/spec/edxnotes/helpers', 'jasmine-jquery'
], function( ], function(
$, TemplateHelpers, NotesCollection, TabsCollection, RecentActivityView, customMatchers $, TemplateHelpers, AjaxHelpers, NotesCollection, TabsCollection, RecentActivityView, customMatchers, Helpers
) { ) {
'use strict'; 'use strict';
describe('EdxNotes RecentActivityView', function() { describe('EdxNotes RecentActivityView', function() {
var notes = [ var notes = {
'count': 3,
'current_page': 1,
'num_pages': 1,
'start': 0,
'next': null,
'previous': null,
'results': [
{ {
created: 'December 11, 2014 at 11:12AM', created: 'December 11, 2014 at 11:12AM',
updated: 'December 11, 2014 at 11:12AM', updated: 'December 11, 2014 at 11:12AM',
...@@ -26,7 +33,8 @@ define([ ...@@ -26,7 +33,8 @@ define([
text: 'First added model', text: 'First added model',
quote: 'Should be listed third' quote: 'Should be listed third'
} }
], getView; ]
}, getView, tabInfo, recentActivityTabId;
getView = function (collection, tabsCollection, options) { getView = function (collection, tabsCollection, options) {
var view; var view;
...@@ -35,6 +43,7 @@ define([ ...@@ -35,6 +43,7 @@ define([
el: $('.wrapper-student-notes'), el: $('.wrapper-student-notes'),
collection: collection, collection: collection,
tabsCollection: tabsCollection, tabsCollection: tabsCollection,
createHeaderFooter: true
}); });
view = new RecentActivityView(options); view = new RecentActivityView(options);
...@@ -43,6 +52,17 @@ define([ ...@@ -43,6 +52,17 @@ define([
return view; return view;
}; };
tabInfo = {
name: 'Recent Activity',
identifier: 'view-recent-activity',
icon: 'fa fa-clock-o',
is_active: true,
is_closable: false,
view: 'Recent Activity'
};
recentActivityTabId = '#recent-panel';
beforeEach(function () { beforeEach(function () {
customMatchers(this); customMatchers(this);
loadFixtures('js/fixtures/edxnotes/edxnotes.html'); loadFixtures('js/fixtures/edxnotes/edxnotes.html');
...@@ -50,28 +70,135 @@ define([ ...@@ -50,28 +70,135 @@ define([
'templates/edxnotes/note-item', 'templates/edxnotes/tab-item' 'templates/edxnotes/note-item', 'templates/edxnotes/tab-item'
]); ]);
this.collection = new NotesCollection(notes); this.collection = new NotesCollection(notes, {perPage: 10, parse: true});
this.tabsCollection = new TabsCollection(); this.tabsCollection = new TabsCollection();
}); });
it('displays a tab and content with proper data and order', function () { it('displays a tab and content with proper data and order', function () {
var view = getView(this.collection, this.tabsCollection); var view = getView(this.collection, this.tabsCollection);
Helpers.verifyPaginationInfo(view, "Showing 1-3 out of 3 total", 1, 1);
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes);
});
expect(this.tabsCollection).toHaveLength(1); it("will not render header and footer if there are no notes", function () {
expect(this.tabsCollection.at(0).toJSON()).toEqual({ var notes = {
name: 'Recent Activity', 'count': 0,
identifier: 'view-recent-activity', 'current_page': 1,
icon: 'fa fa-clock-o', 'num_pages': 1,
is_active: true, 'start': 0,
is_closable: false, 'next': null,
view: 'Recent Activity' 'previous': null,
'results': []
};
var collection = new NotesCollection(notes, {perPage: 10, parse: true});
var view = getView(collection, this.tabsCollection);
expect(view.$('.search-tools.listing-tools')).toHaveLength(0);
expect(view.$('.pagination.pagination-full.bottom')).toHaveLength(0);
}); });
expect(view.$('#recent-panel')).toExist();
expect(view.$('.note')).toHaveLength(3); it("can go to a page number", function () {
_.each(view.$('.note'), function(element, index) { var requests = AjaxHelpers.requests(this);
expect($('.note-comments', element)).toContainText(notes[index].text); var notes = Helpers.createNotesData(
expect($('.note-excerpt', element)).toContainText(notes[index].quote); {
numNotesToCreate: 10,
count: 12,
num_pages: 2,
current_page: 1,
start: 0
}
);
var collection = new NotesCollection(notes, {perPage: 10, parse: true});
var view = getView(collection, this.tabsCollection);
Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 12 total", 1, 2);
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes);
view.$('input#page-number-input').val('2');
view.$('input#page-number-input').trigger('change');
Helpers.verifyRequestParams(
requests[requests.length - 1].url,
{page: '2', page_size: '10'}
);
notes = Helpers.createNotesData(
{
numNotesToCreate: 2,
count: 12,
num_pages: 2,
current_page: 2,
start: 10
}
);
Helpers.respondToRequest(requests, notes, true);
Helpers.verifyPaginationInfo(view, "Showing 11-12 out of 12 total", 2, 2);
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, notes);
}); });
it("can navigate forward and backward", function () {
var requests = AjaxHelpers.requests(this);
var page1Notes = Helpers.createNotesData(
{
numNotesToCreate: 10,
count: 15,
num_pages: 2,
current_page: 1,
start: 0
}
);
var collection = new NotesCollection(page1Notes, {perPage: 10, parse: true});
var view = getView(collection, this.tabsCollection);
Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 15 total", 1, 2);
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, page1Notes);
view.$('.pagination .next-page-link').click();
Helpers.verifyRequestParams(
requests[requests.length - 1].url,
{page: '2', page_size: '10'}
);
var page2Notes = Helpers.createNotesData(
{
numNotesToCreate: 5,
count: 15,
num_pages: 2,
current_page: 2,
start: 10
}
);
Helpers.respondToRequest(requests, page2Notes, true);
Helpers.verifyPaginationInfo(view, "Showing 11-15 out of 15 total", 2, 2);
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, page2Notes);
view.$('.pagination .previous-page-link').click();
Helpers.verifyRequestParams(
requests[requests.length - 1].url,
{page: '1', page_size: '10'}
);
Helpers.respondToRequest(requests, page1Notes);
Helpers.verifyPaginationInfo(view, "Showing 1-10 out of 15 total", 1, 2);
Helpers.verifyPageData(view, this.tabsCollection, tabInfo, recentActivityTabId, page1Notes);
});
it("sends correct page size value", function () {
var requests = AjaxHelpers.requests(this);
var notes = Helpers.createNotesData(
{
numNotesToCreate: 5,
count: 7,
num_pages: 2,
current_page: 1,
start: 0
}
);
var collection = new NotesCollection(notes, {perPage: 5, parse: true});
var view = getView(collection, this.tabsCollection);
view.$('.pagination .next-page-link').click();
Helpers.verifyRequestParams(
requests[requests.length - 1].url,
{page: '2', page_size: '5'}
);
}); });
}); });
}); });
...@@ -44,7 +44,7 @@ define([ ...@@ -44,7 +44,7 @@ define([
'templates/edxnotes/note-item', 'templates/edxnotes/tab-item' 'templates/edxnotes/note-item', 'templates/edxnotes/tab-item'
]); ]);
this.collection = new NotesCollection(notes); this.collection = new NotesCollection(notes, {perPage: 10, parse: true});
this.tabsCollection = new TabsCollection(); this.tabsCollection = new TabsCollection();
}); });
......
...@@ -235,19 +235,17 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4; ...@@ -235,19 +235,17 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
// CASE: tag matches a search query // CASE: tag matches a search query
.reference-meta.reference-tags .note-highlight { .reference-meta.reference-tags .note-highlight {
// needed because .note-highlight is a span, which overrides the color
@extend %shame-link-text;
background-color: $result-highlight-color-base; background-color: $result-highlight-color-base;
} }
// Put commas between tags. // Put commas between tags.
a.reference-meta.reference-tags:after { span.reference-meta.reference-tags:after {
content: ","; content: ",";
color: $m-gray-d2; color: $m-gray-d2;
} }
// But not after the last tag. // But not after the last tag.
a.reference-meta.reference-tags:last-child:after { span.reference-meta.reference-tags:last-child:after {
content: ""; content: "";
} }
...@@ -255,7 +253,6 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4; ...@@ -255,7 +253,6 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
a.reference-meta { a.reference-meta {
@extend %shame-link-text; @extend %shame-link-text;
} }
} }
} }
...@@ -285,6 +282,15 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4; ...@@ -285,6 +282,15 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
.tab-panel, .inline-error, .ui-loading { .tab-panel, .inline-error, .ui-loading {
@extend %no-outline; @extend %no-outline;
border-top: $divider-visual-primary;
.listing-tools {
@include margin($baseline $baseline (-$baseline/2) 0);
}
.note-group:first-of-type {
border-top: none;
}
} }
.tab-panel.note-group { .tab-panel.note-group {
......
...@@ -12,6 +12,10 @@ import json ...@@ -12,6 +12,10 @@ import json
</%block> </%block>
<%include file="/courseware/course_navigation.html" args="active_page='edxnotes'" /> <%include file="/courseware/course_navigation.html" args="active_page='edxnotes'" />
<%
_notes = json.loads(notes)
has_notes = _notes and _notes.get('results')
%>
<section class="container"> <section class="container">
<div class="wrapper-student-notes"> <div class="wrapper-student-notes">
<div class="student-notes"> <div class="student-notes">
...@@ -24,7 +28,7 @@ import json ...@@ -24,7 +28,7 @@ import json
</h1> </h1>
</div> </div>
% if notes: % if has_notes:
<div class="wrapper-notes-search"> <div class="wrapper-notes-search">
<form role="search" action="${notes_endpoint}" method="GET" id="search-notes-form" class="is-hidden"> <form role="search" action="${notes_endpoint}" method="GET" id="search-notes-form" class="is-hidden">
<label for="search-notes-input" class="sr">${_('Search notes for:')}</label> <label for="search-notes-input" class="sr">${_('Search notes for:')}</label>
...@@ -51,7 +55,7 @@ import json ...@@ -51,7 +55,7 @@ import json
<h2 id="tab-view" class="tabs-label">${_('View notes by:')}</h2> <h2 id="tab-view" class="tabs-label">${_('View notes by:')}</h2>
</div> </div>
% if notes: % if has_notes:
<div class="ui-loading" tabindex="-1"> <div class="ui-loading" tabindex="-1">
<span class="spin"> <span class="spin">
<i class="icon fa fa-refresh"></i> <i class="icon fa fa-refresh"></i>
...@@ -103,11 +107,14 @@ import json ...@@ -103,11 +107,14 @@ import json
% endfor % endfor
</%block> </%block>
% if notes: % if has_notes:
<%block name="js_extra"> <%block name="js_extra">
<%static:require_module module_name="js/edxnotes/views/page_factory" class_name="NotesPageFactory"> <%static:require_module module_name="js/edxnotes/views/page_factory" class_name="NotesPageFactory">
NotesPageFactory({ NotesPageFactory({
notesList: ${notes if notes is not None else []}, disabledTabs: ${disabled_tabs},
notes: ${notes},
notesEndpoint: '${notes_endpoint}',
pageSize: '${page_size}',
debugMode: ${debug} debugMode: ${debug}
}); });
</%static:require_module> </%static:require_module>
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
<% if (tags.length > 0) { %> <% if (tags.length > 0) { %>
<p class="reference-title"><%- gettext("Tags:") %></p> <p class="reference-title"><%- gettext("Tags:") %></p>
<% for (var i = 0; i < tags.length; i++) { %> <% for (var i = 0; i < tags.length; i++) { %>
<a class="reference-meta reference-tags" href="#"><%= tags[i] %></a> <span class="reference-meta reference-tags"><%= tags[i] %></span>
<% } %> <% } %>
<% } %> <% } %>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment