diff --git a/common/lib/xmodule/xmodule/annotatable_module.py b/common/lib/xmodule/xmodule/annotatable_module.py index a7327c1430..8e21d28f29 100644 --- a/common/lib/xmodule/xmodule/annotatable_module.py +++ b/common/lib/xmodule/xmodule/annotatable_module.py @@ -29,170 +29,53 @@ class AnnotatableModule(XModule): css = {'scss': [resource_string(__name__, 'css/annotatable/display.scss')]} icon_class = 'annotatable' - def _is_span(self, element): - """ Returns true if the element is a valid annotation span, false otherwise. """ - return element.get('class') == 'annotatable' - - def _iterspans(self, xmltree, callbacks): - """ Iterates over elements and invokes each callback on the span. """ - - index = 0 - for element in xmltree.iter(): - if self._is_span(element): - for callback in callbacks: - callback(element, index, xmltree) - index += 1 - - def _set_span_highlight(self, span, index, xmltree): - """ Adds a highlight class to the span. """ + def _set_annotation_class(self, el): + """ Sets the CSS class on the annotation span. """ cls = ['annotatable-span', 'highlight'] - marker = self._get_marker_color(span) - if marker is not None: - cls.append('highlight-'+marker) + cls.append('highlight-'+self._get_highlight(el)) + el.set('class', ' '.join(cls)) - span.set('class', ' '.join(cls)) - span.tag = 'div' - - def _set_span_comment(self, span, index, xmltree): - """ Sets the comment class. """ + def _set_annotation_data(self, el): + """ Transforms the annotation span's xml attributes to HTML data attributes. """ - comment = span.find('comment') - if comment is not None: - comment.tag = 'div' - comment.set('class', 'annotatable-comment') + attrs_map = {'body': 'data-comment-body', 'title': 'data-comment-title'} + for xml_key in attrs_map.keys(): + if xml_key in el.attrib: + value = el.get(xml_key, '') + html_key = attrs_map[xml_key] + el.set(html_key, value) + del el.attrib[xml_key] - def _set_span_discussion(self, span, index, xmltree): - """ Sets the associated discussion id for the span. """ - - if 'discussion' in span.attrib: - discussion = span.get('discussion') - span.set('data-discussion-id', discussion) - del span.attrib['discussion'] - - def _set_problem(self, span, index, xmltree): - """ Sets the associated problem. """ - - problem_el = span.find('problem') - if problem_el is not None: - problem_id = str(index + 1) - problem_el.set('problem_id', problem_id) - span.set('data-problem-id', problem_id) - parsed_problem = self._parse_problem(problem_el) - parsed_problem['discussion_id'] = span.get('data-discussion-id') - if parsed_problem is not None: - self.problems.append(parsed_problem) - span.remove(problem_el) - - def _parse_problem(self, problem_el): - """ Returns the problem XML as a dict. """ - prompt_el = problem_el.find('prompt') - answer_el = problem_el.find('answer') - tags_el = problem_el.find('tags') - - if any(v is None for v in (prompt_el, answer_el, tags_el)): - return None - - tags = [] - for tag_el in tags_el.iterchildren('tag'): - tags.append({ - 'name': tag_el.get('name', ''), - 'display_name': tag_el.get('display_name', ''), - 'weight': tag_el.get('weight', 0), - 'answer': tag_el.get('answer', 'n') == 'y' - }) - - result = { - 'problem_id': problem_el.get('problem_id'), - 'prompt': prompt_el.text, - 'answer': answer_el.text, - 'tags': tags - } - - return result - - def _get_marker_color(self, span): + def _get_highlight(self, el): """ Returns the name of the marker/highlight color for the span if it is valid, otherwise none.""" - valid_markers = ['yellow', 'orange', 'purple', 'blue', 'green'] - if 'marker' in span.attrib: - marker = span.attrib['marker'] - del span.attrib['marker'] - if marker in valid_markers: - return marker - return None - - def _get_problem_name(self, problem_type): - """ Returns the display name for the problem type. Defaults to annotated reading if none given. """ - problem_types = { - 'classification': 'Classification Exercise + Guided Discussion', - 'annotated_reading': 'Annotated Reading + Guided Discussion' - } - - if problem_type is not None and problem_type in problem_types.keys(): - return problem_types[problem_type] - return problem_types['annotated_reading'] - + valid_highlights = ['yellow', 'orange', 'purple', 'blue', 'green'] + default_highlight = 'yellow' + highlight = el.get('highlight', default_highlight) + if highlight in valid_highlights: + return highlight + return default_highlight def _render_content(self): """ Renders annotatable content by transforming spans and adding discussions. """ - - callbacks = [ - self._set_span_highlight, - self._set_span_comment, - self._set_span_discussion, - self._set_problem - ] - xmltree = etree.fromstring(self.content) xmltree.tag = 'div' - self._iterspans(xmltree, callbacks) + for el in xmltree.findall('.//annotation'): + el.tag = 'div' + self._set_annotation_class(el) + self._set_annotation_data(el) - return etree.tostring(xmltree) - - def _render_items(self): - items = [] - - if self.render_as_problems: - discussions = {} - for child in self.get_display_items(): - discussions[child.discussion_id] = child.get_html() - - for problem in self.problems: - discussion = None - discussion_id = problem['discussion_id'] - if discussion_id in discussions: - discussion = { - 'discussion_id': discussion_id, - 'content': discussions[discussion_id] - } - items.append({ - 'problem': problem, - 'discussion': discussion - }) - else: - for child in self.get_display_items(): - items.append({ 'discussion': { - 'discussion_id': child.discussion_id, - 'content': child.get_html() - }}) - - return items + return etree.tostring(xmltree, encoding='unicode') def get_html(self): """ Renders parameters to template. """ - - html_content = self._render_content() - items = self._render_items() - context = { 'display_name': self.display_name, - 'problem_name': self.problem_name, 'element_id': self.element_id, - 'html_content': html_content, - 'render_as_problems': self.render_as_problems, - 'items': items + 'discussion_id': self.discussion_id, + 'content_html': self._render_content() } return self.system.render_template('annotatable.html', context) @@ -203,51 +86,16 @@ class AnnotatableModule(XModule): instance_state, shared_state, **kwargs) xmltree = etree.fromstring(self.definition['data']) + discussion_id = '' + if 'discussion' in xmltree.attrib: + discussion_id = xmltree.get('discussion') + del xmltree.attrib['discussion'] - self.element_id = self.location.html_id(); - self.content = self.definition['data'] - self.problem_type = xmltree.get('problem_type') - self.render_as_problems = (self.problem_type == 'classification') - self.problem_name = self._get_problem_name(self.problem_type) - self.problems = [] - + self.content = etree.tostring(xmltree, encoding='unicode') + self.element_id = self.location.html_id() + self.discussion_id = discussion_id class AnnotatableDescriptor(RawDescriptor): module_class = AnnotatableModule stores_state = True - template_dir_name = "annotatable" - - - @classmethod - def definition_from_xml(cls, xml_object, system): - children = [] - for child in xml_object.findall('discussion'): - try: - children.append(system.process_xml(etree.tostring(child, encoding='unicode')).location.url()) - xml_object.remove(child) - except Exception as e: - log.exception("Unable to load child when parsing Sequence. Continuing...") - if system.error_tracker is not None: - system.error_tracker("ERROR: " + str(e)) - continue - return { - 'data': etree.tostring(xml_object, pretty_print=True, encoding='unicode'), - 'children': children - } - - def definition_to_xml(self, resource_fs): - try: - root = etree.fromstring(self.definition['data']) - for child in self.get_children(): - root.append(etree.fromstring(child.export_to_xml(resource_fs))) - return root - except etree.XMLSyntaxError as err: - # Can't recover here, so just add some info and - # re-raise - lines = self.definition['data'].split('\n') - line, offset = err.position - msg = ("Unable to create xml for problem {loc}. " - "Context: '{context}'".format( - context=lines[line - 1][offset - 40:offset + 40], - loc=self.location)) - raise Exception, msg, sys.exc_info()[2] \ No newline at end of file + template_dir_name = "annotatable" \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/css/annotatable/display.scss b/common/lib/xmodule/xmodule/css/annotatable/display.scss index b9925c40b5..4279c454cf 100644 --- a/common/lib/xmodule/xmodule/css/annotatable/display.scss +++ b/common/lib/xmodule/xmodule/css/annotatable/display.scss @@ -13,7 +13,6 @@ border-radius: 3px; .annotatable-toggle { position: absolute; - top: 0; right: 30px; } .annotatable-help-icon { @@ -25,7 +24,9 @@ height: 17px; background: url(../images/info-icon.png) no-repeat; } - .annotatable-toggle, .annotatable-help-icon { margin: 2px 7px 2px 0; } + .annotatable-toggle, .annotatable-help-icon { + margin: 2px 7px 2px 0; + } } } @@ -159,16 +160,12 @@ .ui-tooltip.qtip.ui-tooltip-annotatable { max-width: 375px; - .ui-tooltip-title:before { - font-weight: normal; - content: "Guided Discussion: "; - } .ui-tooltip-content { padding: 0 10px; .annotatable-comment { display: block; margin: 0px 0px 10px 0; - max-height: 225px; // truncate the text via JS so we can get an ellipsis + max-height: 225px; overflow: auto; } .annotatable-reply { diff --git a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee index a9a86c8716..237b9f18db 100644 --- a/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/annotatable/display.coffee @@ -4,19 +4,13 @@ class @Annotatable wrapperSelector: '.annotatable-wrapper' toggleSelector: '.annotatable-toggle' spanSelector: '.annotatable-span' - commentSelector: '.annotatable-comment' replySelector: '.annotatable-reply' helpSelector: '.annotatable-help-icon' returnSelector: '.annotatable-return' - problemSelector: '.annotatable-problem' - problemSubmitSelector: '.annotatable-problem-submit' - problemTagSelector: '.annotatable-problem-tags > li' discussionXModuleSelector: '.xmodule_DiscussionModule' discussionSelector: '.discussion-module' - commentMaxLength: 750 # Max length characters to show in the comment hover state - constructor: (el) -> console.log 'loaded Annotatable' if @_debug @el = el @@ -28,6 +22,7 @@ class @Annotatable init: () -> @initEvents() @initTips() + @initDiscussion() initEvents: () -> @annotationsHidden = false @@ -35,13 +30,9 @@ class @Annotatable @$(@wrapperSelector).delegate @replySelector, 'click', @onClickReply $(@discussionXModuleSelector).delegate @returnSelector, 'click', @onClickReturn - problemSelector = @problemSelector - @$(@problemSubmitSelector).bind 'click', (e) -> - $(this).closest(problemSelector).next().show() - - @$(@problemTagSelector).bind 'click', (e) -> - $(this).toggleClass('selected') - + initDiscussion: () -> + 1 + initTips: () -> @savedTips = [] @$(@spanSelector).each (index, el) => $(el).qtip(@getTipOptions el) @@ -86,13 +77,11 @@ class @Annotatable onClickReply: (e) => e.preventDefault() - discussion_el = @getInlineDiscussion e.currentTarget - return_el = discussion_el.prev(@returnSelector) - - if return_el.length == 1 - @scrollTo(return_el, () -> @afterScrollToDiscussion(discussion_el)) + problem_el = @getProblemEl e.currentTarget + if problem_el.length == 1 + @scrollTo(problem_el, @afterScrollToProblem) else - @scrollTo(discussion_el, @afterScrollToDiscussion) + console.log 'Problem not found! Event: ', e onClickReturn: (e) => e.preventDefault() @@ -103,15 +92,24 @@ class @Annotatable @scrollTo(el, @afterScrollToSpan, offset) getSpan: (el) -> - discussion_id = @getDiscussionId(el) - @$(@spanSelector).filter("[data-discussion-id='#{discussion_id}']") + span_id = @getSpanId(el) + @$(@spanSelector).filter("[data-span-id='#{span_id}']") - getInlineDiscussion: (el) -> - discussion_id = @getDiscussionId(el) + getDiscussion: (el) -> + discussion_id = @getDiscussionId() $(@discussionXModuleSelector).find(@discussionSelector).filter("[data-discussion-id='#{discussion_id}']") - getDiscussionId: (el) -> - $(el).data('discussion-id') + getProblem: (el) -> + el # TODO + + getProblemId: (el) -> + $(el).data('problem-id') + + getSpanId: (el) -> + $(el).data('span-id') + + getDiscussionId: () -> + @$(@wrapperSelector).data('discussion-id') toggleAnnotations: () -> hide = (@annotationsHidden = not @annotationsHidden) @@ -144,30 +142,32 @@ class @Annotatable btn = $('.discussion-show', discussion_el) btn.click() if !btn.hasClass('shown') + afterScrollToProblem: (problem_el) -> + problem_el.effect 'highlight', {}, 500 + afterScrollToSpan: (span_el) -> span_el.effect 'highlight', {color: 'rgba(0,0,0,0.5)' }, 1000 makeTipContent: (el) -> (api) => - discussion_id = @getDiscussionId(el) - comment = $(@commentSelector, el).first().clone() - text = @_truncate comment.text().trim(), @commentMaxLength - comment.text(text) - if discussion_id - comment = comment.after(@createReplyLink discussion_id) - comment + text = $(el).data('comment-body') + comment = @createCommentEl(text) + reply = @createReplyLink('dummy-problem-id') + $(comment).add(reply) makeTipTitle: (el) -> (api) => - comment = $(@commentSelector, el).first() - title = comment.attr('title') + title = $(el).data('comment-title') (if title then title else 'Commentary') - createReplyLink: (discussion_id) -> - $("See Full Discussion") + createCommentEl: (text) -> + $("