Merge pull request #7923 from edx/christina/show-notes
Tags Tab in Student Notes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define(['backbone'], function (Backbone) {
|
||||
define(['underscore', 'backbone'], function (_, Backbone) {
|
||||
var TabModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
'identifier': '',
|
||||
|
||||
@@ -3,19 +3,22 @@
|
||||
define([
|
||||
'gettext', 'underscore', 'backbone'
|
||||
], function (gettext, _, Backbone) {
|
||||
var NoteSectionView, NoteGroupView;
|
||||
var GroupView, ChapterView;
|
||||
|
||||
NoteSectionView = Backbone.View.extend({
|
||||
GroupView = Backbone.View.extend({
|
||||
tagName: 'section',
|
||||
className: 'note-section',
|
||||
id: function () {
|
||||
return 'note-section-' + _.uniqueId();
|
||||
},
|
||||
template: _.template('<h4 class="course-subtitle"><%- sectionName %></h4>'),
|
||||
|
||||
initialize: function () {
|
||||
this.template = _.template(this.options.template);
|
||||
this.className = this.options.className;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.prepend(this.template({
|
||||
sectionName: this.options.section.display_name
|
||||
displayName: this.options.displayName
|
||||
}));
|
||||
|
||||
return this;
|
||||
@@ -26,7 +29,7 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
NoteGroupView = Backbone.View.extend({
|
||||
ChapterView = Backbone.View.extend({
|
||||
tagName: 'section',
|
||||
className: 'note-group',
|
||||
id: function () {
|
||||
@@ -52,7 +55,13 @@ define([
|
||||
},
|
||||
|
||||
addChild: function (sectionInfo) {
|
||||
var section = new NoteSectionView({section: sectionInfo});
|
||||
var section = new GroupView(
|
||||
{
|
||||
displayName: sectionInfo.display_name,
|
||||
template: '<h4 class="course-subtitle"><%- displayName %></h4>',
|
||||
className: "note-section"
|
||||
}
|
||||
);
|
||||
this.children.push(section);
|
||||
return section;
|
||||
},
|
||||
@@ -65,6 +74,6 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
return NoteGroupView;
|
||||
return {GroupView: GroupView, ChapterView: ChapterView};
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -13,6 +13,7 @@ define([
|
||||
events: {
|
||||
'click .note-excerpt-more-link': 'moreHandler',
|
||||
'click .reference-unit-link': 'unitLinkHandler',
|
||||
'click .reference-tags': 'tagHandler'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
@@ -56,6 +57,11 @@ define([
|
||||
}, this));
|
||||
},
|
||||
|
||||
tagHandler: function (event) {
|
||||
event.preventDefault();
|
||||
this.options.scrollToTag(event.currentTarget.text);
|
||||
},
|
||||
|
||||
redirectTo: function (uri) {
|
||||
window.location = uri;
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ define([
|
||||
'js/edxnotes/plugins/caret_navigation'
|
||||
], function ($, _, Annotator, NotesLogger) {
|
||||
var plugins = ['Auth', 'Store', 'Scroller', 'Events', 'Accessibility', 'CaretNavigation', 'Tags'],
|
||||
getOptions, setupPlugins, updateHeaders, getAnnotator;
|
||||
getOptions, setupPlugins, getAnnotator;
|
||||
|
||||
/**
|
||||
* Returns options for the annotator.
|
||||
|
||||
@@ -3,33 +3,53 @@
|
||||
define([
|
||||
'backbone', 'js/edxnotes/collections/tabs', 'js/edxnotes/views/tabs_list',
|
||||
'js/edxnotes/views/tabs/recent_activity', 'js/edxnotes/views/tabs/course_structure',
|
||||
'js/edxnotes/views/tabs/search_results'
|
||||
'js/edxnotes/views/tabs/search_results', 'js/edxnotes/views/tabs/tags'
|
||||
], function (
|
||||
Backbone, TabsCollection, TabsListView, RecentActivityView, CourseStructureView,
|
||||
SearchResultsView
|
||||
SearchResultsView, TagsView
|
||||
) {
|
||||
var NotesPageView = Backbone.View.extend({
|
||||
initialize: function (options) {
|
||||
var scrollToTag, tagsModel;
|
||||
|
||||
this.options = options;
|
||||
this.tabsCollection = new TabsCollection();
|
||||
|
||||
// Must create the Tags view first to get the "scrollToTag" method.
|
||||
this.tagsView = new TagsView({
|
||||
el: this.el,
|
||||
collection: this.collection,
|
||||
tabsCollection: this.tabsCollection
|
||||
});
|
||||
|
||||
scrollToTag = this.tagsView.scrollToTag;
|
||||
|
||||
// Remove the Tags model from the tabs collection because it should not appear first.
|
||||
tagsModel = this.tabsCollection.shift();
|
||||
|
||||
this.recentActivityView = new RecentActivityView({
|
||||
el: this.el,
|
||||
collection: this.collection,
|
||||
tabsCollection: this.tabsCollection
|
||||
tabsCollection: this.tabsCollection,
|
||||
scrollToTag: scrollToTag
|
||||
});
|
||||
|
||||
this.courseStructureView = new CourseStructureView({
|
||||
el: this.el,
|
||||
collection: this.collection,
|
||||
tabsCollection: this.tabsCollection
|
||||
tabsCollection: this.tabsCollection,
|
||||
scrollToTag: scrollToTag
|
||||
});
|
||||
|
||||
// Add the Tags model after the Course Structure model.
|
||||
this.tabsCollection.push(tagsModel);
|
||||
|
||||
this.searchResultsView = new SearchResultsView({
|
||||
el: this.el,
|
||||
tabsCollection: this.tabsCollection,
|
||||
debug: this.options.debug,
|
||||
createTabOnInitialization: false
|
||||
createTabOnInitialization: false,
|
||||
scrollToTag: scrollToTag
|
||||
});
|
||||
|
||||
this.tabsView = new TabsListView({collection: this.tabsCollection});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define(['gettext', 'underscore', 'backbone', 'js/edxnotes/utils/template'],
|
||||
function (gettext, _, Backbone, templateUtils) {
|
||||
define(['gettext', 'underscore', 'jquery', 'backbone', 'js/edxnotes/utils/template'],
|
||||
function (gettext, _, $, Backbone, templateUtils) {
|
||||
var TabItemView = Backbone.View.extend({
|
||||
tagName: 'li',
|
||||
className: 'tab',
|
||||
|
||||
@@ -26,9 +26,9 @@ function (gettext, _, Backbone, NoteItemView) {
|
||||
},
|
||||
|
||||
getNotes: function (collection) {
|
||||
var container = document.createDocumentFragment(),
|
||||
var container = document.createDocumentFragment(), scrollToTag = this.options.scrollToTag,
|
||||
notes = _.map(collection, function (model) {
|
||||
var note = new NoteItemView({model: model});
|
||||
var note = new NoteItemView({model: model, scrollToTag: scrollToTag});
|
||||
container.appendChild(note.render().el);
|
||||
return note;
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'underscore', 'backbone', 'js/edxnotes/models/tab'
|
||||
], function (_, Backbone, TabModel) {
|
||||
'jquery', 'underscore', 'backbone', 'js/edxnotes/models/tab'
|
||||
], function ($, _, Backbone, TabModel) {
|
||||
var TabView = Backbone.View.extend({
|
||||
PanelConstructor: null,
|
||||
|
||||
@@ -52,6 +52,7 @@ define([
|
||||
// If the view is already rendered, destroy it.
|
||||
this.destroySubView();
|
||||
this.renderContent().always(this.hideLoadingIndicator);
|
||||
this.$('.sr-is-focusable.sr-tab-panel').focus();
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -63,7 +64,7 @@ define([
|
||||
|
||||
getSubView: function () {
|
||||
var collection = this.getCollection();
|
||||
return new this.PanelConstructor({collection: collection});
|
||||
return new this.PanelConstructor({collection: collection, scrollToTag: this.options.scrollToTag});
|
||||
},
|
||||
|
||||
destroySubView: function () {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'js/edxnotes/views/note_group', 'js/edxnotes/views/tab_panel',
|
||||
'gettext', 'underscore', 'js/edxnotes/views/note_group', 'js/edxnotes/views/tab_panel',
|
||||
'js/edxnotes/views/tab_view'
|
||||
], function (gettext, NoteGroupView, TabPanelView, TabView) {
|
||||
], function (gettext, _, NoteGroupView, TabPanelView, TabView) {
|
||||
var CourseStructureView = TabView.extend({
|
||||
PanelConstructor: TabPanelView.extend({
|
||||
id: 'structure-panel',
|
||||
@@ -14,28 +14,28 @@ define([
|
||||
container = document.createDocumentFragment();
|
||||
|
||||
_.each(courseStructure.chapters, function (chapterInfo) {
|
||||
var group = this.getGroup(chapterInfo);
|
||||
var chapterView = this.getChapterGroupView(chapterInfo);
|
||||
_.each(chapterInfo.children, function (location) {
|
||||
var sectionInfo = courseStructure.sections[location],
|
||||
section;
|
||||
sectionView;
|
||||
if (sectionInfo) {
|
||||
section = group.addChild(sectionInfo);
|
||||
sectionView = chapterView.addChild(sectionInfo);
|
||||
_.each(sectionInfo.children, function (location) {
|
||||
var notes = courseStructure.units[location];
|
||||
if (notes) {
|
||||
section.addChild(this.getNotes(notes))
|
||||
sectionView.addChild(this.getNotes(notes));
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}, this);
|
||||
container.appendChild(group.render().el);
|
||||
container.appendChild(chapterView.render().el);
|
||||
}, this);
|
||||
this.$el.append(container);
|
||||
return this;
|
||||
},
|
||||
|
||||
getGroup: function (chapter, section) {
|
||||
var group = new NoteGroupView({
|
||||
getChapterGroupView: function (chapter, section) {
|
||||
var group = new NoteGroupView.ChapterView({
|
||||
chapter: chapter,
|
||||
section: section
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ define([
|
||||
return [
|
||||
TabPanelView.prototype.className,
|
||||
'note-group'
|
||||
].join(' ')
|
||||
].join(' ');
|
||||
},
|
||||
renderContent: function () {
|
||||
this.$el.append(this.getNotes(this.collection.toArray()));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'js/edxnotes/views/tab_panel', 'js/edxnotes/views/tab_view',
|
||||
'jquery', 'underscore', 'gettext', 'js/edxnotes/views/tab_panel', 'js/edxnotes/views/tab_view',
|
||||
'js/edxnotes/views/search_box'
|
||||
], function (gettext, TabPanelView, TabView, SearchBoxView) {
|
||||
], function ($, _, gettext, TabPanelView, TabView, SearchBoxView) {
|
||||
var SearchResultsView = TabView.extend({
|
||||
PanelConstructor: TabPanelView.extend({
|
||||
id: 'search-results-panel',
|
||||
@@ -78,7 +78,8 @@ define([
|
||||
if (collection.length) {
|
||||
return new this.PanelConstructor({
|
||||
collection: collection,
|
||||
searchQuery: this.searchResults.searchQuery
|
||||
searchQuery: this.searchResults.searchQuery,
|
||||
scrollToTag: this.options.scrollToTag
|
||||
});
|
||||
} else {
|
||||
return new this.NoResultsViewConstructor({
|
||||
|
||||
137
lms/static/js/edxnotes/views/tabs/tags.js
Normal file
137
lms/static/js/edxnotes/views/tabs/tags.js
Normal file
@@ -0,0 +1,137 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define([
|
||||
'gettext', 'jquery', 'underscore', 'js/edxnotes/views/note_group', 'js/edxnotes/views/tab_panel',
|
||||
'js/edxnotes/views/tab_view'
|
||||
], function (gettext, $, _, NoteGroupView, TabPanelView, TabView) {
|
||||
|
||||
var TagsView = TabView.extend({
|
||||
scrollToTag: function(tagName) {
|
||||
var titleElement, displayedTitle;
|
||||
if (!this.tabModel.isActive()) {
|
||||
this.tabModel.activate();
|
||||
}
|
||||
|
||||
displayedTitle = this.contentView.titleMap[tagName.toLowerCase()];
|
||||
titleElement = this.$el.find('.tags-title').filter(
|
||||
function(){ return $(this).text() === displayedTitle; }
|
||||
);
|
||||
$('html,body').animate(
|
||||
{ scrollTop: titleElement.offset().top - 10 },
|
||||
'slow',
|
||||
function () { titleElement.focus(); }
|
||||
);
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
TabView.prototype.initialize.call(this, options);
|
||||
_.bindAll(this, 'scrollToTag');
|
||||
this.options.scrollToTag = this.scrollToTag;
|
||||
},
|
||||
|
||||
PanelConstructor: TabPanelView.extend({
|
||||
id: 'tags-panel',
|
||||
title: 'Tags',
|
||||
// Translators: this is a title shown before all Notes that have no associated tags. It is put within
|
||||
// brackets to differentiate it from user-defined tags, but it should still be translated.
|
||||
noTags: gettext('[no tags]'), // User-defined tags cannot have spaces, so no risk of a collision.
|
||||
|
||||
renderContent: function () {
|
||||
var notesByTag = {}, noTags = this.noTags, addNoteForTag, noteList, tags, i,
|
||||
sortedTagNames, container, group, noteGroup, tagTitle, titleMap;
|
||||
|
||||
// Iterate through all the notes and build up a dictionary structure by tag.
|
||||
// Note that the collection will be in most-recently updated order already.
|
||||
addNoteForTag = function (note, tag) {
|
||||
noteList = notesByTag[tag.toLowerCase()];
|
||||
if (noteList === undefined) {
|
||||
noteList = [];
|
||||
notesByTag[tag.toLowerCase()] = noteList;
|
||||
}
|
||||
// If a note was tagged with the same tag more than once, don't add again.
|
||||
// We can assume it would be the last element of the list because we iterate through
|
||||
// all tags on a given note before moving on to the text note.
|
||||
if (noteList.length === 0 || noteList[noteList.length - 1] !== note) {
|
||||
noteList.push(note);
|
||||
}
|
||||
};
|
||||
|
||||
this.collection.each(function(note){
|
||||
tags = note.get('tags');
|
||||
if (tags.length === 0) {
|
||||
addNoteForTag(note, noTags);
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < tags.length; i++) {
|
||||
addNoteForTag(note, tags[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sortedTagNames = Object.keys(notesByTag).sort(function (a, b) {
|
||||
// "no tags" should always appear last
|
||||
if (a === noTags) {
|
||||
return 1;
|
||||
}
|
||||
else if (b === noTags) {
|
||||
return -1;
|
||||
}
|
||||
else if (notesByTag[a].length > notesByTag[b].length) {
|
||||
return -1;
|
||||
}
|
||||
else if (notesByTag[a].length < notesByTag[b].length) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return a.toLowerCase() <= b.toLowerCase() ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
container = document.createDocumentFragment();
|
||||
|
||||
// Store map of titles for scrollToTag functionality.
|
||||
this.titleMap = {};
|
||||
titleMap = this.titleMap;
|
||||
|
||||
_.each(sortedTagNames, function (tagName) {
|
||||
noteGroup = notesByTag[tagName];
|
||||
var tagTitle = interpolate_text(
|
||||
"{tagName} ({numberOfNotesWithTag})",
|
||||
{tagName: tagName, numberOfNotesWithTag: noteGroup.length}
|
||||
);
|
||||
group = this.getGroup(tagTitle);
|
||||
titleMap[tagName] = tagTitle;
|
||||
|
||||
group.addChild(this.getNotes(noteGroup));
|
||||
container.appendChild(group.render().el);
|
||||
}, this);
|
||||
|
||||
this.$el.append(container);
|
||||
return this;
|
||||
},
|
||||
|
||||
getGroup: function (tagName) {
|
||||
var group = new NoteGroupView.GroupView({
|
||||
displayName: tagName,
|
||||
template: '<h3 class="tags-title sr-is-focusable" tabindex="-1"><%- displayName %></h3>',
|
||||
className: "note-group"
|
||||
});
|
||||
this.children.push(group);
|
||||
return group;
|
||||
}
|
||||
}),
|
||||
|
||||
tabInfo: {
|
||||
// Translators: 'Tags' is the name of the view (noun) within the Student Notes page that shows all
|
||||
// notes organized by the tags the student has associated with them (if any). When defining a
|
||||
// note in the courseware, the student can choose to associate 1 or more tags with the note
|
||||
// in order to group similar notes together and help with search.
|
||||
name: gettext('Tags'),
|
||||
identifier: 'view-tags',
|
||||
icon: 'fa fa-tag'
|
||||
}
|
||||
});
|
||||
|
||||
return TagsView;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -105,6 +105,7 @@ define(['underscore'], function(_) {
|
||||
};
|
||||
|
||||
getDefaultNotes = function () {
|
||||
// Note that the server returns notes in reverse chronological order (newest first).
|
||||
return [
|
||||
{
|
||||
chapter: getChapter('Second Chapter', 0, 1, [1, 'w_n', 0]),
|
||||
@@ -113,7 +114,8 @@ define(['underscore'], function(_) {
|
||||
created: 'December 11, 2014 at 11:12AM',
|
||||
updated: 'December 11, 2014 at 11:12AM',
|
||||
text: 'Third added model',
|
||||
quote: 'Note 4'
|
||||
quote: 'Note 4',
|
||||
tags: ['Pumpkin', 'pumpkin', 'yummy']
|
||||
},
|
||||
{
|
||||
chapter: getChapter('Second Chapter', 0, 1, [1, 'w_n', 0]),
|
||||
@@ -131,7 +133,8 @@ define(['underscore'], function(_) {
|
||||
created: 'December 11, 2014 at 11:11AM',
|
||||
updated: 'December 11, 2014 at 11:11AM',
|
||||
text: 'Second added model',
|
||||
quote: 'Note 3'
|
||||
quote: 'Note 3',
|
||||
tags: ['yummy']
|
||||
},
|
||||
{
|
||||
chapter: getChapter('Second Chapter', 0, 1, [1, 'w_n', 0]),
|
||||
@@ -140,7 +143,8 @@ define(['underscore'], function(_) {
|
||||
created: 'December 11, 2014 at 11:10AM',
|
||||
updated: 'December 11, 2014 at 11:10AM',
|
||||
text: 'First added model',
|
||||
quote: 'Note 2'
|
||||
quote: 'Note 2',
|
||||
tags: ['PUMPKIN', 'pie']
|
||||
},
|
||||
{
|
||||
chapter: getChapter('First Chapter', 1, 0, [2]),
|
||||
@@ -149,7 +153,8 @@ define(['underscore'], function(_) {
|
||||
created: 'December 11, 2014 at 11:10AM',
|
||||
updated: 'December 11, 2014 at 11:10AM',
|
||||
text: 'First added model',
|
||||
quote: 'Note 1'
|
||||
quote: 'Note 1',
|
||||
tags: ['pie', 'pumpkin']
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ define([
|
||||
) {
|
||||
'use strict';
|
||||
describe('EdxNotes NoteItemView', function() {
|
||||
var getView = function (model) {
|
||||
var getView = function (model, scrollToTag) {
|
||||
model = new NoteModel(_.defaults(model || {}, {
|
||||
id: 'id-123',
|
||||
user: 'user-123',
|
||||
@@ -23,7 +23,7 @@ define([
|
||||
}
|
||||
}));
|
||||
|
||||
return new NoteItemView({model: model}).render();
|
||||
return new NoteItemView({model: model, scrollToTag: scrollToTag}).render();
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -67,7 +67,19 @@ define([
|
||||
var view = getView({tags: ["First", "Second"]});
|
||||
expect(view.$('.reference-title').length).toBe(3);
|
||||
expect(view.$('.reference-title')[2]).toContainText('Tags:');
|
||||
expect(view.$('.reference-tags').last()).toContainText('First, Second');
|
||||
expect(view.$('a.reference-tags').length).toBe(2);
|
||||
expect(view.$('a.reference-tags')[0]).toContainText('First');
|
||||
expect(view.$('a.reference-tags')[1]).toContainText('Second');
|
||||
});
|
||||
|
||||
it('should handle a click event on the tag', function() {
|
||||
var scrollToTagSpy = {
|
||||
scrollToTag: function (tagName){}
|
||||
};
|
||||
spyOn(scrollToTagSpy, 'scrollToTag');
|
||||
var view = getView({tags: ["only"]}, scrollToTagSpy.scrollToTag);
|
||||
view.$('a.reference-tags').click();
|
||||
expect(scrollToTagSpy.scrollToTag).toHaveBeenCalledWith("only");
|
||||
});
|
||||
|
||||
it('should log the edx.student_notes.used_unit_link event properly', function () {
|
||||
|
||||
80
lms/static/js/spec/edxnotes/views/tabs/tags_spec.js
Normal file
80
lms/static/js/spec/edxnotes/views/tabs/tags_spec.js
Normal file
@@ -0,0 +1,80 @@
|
||||
define([
|
||||
'jquery', 'underscore', 'js/common_helpers/template_helpers', 'js/spec/edxnotes/helpers',
|
||||
'js/edxnotes/collections/notes', 'js/edxnotes/collections/tabs',
|
||||
'js/edxnotes/views/tabs/tags', 'js/spec/edxnotes/custom_matchers',
|
||||
'jasmine-jquery'
|
||||
], function(
|
||||
$, _, TemplateHelpers, Helpers, NotesCollection, TabsCollection, TagsView,
|
||||
customMatchers
|
||||
) {
|
||||
'use strict';
|
||||
describe('EdxNotes TagsView', function() {
|
||||
var notes = Helpers.getDefaultNotes(),
|
||||
getView, getText, getNoteText;
|
||||
|
||||
getText = function (selector) {
|
||||
return $(selector).map(function () { return _.trim($(this).text()); }).toArray();
|
||||
};
|
||||
|
||||
getNoteText = function (groupIndex) {
|
||||
return $($('.note-group')[groupIndex]).find('.note-excerpt-p').map(function () {
|
||||
return _.trim($(this).text());
|
||||
}).toArray();
|
||||
};
|
||||
|
||||
getView = function (collection, tabsCollection, options) {
|
||||
var view;
|
||||
|
||||
options = _.defaults(options || {}, {
|
||||
el: $('.wrapper-student-notes'),
|
||||
collection: collection,
|
||||
tabsCollection: tabsCollection
|
||||
});
|
||||
|
||||
view = new TagsView(options);
|
||||
tabsCollection.at(0).activate();
|
||||
|
||||
return view;
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
customMatchers(this);
|
||||
loadFixtures('js/fixtures/edxnotes/edxnotes.html');
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/edxnotes/note-item', 'templates/edxnotes/tab-item'
|
||||
]);
|
||||
|
||||
this.collection = new NotesCollection(notes);
|
||||
this.tabsCollection = new TabsCollection();
|
||||
});
|
||||
|
||||
it('displays a tab and content properly ordered by tag', function () {
|
||||
var view = getView(this.collection, this.tabsCollection),
|
||||
tags = getText('.tags-title'),
|
||||
pumpkinNotes = getNoteText(0),
|
||||
pieNotes = getNoteText(1),
|
||||
yummyNotes = getNoteText(2),
|
||||
noTagsNotes = getNoteText(3);
|
||||
|
||||
expect(this.tabsCollection).toHaveLength(1);
|
||||
expect(this.tabsCollection.at(0).toJSON()).toEqual({
|
||||
name: 'Tags',
|
||||
identifier: 'view-tags',
|
||||
icon: 'fa fa-tag',
|
||||
is_active: true,
|
||||
is_closable: false
|
||||
});
|
||||
expect(view.$('#tags-panel')).toExist();
|
||||
|
||||
// Pumpkin notes has the greatest number of notes, and therefore should come first.
|
||||
// Yummy and pie notes have the same number of notes. They should be sorted alphabetically.
|
||||
// "no tags" should always be last.
|
||||
expect(tags).toEqual(['pumpkin (3)', 'pie (2)', 'yummy (2)', '[no tags] (1)']);
|
||||
|
||||
expect(pumpkinNotes).toEqual(['Note 4', 'Note 2', 'Note 1']);
|
||||
expect(yummyNotes).toEqual(['Note 4', 'Note 3']);
|
||||
expect(pieNotes).toEqual(['Note 2', 'Note 1']);
|
||||
expect(noTagsNotes).toEqual(['Note 5']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -616,6 +616,7 @@
|
||||
'lms/include/js/spec/edxnotes/views/tabs/search_results_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/tabs/recent_activity_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/tabs/course_structure_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/tabs/tags_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/visibility_decorator_spec.js',
|
||||
'lms/include/js/spec/edxnotes/views/toggle_notes_factory_spec.js',
|
||||
'lms/include/js/spec/edxnotes/models/tab_spec.js',
|
||||
|
||||
@@ -108,7 +108,7 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
|
||||
padding-top: ($baseline*1.5);
|
||||
|
||||
// course structure labels
|
||||
.course-title {
|
||||
.course-title, .tags-title {
|
||||
@extend %t-title6;
|
||||
@extend %t-weight4;
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
@@ -124,6 +124,11 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
|
||||
color: $gray-d3;
|
||||
}
|
||||
|
||||
.tags-title {
|
||||
border-bottom: $divider-visual-tertiary;
|
||||
padding-bottom: ($baseline/2);
|
||||
}
|
||||
|
||||
// individual note
|
||||
.note {
|
||||
@include clearfix();
|
||||
@@ -228,10 +233,22 @@ $divider-visual-tertiary: ($baseline/20) solid $gray-l4;
|
||||
color: $m-gray-d2;
|
||||
}
|
||||
|
||||
// Put commas between tags.
|
||||
a.reference-meta.reference-tags:after {
|
||||
content: ",";
|
||||
color: $m-gray-d2;
|
||||
}
|
||||
|
||||
// But not after the last tag.
|
||||
a.reference-meta.reference-tags:last-child:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
// needed for poor base LMS styling scope
|
||||
a.reference-meta {
|
||||
@extend %shame-link-text;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
</span>
|
||||
<span class="copy">${_("Loading")}</span>
|
||||
</div>
|
||||
<div class="sr-is-focusable sr-tab-panel" tabindex="-1"></div>
|
||||
|
||||
% else:
|
||||
<section class="placeholder is-empty">
|
||||
|
||||
@@ -16,28 +16,30 @@
|
||||
<% if (text) { %>
|
||||
<ol class="note-comments" role="region" aria-label="<%- gettext('Note') %>">
|
||||
<li class="note-comment">
|
||||
<h3 class="note-comment-title"><%- gettext("You commented...") %></h3>
|
||||
<p class="note-comment-title"><%- gettext("You commented...") %></p>
|
||||
<p class="note-comment-p"><%= text %></p>
|
||||
</li>
|
||||
</ol>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<footer class="reference" role="complementary" aria-label="<%- gettext('References') %>">
|
||||
<footer class="reference">
|
||||
<div class="wrapper-reference-content">
|
||||
<h3 class="reference-title"><%- gettext("Noted in:") %></h3>
|
||||
<p class="reference-title"><%- gettext("Noted in:") %></p>
|
||||
<% if (unit.url) { %>
|
||||
<a class="reference-meta reference-unit-link" href="<%= unit.url %>#<%= id %>"><%- unit.display_name %></a>
|
||||
<% } else { %>
|
||||
<span class="reference-meta"><%- unit.display_name %></span>
|
||||
<% } %>
|
||||
|
||||
<h3 class="reference-title"><%- gettext("Last Edited:") %></h3>
|
||||
<p class="reference-title"><%- gettext("Last Edited:") %></p>
|
||||
<span class="reference-meta reference-updated-date"><%- updated %></span>
|
||||
|
||||
<% if (tags.length > 0) { %>
|
||||
<h3 class="reference-title"><%- gettext("Tags:") %></h3>
|
||||
<span class="reference-meta reference-tags"><%- tags.join(", ") %></span>
|
||||
<p class="reference-title"><%- gettext("Tags:") %></p>
|
||||
<% for (var i = 0; i < tags.length; i++) { %>
|
||||
<a class="reference-meta reference-tags" href="#"><%- tags[i] %></a>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<% var hasIcon = icon ? 1 : 0; %>
|
||||
|
||||
<a class="tab-label <% if (hasIcon) { print('has-icon') } %>" href="#">
|
||||
<% if (hasIcon) { %><i class="icon <%= icon %>"></i> <% } %><%- gettext(name) %>
|
||||
<% if (hasIcon) { %><i class="icon <%= icon %>" aria-hidden="true"></i> <% } %><%- gettext(name) %>
|
||||
</a>
|
||||
|
||||
<% if (is_closable) { %>
|
||||
|
||||
Reference in New Issue
Block a user