refactor: move xmodule folder to root
- Moving xmodule folder to root as we're dissolving sub-projects of common folder in edx-platform
- More info: https://openedx.atlassian.net/browse/BOM-2579
- -e common/lib/xmodule has been removed from the requirements as xmodule has itself become the part of edx-platform and not being installed through requirements
- The test files common/lib/xmodule/test_files/ have been removed as they are not being used anymore
This commit is contained in:
443
xmodule/js/src/annotatable/display.js
Normal file
443
xmodule/js/src/annotatable/display.js
Normal file
@@ -0,0 +1,443 @@
|
||||
// Once generated by CoffeeScript 1.9.3, but now lives as pure JS
|
||||
/* eslint-disable */
|
||||
// TODO: Examine all of the xss-lint exceptions (https://openedx.atlassian.net/browse/PLAT-2084)
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
this.Annotatable = (function() {
|
||||
Annotatable.prototype._debug = false;
|
||||
|
||||
|
||||
/*
|
||||
selectors for the annotatable xmodule
|
||||
*/
|
||||
|
||||
Annotatable.prototype.wrapperSelector = '.annotatable-wrapper';
|
||||
|
||||
Annotatable.prototype.toggleAnnotationsSelector = '.annotatable-toggle-annotations';
|
||||
|
||||
Annotatable.prototype.toggleInstructionsSelector = '.annotatable-toggle-instructions';
|
||||
|
||||
Annotatable.prototype.instructionsSelector = '.annotatable-instructions';
|
||||
|
||||
Annotatable.prototype.sectionSelector = '.annotatable-section';
|
||||
|
||||
Annotatable.prototype.spanSelector = '.annotatable-span';
|
||||
|
||||
Annotatable.prototype.replySelector = '.annotatable-reply';
|
||||
|
||||
|
||||
/*
|
||||
these selectors are for responding to events from the annotation capa problem type
|
||||
*/
|
||||
|
||||
Annotatable.prototype.problemXModuleSelector = '.xmodule_CapaModule';
|
||||
|
||||
Annotatable.prototype.problemSelector = 'div.problem';
|
||||
|
||||
Annotatable.prototype.problemInputSelector = 'div.problem .annotation-input';
|
||||
|
||||
Annotatable.prototype.problemReturnSelector = 'div.problem .annotation-return';
|
||||
|
||||
function Annotatable(el) {
|
||||
this.onMoveTip = bind(this.onMoveTip, this);
|
||||
this.onShowTip = bind(this.onShowTip, this);
|
||||
this.onClickReturn = bind(this.onClickReturn, this);
|
||||
this.onClickReply = bind(this.onClickReply, this);
|
||||
this.onClickToggleInstructions = bind(this.onClickToggleInstructions, this);
|
||||
this.onClickToggleAnnotations = bind(this.onClickToggleAnnotations, this);
|
||||
if (this._debug) {
|
||||
console.log('loaded Annotatable');
|
||||
}
|
||||
this.el = el;
|
||||
this.$el = $(el);
|
||||
this.init();
|
||||
}
|
||||
|
||||
Annotatable.prototype.$ = function(selector) {
|
||||
return $(selector, this.el);
|
||||
};
|
||||
|
||||
Annotatable.prototype.init = function() {
|
||||
this.initEvents();
|
||||
return this.initTips();
|
||||
};
|
||||
|
||||
Annotatable.prototype.initEvents = function() {
|
||||
|
||||
/*
|
||||
Initialize toggle handlers for the instructions and annotations sections
|
||||
*/
|
||||
var ref;
|
||||
ref = [false, false], this.annotationsHidden = ref[0], this.instructionsHidden = ref[1];
|
||||
this.$(this.toggleAnnotationsSelector).bind('click', this.onClickToggleAnnotations);
|
||||
this.$(this.toggleInstructionsSelector).bind('click', this.onClickToggleInstructions);
|
||||
|
||||
/*
|
||||
Initialize handler for 'reply to annotation' events that scroll to
|
||||
the associated problem. The reply buttons are part of the tooltip
|
||||
content. It's important that the tooltips be configured to render
|
||||
as descendants of the annotation module and *not* the document.body.
|
||||
*/
|
||||
this.$el.on('click', this.replySelector, this.onClickReply);
|
||||
|
||||
/*
|
||||
Initialize handler for 'return to annotation' events triggered from problems.
|
||||
1) There are annotationinput capa problems rendered on the page
|
||||
2) Each one has an embedded return link (see annotation capa problem template).
|
||||
Since the capa problem injects HTML content via AJAX, the best we can do is
|
||||
is let the click events bubble up to the body and handle them there.
|
||||
*/
|
||||
return $(document).on('click', this.problemReturnSelector, this.onClickReturn);
|
||||
};
|
||||
|
||||
Annotatable.prototype.initTips = function() {
|
||||
|
||||
/*
|
||||
tooltips are used to display annotations for highlighted text spans
|
||||
*/
|
||||
return this.$(this.spanSelector).each((function(_this) {
|
||||
return function(index, el) {
|
||||
return $(el).qtip(_this.getSpanTipOptions(el));
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
Annotatable.prototype.getSpanTipOptions = function(el) {
|
||||
return {
|
||||
content: {
|
||||
title: {
|
||||
text: this.makeTipTitle(el)
|
||||
},
|
||||
text: this.makeTipContent(el)
|
||||
},
|
||||
position: {
|
||||
|
||||
/*
|
||||
of tooltip
|
||||
*/
|
||||
my: 'bottom center',
|
||||
|
||||
/*
|
||||
of target
|
||||
*/
|
||||
at: 'top center',
|
||||
|
||||
/*
|
||||
where the tooltip was triggered (i.e. the annotation span)
|
||||
*/
|
||||
target: $(el),
|
||||
container: this.$(this.wrapperSelector),
|
||||
adjust: {
|
||||
y: -5
|
||||
}
|
||||
},
|
||||
show: {
|
||||
event: 'click mouseenter',
|
||||
solo: true
|
||||
},
|
||||
hide: {
|
||||
event: 'click mouseleave',
|
||||
delay: 500,
|
||||
|
||||
/*
|
||||
don't hide the tooltip if it is moused over
|
||||
*/
|
||||
fixed: true
|
||||
},
|
||||
style: {
|
||||
classes: 'ui-tooltip-annotatable'
|
||||
},
|
||||
events: {
|
||||
show: this.onShowTip,
|
||||
move: this.onMoveTip
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Annotatable.prototype.onClickToggleAnnotations = function(e) {
|
||||
return this.toggleAnnotations();
|
||||
};
|
||||
|
||||
Annotatable.prototype.onClickToggleInstructions = function(e) {
|
||||
return this.toggleInstructions();
|
||||
};
|
||||
|
||||
Annotatable.prototype.onClickReply = function(e) {
|
||||
return this.replyTo(e.currentTarget);
|
||||
};
|
||||
|
||||
Annotatable.prototype.onClickReturn = function(e) {
|
||||
return this.returnFrom(e.currentTarget);
|
||||
};
|
||||
|
||||
Annotatable.prototype.onShowTip = function(event, api) {
|
||||
if (this.annotationsHidden) {
|
||||
return event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
Annotatable.prototype.onMoveTip = function(event, api, position) {
|
||||
|
||||
/*
|
||||
This method handles a vertical positioning bug in Firefox as
|
||||
well as an edge case in which a tooltip is displayed above a
|
||||
non-overlapping span like this:
|
||||
|
||||
(( TOOLTIP ))
|
||||
\/
|
||||
text text text ... text text text ...... <span span span>
|
||||
<span span span>
|
||||
|
||||
The problem is that the tooltip looks disconnected from both spans, so
|
||||
we should re-position the tooltip to appear above the span.
|
||||
*/
|
||||
var adjust_y, container, container_offset, focus_rect, is_non_overlapping, offset_left, offset_top, rect_center, rect_top, rects, ref, ref1, ref2, target, tip, tip_height, tip_left, tip_top, tip_width, win_width;
|
||||
tip = api.elements.tooltip;
|
||||
adjust_y = ((ref = api.options.position) != null ? (ref1 = ref.adjust) != null ? ref1.y : void 0 : void 0) || 0;
|
||||
container = ((ref2 = api.options.position) != null ? ref2.container : void 0) || $('body');
|
||||
target = api.elements.target;
|
||||
rects = $(target).get(0).getClientRects();
|
||||
is_non_overlapping = (rects != null ? rects.length : void 0) === 2 && rects[0].left > rects[1].right;
|
||||
if (is_non_overlapping) {
|
||||
|
||||
/*
|
||||
we want to choose the largest of the two non-overlapping spans and display
|
||||
the tooltip above the center of it (see api.options.position settings)
|
||||
*/
|
||||
focus_rect = (rects[0].width > rects[1].width ? rects[0] : rects[1]);
|
||||
} else {
|
||||
|
||||
/*
|
||||
always compute the new position because Firefox doesn't
|
||||
properly vertically position the tooltip
|
||||
*/
|
||||
focus_rect = rects[0];
|
||||
}
|
||||
rect_center = focus_rect.left + (focus_rect.width / 2);
|
||||
rect_top = focus_rect.top;
|
||||
tip_width = $(tip).width();
|
||||
tip_height = $(tip).height();
|
||||
|
||||
/*
|
||||
tooltip is positioned relative to its container, so we need to factor in offsets
|
||||
*/
|
||||
container_offset = $(container).offset();
|
||||
offset_left = -container_offset.left;
|
||||
offset_top = $(document).scrollTop() - container_offset.top;
|
||||
tip_left = offset_left + rect_center - (tip_width / 2);
|
||||
tip_top = offset_top + rect_top - tip_height + adjust_y;
|
||||
|
||||
/*
|
||||
make sure the new tip position doesn't clip the edges of the screen
|
||||
*/
|
||||
win_width = $(window).width();
|
||||
if (tip_left < offset_left) {
|
||||
tip_left = offset_left;
|
||||
} else if (tip_left + tip_width > win_width + offset_left) {
|
||||
tip_left = win_width + offset_left - tip_width;
|
||||
}
|
||||
|
||||
/*
|
||||
final step: update the position object (used by qtip2 to show the tip after the move event)
|
||||
*/
|
||||
return $.extend(position, {
|
||||
'left': tip_left,
|
||||
'top': tip_top
|
||||
});
|
||||
};
|
||||
|
||||
Annotatable.prototype.getSpanForProblemReturn = function(el) {
|
||||
var problem_id;
|
||||
problem_id = $(this.problemReturnSelector).index(el);
|
||||
return this.$(this.spanSelector).filter("[data-problem-id='" + problem_id + "']");
|
||||
};
|
||||
|
||||
Annotatable.prototype.getProblem = function(el) {
|
||||
var problem_id;
|
||||
problem_id = this.getProblemId(el);
|
||||
return $(this.problemInputSelector).eq(problem_id);
|
||||
};
|
||||
|
||||
Annotatable.prototype.getProblemId = function(el) {
|
||||
return $(el).data('problem-id');
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleAnnotations = function() {
|
||||
var hide;
|
||||
hide = (this.annotationsHidden = !this.annotationsHidden);
|
||||
this.toggleAnnotationButtonText(hide);
|
||||
this.toggleSpans(hide);
|
||||
return this.toggleTips(hide);
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleTips = function(hide) {
|
||||
var visible;
|
||||
visible = this.findVisibleTips();
|
||||
return this.hideTips(visible);
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleAnnotationButtonText = function(hide) {
|
||||
var buttonText;
|
||||
if (hide) {
|
||||
buttonText = gettext('Show Annotations');
|
||||
} else {
|
||||
buttonText = gettext('Hide Annotations');
|
||||
}
|
||||
return this.$(this.toggleAnnotationsSelector).text(buttonText);
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleInstructions = function() {
|
||||
var hide;
|
||||
hide = (this.instructionsHidden = !this.instructionsHidden);
|
||||
this.toggleInstructionsButton(hide);
|
||||
return this.toggleInstructionsText(hide);
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleInstructionsButton = function(hide) {
|
||||
var cls, txt;
|
||||
if (hide) {
|
||||
txt = gettext('Expand Instructions');
|
||||
} else {
|
||||
txt = gettext('Collapse Instructions');
|
||||
}
|
||||
cls = (hide ? ['expanded', 'collapsed'] : ['collapsed', 'expanded']);
|
||||
return this.$(this.toggleInstructionsSelector).text(txt).removeClass(cls[0]).addClass(cls[1]);
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleInstructionsText = function(hide) {
|
||||
var slideMethod;
|
||||
slideMethod = (hide ? 'slideUp' : 'slideDown');
|
||||
return this.$(this.instructionsSelector)[slideMethod]();
|
||||
};
|
||||
|
||||
Annotatable.prototype.toggleSpans = function(hide) {
|
||||
return this.$(this.spanSelector).toggleClass('hide', hide, 250);
|
||||
};
|
||||
|
||||
Annotatable.prototype.replyTo = function(buttonEl) {
|
||||
var el, offset;
|
||||
offset = -20;
|
||||
el = this.getProblem(buttonEl);
|
||||
if (el.length > 0) {
|
||||
return this.scrollTo(el, this.afterScrollToProblem, offset);
|
||||
} else {
|
||||
if (this._debug) {
|
||||
return console.log('problem not found. event: ', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Annotatable.prototype.returnFrom = function(buttonEl) {
|
||||
var el, offset;
|
||||
offset = -200;
|
||||
el = this.getSpanForProblemReturn(buttonEl);
|
||||
if (el.length > 0) {
|
||||
return this.scrollTo(el, this.afterScrollToSpan, offset);
|
||||
} else {
|
||||
if (this._debug) {
|
||||
return console.log('span not found. event:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Annotatable.prototype.scrollTo = function(el, after, offset) {
|
||||
if (offset == null) {
|
||||
offset = -20;
|
||||
}
|
||||
if ($(el).length > 0) {
|
||||
return $('html,body').scrollTo(el, {
|
||||
duration: 500,
|
||||
onAfter: this._once((function(_this) {
|
||||
return function() {
|
||||
return after != null ? after.call(_this, el) : void 0;
|
||||
};
|
||||
})(this)),
|
||||
offset: offset
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Annotatable.prototype.afterScrollToProblem = function(problem_el) {
|
||||
return problem_el.effect('highlight', {}, 500);
|
||||
};
|
||||
|
||||
Annotatable.prototype.afterScrollToSpan = function(span_el) {
|
||||
return span_el.addClass('selected', 400, 'swing', function() {
|
||||
return span_el.removeClass('selected', 400, 'swing');
|
||||
});
|
||||
};
|
||||
|
||||
Annotatable.prototype.makeTipContent = function(el) {
|
||||
return (function(_this) {
|
||||
return function(api) {
|
||||
var comment, problem_id, reply, text;
|
||||
text = $(el).data('comment-body');
|
||||
comment = _this.createComment(text);
|
||||
problem_id = _this.getProblemId(el);
|
||||
reply = _this.createReplyLink(problem_id);
|
||||
return $(comment).add(reply);
|
||||
};
|
||||
})(this);
|
||||
};
|
||||
|
||||
Annotatable.prototype.makeTipTitle = function(el) {
|
||||
return (function(_this) {
|
||||
return function(api) {
|
||||
var title;
|
||||
title = $(el).data('comment-title');
|
||||
if (title) {
|
||||
return title;
|
||||
} else {
|
||||
return gettext('Commentary');
|
||||
}
|
||||
};
|
||||
})(this);
|
||||
};
|
||||
|
||||
Annotatable.prototype.createComment = function(text) {
|
||||
return $("<div class=\"annotatable-comment\">" + text + "</div>"); // xss-lint: disable=javascript-concat-html
|
||||
};
|
||||
|
||||
Annotatable.prototype.createReplyLink = function(problem_id) {
|
||||
var linktxt;
|
||||
linktxt = gettext('Reply to Annotation');
|
||||
return $("<a class=\"annotatable-reply\" href=\"javascript:void(0);\" data-problem-id=\"" + problem_id + "\">" + linktxt + "</a>"); // xss-lint: disable=javascript-concat-html
|
||||
};
|
||||
|
||||
Annotatable.prototype.findVisibleTips = function() {
|
||||
var visible;
|
||||
visible = [];
|
||||
this.$(this.spanSelector).each(function(index, el) {
|
||||
var api, tip;
|
||||
api = $(el).qtip('api');
|
||||
tip = $(api != null ? api.elements.tooltip : void 0);
|
||||
if (tip.is(':visible')) {
|
||||
return visible.push(el);
|
||||
}
|
||||
});
|
||||
return visible;
|
||||
};
|
||||
|
||||
Annotatable.prototype.hideTips = function(elements) {
|
||||
return $(elements).qtip('hide');
|
||||
};
|
||||
|
||||
Annotatable.prototype._once = function(fn) {
|
||||
var done;
|
||||
done = false;
|
||||
return (function(_this) {
|
||||
return function() {
|
||||
if (!done) {
|
||||
fn.call(_this);
|
||||
}
|
||||
return done = true;
|
||||
};
|
||||
})(this);
|
||||
};
|
||||
|
||||
return Annotatable;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
||||
Reference in New Issue
Block a user