From 3bd09d50104c3f85068250f2bca190b79b2444ec Mon Sep 17 00:00:00 2001 From: polesye Date: Sat, 22 Nov 2014 22:00:31 +0200 Subject: [PATCH] TNL-731: Add possibility to search notes. --- cms/envs/test.py | 2 +- common/djangoapps/terrain/stubs/edxnotes.py | 13 +- common/static/js/vendor/jquery.highlight.js | 108 +++++ common/templates/edxnotes_wrapper.html | 2 +- common/test/acceptance/pages/lms/edxnotes.py | 158 ++++++- .../acceptance/tests/lms/test_lms_edxnotes.py | 196 ++++++--- lms/djangoapps/edxnotes/exceptions.py | 10 + lms/djangoapps/edxnotes/helpers.py | 138 ++++-- lms/djangoapps/edxnotes/tests.py | 414 +++++++++++++----- lms/djangoapps/edxnotes/urls.py | 11 + lms/djangoapps/edxnotes/views.py | 39 +- lms/envs/bok_choy.py | 2 +- lms/envs/common.py | 2 +- lms/static/js/edxnotes/collections/notes.js | 18 +- lms/static/js/edxnotes/collections/tabs.js | 12 + lms/static/js/edxnotes/models/note.js | 46 +- lms/static/js/edxnotes/models/tab.js | 33 ++ lms/static/js/edxnotes/utils/logger.js | 183 +++++--- lms/static/js/edxnotes/views/note_item.js | 28 -- lms/static/js/edxnotes/views/notes.js | 90 ---- lms/static/js/edxnotes/views/notes_factory.js | 95 ++++ lms/static/js/edxnotes/views/notes_page.js | 66 ++- lms/static/js/edxnotes/views/page_factory.js | 54 ++- .../js/edxnotes/views/recent_activity_view.js | 33 -- lms/static/js/edxnotes/views/search_box.js | 160 +++++++ lms/static/js/edxnotes/views/shim.js | 238 +++++----- lms/static/js/edxnotes/views/subview.js | 33 ++ lms/static/js/edxnotes/views/tab_item.js | 62 +++ lms/static/js/edxnotes/views/tab_view.js | 122 ++++++ .../js/edxnotes/views/tabs/recent_activity.js | 25 ++ .../js/edxnotes/views/tabs/search_results.js | 135 ++++++ lms/static/js/edxnotes/views/tabs_list.js | 41 ++ lms/static/js/fixtures/edxnotes/edxnotes.html | 27 +- .../fixtures/edxnotes/edxnotes_wrapper.html | 6 + .../js/spec/edxnotes/custom_matchers.js | 28 ++ .../js/spec/edxnotes/models/tab_spec.js | 33 ++ .../js/spec/edxnotes/notes_factory_spec.js | 38 ++ lms/static/js/spec/edxnotes/notes_spec.js | 35 -- lms/static/js/spec/edxnotes/shim_spec.js | 200 ++++----- .../js/spec/edxnotes/utils/logger_spec.js | 168 ++++--- .../js/spec/edxnotes/views/notes_page_spec.js | 69 ++- .../js/spec/edxnotes/views/search_box_spec.js | 153 +++++++ .../js/spec/edxnotes/views/tab_item_spec.js | 41 ++ .../js/spec/edxnotes/views/tab_view_spec.js | 106 +++++ .../views/tabs/recent_activity_spec.js | 75 ++++ .../views/tabs/search_results_spec.js | 206 +++++++++ .../js/spec/edxnotes/views/tabs_list_spec.js | 50 +++ lms/static/js/spec/main.js | 20 +- lms/static/js_test.yml | 1 + lms/static/require-config-lms.js | 14 +- lms/static/sass/course/_edxnotes.scss | 102 ++++- lms/templates/{ => edxnotes}/edxnotes.html | 34 +- lms/templates/edxnotes/note-item.underscore | 24 - .../edxnotes/recent-activity-item.underscore | 28 ++ lms/templates/edxnotes/tab-item.underscore | 4 + lms/urls.py | 2 +- 56 files changed, 3084 insertions(+), 949 deletions(-) create mode 100644 common/static/js/vendor/jquery.highlight.js create mode 100644 lms/djangoapps/edxnotes/exceptions.py create mode 100644 lms/djangoapps/edxnotes/urls.py create mode 100644 lms/static/js/edxnotes/collections/tabs.js create mode 100644 lms/static/js/edxnotes/models/tab.js delete mode 100644 lms/static/js/edxnotes/views/note_item.js delete mode 100644 lms/static/js/edxnotes/views/notes.js create mode 100644 lms/static/js/edxnotes/views/notes_factory.js delete mode 100644 lms/static/js/edxnotes/views/recent_activity_view.js create mode 100644 lms/static/js/edxnotes/views/search_box.js create mode 100644 lms/static/js/edxnotes/views/subview.js create mode 100644 lms/static/js/edxnotes/views/tab_item.js create mode 100644 lms/static/js/edxnotes/views/tab_view.js create mode 100644 lms/static/js/edxnotes/views/tabs/recent_activity.js create mode 100644 lms/static/js/edxnotes/views/tabs/search_results.js create mode 100644 lms/static/js/edxnotes/views/tabs_list.js create mode 100644 lms/static/js/fixtures/edxnotes/edxnotes_wrapper.html create mode 100644 lms/static/js/spec/edxnotes/custom_matchers.js create mode 100644 lms/static/js/spec/edxnotes/models/tab_spec.js create mode 100644 lms/static/js/spec/edxnotes/notes_factory_spec.js delete mode 100644 lms/static/js/spec/edxnotes/notes_spec.js create mode 100644 lms/static/js/spec/edxnotes/views/search_box_spec.js create mode 100644 lms/static/js/spec/edxnotes/views/tab_item_spec.js create mode 100644 lms/static/js/spec/edxnotes/views/tab_view_spec.js create mode 100644 lms/static/js/spec/edxnotes/views/tabs/recent_activity_spec.js create mode 100644 lms/static/js/spec/edxnotes/views/tabs/search_results_spec.js create mode 100644 lms/static/js/spec/edxnotes/views/tabs_list_spec.js rename lms/templates/{ => edxnotes}/edxnotes.html (59%) delete mode 100644 lms/templates/edxnotes/note-item.underscore create mode 100644 lms/templates/edxnotes/recent-activity-item.underscore create mode 100644 lms/templates/edxnotes/tab-item.underscore diff --git a/cms/envs/test.py b/cms/envs/test.py index ab478f9c8ef8..1b20dbe757aa 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -227,5 +227,5 @@ FEATURES['ENABLE_EDXNOTES'] = True EDXNOTES_INTERFACE = { - 'url': 'http://localhost:8042/', + 'url': 'http://localhost:8042/api/v1', } diff --git a/common/djangoapps/terrain/stubs/edxnotes.py b/common/djangoapps/terrain/stubs/edxnotes.py index 38b43ff45775..56664ca061d4 100644 --- a/common/djangoapps/terrain/stubs/edxnotes.py +++ b/common/djangoapps/terrain/stubs/edxnotes.py @@ -198,6 +198,7 @@ def _search(self): user = self.get_params.get("user", None) usage_id = self.get_params.get("usage_id", None) course_id = self.get_params.get("course_id", None) + text = self.get_params.get("text", None) if user is None: self.respond(400, "Bad Request") @@ -209,6 +210,8 @@ def _search(self): results = self.server.filter_by_course_id(results, course_id) if usage_id is not None: results = self.server.filter_by_usage_id(results, usage_id) + if text: + results = self.server.search(results, text) self.respond(content={ "total": len(results), "rows": results, @@ -247,7 +250,9 @@ def get_notes(self): """ Returns a list of all notes. """ - return deepcopy(self.notes) + notes = deepcopy(self.notes) + notes.reverse() + return notes def add_notes(self, notes): """ @@ -318,3 +323,9 @@ def filter_by(self, data, field_name, value): Filters provided `data(list)` by the `field_name(str)` with `value`. """ return [note for note in data if note.get(field_name) == value] + + def search(self, data, query): + """ + Search the `query(str)` text in the provided `data(list)`. + """ + return [note for note in data if unicode(query).strip() in note.get("text")] diff --git a/common/static/js/vendor/jquery.highlight.js b/common/static/js/vendor/jquery.highlight.js new file mode 100644 index 000000000000..9dcf3c7af3ff --- /dev/null +++ b/common/static/js/vendor/jquery.highlight.js @@ -0,0 +1,108 @@ +/* + * jQuery Highlight plugin + * + * Based on highlight v3 by Johann Burkard + * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html + * + * Code a little bit refactored and cleaned (in my humble opinion). + * Most important changes: + * - has an option to highlight only entire words (wordsOnly - false by default), + * - has an option to be case sensitive (caseSensitive - false by default) + * - highlight element tag and class names can be specified in options + * + * Usage: + * // wrap every occurrance of text 'lorem' in content + * // with (default options) + * $('#content').highlight('lorem'); + * + * // search for and highlight more terms at once + * // so you can save some time on traversing DOM + * $('#content').highlight(['lorem', 'ipsum']); + * $('#content').highlight('lorem ipsum'); + * + * // search only for entire word 'lorem' + * $('#content').highlight('lorem', { wordsOnly: true }); + * + * // don't ignore case during search of term 'lorem' + * $('#content').highlight('lorem', { caseSensitive: true }); + * + * // wrap every occurrance of term 'ipsum' in content + * // with + * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); + * + * // remove default highlight + * $('#content').unhighlight(); + * + * // remove custom highlight + * $('#content').unhighlight({ element: 'em', className: 'important' }); + * + * + * Copyright (c) 2009 Bartek Szopka + * + * Licensed under MIT license. + * + */ + +jQuery.extend({ + highlight: function (node, re, nodeName, className) { + if (node.nodeType === 3) { + var match = node.data.match(re); + if (match) { + var highlight = document.createElement(nodeName || 'span'); + highlight.className = className || 'highlight'; + var wordNode = node.splitText(match.index); + wordNode.splitText(match[0].length); + var wordClone = wordNode.cloneNode(true); + highlight.appendChild(wordClone); + wordNode.parentNode.replaceChild(highlight, wordNode); + return 1; //skip added node in parent + } + } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children + !/(script|style)/i.test(node.tagName) && // ignore script and style nodes + !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted + for (var i = 0; i < node.childNodes.length; i++) { + i += jQuery.highlight(node.childNodes[i], re, nodeName, className); + } + } + return 0; + } +}); + +jQuery.fn.unhighlight = function (options) { + var settings = { className: 'highlight', element: 'span' }; + jQuery.extend(settings, options); + + return this.find(settings.element + "." + settings.className).each(function () { + var parent = this.parentNode; + parent.replaceChild(this.firstChild, this); + parent.normalize(); + }).end(); +}; + +jQuery.fn.highlight = function (words, options) { + var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; + jQuery.extend(settings, options); + + if (words.constructor === String) { + words = [words]; + } + words = jQuery.grep(words, function(word, i){ + return word != ''; + }); + words = jQuery.map(words, function(word, i) { + return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + }); + if (words.length == 0) { return this; }; + + var flag = settings.caseSensitive ? "" : "i"; + var pattern = "(" + words.join("|") + ")"; + if (settings.wordsOnly) { + pattern = "\\b" + pattern + "\\b"; + } + var re = new RegExp(pattern, flag); + + return this.each(function () { + jQuery.highlight(this, re, settings.element, settings.className); + }); +}; + diff --git a/common/templates/edxnotes_wrapper.html b/common/templates/edxnotes_wrapper.html index 52d5f4255eda..3030d31e8078 100644 --- a/common/templates/edxnotes_wrapper.html +++ b/common/templates/edxnotes_wrapper.html @@ -8,7 +8,7 @@ @@ -43,10 +55,8 @@

${_('Notes')}

var pageView = new NotesFactory({ notesList: ${notes}, debugMode: ${debug}, - authToken: '${token}', user: '${user.username}', - courseId: '${course.id}', - endpoint: '${endpoint}' + courseId: '${course.id}' }); }); }).call(this, require || RequireJS.require); diff --git a/lms/templates/edxnotes/note-item.underscore b/lms/templates/edxnotes/note-item.underscore deleted file mode 100644 index cd3ee4179b29..000000000000 --- a/lms/templates/edxnotes/note-item.underscore +++ /dev/null @@ -1,24 +0,0 @@ -
-<% if (quote) { %> -
<%- quote %>
-<% } %> -<% if (text) { %> -
<%- text %>
-<% } %> -
-
-
- <% if (text && quote) { %> -
<%- gettext("Highlighted & Noted in:") %>
- <% } else if (text) { %> -
<%- gettext("Highlighted in:") %>
- <% } else if (quote) { %> -
<%- gettext("Noted in:") %>
- <% } %> -
<%- unit.display_name %>
- <% if (updated) { %> -
<%- gettext("Last Edited:") %>
-
<%- updated %>
- <% } %> -
-
diff --git a/lms/templates/edxnotes/recent-activity-item.underscore b/lms/templates/edxnotes/recent-activity-item.underscore new file mode 100644 index 000000000000..3d997880ed9a --- /dev/null +++ b/lms/templates/edxnotes/recent-activity-item.underscore @@ -0,0 +1,28 @@ +<% collection.each(function (model) { %> +
+
+ <% if (model.get('quote')) { %> +
<%= model.escape('quote') %>
+ <% } %> + <% if (model.get('text')) { %> +
<%= model.escape('text') %>
+ <% } %> +
+
+
+ <% if (model.get('text') && model.get('quote')) { %> +
<%- gettext("Highlighted & Noted in:") %>
+ <% } else if (model.get('text')) { %> +
<%- gettext("Highlighted in:") %>
+ <% } else if (model.get('quote')) { %> +
<%- gettext("Noted in:") %>
+ <% } %> +
<%- model.get('unit').display_name %>
+ <% if (model.get('updated')) { %> +
<%- gettext("Last Edited:") %>
+
<%- model.get('updated') %>
+ <% } %> +
+
+
+<% }) %> diff --git a/lms/templates/edxnotes/tab-item.underscore b/lms/templates/edxnotes/tab-item.underscore new file mode 100644 index 000000000000..a5eff00dc619 --- /dev/null +++ b/lms/templates/edxnotes/tab-item.underscore @@ -0,0 +1,4 @@ +<%- gettext(name) %> +<% if (is_closable) { %> + x +<% } %> diff --git a/lms/urls.py b/lms/urls.py index 79304abc3b45..28b4d757e0f6 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -379,7 +379,7 @@ url(r'^profile/', include('student_profile.urls')), # Student Notes - url(r'^courses/{}/edxnotes$'.format(settings.COURSE_ID_PATTERN), 'edxnotes.views.edxnotes', name='edxnotes'), + url(r'^courses/{}/edxnotes'.format(settings.COURSE_ID_PATTERN), include('edxnotes.urls'), name="edxnotes_endpoints"), ) # allow course staff to change to student view of courseware