Module Bundle .Js
Module Bundle .Js
sAttachment = function(context){
$('.attachments-video-thumbnails-play:not(.sAttachment-processed)',
context).addClass('sAttachment-processed').each(function(){
var btn = $(this);
btn.bind('click', function(){
var wrapper = btn.parents(".attachments-video");
var video = $(".video-video", wrapper);
wrapper.after(video);
video.show();
wrapper.hide();
thePopup = Popups.activePopup();
if(thePopup != null){
Popups.resizeAndCenter(thePopup);
}
return false;
});
});
$('.embed-cover:not(.sAttachment-processed)', context).addClass('sAttachment-
processed').each(function(){
$(this).click(function(){
var cover = $(this),
embedContentObj = cover.siblings('.embed-content:first');
cover.hide();
cover.siblings('.embed-title').hide();
$('.attachments-link:not(.sAttachment-processed)',
context).addClass('sAttachment-processed').each(function(){
var link = $(this);
var intPopup = $('.attachment-link-popup', link);
link.bind('mouseenter', function(){
if(intPopup.length)
intPopup.show();
}).bind('mouseleave', function(){
if(intPopup.length)
intPopup.hide();
});
//hide the popup if the user goes from the tip arrow in
intPopup.bind('mouseenter', function(){
$(this).hide();
});
});
;Drupal.behaviors.sCommonMediaFileIframeUseRelativeUrl = function(context) {
// If the source of this iframe appears to be a Schoology media file, make the
source URL relative.
var iframeObj = $(this);
var iframeSrc = iframeObj.attr('src');
if($match){
// In IE, the leading slash is not included in pathname, so we need to
prepend
// SGY-23250
if(pathName[0] !== '/'){
pathName = '/' + pathName;
}
function toggleSubmit(show){
submitButton.prop('disabled', !show);
submitSpan.toggleClass('disabled', !show);
}
function processInput(editor){
editor.onChange.add(function(){
var hasContent = !!$(editor.getBody().innerHTML).text().length;
toggleSubmit(hasContent);
});
editor.onInit.add(function(){
// tinymce might take some time to set up...
setTimeout(function(){
tinyMCE.execCommand('mceFocus', true, editor.id);
}, 0);
if(isEdgeFeed){
// on the feeds, space is limited so when the user focus out of an empty
input
// the RTE is disabled and reverts back to a good ol' textarea
tinyMCE.dom.Event.add(editor.getWin(), "blur", function(){
// bubble up the form focusout defined below when the box has no
content
if(!editor.getContent().length){
tinyMCE.execCommand('mceRemoveControl', true, editor.id);
editComment.val(editComment.attr('defaulttext')).trigger('blur');
}
});
}
});
}
if(window.tinyMCE){
if (tinyMCE.activeEditor != null) {
processInput(tinyMCE.activeEditor);
}
else if(!submitEnabledForm && isRichText) {
tinyMCE.onAddEditor.add(function (tme, editor) {
if (editor.id == editComment.attr('id')) {
processInput(editor);
}
});
}
}
form
.on('focusin', function(e) {
// for non-threaded comment areas like blog, show/hide profile
picture/submit button
// and resize the comment textarea accordingly
$("div.author-picture.threadless",form).show();
$("div.author-picture.threadless",form).siblings('span.submit-span-
wrapper').show();
form.addClass('mouse-focus');
// if the currently focused item is outside of the form and the text in
textarea is blank or the default text, change it back to the "slim" view
if(focusedItemIndex === -1 && editComment.val() ==
editComment.attr('defaulttext')) {
$("div.author-picture.threadless",form).hide();
$("div.author-picture.threadless",form).siblings('span.submit-span-
wrapper').hide();
form.removeClass('mouse-focus');
if( editComment.hasClass('threadless') ) {
editComment.removeClass('add-comment-resize');
}
}
}, 0);
});
if(!isRichText){
editComment
.on('focus', function() {
if(editComment.val() == editComment.attr('defaulttext') &&
editComment.hasClass('pre-fill')) {
editComment.val('').removeClass('pre-fill');
}
if( editComment.hasClass('threadless') ) {
editComment.addClass('add-comment-resize');
}
$(this).trigger("resize");//necessary to get jquery-elastic to know of
change in size if textarea changes when we add a avatar icon to next to textarea
})
.on('blur', function() {
if(editComment.val() == '') {
editComment.val( editComment.attr('defaulttext') ).not('.is-
locked').addClass('pre-fill');
}
})
.on('keyup', function(){
toggleSubmit(editComment.val().length);
})
.trigger('blur')
.elastic();
}
function togglePlaceholderForm(){
if(rtePlaceholder.css('display') == 'none'){
rtePlaceholder.show();
rteActualWrapper.hide();
} else {
rtePlaceholder.hide();
rteActualWrapper.show();
tinyMCE.activeEditor.focus();
tinyMCE.activeEditor.getBody().focus();
}
}
rtePlaceholder.on('click', togglePlaceholderForm);
});
}
;Drupal.behaviors.s_user = function(context){
$('#s-user-custom-notifications-form:not(.sUser-processed)').addClass('sUser-
processed').each(function(){
//handle select all functionality in custom notification form
var form = $(this);
var selectAll = $('.select-all', form);
selectAll.click(function(){
var checked = $(this).is(':checked');
$('.realm-setting', form).each(function(){
$(this).prop('checked', checked);
});
});
$('.edit-select-all-wrapper', form).click(function(){
$('input', this).click();
});
$('.realm-setting').each(function(){
var realmSetting = $(this);
realmSetting.change(function(){
if(!realmSetting.attr('checked') && selectAll.attr('checked')){
selectAll.prop('checked', false);
}
});
});
$('.cancel-btn', form).click(function(e){
//reset the value of the correspoding select form in the notifications
setting to its original value
var setting = $('.setting-name', form).val();
var selectVal = $('.original-setting', form).html();
$('select[name=' + setting + ']').selectmenu('value', selectVal);
var popup = Popups.activePopup();
if(popup != null){
popup.close();
}
e.preventDefault();
});
});
$('.notifications-header:not(.sUser-processed)').addClass('sUser-
processed').each(function(){
// handles reset to default for notification settings
if(Drupal.settings.s_user != 'undefined' && typeof
Drupal.settings.s_user.notif_defaults != 'undefined'){
var setting = Drupal.settings.s_user;
$('.reset-default').click(function(){
$('select.notif-setting-select').each(function(){
var name = $(this).attr('name');
if(typeof setting.notif_defaults[name] != 'undefined'){
$(this).selectmenu('value', setting.notif_defaults[name]);
}
});
});
}
});
$('#s-user-set-notifications-form:not(.sUser-processed)').addClass('sUser-
processed').each(function(){
//open popup for for when "custom" is selected
$('select.notif-setting-select').each(function(){
var select = $(this);
select.selectmenu({
style: 'dropdown'
});
select.change(function(){
if(select.val() == '2'){
var href = '/user/custom_notification/' + select.attr('name');
Popups.openPath(this, {
href : href,
ajaxForm : false,
extraClass : 'popups-custom-notification popups-medium'
}, window);
}
});
});
});
$('#s-user-invite-colleague-form:not(.sUser-processed)').addClass('sUser-
processed').each(function(){
ZeroClipboard.setMoviePath( '/sites/all/misc/zeroclipboard/zeroclipboard.swf' );
var clip = new ZeroClipboard.Client();
clip.setHandCursor( true );
var text = $('#invite_link #url').html();
text = text.replace(/&/g, '&');
clip.setText(text);
var html = '<span class="zclipboard" style="position:absolute;">' +
clip.getHTML( $('#invite_link').outerWidth(), $('#invite_link').outerHeight() ) +
'</span>';
$('.popups-body').append(html);
repositionZClipboard();
$('.addresses').focus(function(){
if($(this).hasClass('pre-fill')){
$(this).empty();
$(this).removeClass('pre-fill');
}
repositionZClipboard();
});
if($('#s-user-invite-colleague-form').hasClass('emails-sent'))
{
$('#s-user-invite-colleague-form').removeClass('emails-sent');
$('.popup-invite').addClass('emails-sent');
sPopupsResizeCenter();
}
$('#s-user-invite-colleagues-invite-by-email').bind('click',
function(e) {
$('.popup-invite').addClass('sending-emails');
$('#s-user-invite-colleagues-invite-choices,#s-user-invite-
colleagues-click-to-copy-link,#s-user-invite-colleagues-click-to-copy-
title').fadeOut(300,
function() {
$('#edit-emails,.s-user-invite-colleagues-invite-by-email-
buttons').fadeIn(300,
function() {
repositionZClipboard();
}
);
}
);
e.preventDefault();
}
);
$('#s-user-invite-colleagues-invite-by-email-cancel').unbind('click');
$('#s-user-invite-colleagues-invite-by-email-cancel').bind('click',
function(e) {
$('.popup-invite').removeClass('sending-emails');
$('#edit-emails,.s-user-invite-colleagues-invite-by-email-
buttons').fadeOut(300,
function() {
$('#s-user-invite-colleagues-invite-choices,#s-user-invite-
colleagues-click-to-copy-link,#s-user-invite-colleagues-click-to-copy-
title').fadeIn(300,
function() {
repositionZClipboard();
}
);
}
);
e.preventDefault();
}
);
$("#s-user-invite-colleagues-fb-button,#s-user-invite-colleagues-twitter-
button").click(
function(e) {
window.open($(this).attr('realhref'), 'popup',
'width=650,height=380', false);
e.preventDefault();
return false;
}
);
});
$("a.show-code").click(function(){
var value = $(this).attr('id');
var value = value.split("-");
var html = '<div class="formatted-code">Code: '+value[1]+'</div>';
$.prompt(html);
return false;
});
$("input#action-select-all").click(function(){
var parent = $(this);
$("#s-user-manage-activation-form table input:checkbox").each(function(){
if($(this).attr('checked')){
$(parent).prop('checked', false);
$(this).prop('checked', false);
}
else{
$(parent).prop('checked', true);
$(this).prop('checked', true);
}
});
});
$('#s-user-edit-calendar-feed-settings-form:not(.sUser-
processed)').addClass('sUser-processed').each(function(){
$('#webcal-addr', $(this)).focus(function(){
document.getElementById('webcal-addr').select();
}).mouseup(function(e){
e.preventDefault();
});
});
$('#s-user-profile-editor-profile-form:not(.sUser-processed)').addClass('sUser-
processed').each(function(){
$('textarea', $(this)).elastic();
$('#s-user-personal-account-settings-form:not(.sUser-
processed)').addClass('sUser-processed').each(function(){
var form = $(this);
$('.timezone-select-time .timezone-use-list', form).bind('click', function(){
$(this).parent().hide();
$('.timezone-select-timezone', form).show();
$('#edit-timezone-select-type', form).val('timezone');
});
$('.timezone-select-timezone .timezone-use-time', form).bind('click',
function(){
$(this).parent().hide();
$('.timezone-select-time', form).show();
$('#edit-timezone-select-type', form).val('time');
});
//only display the show preferred name dropdown if a preferred name has been
set
$('#edit-user-name-first-preferred').on('keydown paste input', function(e){
var $textbox = $(this);
var hasPreferredName = !!$textbox.val().length;
var $namePreferredWrapper = $textbox.closest('.name-first-preferred-
wrapper');
var $usePreferredName = $('.use-name-first-preferred-wrapper');
var usePreferredNameVisible = !$usePreferredName.hasClass('hidden');
if(hasPreferredName != usePreferredNameVisible){
$usePreferredName.toggleClass('hidden', usePreferredNameVisible);
$namePreferredWrapper.toggleClass('has-preferred-first-name',
hasPreferredName);
}
});
});
$('.s-js-name-title-wrapper:not(.sUser-processed)', context).addClass('sUser-
processed').each(function(){
var titleWrapper = $(this),
customField = $('.s-js-name-title-custom', titleWrapper);
$('.s-js-name-title-select', titleWrapper).change(function(e){
if($(this).val() == 'custom'){
titleWrapper.addClass('s-js-name-title-custom-enabled');
}
else{
titleWrapper.removeClass('s-js-name-title-custom-enabled');
customField.val('');
}
}).triggerHandler('change');
});
$('.profile-picture-wrapper:not(.sUser-processed)', context).addClass('sUser-
processed').each(function(){
var wrapper = $(this);
var link = $('.edit-profile-picture-hover', wrapper);
var pic = $('.profile-picture', wrapper);
var uploader = $('#profile-picture-uploader-wrapper', wrapper);
link.bind('click', function(){
if(uploader.is(':visible')){
uploader.hide();
}
else {
uploader.show();
}
});
$('body').bind('click', function(e){
var target = $(e.target);
if(!target.hasClass('profile-picture-wrapper') &&
target.parents('.profile-picture-wrapper').length == 0){
uploader.hide();
}
});
});
sCommonConfirmationPopup({
title: Drupal.t('Deactivate Device'),
body: removeDevicePopupBody,
extraClass: 'push-remove',
confirm: {
text: Drupal.t('Deactivate'),
func: function(){
$.ajax(linkObj.attr('href'), {
data: {ajax: true},
success: function(data){
if(data.body){
$('#content-wrapper').html(data.body);
if(data.messages){
var mainWrapperObj = $('#main-content-wrapper');
mainWrapperObj.children('.messages').remove();
mainWrapperObj.prepend(data.messages);
}
Drupal.attachBehaviors($('#main-content-wrapper'));
}
},
complete: function(){
Popups.activePopup().close();
}
});
}
}
});
});
}
function repositionZClipboard(){
var offset = $('#invite_link').offset();
var pOffset = $('.popups-box').offset();
$('.zclipboard').css('left', offset.left - pOffset.left);
$('.zclipboard').css('top', offset.top - pOffset.top);
}
;Drupal.behaviors.sDropItem = function(context){
sDropUploads = [];
sDropsel_ids = new Array();
// ajax behavior for dropbox filter select
$('.drop-items:not(.sDropItem-processed)', context).addClass('sDropItem-
processed').each(function(){
var wrapper = $(this);
var dropbox = $('.drop-item-display-all', wrapper);
$('.dropbox-download-all', dropbox).tipsy({
gravity: 's'
});
});
$('.item-list:not(.sDropItem-processed)', context).addClass('sDropItem-
processed').each(function() {
var reactElements = $('.portfolio-add-submission-btn-wrapper', this).toArray();
if (reactElements.length) {
window.sgyModules.bootstrapReactApp({appInstanceIndex:
'sgyPortfolioComponents'})
.then(function() {
$.each(window.addtoPFLBtnRenders, function(){this()});
window.addtoPFLBtnRenders = null;
});
}
$('#dropbox-submitted-filter:not(.sDropItem-processed)',
context).addClass('sDropItem-processed').each(function(){
var filter = $(this);
var wrapper = filter.closest('.drop-items');
var dropbox = $('.drop-item-display-all', wrapper);
var csmToggle = $('.csm-toggle-sections', wrapper);
$(this).selectmenu({
style: 'dropdown',
align: 'right'
}).change(function(){
$('.right-block-big', dropbox).html('<img
src="/sites/all/themes/schoology_theme/images/ajax-loader.gif" alt="' +
Drupal.t('Loading...') + '" />');
$.ajax({
type: "GET",
url: ajaxUrl,
dataType: "json",
success: function(json){
var output = $(json);
Drupal.attachBehaviors(output);
$('.right-block-big', dropbox).empty().append(output);
}
});
});
});
$('.drop-item-display-own ul:not(.sDropItem-processed)',
context).addClass('sDropItem-processed').each(function(){
var wrapper = $(this);
$('li', wrapper).tipsy({
html: false,
gravity: 'e'
});
});
if( id =='dropbox-submit-create-tab' )
$('#edit-submission_tbl').css('width','100%');
$('.popups-tabs li.active',
wrapper).not(selectedTab).removeClass('active');
selectedTab.addClass('active');
$('.popups-tab-content',
wrapper).not(selectedContent).removeClass('visible');
selectedContent.addClass('visible');
if( id =='dropbox-submit-create-tab' ) {
var activeEditorId = tinyMCE.activeEditor.id;
tinyMCE.execCommand('mceRemoveControl', true, activeEditorId);
tinyMCE.execCommand('mceAddControl', true, activeEditorId);
}
popup = Popups.activePopup();
var popupId = popup.id;
$('#' + popupId + ' .popups-title .title').html(selectedTab.html());
if(popup != null){
var popupObj = popup.$popup();
var classes = {
'popups-compose': 'dropbox-submit-create-tab',
'popups-library': 'dropbox-submit-resources-tab'
};
// only set the popupObj to have the class associated with the current
tab, remove all other classes
$.each(classes, function(className, tabId){
if(id == tabId){
popupObj.addClass(className);
}
else{
popupObj.removeClass(className);
}
});
Popups.resizeAndCenter(popup);
}
})
});
// this is to allow the tinymce editor to steal focus for a quick moment;
// otherwise, other textareas cannot be edited in IE
setTimeout("$('#s-drop-item-submit-wrapper .popups-tabs li:eq(0)').click()",
1);
//Fairly hacky way of ensuring we only rebind the Popups error handler once
$('body:not(.sDropItem-processed)').addClass('sDropItem-
processed').each(function(){
var popupErrorFunc = Popups.errorMessage;
Popups.errorPassthru = function ($form) {
var formId = $form.attr('id');
if (formId === "s-drop-item-submit-create-form") {
Popups.message(
Drupal.t('Submission Error'),
'<p>' + Drupal.t('There was an error submitting your assignment. A
draft of your assignment was saved in the Submissions panel.') + '</p>' +
'<p>' + Drupal.t('Please try submitting again in a few minutes.') +
'</p>'
);
}
else {
newArgs = Array.from(arguments);
popupErrorFunc(...newArgs.slice(1));
}
}
});
//Hijack the popups "before submit" event to bind the form being submitted to
the error handler closure
//This allows us to display a custom error message for submissions created
within the RTE
$(document).one('popups_before_submit', function(event, formData, $form,
options) {
Popups.errorMessage = Popups.errorPassthru.bind(this, $form);
});
});
}
;/**
* Copyleft 2010-2011 Jay and Han ([email protected])
* under the GNU Lesser General Public License
* http://www.gnu.org/licenses/lgpl.html
* Project Website: http://mathquill.com
*
* @note v0.9.1
*
* 2013-3-22 removed single letter commands such as LatexCmds.C since they cause
problems when parsing something like
* \sqrt{C}. The parser will turn the C into a \complex which is not desirable
* Commands affected: NPZQRCHoO
* @see https://github.com/mathquill/mathquill/issues/164
*
*/
(function() {
var $ = jQuery,
undefined,
_, //temp variable of prototypes
mqCmdId = 'mathquill-command-id',
mqBlockId = 'mathquill-block-id',
min = Math.min,
max = Math.max;
function noop() {}
/**
* sugar to make defining lots of commands easier.
* TODO: rethink this.
*/
function bind(cons /*, args... */) {
var args = __slice.call(arguments, 1);
return function() {
return cons.apply(this, args);
};
}
/**
* a development-only debug method. This definition and all
* calls to `pray` will be stripped from the minified
* build of mathquill.
*
* This function must be called by name to be removed
* at compile time. Do not define another function
* with the same name, and only call this function by
* name.
*/
function pray(message, cond) {
if (!cond) throw new Error('prayer failed: '+message);
}
var P = (function(prototype, ownProperty, undefined) {
// helper functions that also help minification
function isObject(o) { return typeof o === 'object'; }
function isFunction(f) { return typeof f === 'function'; }
function P(_superclass /* = Object */, definition) {
// handle the case where no superclass is given
if (definition === undefined) {
definition = _superclass;
_superclass = Object;
}
if (isFunction(def)) {
// call the defining function with all the arguments you need
// extensions captures the return value.
extensions = def.call(C, proto, _super, C, _superclass);
}
else if (isObject(def)) {
// if you passed an object instead, we'll take it
extensions = def;
}
// ...and extend it
if (isObject(extensions)) {
for (var ext in extensions) {
if (ownProperty.call(extensions, ext)) {
proto[ext] = extensions[ext];
}
}
}
return C;
};
return mixin(definition);
}
// ship it
return P;
13: 'Enter',
16: 'Shift',
17: 'Control',
18: 'Alt',
20: 'CapsLock',
27: 'Esc',
32: 'Spacebar',
33: 'PageUp',
34: 'PageDown',
35: 'End',
36: 'Home',
37: 'Left',
38: 'Up',
39: 'Right',
40: 'Down',
45: 'Insert',
46: 'Del',
144: 'NumLock'
};
if (evt.ctrlKey) modifiers.push('Ctrl');
if (evt.originalEvent && evt.originalEvent.metaKey) modifiers.push('Meta');
if (evt.altKey) modifiers.push('Alt');
if (evt.shiftKey) modifiers.push('Shift');
modifiers.push(key);
return modifiers.join('-');
}
function defer(fn) {
timeout = setTimeout(fn);
deferredFn = fn;
}
function flush() {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
deferredFn();
}
}
textarea.val(text);
if (text) textarea[0].select();
}
function popText(callback) {
var text = textarea.val();
textarea.val('');
if (text) callback(text);
}
function handleKey() {
keyCallback(stringify(keydown), keydown);
}
handleKey();
}
function onKeypress(e) {
// call the key handler for repeated keypresses.
// This excludes keypresses that happen directly
// after keydown. In that case, there will be
// no previous keypress, so we skip it here
if (keydown && keypress) handleKey();
keypress = e;
defer(function() {
// If there is a selection, the contents of the textarea couldn't
// possibly have just been typed in.
// This happens in browsers like Firefox and Opera that fire
// keypress for keystrokes that are not text entry and leave the
// selection in the textarea alone, such as Ctrl-C.
// Note: we assume that browsers that don't support hasSelection()
// also never fire keypress on keystrokes that are not text entry.
// This seems reasonably safe because:
// - all modern browsers including IE 9+ support hasSelection(),
// making it extremely unlikely any browser besides IE < 9 won't
// - as far as we know IE < 9 never fires keypress on keystrokes
// that aren't text entry, which is only as reliable as our
// tests are comprehensive, but the IE < 9 way to do
// hasSelection() is poorly documented and is also only as
// reliable as our tests are comprehensive
// If anything like #40 or #71 is reported in IE < 9, see
// b1318e5349160b665003e36d4eedd64101ceacd8
if (hasSelection()) return;
popText(textCallback);
});
}
function onPaste(e) {
// browsers are dumb.
//
// In Linux, middle-click pasting causes onPaste to be called,
// when the textarea is not necessarily focused. We focus it
// here to ensure that the pasted text actually ends up in the
// textarea.
//
// It's pretty nifty that by changing focus in this handler,
// we can change the target of the default action. (This works
// on keydown too, FWIW).
//
// And by nifty, we mean dumb (but useful sometimes).
textarea.focus();
defer(function() {
popText(pasteCallback);
});
}
_.parse = function(stream) {
return this.skip(eof)._(stream, success, parseError);
function failure(newStream) {
return alternative._(stream, onSuccess, onFailure);
}
});
};
_.then = function(next) {
var self = this;
function success(newStream, x) {
stream = newStream;
xs.push(x);
return true;
}
function failure() {
return false;
}
});
};
function success(newStream, x) {
xs.push(x);
stream = newStream;
return true;
}
_.map = function(fn) {
return this.then(function(result) { return succeed(fn(result)); });
};
_.skip = function(two) {
return this.then(function(result) { return two.result(result); });
};
if (match) {
var result = match[0];
return onSuccess(stream.slice(result.length), result);
}
else {
return onFailure(stream, expected);
}
});
};
/**
* MathQuill virtual-DOM tree-node abstract base class
*/
var Node = P(function(_) {
_.prev = 0;
_.next = 0;
_.parent = 0;
_.firstChild = 0;
_.lastChild = 0;
_.children = function() {
return Fragment(this.firstChild, this.lastChild);
};
_.eachChild = function(fn) {
return this.children().each(fn);
};
_.disown = function() {
Fragment(this, this).disown();
return this;
};
});
/**
* An entity outside the virtual tree with one-way pointers (so it's only a
* "view" of part of the tree, not an actual node/entity in the tree) that
* delimits a doubly-linked list of sibling nodes.
* It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
* classes: like DocumentFragment, its contents must be sibling nodes
* (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
* but like Range, it has only one-way pointers to its contents, its contents
* have no reference to it and in fact may still be in the visible tree (unlike
* DocumentFragment, whose contents must be detached from the visible tree
* and have their 'parent' pointers set to the DocumentFragment).
*/
var Fragment = P(function(_) {
_.first = 0;
_.last = 0;
_.init = function(first, last) {
pray('no half-empty fragments', !first === !last);
if (!first) return;
this.first = first;
this.last = last;
};
// or it's there and its next and parent are properly set up
return prev.next === next && prev.parent === parent;
})());
// or it's there and its next and parent are properly set up
return next.prev === prev && next.parent === parent;
})());
}
if (prev) {
// NB: this is handled in the ::each() block
// prev.next = first
} else {
parent.firstChild = first;
}
if (next) {
next.prev = last;
} else {
parent.lastChild = last;
}
self.last.next = next;
self.each(function(el) {
el.prev = prev;
el.parent = parent;
if (prev) prev.next = el;
prev = el;
});
return self;
};
_.disown = function() {
var self = this;
var first = self.first;
self.disowned = true;
if (first.prev) {
first.prev.next = last.next;
} else {
parent.firstChild = last.next;
}
if (last.next) {
last.next.prev = first.prev;
} else {
parent.lastChild = first.prev;
}
return self;
};
_.each = function(fn) {
var self = this;
var el = self.first;
if (!el) return self;
return self;
};
return fold;
};
});
/*************************************************
* Abstract classes of math blocks and commands.
************************************************/
/**
* Math tree node base class.
* Some math-tree-specific extensions to Node.
* Both MathBlock's and MathCommand's descend from it.
*/
var MathElement = P(Node, function(_) {
_.init = function(obj) {
this.id = uuid();
MathElement[this.id] = this;
};
_.toString = function() {
return '[MathElement '+this.id+']';
};
return this;
};
(function recurse(desc) {
desc.eachChild(recurse);
fn(desc);
})(this);
};
_.jQ = $();
_.jQadd = function(jQ) { this.jQ = this.jQ.add(jQ); };
this.jQize = function(html) {
// Sets the .jQ of the entire math subtree rooted at this command.
// Expects .createBlocks() to have been called already, since it
// calls .html().
var jQ = $(html);
jQ.find('*').andSelf().each(function() {
var jQ = $(this),
cmdId = jQ.attr('mathquill-command-id'),
blockId = jQ.attr('mathquill-block-id');
if (cmdId) MathElement[cmdId].jQadd(jQ);
if (blockId) MathElement[blockId].jQadd(jQ);
});
return jQ;
};
_.finalizeInsert = function() {
var self = this;
self.postOrder('finalizeTree');
self.postOrder('redraw');
self.bubble('redraw');
};
});
/**
* Commands and operators, like subscripts, exponents, or fractions.
* Descendant commands are organized into blocks.
*/
var MathCommand = P(MathElement, function(_, _super) {
_.init = function(ctrlSeq, htmlTemplate, textTemplate) {
var cmd = this;
_super.init.call(cmd);
// obvious methods
_.replaces = function(replacedFragment) {
replacedFragment.disown();
this.replacedFragment = replacedFragment;
};
_.isEmpty = function() {
return this.foldChildren(true, function(isEmpty, child) {
return isEmpty && child.isEmpty();
});
};
_.parser = function() {
var block = latexMathParser.block;
var self = this;
return block.times(self.numBlocks()).map(function(blocks) {
self.blocks = blocks;
return self;
});
};
cmd.createBlocks();
MathElement.jQize(cmd.html());
if (replacedFragment) {
replacedFragment.adopt(cmd.firstChild, 0, 0);
replacedFragment.jQ.appendTo(cmd.firstChild.jQ);
}
cursor.jQ.before(cmd.jQ);
cursor.prev = cmd.adopt(cursor.parent, cursor.prev, cursor.next);
cmd.finalizeInsert(cursor);
cmd.placeCursor(cursor);
};
_.createBlocks = function() {
var cmd = this,
numBlocks = cmd.numBlocks(),
blocks = cmd.blocks = Array(numBlocks);
// remove()
_.remove = function() {
this.disown()
this.jQ.remove();
return this;
};
Requirements:
- For each block of the command, there must be exactly one "block content
marker" of the form '&<number>' where <number> is the 0-based index of the
block. (Like the LaTeX \newcommand syntax, but with a 0-based rather than
1-based index, because JavaScript because C because Dijkstra.)
- The block content marker must be the sole contents of the containing
element, there can't even be surrounding whitespace, or else we can't
guarantee sticking to within the bounds of the block content marker when
mucking with the HTML DOM.
- The HTML not only must be well-formed HTML (of course), but also must
conform to the XHTML requirements on tags, specifically all tags must
either be self-closing (like '<br/>') or come in matching pairs.
Close tags are never optional.
Note that &<number> isn't well-formed HTML; if you wanted a literal '&123',
your HTML template would have to have '&123'.
*/
_.numBlocks = function() {
var matches = this.htmlTemplate.match(/&\d+/g);
return matches ? matches.length : 0;
};
_.html = function() {
// Render the entire math subtree rooted at this command, as HTML.
// Expects .createBlocks() to have been called already, since it uses the
// .blocks array of child blocks.
//
// See html.test.js for example templates and intended outputs.
//
// Given an .htmlTemplate as described above,
// - insert the mathquill-command-id attribute into all top-level tags,
// which will be used to set this.jQ in .jQize().
// This is straightforward:
// * tokenize into tags and non-tags
// * loop through top-level tokens:
// * add #cmdId attribute macro to top-level self-closing tags
// * else add #cmdId attribute macro to top-level open tags
// * skip the matching top-level close tag and all tag pairs
// in between
// - for each block content marker,
// + replace it with the contents of the corresponding block,
// rendered as HTML
// + insert the mathquill-block-id attribute into the containing tag
// This is even easier, a quick regex replace, since block tags cannot
// contain anything besides the block content marker.
//
// Two notes:
// - The outermost loop through top-level tokens should never encounter any
// top-level close tags, because we should have first encountered a
// matching top-level open tag, all inner tags should have appeared in
// matching pairs and been skipped, and then we should have skipped the
// close tag in question.
// - All open tags should have matching close tags, which means our inner
// loop should always encounter a close tag and drop nesting to 0. If
// a close tag is missing, the loop will continue until i >= tokens.length
// and token becomes undefined. This will not infinite loop, even in
// production without pray(), because it will then TypeError on .slice().
// skip matching top-level close tag and all tag pairs in between
var nesting = 1;
do {
i += 1, token = tokens[i];
pray('no missing close tags', token);
// close tags
if (token.slice(0,2) === '</') {
nesting -= 1;
}
// non-self-closing open tags
else if (token.charAt(0) === '<' && token.slice(-2) !== '/>') {
nesting += 1;
}
} while (nesting > 0);
}
}
return tokens.join('').replace(/>&(\d+)/g, function($0, $1) {
return ' mathquill-block-id=' + blocks[$1].id + '>' +
blocks[$1].join('html');
});
};
/**
* Lightweight command without blocks or children.
*/
var Symbol = P(MathCommand, function(_, _super) {
_.init = function(ctrlSeq, html, text) {
if (!text) text = ctrlSeq && ctrlSeq.length > 1 ? ctrlSeq.slice(1) : ctrlSeq;
_.replaces = function(replacedFragment) {
replacedFragment.remove();
};
_.createBlocks = noop;
_.latex = function(){ return this.ctrlSeq; };
_.text = function(){ return this.textTemplate; };
_.placeCursor = noop;
_.isEmpty = function(){ return true; };
});
/**
* Children and parent of MathCommand's. Basically partitions all the
* symbols and operators that descend (in the Math DOM tree) from
* ancestor operators.
*/
var MathBlock = P(MathElement, function(_) {
_.join = function(methodName) {
return this.foldChildren('', function(fold, child) {
return fold + child[methodName]();
});
};
_.latex = function() { return this.join('latex'); };
_.text = function() {
return this.firstChild === this.lastChild ?
this.firstChild.text() :
'(' + this.join('text') + ')'
;
};
_.isEmpty = function() {
return this.firstChild === 0 && this.lastChild === 0;
};
_.focus = function() {
this.jQ.addClass('hasCursor');
this.jQ.removeClass('empty');
return this;
};
_.blur = function() {
this.jQ.removeClass('hasCursor');
if (this.isEmpty())
this.jQ.addClass('empty');
return this;
};
});
/**
* Math tree fragment base class.
* Some math-tree-specific extensions to Fragment.
*/
var MathFragment = P(Fragment, function(_, _super) {
_.init = function(first, last) {
// just select one thing if only one argument
_super.init.call(this, first, last || first);
this.jQ = this.fold($(), function(jQ, child){ return child.jQ.add(jQ); });
};
_.latex = function() {
return this.fold('', function(latex, el){ return latex + el.latex(); });
};
_.remove = function() {
this.jQ.remove();
this.each(function(el) {
el.postOrder(function(desc) {
delete MathElement[desc.id];
});
});
return this.disown();
};
});
/*********************************************
* Root math elements with event delegation.
********************************************/
if (!textbox) {
jQ.addClass('mathquill-rendered-math');
}
root.renderLatex(contents.text());
//textarea stuff
var textareaSpan = root.textarea = $('<span
class="textarea"><textarea></textarea></span>'),
textarea = textareaSpan.children();
/******
* TODO [Han]: Document this
*/
var textareaSelectionTimeout;
root.selectionChanged = function() {
if (textareaSelectionTimeout === undefined) {
textareaSelectionTimeout = setTimeout(setTextareaSelection);
}
forceIERedraw(jQ[0]);
};
function setTextareaSelection() {
textareaSelectionTimeout = undefined;
var latex = cursor.selection ? '$'+cursor.selection.latex()+'$' : '';
textareaManager.select(latex);
}
return false;
}
return mousemove(e);
}
function mouseup(e) {
anticursor = undefined;
cursor.blink = blink;
if (!cursor.selection) {
if (editable) {
cursor.show();
}
else {
textareaSpan.detach();
}
}
// delete the mouse handlers now that we're not dragging anymore
jQ.unbind('mousemove', mousemove);
$(e.target.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup',
mouseup);
}
cursor.blink = noop;
cursor.seek($(e.target), e.pageX, e.pageY);
if (!editable) jQ.prepend(textareaSpan);
jQ.mousemove(mousemove);
$(e.target.ownerDocument).mousemove(docmousemove).mouseup(mouseup);
return false;
});
if (!editable) {
var textareaManager = manageTextarea(textarea, { container: jQ });
jQ.bind('cut paste', false).bind('copy', setTextareaSelection)
.prepend('<span class="selectable">$'+root.latex()+'$</span>');
textarea.blur(function() {
cursor.clearSelection();
setTimeout(detach); //detaching during blur explodes in WebKit
});
function detach() {
textareaSpan.detach();
}
return;
}
e.stopPropagation();
},
paste: function(text) {
// FIXME HACK the parser in RootTextBlock needs to be moved to
// Cursor::writeLatex or something so this'll work with
// MathQuill textboxes
if (text.slice(0,1) === '$' && text.slice(-1) === '$') {
text = text.slice(1, -1);
}
else {
text = '\\text{' + text + '}';
}
cursor.writeLatex(text).show();
}
});
jQ.prepend(textareaSpan);
jQ.children().slice(1).remove();
this.firstChild = this.lastChild = 0;
this.cursor.appendTo(this).writeLatex(latex);
};
_.onKey = function(key, e) {
switch (key) {
case 'Ctrl-Shift-Backspace':
case 'Ctrl-Backspace':
while (this.cursor.prev || this.cursor.selection) {
this.cursor.backspace();
}
break;
case 'Shift-Backspace':
case 'Backspace':
this.cursor.backspace();
break;
// Tab or Esc -> go one block right if it exists, else escape right.
case 'Esc':
case 'Tab':
case 'Spacebar':
var parent = this.cursor.parent;
// cursor is in root editable, continue default
if (parent === this.cursor.root) {
if (key === 'Spacebar') e.preventDefault();
return;
}
this.cursor.prepareMove();
if (parent.next) {
// go one block right
this.cursor.prependTo(parent.next);
} else {
// get out of the block
this.cursor.insertAfter(parent.parent);
}
break;
this.cursor.prepareMove();
if (parent.prev) {
// go one block left
this.cursor.appendTo(parent.prev);
} else {
//get out of the block
this.cursor.insertBefore(parent.parent);
}
break;
// Ctrl-End -> move all the way to the end of the root block.
case 'Ctrl-End':
this.cursor.prepareMove().appendTo(this);
break;
// Home -> move to the start of the root block or the current block.
case 'Home':
this.cursor.prepareMove().prependTo(this.cursor.parent);
break;
case 'Shift-Up':
if (this.cursor.prev) {
while (this.cursor.prev) this.cursor.selectLeft();
} else {
this.cursor.selectLeft();
}
case 'Shift-Down':
if (this.cursor.next) {
while (this.cursor.next) this.cursor.selectRight();
}
else {
this.cursor.selectRight();
}
case 'Ctrl-Shift-Del':
case 'Ctrl-Del':
while (this.cursor.next || this.cursor.selection) {
this.cursor.deleteForward();
}
break;
case 'Shift-Del':
case 'Del':
this.cursor.deleteForward();
break;
case 'Meta-A':
case 'Ctrl-A':
//so not stopPropagation'd at RootMathCommand
if (this !== this.cursor.root) return;
this.cursor.prepareMove().appendTo(this);
while (this.cursor.prev) this.cursor.selectLeft();
break;
default:
return false;
}
e.preventDefault();
return false;
};
_.onText = function(ch) {
this.cursor.write(ch);
return false;
};
});
this.blocks = [ this.firstChild ];
this.firstChild.parent = this;
return false;
};
};
_.latex = function() {
return '$' + this.firstChild.latex() + '$';
};
});
// Parser RootMathCommand
var mathMode = string('$').then(latexMathParser)
// because TeX is insane, math mode doesn't necessarily
// have to end. So we allow for the case that math mode
// continues to the end of the stream.
.skip(string('$').or(eof))
.map(function(block) {
// HACK FIXME: this shouldn't have to have access to cursor
var rootMathCommand = RootMathCommand(cursor);
rootMathCommand.createBlocks();
var rootMathBlock = rootMathCommand.firstChild;
block.children().adopt(rootMathBlock, 0, 0);
return rootMathCommand;
})
;
if (commands) {
for (var i = 0; i < commands.length; i += 1) {
commands[i].adopt(self, self.lastChild, 0);
}
this.finalizeInsert();
}
};
_.onKey = RootMathBlock.prototype.onKey;
_.onText = function(ch) {
this.cursor.prepareEdit();
if (ch === '$')
this.cursor.insertNew(RootMathCommand(this.cursor));
else
this.cursor.insertNew(VanillaSymbol(ch));
return false;
};
});
/***************************
* Commands and Operators.
**************************/
var CharCmds = {}, LatexCmds = {}; //single character commands, LaTeX commands
forceIERedraw = noop,
div = document.createElement('div'),
div_style = div.style,
transformPropNames = {
transform:1,
WebkitTransform:1,
MozTransform:1,
OTransform:1,
msTransform:1
},
transformPropName;
if (transformPropName) {
scale = function(jQ, x, y) {
jQ.css(transformPropName, 'scale('+x+','+y+')');
};
}
else if ('filter' in div_style) { //IE 6, 7, & 8 fallback, see
https://github.com/laughinghan/mathquill/wiki/Transforms
forceIERedraw = function(el){ el.className = el.className; };
scale = function(jQ, x, y) { //NOTE: assumes y > x
x /= (1+(y-1)/2);
jQ.css('fontSize', y + 'em');
if (!jQ.hasClass('matrixed-container')) {
jQ.addClass('matrixed-container')
.wrapInner('<span class="matrixed"></span>');
}
var innerjQ = jQ.children()
.css('filter', 'progid:DXImageTransform.Microsoft'
+ '.Matrix(M11=' + x + ",SizingMethod='auto expand')"
);
function calculateMarginRight() {
jQ.css('marginRight', (innerjQ.width()-1)*(x-1)/x + 'px');
}
calculateMarginRight();
var intervalId = setInterval(calculateMarginRight);
$(window).load(function() {
clearTimeout(intervalId);
calculateMarginRight();
});
};
}
else {
scale = function(jQ, x, y) {
jQ.css('fontSize', y + 'em');
};
}
//fonts
LatexCmds.mathrm = bind(Style, '\\mathrm', 'span', 'class="roman font"');
LatexCmds.mathit = bind(Style, '\\mathit', 'i', 'class="font"');
LatexCmds.mathbf = bind(Style, '\\mathbf', 'b', 'class="font"');
LatexCmds.mathsf = bind(Style, '\\mathsf', 'span', 'class="sans-serif font"');
LatexCmds.mathtt = bind(Style, '\\mathtt', 'span', 'class="monospace font"');
//text-decoration
LatexCmds.underline = bind(Style, '\\underline', 'span', 'class="non-leaf
underline"');
LatexCmds.overline = LatexCmds.bar = bind(Style, '\\overline', 'span', 'class="non-
leaf overline"');
LatexCmds.overleftrightarrow = LatexCmds.bar = bind(Style, '\\overleftrightarrow',
'span', 'class="non-leaf overleftrightarrow"');
LatexCmds.overrightarrow = LatexCmds.bar = bind(Style, '\\overrightarrow', 'span',
'class="non-leaf overrightarrow"');
return this;
};
});
LatexCmds.subscript =
LatexCmds._ = bind(SupSub, '_', 'sub', '_');
LatexCmds.superscript =
LatexCmds.supscript =
LatexCmds['^'] = bind(SupSub, '^', 'sup', '**');
var Fraction =
LatexCmds.frac =
LatexCmds.dfrac =
LatexCmds.cfrac =
LatexCmds.fraction = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\frac';
_.htmlTemplate =
'<span class="fraction non-leaf">'
+ '<span class="numerator">&0</span>'
+ '<span class="denominator">&1</span>'
+ '<span style="display:inline-block;width:0"> </span>'
+ '</span>'
;
_.textTemplate = ['(', '/', ')'];
_.finalizeTree = function() {
this.up = this.lastChild.up = this.firstChild;
this.down = this.firstChild.down = this.lastChild;
};
});
var LiveFraction =
LatexCmds.over =
CharCmds['/'] = P(Fraction, function(_, _super) {
_.createBefore = function(cursor) {
if (!this.replacedFragment) {
var prev = cursor.prev;
while (prev &&
!(
prev instanceof BinaryOperator ||
prev instanceof TextBlock ||
prev instanceof BigSymbol
) //lookbehind for operator
)
prev = prev.prev;
var SquareRoot =
LatexCmds.sqrt =
LatexCmds['√'] = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\sqrt';
_.htmlTemplate =
'<span class="non-leaf">'
+ '<span class="scaled sqrt-prefix">√</span>'
+ '<span class="non-leaf sqrt-stem">&0</span>'
+ '</span>'
;
_.textTemplate = ['sqrt(', ')'];
_.parser = function() {
return latexMathParser.optBlock.then(function(optBlock) {
return latexMathParser.block.map(function(block) {
var nthroot = NthRoot();
nthroot.blocks = [ optBlock, block ];
optBlock.adopt(nthroot, 0, 0);
block.adopt(nthroot, optBlock, 0);
return nthroot;
});
}).or(_super.parser.call(this));
};
_.redraw = function() {
var block = this.lastChild.jQ;
scale(block.prev(), 1, block.innerHeight()/+block.css('fontSize').slice(0,-2) -
.1);
};
});
var NthRoot =
LatexCmds.nthroot = P(SquareRoot, function(_, _super) {
_.htmlTemplate =
'<sup class="nthroot non-leaf">&0</sup>'
+ '<span class="scaled">'
+ '<span class="sqrt-prefix scaled">√</span>'
+ '<span class="sqrt-stem non-leaf">&1</span>'
+ '</span>'
;
_.textTemplate = ['sqrt[', '](', ')'];
_.latex = function() {
return '\\sqrt['+this.firstChild.latex()+']{'+this.lastChild.latex()+'}';
};
});
return optWhitespace.then(regex(/^(?:[([|]|\\\{)/))
.then(function(open) {
if (open.charAt(0) === '\\') open = open.slice(1);
return latexMathParser
.map(function (block) {
cmd.blocks = [ block ];
block.adopt(cmd, 0, 0);
})
.then(string('\\right'))
.skip(optWhitespace)
.then(regex(/^(?:[\])|]|\\\})/))
.then(function(close) {
if (close.slice(-1) !== cmd.end.slice(-1)) {
return Parser.fail('open doesn\'t match close');
}
return succeed(cmd);
})
;
})
;
};
});
LatexCmds.lbrace =
CharCmds['{'] = bind(Bracket, '{', '}', '\\{', '\\}');
LatexCmds.langle =
LatexCmds.lang = bind(Bracket, '⟨','⟩','\\langle ','\\rangle ');
LatexCmds.rbrace =
CharCmds['}'] = bind(CloseBracket, '{','}','\\{','\\}');
LatexCmds.rangle =
LatexCmds.rang = bind(CloseBracket, '⟨','⟩','\\langle ','\\rangle ');
LatexCmds.lparen =
CharCmds['('] = bind(Paren, '(', ')');
LatexCmds.lbrack =
LatexCmds.lbracket =
CharCmds['['] = bind(Paren, '[', ']');
LatexCmds.rparen =
CharCmds[')'] = bind(CloseParen, '(', ')');
LatexCmds.rbrack =
LatexCmds.rbracket =
CharCmds[']'] = bind(CloseParen, '[', ']');
var Pipes =
LatexCmds.lpipe =
LatexCmds.rpipe =
CharCmds['|'] = P(Paren, function(_, _super) {
_.init = function() {
_super.init.call(this, '|', '|');
}
_.createBefore = CloseBracket.prototype.createBefore;
});
var TextBlock =
CharCmds.$ =
LatexCmds.text =
LatexCmds.textnormal =
LatexCmds.textrm =
LatexCmds.textup =
LatexCmds.textmd = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\text';
_.htmlTemplate = '<span class="text">&0</span>';
_.replaces = function(replacedText) {
if (replacedText instanceof MathFragment)
this.replacedText = replacedText.remove().jQ.text();
else if (typeof replacedText === 'string')
this.replacedText = replacedText;
};
_.textTemplate = ['"', '"'];
_.parser = function() {
// TODO: correctly parse text mode
var string = Parser.string;
var regex = Parser.regex;
var optWhitespace = Parser.optWhitespace;
return optWhitespace
.then(string('{')).then(regex(/^[^}]*/)).skip(string('}'))
.map(function(text) {
var cmd = TextBlock();
cmd.createBlocks();
var block = cmd.firstChild;
for (var i = 0; i < text.length; i += 1) {
var ch = VanillaSymbol(text.charAt(i));
ch.adopt(block, block.lastChild, 0);
}
return cmd;
})
;
};
_.createBlocks = function() {
//FIXME: another possible Law of Demeter violation, but this seems much
cleaner, like it was supposed to be done this way
this.firstChild =
this.lastChild =
InnerTextBlock();
this.blocks = [ this.firstChild ];
this.firstChild.parent = this;
};
_.finalizeInsert = function() {
//FIXME HACK blur removes the TextBlock
this.firstChild.blur = function() { delete this.blur; return this; };
_super.finalizeInsert.call(this);
};
_.createBefore = function(cursor) {
_super.createBefore.call(this, this.cursor = cursor);
if (this.replacedText)
for (var i = 0; i < this.replacedText.length; i += 1)
this.write(this.replacedText.charAt(i));
};
_.write = function(ch) {
this.cursor.insertNew(VanillaSymbol(ch));
};
_.onKey = function(key, e) {
//backspace and delete and ends of block don't unwrap
if (!this.cursor.selection &&
(
(key === 'Backspace' && !this.cursor.prev) ||
(key === 'Del' && !this.cursor.next)
)
) {
if (this.isEmpty())
this.cursor.insertAfter(this);
return false;
}
};
_.onText = function(ch) {
this.cursor.prepareEdit();
if (ch !== '$')
this.write(ch);
else if (this.isEmpty())
this.cursor.insertAfter(this).backspace().insertNew(VanillaSymbol('\\
$','$'));
else if (!this.cursor.next)
this.cursor.insertAfter(this);
else if (!this.cursor.prev)
this.cursor.insertBefore(this);
else { //split apart
var next = TextBlock(MathFragment(this.cursor.next,
this.firstChild.lastChild));
next.placeCursor = function(cursor) { //FIXME HACK: pretend no prev so they
don't get merged
this.prev = 0;
delete this.placeCursor;
this.placeCursor(cursor);
};
next.firstChild.focus = function(){ return this; };
this.cursor.insertAfter(this).insertNew(next);
next.prev = this;
this.cursor.insertBefore(next);
delete next.firstChild.focus;
}
return false;
};
});
next.eachChild(function(child){
child.parent = innerblock;
child.jQ.appendTo(innerblock.jQ);
});
if (this.lastChild)
this.lastChild.next = next.firstChild;
else
this.firstChild = next.firstChild;
next.firstChild.prev = this.lastChild;
this.lastChild = next.lastChild;
next.parent.remove();
if (cursor.prev)
cursor.insertAfter(cursor.prev);
else
cursor.prependTo(this);
cursor.parent.bubble('redraw');
}
else if (textblock.prev.ctrlSeq === textblock.ctrlSeq) {
var cursor = textblock.cursor;
if (cursor.prev)
textblock.prev.firstChild.focus();
else
cursor.appendTo(textblock.prev.firstChild);
}
return this;
};
});
return this;
};
this.firstChild.blur = function() {
this.parent.jQ.removeClass('hasCursor');
if (this.isEmpty())
this.parent.jQ.addClass('empty');
return this;
};
};
_.createBefore = function(cursor) {
_super.createBefore.call(this, cursor);
this.cursor = cursor.appendTo(this.firstChild);
if (this._replacedFragment) {
var el = this.jQ[0];
this.jQ =
this._replacedFragment.jQ.addClass('blur').bind(
'mousedown mousemove', //FIXME: is monkey-patching the mousedown and
mousemove handlers the right way to do this?
function(e) {
$(e.target = el).trigger(e);
return false;
}
).insertBefore(this.jQ).add(this.jQ);
}
};
_.latex = function() {
return '\\' + this.firstChild.latex() + ' ';
};
_.onKey = function(key, e) {
if (key === 'Tab' || key === 'Enter' || key === 'Spacebar') {
this.renderCommand();
e.preventDefault();
return false;
}
};
_.onText = function(ch) {
if (ch.match(/[a-z]/i)) {
this.cursor.prepareEdit();
this.cursor.insertNew(VanillaSymbol(ch));
return false;
}
this.renderCommand();
if (ch === '\\' && this.firstChild.isEmpty())
return false;
};
_.renderCommand = function() {
this.jQ = this.jQ.last();
this.remove();
if (this.next) {
this.cursor.insertBefore(this.next);
} else {
this.cursor.appendTo(this.parent);
}
var Binomial =
LatexCmds.binom =
LatexCmds.binomial = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\binom';
_.htmlTemplate =
'<span class="paren scaled">(</span>'
+ '<span class="non-leaf">'
+ '<span class="array non-leaf">'
+ '<span>&0</span>'
+ '<span>&1</span>'
+ '</span>'
+ '</span>'
+ '<span class="paren scaled">)</span>'
;
_.textTemplate = ['choose(',',',')'];
_.redraw = function() {
var blockjQ = this.jQ.eq(1);
var Choose =
LatexCmds.choose = P(Binomial, function(_) {
_.createBefore = LiveFraction.prototype.createBefore;
});
var Vector =
LatexCmds.vector = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\vector';
_.htmlTemplate = '<span class="array"><span>&0</span></span>';
_.latex = function() {
return '\\begin{matrix}' + this.foldChildren([], function(latex, child) {
latex.push(child.latex());
return latex;
}).join('\\\\') + '\\end{matrix}';
};
_.text = function() {
return '[' + this.foldChildren([], function(text, child) {
text.push(child.text());
return text;
}).join() + ']';
}
_.createBefore = function(cursor) {
_super.createBefore.call(this, this.cursor = cursor);
};
_.onKey = function(key, e) {
var currentBlock = this.cursor.parent;
newBlock.next = currentBlock.next;
currentBlock.next = newBlock;
newBlock.prev = currentBlock;
this.bubble('redraw').cursor.appendTo(newBlock);
e.preventDefault();
return false;
}
else if (key === 'Tab' && !currentBlock.next) {
if (currentBlock.isEmpty()) {
if (currentBlock.prev) {
this.cursor.insertAfter(this);
delete currentBlock.prev.next;
this.lastChild = currentBlock.prev;
currentBlock.jQ.remove();
this.bubble('redraw');
e.preventDefault();
return false;
}
else
return;
}
e.preventDefault();
return false;
}
else if (e.which === 8) { //backspace
if (currentBlock.isEmpty()) {
if (currentBlock.prev) {
this.cursor.appendTo(currentBlock.prev)
currentBlock.prev.next = currentBlock.next;
}
else {
this.cursor.insertBefore(this);
this.firstChild = currentBlock.next;
}
if (currentBlock.next)
currentBlock.next.prev = currentBlock.prev;
else
this.lastChild = currentBlock.prev;
currentBlock.jQ.remove();
if (this.isEmpty())
this.cursor.deleteForward();
else
this.bubble('redraw');
e.preventDefault();
return false;
}
else if (!this.cursor.prev) {
e.preventDefault();
return false;
}
}
}
};
});
_.jQadd = function() {
var self = this;
// FIXME: this entire method is a giant hack to get around
// having to call createBlocks, and createRoot expecting to
// render the contents' LaTeX. Both need to be refactored.
_super.jQadd.apply(self, arguments);
var block = self.firstChild.disown();
var blockjQ = self.jQ.children().detach();
self.firstChild =
self.lastChild =
RootMathBlock();
self.blocks = [ self.firstChild ];
self.firstChild.parent = self;
block.children().adopt(self.firstChild, 0, 0);
blockjQ.appendTo(self.firstChild.jQ);
self.firstChild.cursor.appendTo(self.firstChild);
};
LatexCmds['@'] = NonSymbolaSymbol;
LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&');
LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
//the following are all Greek to me, but this helped a lot:
http://www.ams.org/STIX/ion/stixsig03.html
LatexCmds.kappav = //Elsevier
LatexCmds.varkappa = //AMS and LaTeX
bind(Variable,'\\varkappa ','ϰ');
LatexCmds.Upsilon = //LaTeX
LatexCmds.Upsi = //Elsevier and 9573-13
LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
LatexCmds.Upsih = //'cos it makes sense to me
bind(Symbol,'\\Upsilon ','<var style="font-family: serif">ϒ</var>');
//Symbola's 'upsilon with a hook' is a capital Y without hooks :(
//other symbols with the same LaTeX command and HTML character entity reference
LatexCmds.Gamma =
LatexCmds.Delta =
LatexCmds.Theta =
LatexCmds.Lambda =
LatexCmds.Xi =
LatexCmds.Pi =
LatexCmds.Sigma =
LatexCmds.Phi =
LatexCmds.Psi =
LatexCmds.Omega =
LatexCmds.forall = P(VanillaSymbol, function(_, _super) {
_.init = function(latex) {
_super.init.call(this,'\\'+latex+' ','&'+latex+';');
};
});
// symbols that aren't a single MathCommand, but are instead a whole
// Fragment. Creates the Fragment from a LaTeX string
var LatexFragment = P(MathCommand, function(_) {
_.init = function(latex) { this.latex = latex; };
_.createBefore = function(cursor) { cursor.writeLatex(this.latex); };
_.parser = function() {
var frag = latexMathParser.parse(this.latex).children();
return Parser.succeed(frag);
};
});
_.respace = function() {
if (!this.prev) {
this.jQ[0].className = '';
}
else if (
this.prev instanceof BinaryOperator &&
this.next && !(this.next instanceof BinaryOperator)
) {
this.jQ[0].className = 'unary-operator';
}
else {
this.jQ[0].className = 'binary-operator';
}
return this;
};
});
LatexCmds.notin =
LatexCmds.sim =
LatexCmds.cong =
LatexCmds.equiv =
LatexCmds.oplus =
LatexCmds.otimes = P(BinaryOperator, function(_, _super) {
_.init = function(latex) {
_super.init.call(this, '\\'+latex+' ', '&'+latex+';');
};
});
LatexCmds.cuz = // l33t
LatexCmds.because = bind(BinaryOperator,'\\because ','∵');
LatexCmds.lt = bind(BinaryOperator,'<','<');
LatexCmds.gt = bind(BinaryOperator,'>','>');
LatexCmds.nsub = LatexCmds.notsub =
LatexCmds.nsubset = LatexCmds.notsubset =
bind(BinaryOperator,'\\not\\subset ','⊄');
LatexCmds.nsup = LatexCmds.notsup =
LatexCmds.nsupset = LatexCmds.notsupset =
LatexCmds.nsuperset = LatexCmds.notsuperset =
bind(BinaryOperator,'\\not\\supset ','⊅');
LatexCmds.supe = LatexCmds.supeq =
LatexCmds.supsete = LatexCmds.supseteq =
LatexCmds.supersete = LatexCmds.superseteq =
bind(BinaryOperator,'\\supseteq ','⊇');
LatexCmds.nsube = LatexCmds.nsubeq =
LatexCmds.notsube = LatexCmds.notsubeq =
LatexCmds.nsubsete = LatexCmds.nsubseteq =
LatexCmds.notsubsete = LatexCmds.notsubseteq =
bind(BinaryOperator,'\\not\\subseteq ','⊈');
LatexCmds.nsupe = LatexCmds.nsupeq =
LatexCmds.notsupe = LatexCmds.notsupeq =
LatexCmds.nsupsete = LatexCmds.nsupseteq =
LatexCmds.notsupsete = LatexCmds.notsupseteq =
LatexCmds.nsupersete = LatexCmds.nsuperseteq =
LatexCmds.notsupersete = LatexCmds.notsuperseteq =
bind(BinaryOperator,'\\not\\supseteq ','⊉');
// LatexCmds.P =
LatexCmds.primes = LatexCmds.Primes =
LatexCmds.projective = LatexCmds.Projective =
LatexCmds.probability = LatexCmds.Probability =
bind(VanillaSymbol,'\\mathbb{P}','ℙ');
// LatexCmds.Z =
LatexCmds.integers = LatexCmds.Integers =
bind(VanillaSymbol,'\\mathbb{Z}','ℤ');
// LatexCmds.Q =
LatexCmds.rationals = LatexCmds.Rationals =
bind(VanillaSymbol,'\\mathbb{Q}','ℚ');
// LatexCmds.R =
LatexCmds.reals = LatexCmds.Reals =
bind(VanillaSymbol,'\\mathbb{R}','ℝ');
// LatexCmds.C =
LatexCmds.complex = LatexCmds.Complex =
LatexCmds.complexes = LatexCmds.Complexes =
LatexCmds.complexplane = LatexCmds.Complexplane = LatexCmds.ComplexPlane =
bind(VanillaSymbol,'\\mathbb{C}','ℂ');
// LatexCmds.H =
LatexCmds.Hamiltonian = LatexCmds.quaternions = LatexCmds.Quaternions =
bind(VanillaSymbol,'\\mathbb{H}','ℍ');
//spacing
LatexCmds.quad = LatexCmds.emsp = bind(VanillaSymbol,'\\quad ',' ');
LatexCmds.qquad = bind(VanillaSymbol,'\\qquad ',' ');
/* spacing special characters, gonna have to implement this in
LatexCommandInput::onText somehow
case ',':
return VanillaSymbol('\\, ',' ');
case ':':
return VanillaSymbol('\\: ',' ');
case ';':
return VanillaSymbol('\\; ',' ');
case '!':
return Symbol('\\! ','<span style="margin-right:-.2em"></span>');
*/
//binary operators
LatexCmds.diamond = bind(VanillaSymbol, '\\diamond ', '◇');
LatexCmds.bigtriangleup = bind(VanillaSymbol, '\\bigtriangleup ', '△');
LatexCmds.ominus = bind(VanillaSymbol, '\\ominus ', '⊖');
LatexCmds.uplus = bind(VanillaSymbol, '\\uplus ', '⊎');
LatexCmds.bigtriangledown = bind(VanillaSymbol, '\\bigtriangledown ', '▽');
LatexCmds.sqcap = bind(VanillaSymbol, '\\sqcap ', '⊓');
LatexCmds.lhd = bind(VanillaSymbol, '\\lhd ', '⊲');
LatexCmds.triangleleft = bind(VanillaSymbol, '\\triangleleft ', '⊲');
LatexCmds.sqcup = bind(VanillaSymbol, '\\sqcup ', '⊔');
LatexCmds.rhd = bind(VanillaSymbol, '\\rhd ', '⊳');
LatexCmds.triangleright = bind(VanillaSymbol, '\\triangleright ', '⊳');
LatexCmds.odot = bind(VanillaSymbol, '\\odot ', '⊙');
LatexCmds.bigcirc = bind(VanillaSymbol, '\\bigcirc ', '◯');
LatexCmds.dagger = bind(VanillaSymbol, '\\dagger ', '†');
LatexCmds.ddagger = bind(VanillaSymbol, '\\ddagger ', '‡');
LatexCmds.wr = bind(VanillaSymbol, '\\wr ', '≀');
LatexCmds.amalg = bind(VanillaSymbol, '\\amalg ', '∐');
//relationship symbols
LatexCmds.models = bind(VanillaSymbol, '\\models ', '⊨');
LatexCmds.prec = bind(VanillaSymbol, '\\prec ', '≺');
LatexCmds.succ = bind(VanillaSymbol, '\\succ ', '≻');
LatexCmds.preceq = bind(VanillaSymbol, '\\preceq ', '≼');
LatexCmds.succeq = bind(VanillaSymbol, '\\succeq ', '≽');
LatexCmds.simeq = bind(VanillaSymbol, '\\simeq ', '≃');
LatexCmds.mid = bind(VanillaSymbol, '\\mid ', '∣');
LatexCmds.ll = bind(VanillaSymbol, '\\ll ', '≪');
LatexCmds.gg = bind(VanillaSymbol, '\\gg ', '≫');
LatexCmds.parallel = bind(VanillaSymbol, '\\parallel ', '∥');
LatexCmds.nparallel = bind(VanillaSymbol, '\\nparallel ', '∦');
LatexCmds.bowtie = bind(VanillaSymbol, '\\bowtie ', '⋈');
LatexCmds.sqsubset = bind(VanillaSymbol, '\\sqsubset ', '⊏');
LatexCmds.sqsupset = bind(VanillaSymbol, '\\sqsupset ', '⊐');
LatexCmds.smile = bind(VanillaSymbol, '\\smile ', '⌣');
LatexCmds.sqsubseteq = bind(VanillaSymbol, '\\sqsubseteq ', '⊑');
LatexCmds.sqsupseteq = bind(VanillaSymbol, '\\sqsupseteq ', '⊒');
LatexCmds.doteq = bind(VanillaSymbol, '\\doteq ', '≐');
LatexCmds.frown = bind(VanillaSymbol, '\\frown ', '⌢');
LatexCmds.vdash = bind(VanillaSymbol, '\\vdash ', '⊦');
LatexCmds.dashv = bind(VanillaSymbol, '\\dashv ', '⊣');
//arrows
LatexCmds.longleftarrow = bind(VanillaSymbol, '\\longleftarrow ', '←');
LatexCmds.longrightarrow = bind(VanillaSymbol, '\\longrightarrow ', '→');
LatexCmds.Longleftarrow = bind(VanillaSymbol, '\\Longleftarrow ', '⇐');
LatexCmds.Longrightarrow = bind(VanillaSymbol, '\\Longrightarrow ', '⇒');
LatexCmds.longleftrightarrow = bind(VanillaSymbol, '\\longleftrightarrow ',
'↔');
LatexCmds.updownarrow = bind(VanillaSymbol, '\\updownarrow ', '↕');
LatexCmds.Longleftrightarrow = bind(VanillaSymbol, '\\Longleftrightarrow ',
'⇔');
LatexCmds.Updownarrow = bind(VanillaSymbol, '\\Updownarrow ', '⇕');
LatexCmds.mapsto = bind(VanillaSymbol, '\\mapsto ', '↦');
LatexCmds.nearrow = bind(VanillaSymbol, '\\nearrow ', '↗');
LatexCmds.hookleftarrow = bind(VanillaSymbol, '\\hookleftarrow ', '↩');
LatexCmds.hookrightarrow = bind(VanillaSymbol, '\\hookrightarrow ', '↪');
LatexCmds.searrow = bind(VanillaSymbol, '\\searrow ', '↘');
LatexCmds.leftharpoonup = bind(VanillaSymbol, '\\leftharpoonup ', '↼');
LatexCmds.rightharpoonup = bind(VanillaSymbol, '\\rightharpoonup ', '⇀');
LatexCmds.swarrow = bind(VanillaSymbol, '\\swarrow ', '↙');
LatexCmds.leftharpoondown = bind(VanillaSymbol, '\\leftharpoondown ', '↽');
LatexCmds.rightharpoondown = bind(VanillaSymbol, '\\rightharpoondown ', '⇁');
LatexCmds.nwarrow = bind(VanillaSymbol, '\\nwarrow ', '↖');
LatexCmds.rightleftarrows = bind(VanillaSymbol, '\\rightleftarrows ', '⇄');
//Misc
LatexCmds.ldots = bind(VanillaSymbol, '\\ldots ', '…');
LatexCmds.cdots = bind(VanillaSymbol, '\\cdots ', '⋯');
LatexCmds.vdots = bind(VanillaSymbol, '\\vdots ', '⋮');
LatexCmds.ddots = bind(VanillaSymbol, '\\ddots ', '⋰');
LatexCmds.surd = bind(VanillaSymbol, '\\surd ', '√');
LatexCmds.triangle = bind(VanillaSymbol, '\\triangle ', '▵');
LatexCmds.ell = bind(VanillaSymbol, '\\ell ', 'ℓ');
LatexCmds.top = bind(VanillaSymbol, '\\top ', '⊤');
LatexCmds.flat = bind(VanillaSymbol, '\\flat ', '♭');
LatexCmds.natural = bind(VanillaSymbol, '\\natural ', '♮');
LatexCmds.sharp = bind(VanillaSymbol, '\\sharp ', '♯');
LatexCmds.wp = bind(VanillaSymbol, '\\wp ', '℘');
LatexCmds.bot = bind(VanillaSymbol, '\\bot ', '⊥');
LatexCmds.clubsuit = bind(VanillaSymbol, '\\clubsuit ', '♣');
LatexCmds.diamondsuit = bind(VanillaSymbol, '\\diamondsuit ', '♢');
LatexCmds.heartsuit = bind(VanillaSymbol, '\\heartsuit ', '♡');
LatexCmds.spadesuit = bind(VanillaSymbol, '\\spadesuit ', '♠');
//variable-sized
LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '∮');
LatexCmds.bigcap = bind(VanillaSymbol, '\\bigcap ', '∩');
LatexCmds.bigcup = bind(VanillaSymbol, '\\bigcup ', '∪');
LatexCmds.bigsqcup = bind(VanillaSymbol, '\\bigsqcup ', '⊔');
LatexCmds.bigvee = bind(VanillaSymbol, '\\bigvee ', '∨');
LatexCmds.bigwedge = bind(VanillaSymbol, '\\bigwedge ', '∧');
LatexCmds.bigodot = bind(VanillaSymbol, '\\bigodot ', '⊙');
LatexCmds.bigotimes = bind(VanillaSymbol, '\\bigotimes ', '⊗');
LatexCmds.bigoplus = bind(VanillaSymbol, '\\bigoplus ', '⊕');
LatexCmds.biguplus = bind(VanillaSymbol, '\\biguplus ', '⊎');
//delimiters
LatexCmds.lfloor = bind(VanillaSymbol, '\\lfloor ', '⌊');
LatexCmds.rfloor = bind(VanillaSymbol, '\\rfloor ', '⌋');
LatexCmds.lceil = bind(VanillaSymbol, '\\lceil ', '⌈');
LatexCmds.rceil = bind(VanillaSymbol, '\\rceil ', '⌉');
LatexCmds.slash = bind(VanillaSymbol, '\\slash ', '/');
LatexCmds.opencurlybrace = bind(VanillaSymbol, '\\opencurlybrace ', '{');
LatexCmds.closecurlybrace = bind(VanillaSymbol, '\\closecurlybrace ', '}');
//various symbols
LatexCmds.setminus = LatexCmds.smallsetminus =
bind(VanillaSymbol,'\\setminus ','∖');
LatexCmds.converges =
LatexCmds.darr = LatexCmds.dnarr = LatexCmds.dnarrow = LatexCmds.downarrow =
bind(VanillaSymbol,'\\downarrow ','↓');
LatexCmds.Im = LatexCmds.imag =
LatexCmds.image = LatexCmds.imagin = LatexCmds.imaginary = LatexCmds.Imaginary =
bind(VanillaSymbol,'\\Im ','ℑ');
LatexCmds.xist = //LOL
LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
bind(VanillaSymbol,'\\exists ','∃');
LatexCmds.nexists = bind(VanillaSymbol,'\\nexists','∄');
// LatexCmds.o =
// LatexCmds.O =
LatexCmds.empty = LatexCmds.emptyset =
LatexCmds.oslash = LatexCmds.Oslash =
LatexCmds.nothing = LatexCmds.varnothing =
bind(BinaryOperator,'\\varnothing ','∅');
LatexCmds.ln =
LatexCmds.lg =
LatexCmds.log =
LatexCmds.span =
LatexCmds.proj =
LatexCmds.det =
LatexCmds.dim =
LatexCmds.min =
LatexCmds.max =
LatexCmds.mod =
LatexCmds.lcm =
LatexCmds.gcd =
LatexCmds.gcf =
LatexCmds.hcf =
LatexCmds.lim = NonItalicizedFunction;
(function() {
var trig = ['sin', 'cos', 'tan', 'sec', 'cosec', 'csc', 'cotan', 'cot'];
for (var i in trig) {
LatexCmds[trig[i]] =
LatexCmds[trig[i]+'h'] =
LatexCmds['a'+trig[i]] = LatexCmds['arc'+trig[i]] =
LatexCmds['a'+trig[i]+'h'] = LatexCmds['arc'+trig[i]+'h'] =
NonItalicizedFunction;
}
}());
// Parser MathCommand
var latexMathParser = (function() {
function commandToBlock(cmd) {
var block = MathBlock();
cmd.adopt(block, 0, 0);
return block;
}
function joinBlocks(blocks) {
var firstBlock = blocks[0] || MathBlock();
return firstBlock;
}
var controlSequence =
regex(/^[^\\]/)
.or(string('\\').then(
regex(/^[a-z]+/i)
.or(regex(/^\s+/).result(' '))
.or(any)
)).then(function(ctrlSeq) {
var cmdKlass = LatexCmds[ctrlSeq];
if (cmdKlass) {
return cmdKlass(ctrlSeq).parser();
}
else {
return fail('unknown command: \\'+ctrlSeq);
}
})
;
var command =
controlSequence
.or(variable)
.or(symbol)
;
var optMathBlock =
string('[').then(
mathBlock.then(function(block) {
return block.join('latex') !== ']' ? succeed(block) : fail();
})
.many().map(joinBlocks).skip(optWhitespace)
).skip(string(']'))
;
latexMath.block = mathBlock;
latexMath.optBlock = optMathBlock;
return latexMath;
})();
/********************************************
* Cursor and Selection "singleton" classes
*******************************************/
/* The main thing that manipulates the Math DOM. Makes sure to manipulate the
HTML DOM to match. */
/* Sort of singletons, since there should only be one per editable math
textbox, but any one HTML document can contain many such textboxes, so any one
JS environment could actually contain many instances. */
//A fake cursor in the fake textbox that the math is rendered in.
var Cursor = P(function(_) {
_.init = function(root) {
this.parent = this.root = root;
var jQ = this.jQ = this._jQ = $('<span class="cursor">‍</span>');
this.upDownCache = {};
};
_.prev = 0;
_.next = 0;
_.parent = 0;
_.show = function() {
this.jQ = this._jQ.removeClass('blink');
if ('intervalId' in this) //already was shown, just restart interval
clearInterval(this.intervalId);
else { //was hidden and detached, insert this.jQ back into HTML DOM
if (this.next) {
if (this.selection && this.selection.first.prev === this.prev)
this.jQ.insertBefore(this.selection.jQ);
else
this.jQ.insertBefore(this.next.jQ.first());
}
else
this.jQ.appendTo(this.parent.jQ);
this.parent.focus();
}
this.intervalId = setInterval(this.blink, 500);
return this;
};
_.hide = function() {
if ('intervalId' in this)
clearInterval(this.intervalId);
delete this.intervalId;
this.jQ.detach();
this.jQ = $();
return this;
};
_.insertAt = function(parent, prev, next) {
var old_parent = this.parent;
this.parent = parent;
this.prev = prev;
this.next = next;
if (this.parent.prev) this.appendTo(this.parent.prev);
else this.insertBefore(this.parent.parent);
}
};
_.moveRightWithin = function(block) {
if (this.next) {
if (this.next.firstChild) this.prependTo(this.next.firstChild)
else this.hopRight();
}
else {
// we're at the end of the containing block, so do nothing.
if (this.parent === block) return;
if (this.parent.next) this.prependTo(this.parent.next);
else this.insertAfter(this.parent.parent);
}
};
_.moveLeft = function() {
clearUpDownCache(this);
if (this.selection)
this.insertBefore(this.selection.first).clearSelection();
else {
this.moveLeftWithin(this.root);
}
return this.show();
};
_.moveRight = function() {
clearUpDownCache(this);
if (this.selection)
this.insertAfter(this.selection.last).clearSelection();
else {
this.moveRightWithin(this.root);
}
return this.show();
};
/**
* moveUp and moveDown have almost identical algorithms:
* - first check next and prev, if so prepend/appendTo them
* - else check the parent's 'up'/'down' property - if it's a function,
* call it with the cursor as the sole argument and use the return value.
*
* Given undefined, will bubble up to the next ancestor block.
* Given false, will stop bubbling.
* Given a MathBlock,
* + moveUp will appendTo it
* + moveDown will prependTo it
*
*/
_.moveUp = function() { return moveUpDown(this, 'up'); };
_.moveDown = function() { return moveUpDown(this, 'down'); };
function moveUpDown(self, dir) {
if (self.next[dir]) self.prependTo(self.next[dir]);
else if (self.prev[dir]) self.appendTo(self.prev[dir]);
else {
var ancestorBlock = self.parent;
do {
var prop = ancestorBlock[dir];
if (prop) {
if (typeof prop === 'function') prop = ancestorBlock[dir](self);
if (prop === false || prop instanceof MathBlock) {
self.upDownCache[ancestorBlock.id] = { parent: self.parent, prev:
self.prev, next: self.next };
if (cached) {
if (cached.next) {
self.insertBefore(cached.next);
} else {
self.appendTo(cached.parent);
}
} else {
var pageX = offset(self).left;
self.appendTo(prop);
self.seekHoriz(pageX, prop);
}
}
break;
}
}
ancestorBlock = ancestorBlock.parent.parent;
} while (ancestorBlock);
}
return self.clearSelection().show();
}
_.seek = function(target, pageX, pageY) {
clearUpDownCache(this);
var cmd, block, cursor = this.clearSelection().show();
if (target.hasClass('empty')) {
cursor.prependTo(MathElement[target.attr(mqBlockId)]);
return cursor;
}
cmd = MathElement[target.attr(mqCmdId)];
if (cmd instanceof Symbol) { //insert at whichever side is closer
if (target.outerWidth() > 2*(pageX - target.offset().left))
cursor.insertBefore(cmd);
else
cursor.insertAfter(cmd);
return cursor;
}
if (!cmd) {
block = MathElement[target.attr(mqBlockId)];
if (!block) { //if no MathQuill data, try parent, if still no, just start
from the root
target = target.parent();
cmd = MathElement[target.attr(mqCmdId)];
if (!cmd) {
block = MathElement[target.attr(mqBlockId)];
if (!block) block = cursor.root;
}
}
}
if (cmd)
cursor.insertAfter(cmd);
else
cursor.appendTo(block);
do {
cursor.moveLeftWithin(block);
prevDist = dist;
dist = offset(cursor).left - pageX;
}
while (dist > 0 && (cursor.prev || cursor.parent !== block));
return cursor;
};
function offset(self) {
//in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset()
//returns all 0's on inline elements with negative margin-right (like
//the cursor) at the end of their parent, so temporarily remove the
//negative margin-right when calling jQuery::offset()
//Opera bug DSK-360043
//http://bugs.jquery.com/ticket/11523
//https://github.com/jquery/jquery/pull/717
var offset = self.jQ.removeClass('cursor').offset();
self.jQ.addClass('cursor');
return offset;
}
_.writeLatex = function(latex) {
var self = this;
clearUpDownCache(self);
self.show().deleteSelection();
if (block) {
block.children().adopt(self.parent, self.prev, self.next);
MathElement.jQize(block.join('html')).insertBefore(self.jQ);
self.prev = block.lastChild;
block.finalizeInsert();
self.parent.bubble('redraw');
}
return this.hide();
};
_.write = function(ch) {
clearUpDownCache(this);
return this.show().insertCh(ch);
};
_.insertCh = function(ch) {
var cmd;
if (ch.match(/^[a-eg-zA-Z]$/)) //exclude f because want florin
cmd = Variable(ch);
else if (cmd = CharCmds[ch] || LatexCmds[ch])
cmd = cmd(ch);
else
cmd = VanillaSymbol(ch);
if (this.selection) {
this.prev = this.selection.first.prev;
this.next = this.selection.last.next;
cmd.replaces(this.selection);
delete this.selection;
}
return this.insertNew(cmd);
};
_.insertNew = function(cmd) {
cmd.createBefore(this);
return this;
};
_.insertCmd = function(latexCmd, replacedFragment) {
var cmd = LatexCmds[latexCmd];
if (cmd) {
cmd = cmd(latexCmd);
if (replacedFragment) cmd.replaces(replacedFragment);
this.insertNew(cmd);
}
else {
cmd = TextBlock();
cmd.replaces(latexCmd);
cmd.firstChild.focus = function(){ delete this.focus; return this; };
this.insertNew(cmd).insertAfter(cmd);
if (replacedFragment)
replacedFragment.remove();
}
return this;
};
_.unwrapGramp = function() {
var gramp = this.parent.parent;
var greatgramp = gramp.parent;
var next = gramp.next;
var cursor = this;
uncle.children()
.adopt(greatgramp, prev, next)
.each(function(cousin) {
cousin.jQ.insertBefore(gramp.jQ.first());
})
;
prev = uncle.lastChild;
});
gramp.jQ.remove();
if (gramp.prev)
gramp.prev.respace();
if (gramp.next)
gramp.next.respace();
};
_.backspace = function() {
clearUpDownCache(this);
this.show();
if (this.deleteSelection()); // pass
else if (this.prev) {
if (this.prev.isEmpty())
this.prev = this.prev.remove().prev;
else
this.selectLeft();
}
else if (this.parent !== this.root) {
if (this.parent.parent.isEmpty())
return this.insertAfter(this.parent.parent).backspace();
else
this.unwrapGramp();
}
if (this.prev)
this.prev.respace();
if (this.next)
this.next.respace();
this.parent.bubble('redraw');
return this;
};
_.deleteForward = function() {
clearUpDownCache(this);
this.show();
if (this.deleteSelection()); // pass
else if (this.next) {
if (this.next.isEmpty())
this.next = this.next.remove().next;
else
this.selectRight();
}
else if (this.parent !== this.root) {
if (this.parent.parent.isEmpty())
return this.insertBefore(this.parent.parent).deleteForward();
else
this.unwrapGramp();
}
if (this.prev)
this.prev.respace();
if (this.next)
this.next.respace();
this.parent.bubble('redraw');
return this;
};
_.selectFrom = function(anticursor) {
//find ancestors of each with common parent
var oneA = this, otherA = anticursor; //one ancestor, the other ancestor
loopThroughAncestors: while (true) {
for (var oneI = this; oneI !== oneA.parent.parent; oneI = oneI.parent.parent)
//one intermediate, the other intermediate
if (oneI.parent === otherA.parent) {
left = oneI;
right = otherA;
break loopThroughAncestors;
}
if (oneA.parent.parent)
oneA = oneA.parent.parent;
if (otherA.parent.parent)
otherA = otherA.parent.parent;
}
//figure out which is left/prev and which is right/next
var left, right, leftRight;
if (left.next !== right) {
for (var next = left; next; next = next.next) {
if (next === right.prev) {
leftRight = true;
break;
}
}
if (!leftRight) {
leftRight = right;
right = left;
left = leftRight;
}
}
this.hide().selection = Selection(left.prev.next || left.parent.firstChild,
right.next.prev || right.parent.lastChild);
this.insertAfter(right.next.prev || right.parent.lastChild);
this.root.selectionChanged();
};
_.selectLeft = function() {
clearUpDownCache(this);
if (this.selection) {
if (this.selection.first === this.next) { //if cursor is at left edge of
selection;
if (this.prev) //then extend left if possible
this.hopLeft().selection.extendLeft();
else if (this.parent !== this.root) //else level up if possible
this.insertBefore(this.parent.parent).selection.levelUp();
}
else { //else cursor is at right edge of selection, retract left if possible
this.hopLeft();
if (this.selection.first === this.selection.last) {
this.clearSelection().show(); //clear selection if retracting to nothing
return; //skip this.root.selectionChanged(), this.clearSelection() does
it anyway
}
this.selection.retractLeft();
}
}
else {
if (this.prev)
this.hopLeft();
else //end of a block
if (this.parent !== this.root)
this.insertBefore(this.parent.parent);
else
return;
this.hide().selection = Selection(this.next);
}
this.root.selectionChanged();
};
_.selectRight = function() {
clearUpDownCache(this);
if (this.selection) {
if (this.selection.last === this.prev) { //if cursor is at right edge of
selection;
if (this.next) //then extend right if possible
this.hopRight().selection.extendRight();
else if (this.parent !== this.root) //else level up if possible
this.insertAfter(this.parent.parent).selection.levelUp();
}
else { //else cursor is at left edge of selection, retract right if possible
this.hopRight();
if (this.selection.first === this.selection.last) {
this.clearSelection().show(); //clear selection if retracting to nothing
return; //skip this.root.selectionChanged(), this.clearSelection() does
it anyway
}
this.selection.retractRight();
}
}
else {
if (this.next)
this.hopRight();
else //end of a block
if (this.parent !== this.root)
this.insertAfter(this.parent.parent);
else
return;
this.hide().selection = Selection(this.prev);
}
this.root.selectionChanged();
};
function clearUpDownCache(self) {
self.upDownCache = {};
}
_.prepareMove = function() {
clearUpDownCache(this);
return this.show().clearSelection();
};
_.prepareEdit = function() {
clearUpDownCache(this);
return this.show().deleteSelection();
}
_.clearSelection = function() {
if (this.selection) {
this.selection.clear();
delete this.selection;
this.root.selectionChanged();
}
return this;
};
_.deleteSelection = function() {
if (!this.selection) return false;
this.prev = this.selection.first.prev;
this.next = this.selection.last.next;
this.selection.remove();
this.root.selectionChanged();
return delete this.selection;
};
});
frag.jQwrap(frag.jQ);
};
_.jQwrap = function(children) {
this.jQ = children.wrapAll('<span class="selection"></span>').parent();
//can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it
};
_.adopt = function() {
this.jQ.replaceWith(this.jQ = this.jQ.children());
return _super.adopt.apply(this, arguments);
};
_.clear = function() {
this.jQ.replaceWith(this.jQ.children());
return this;
};
_.levelUp = function() {
var seln = this,
gramp = seln.first = seln.last = seln.last.parent.parent;
seln.clear().jQwrap(gramp.jQ);
return seln;
};
_.extendLeft = function() {
this.first = this.first.prev;
this.first.jQ.prependTo(this.jQ);
};
_.extendRight = function() {
this.last = this.last.next;
this.last.jQ.appendTo(this.jQ);
};
_.retractRight = function() {
this.first.jQ.insertBefore(this.jQ);
this.first = this.first.next;
};
_.retractLeft = function() {
this.last.jQ.insertAfter(this.jQ);
this.last = this.last.prev;
};
});
/*********************************************************
* The actual jQuery plugin and document ready handlers.
********************************************************/
if (cursor) {
cursor.show();
if (/^\\[a-z]+$/i.test(latex)) {
var selection = cursor.selection;
if (selection) {
cursor.prev = selection.first.prev;
cursor.next = selection.last.next;
delete cursor.selection;
}
cursor.insertCmd(latex.slice(1), selection);
}
else
cursor.insertCh(latex);
cursor.hide().parent.blur();
}
});
default:
var textbox = cmd === 'textbox',
editable = textbox || cmd === 'editable',
RootBlock = textbox ? RootTextBlock : RootMathBlock;
return this.each(function() {
createRoot($(this), RootBlock(), textbox, editable);
});
}
};
}());
;Drupal.behaviors.sTinymceMathquill = function(context){
// initialize formula editor elements
$('.s-formula-editable:not(.sTinymceMathquill-processed)',
context).addClass('sTinymceMathquill-processed').each(function(){
sMathquill.initMathquill(this);
});
$(document).data('s_content_saved_formula_image',null);
Popups.close();
/**
* Initialize a new markup element to be placed in a menu bar
*
* @param object cfg
*/
_newMarkup = function(cfg){
return {
type: 'markup',
html: cfg.html
};
};
/**
* Initialize a new command button
* Executes a command in Mathquill and triggers a refocus.
*
* @param object cfg
*/
_newCommandButton = function(cfg){
var cmd = typeof cfg.cmd == 'object' ? cfg.cmd.join('') : cfg.cmd;
return {
type: 'button',
extraClass: 's-mq-btn s-mq-icon',
label: cmd,
tooltip: cmd.replace('\\', ''),
fn: function(args){
args.event.preventDefault();
if(typeof cfg.cmd == 'object'){
$.each(cfg.cmd, function(k, cmd){
args.editor.mathquill('cmd', cmd);
});
}
else{
args.editor.mathquill('cmd', cfg.cmd);
}
args.editor.trigger('focus');
}
};
};
/**
* Initialize a new text button
* Executes a write in Mathquill and triggers a refocus.
*
* @param object cfg
*/
_newTextButton = function(cfg){
return {
type: 'button',
extraClass: 's-mq-btn s-mq-icon',
label: cfg.text,
tooltip: cfg.text,
fn: function(args){
args.event.preventDefault();
args.editor.mathquill('write', cfg.text);
args.editor.trigger('focus');
}
};
};
/**
* Initialize a new menu toggle button that toggles visibility of sub menus and
refocuses on the editor
*
* @param object cfg
*/
_newMenuToggleButton = function(cfg){
var menuClass = 's-mq-' + cfg.key.replace(/_/g, '-').toLowerCase();
return {
type: 'button',
extraClass: 's-mq-btn s-mq-menu-toggle',
label: cfg.label,
tooltip: cfg.label,
fn: function(args){
var e = args.event,
ed = args.editor;
e.preventDefault();
var btnObj = $(this);
if(btnObj.hasClass('active')){
//closing a menu
btnObj.removeClass('active');
ed.siblings('.s-mq-submenu.' + menuClass).addClass('hidden');
}
else{
//opening a menu
btnObj.addClass('active')
.siblings('.s-mq-menu-toggle')
.removeClass('active');
var subMenus = ed.siblings('.s-mq-submenu');
subMenus.not('.' + menuClass).addClass('hidden');
subMenus.filter('.' + menuClass).removeClass('hidden');
}
obj.resize();
ed.trigger('focus');
}
};
};
/**
* Initialize a default button.
* This just adds an element to the DOM, any bindings will be done externally.
* Function may be passed in to the fn member of cfg.
*
* @param object cfg
*/
_newDefaultButton = function(cfg){
var btn = {
type: 'button',
label: cfg.label,
extraClass: 's-mq-btn'
};
if(typeof cfg.fn == 'function'){
btn.fn = cfg.fn;
}
return btn
};
/**
* Initialize Mathquill editor on a given element
*
* @param string/object selector or DOM element
* @param object opts
*/
obj.initMathquill = function(element, opts){
var menuBars = null;
if(typeof opts == 'undefined'){
var opts = {};
}
var defaults = {
editable: true,
autoHeight: false
};
opts = $.extend(defaults, opts);
_execAllHooks('onBeforeEditorCreate', [el]);
el.mathquill('editable');
el.initialHeight = el.innerHeight();
obj.editors.push(el);
_execAllHooks('onEditorCreate', [el]);
}
else{
el.mathquill();
}
return el;
};
/**
* Add a command bar to the active editor
*
* @param object opts
* string menuItem comma separated list of keys representing a menu item
*
* @return object
*/
obj.addMenuBar = function(opts){
var ed = obj.activeEditor,
menuItems = [];
if(!ed){
return;
}
case 'markup':
newBarObj.append(menuItem.html);
break;
}
newBarObj.insertBefore(ed);
ed.menuBars.push(newBarObj);
return newBarObj;
};
/**
* Add a button to the menu bar
*
* @param int/object menuBar provide int to reference a menu bar in the active
editor
* provide object of the menu bar object
* @param object btn
*
* @return object
*/
obj.addButtonToMenuBar = function(menuBar, btn){
var menuBarObj = null,
ed = obj.activeEditor;
if(!menuBarObj){
return null;
}
var defaults = {
label: '',
extraClass: '',
size: 20,
fn: function() {}
};
btn = $.extend(defaults, btn);
if(btn.label.length){
btnLabel.text(btn.label);
}
if(btn.tooltip.length){
btnObj.attr('title', btn.tooltip);
}
if(btn.extraClass.length){
btnObj.addClass(btn.extraClass);
}
btnObj.addClass('s-mq-size' + btn.size);
if(btnObj.hasClass('s-mq-embed')){
// has latex command embedded
// need to run mathquill on the label text and render it
var renderedHTML = $
('<span/>').text(btnLabel.text()).mathquill('editable').mathquill('html');
btnLabel.html(renderedHTML).addClass('mathquill-rendered-math');
}
else{
// does an html entities decode
btnLabel.html(btnLabel.text());
}
btnObj.appendTo(menuBarObj);
return btnObj;
};
/**
* Retrieve the menu item object given the key.
*
* @param string key unique key that defines a menu item
*/
obj.getMenuItem = function(key){
var template = null,
cfg = null;
if(typeof this.menuItemList[key] == 'undefined'){
if(key.length){
// if there is no config for it, create a standard command button
cfg = {
key: key,
cmd: '\\' + key
};
}
}
else {
cfg = this.menuItemList[key];
}
if(cfg){
cfg.key = key;
if(typeof cfg.html != 'undefined'){
template = _newMarkup(cfg);
}
else if(typeof cfg.cmd != 'undefined'){
// command button will trigger a cmd and refocus on the editor
template = _newCommandButton(cfg);
}
else if(typeof cfg.text == 'string'){
// text button will trigger a text call and refocus on the editor
template = _newTextButton(cfg);
}
else if(typeof cfg.menu_toggle != 'undefined'){
template = _newMenuToggleButton(cfg);
}
else{
template = _newDefaultButton(cfg);
}
return template;
};
/**
* Adjust the size of the editor to fit the menu bars.
*/
obj.resize = function(){
var ed = obj.activeEditor;
if(ed && typeof ed.initialHeight != 'undefined' && typeof
ed.opts.autoHeight != 'undefined' && ed.opts.autoHeight){
var height = ed.initialHeight;
if(ed.menuBars.length){
$.each(ed.menuBars, function(k, menuBar){
if(menuBar.is(':visible')){
height -= menuBar.outerHeight();
}
});
ed.height(height);
}
}
};
/**
* Execute a command on the active editor if there is one.
*
* @param string cmd
* @param mixed,... args
*/
obj.exec = function(){
var ed;
if(ed = this.activeEditor){
var args = Array.prototype.slice.call(arguments);
return ed.mathquill.apply(ed, args);
}
return null;
};
/**
* Convenience method to write to the active editor
*
* @param string latex
*/
obj.write = function(latex){
return obj.exec('write', latex);
};
/**
* Convenience method for executing a Mathquill command on the active editor.
*
* @param string cmd
*/
obj.cmd = function(cmd){
return obj.exec('cmd', cmd);
};
/**
* Convenience method for resetting the contents of the active editor.
*/
obj.reset = function(){
return obj.exec('revert');
};
/**
* Convenience method to retrieve the latex representation of the formula in
the active editor
*/
obj.getLatex = function(){
return obj.exec('latex');
};
/**
* Convenience method to retrieve the HTML representation of the formula in the
active editor
*/
obj.getHTML = function(){
return obj.exec('html');
};
/**
* Saves the content.
* Returns the final LaTeX after hooks have been run
*/
obj.save = function(){
var output = { latex: obj.getLatex() };
// passing output as object so it will be passed to all hooks by reference
and not by value
_execAllHooks('onSave', [obj.activeEditor, output]);
return output.latex;
};
/**
* Register a hook to the provided namespace and event queue.
* May also provide an argument list as an array
*
* @param string namespace
* @param string event
* @param function fn
*/
_addHook = function(namespace, event, fn){
if(typeof hook_namespaces[namespace] == 'undefined'){
hook_namespaces[namespace] = {};
}
if(typeof hook_namespaces[namespace][event] == 'undefined'){
hook_namespaces[namespace][event] = [fn];
}
else{
hook_namespaces[namespace][event].push(fn);
}
};
/**
* Execute the hooks associated with the event.
* May also provide an argument list as an array
*
* @param string namespace
* @param string event
* @param array args
*/
_execHooks = function(namespace, event, args){
if(typeof hook_namespaces[namespace] == 'undefined'){
return;
}
/**
* Execute the hooks associated with the event in the global namespace.
* May also provide an argument list as an array
*
* @param string event
* @param array args
*/
_execGlobalHooks = function(event, args){
_execHooks('global', event, args);
};
/**
* Execute the hooks associated with the event for the current editor.
* May also provide an argument list as an array
*
* @param string event
* @param array args
*/
_execEditorHooks = function(event, args){
var ed = obj.activeEditor;
if(ed){
_execHooks('editor_' + ed.index, event, args);
}
};
/**
* Execute the hooks in all namespces for the given event
* May also provide an argument list as an array
*
* @param string event
* @param array args
*/
_execAllHooks = function(event, args){
$.each(hook_namespaces, function(namespace, queues){
_execHooks(namespace, event, args);
});
};
/**
* Register a hook to be called in an event that's not associated with an
editor
*
* @param string event
* @param function fn
*/
obj.addGlobalHook = function(event, fn){
_addHook('global', event, fn);
};
/**
* Register a hook to be called in an event on the current active editor
*
* @param string event
* @param function fn
*/
obj.addEditorHook = function(event, fn){
var ed = obj.activeEditor;
if(ed){
_addHook('editor_' + ed.index, event, fn);
}
};
return obj;
})();
/**
* Mathquill editor configuration
*/
sMathquill.config = {
menu_bars: {}
};
/**
* a list of menu item definitions
*
* string html - an html markup that can be inserted into a menu bar
* string cmd - a Mathquill command that runs as a result of clicking on the
button. creates a command button
* string text - a Mathquill "write" that runs as a result of clicking on the
button. creates a text button
*
* bool menu_toggle - the button is treated as a toggle, creates a menu toggle
button
* bool compile_label - defaults true. set to false to prevent the label from
being compiled in Mathquill
*
* string label - the text that appears on the control
* string tooltip - the text that appears on mouseover
*/
sMathquill.menuItemList = {
// markup
_line_break: { html: '<br/>' },
_separator: { html: '<span class="s-mq-separator"></span>' }
};
/**
* Main menu buttons and cluetip roll-over on the buttons
*/
(function(obj){
var mainMenuConfig = {
// main menus
main: {
menu_items: [
//
'menu_greek,menu_operators,menu_relationships,menu_equations,menu_arrows,menu_misc'
'menu_operators,menu_relationships,menu_equations,menu_arrows,menu_misc,menu_greek'
]
},
// sub menus
greek: {
submenu: true,
multiline: true,
menu_items: [
'alpha,beta,gamma,delta,epsilon,zeta,eta,theta,iota,kappa,lambda,mu,nu,xi,omicron,p
i,rho,sigma,tau,upsilon,phi,chi,psi,omega',
'digamma,varepsilon,varkappa,varphi,varpi,varrho,varsigma,vartheta',
'Gamma,Delta,Theta,Lambda,Xi,Pi,Sigma,Upsilon,Phi,Psi,Omega'
]
},
operators: {
submenu: true,
multiline: true,
menu_items: [
'equal,plus,minus,ast,cdot,times,div,pm,mp,therefore,because',
'bigcirc,diamond,amalg,odot,ominus,oplus,otimes,wr',
'union,intersect,uplus,sqcap,sqcup,wedge,vee,dagger,ddagger',
'lhd,rhd,bigtriangledown,bigtriangleup'
]
},
relationships: {
submenu: true,
multiline: true,
menu_items: [
'equiv,cong,neq,sim,simeq,approx,napprox,doteq,models',
'leq,prec,preceq,lt,ll,subset,subseteq,nsubset,nsubseteq,sqsubset,sqsubseteq,dashv,
in,notin',
'geq,succ,succeq,gt,gg,supset,supseteq,nsupset,nsupseteq,sqsupset,sqsupseteq,vdash,
ni,notni',
'mid,parallel,nparallel,perp,bowtie,smile,frown,propto,exists,nexists,varnothing'
]
},
equations: {
submenu: true,
multiline: true,
menu_items: [
'frac,fprime,sqrt,nthroot,supscript,subscript,curly_braces,angle_brackets,lfloor,rf
loor,lceil,rceil,slash',
'sum,prod,coprod,limit,int,oint,binomial,vector,prime'
]
},
arrows: {
submenu: true,
multiline: true,
menu_items: [
'leftarrow,Leftarrow,rightarrow,Rightarrow,leftrightarrow,Leftrightarrow',
'longleftarrow,Longleftarrow,longrightarrow,Longrightarrow,longleftrightarrow,Longl
eftrightarrow',
'rightleftarrows,uparrow,Uparrow,downarrow,Downarrow,updownarrow,Updownarrow',
'mapsto,hookleftarrow,leftharpoonup,leftharpoondown,hookrightarrow,rightharpoonup,r
ightharpoondown',
'nearrow,searrow,swarrow,nwarrow'
]
},
misc: {
submenu: true,
multiline: true,
menu_items: [
'infty,nabla,partial,clubsuit,diamondsuit,heartsuit,spadesuit,cdots,vdots,ldots,ddo
ts,imaginary,real',
'forall,reals,complex,naturals,rationals,integers,ell,sharp,flat,natural,hbar,surd,
wp',
'angle,measuredangle,overline,overrightarrow,overleftrightarrow,triangle,top,bot,ca
ret,underscore,backslash,vert,AA',
'circ,bullet,setminus,neg,dots,aleph,deg'
]
}
};
var mainMenuButtons = {
menu_greek: { menu_toggle: true,
label: '\\alpha \\pi \\Delta',
tooltip: Drupal.t('Greek'),
extraClass: 's-mq-embed' },
menu_operators: { menu_toggle: true,
label: '\\pm\\times=',
tooltip: Drupal.t('Operators'),
extraClass: 's-mq-embed' },
menu_relationships: { menu_toggle: true,
label: '\\leq\\ne\\in',
tooltip: Drupal.t('Relationships'),
extraClass: 's-mq-embed' },
menu_equations: { menu_toggle: true,
compile_label: false,
label: '<var class="florin">ƒ</var>'
+ '<span>\'</span>'
+ '<span class="non-leaf">{ }</span>'
+ '<span class="non-leaf">'
+ '<span class="scaled sqrt-prefix">√</span>'
+ '<span class="non-leaf sqrt-stem">'
+ '<var>x</var>'
+ '</span>'
+ '</span>',
tooltip: Drupal.t('Equations'),
extraClass: 'mathquill-rendered-math' },
menu_arrows: { menu_toggle: true,
label: '\\Leftarrow\\updownarrow\\Rightarrow',
tooltip: Drupal.t('Arrows'),
extraClass: 's-mq-embed' },
menu_misc: { menu_toggle: true,
label: '\\infty\\angle\\partial',
tooltip: Drupal.t('Miscellaneous'),
extraClass: 's-mq-embed' },
// arithmetic
plus: { cmd: '+' },
minus: { cmd: '-' },
equal: { cmd: '=' },
//greek letters
omicron: { text: 'o', compile_label: false },
/**
* Apply the cluetip effect on the menu items when the editor gets created
*/
obj.addGlobalHook('onEditorCreate', function(ed){
var mainMenu = ed.siblings('.s-mq-menu-main');
mainMenu.children('.s-mq-menu-toggle').each(function(){
var toggleBtn = $(this);
toggleBtn.tipsy({
gravity: 's',
title: function(){
return toggleBtn.attr('original-title');
}
});
});
});
}(sMathquill));
/**
* Add a font re-sizing plugin
*
* LaTeX supports 10 different font sizes using certain commands listed in
fontSizeMapping.
* The font size index will be stored as an index to that mapping and is saved
per editor.
*/
(function(obj){
var DEFAULT_SIZE = 5;
var fontSizeMapping = [
{ cmd: '\\tiny', font_size: '10px' },
{ cmd: '\\scriptsize', font_size: '11px' },
{ cmd: '\\footnotesize', font_size: '12px' },
{ cmd: '\\small', font_size: '14px' },
{ font_size: '16px' },
{ cmd: '\\large', font_size: '18px' },
{ cmd: '\\Large', font_size: '24px' },
{ cmd: '\\LARGE', font_size: '28px' },
{ cmd: '\\huge', font_size: '34px' },
{ cmd: '\\Huge', font_size: '40px' }
];
var mappingByCommand = {};
$.each(fontSizeMapping, function(idx, size){
if(typeof size.cmd != 'undefined'){
mappingByCommand[size.cmd.substr(1)] = idx;
}
});
_getEditorFontSize = function(ed){
var ret = null;
if(ed && typeof ed.index != 'undefined'){
ret = editorSizes[ed.index];
}
return ret;
};
/**
* Given a LaTeX input, parse out a potential size command.
*
* @param string latex
* @return string
*/
_getSizeString = function(latex){
var matches = latex.match(/\\(tiny|scriptsize|small|normalsize|large|Large|
LARGE|huge|Huge)/);
if(matches && typeof mappingByCommand[matches[1]] != 'undefined'){
return matches[1];
}
return null;
};
/**
* Attempt to update the font size to the provided size for the current editor
*
* @param int newSize
* @return object
*/
obj.updateFontSize = function(newSize){
var ed = obj.activeEditor;
if(!ed){
return null;
}
return fontSizeMapping[_getEditorFontSize(ed)];
};
/**
* Increase the font size of the current editor by diff
*
* @param int diff
* @return object
*/
obj.increaseFontSize = function(diff){
var ed = obj.activeEditor;
if(!ed){
return null;
}
// Add a hook to extract any size string that is in the markup and set the
default font size
obj.addGlobalHook('onBeforeEditorCreate', function(element){
var latex = element.text();
if(latex.length){
var sizeString = _getSizeString(latex);
if(sizeString){
element.text(latex.replace('\\' + sizeString + ' ', ''));
_setEditorFontSize(element, mappingByCommand[sizeString]);
}
}
});
// Add a hook to set the default font size of the new editor
obj.addGlobalHook('onEditorCreate', function(newEditor){
if(!_getEditorFontSize(newEditor)){
_setEditorFontSize(newEditor, DEFAULT_SIZE);
}
newEditor.css('font-size',
fontSizeMapping[_getEditorFontSize(newEditor)].font_size);
// Add a hook to prepend the latex output with the corresponding size command
obj.addGlobalHook('onSave', function(ed, output){
var editorFontSize = _getEditorFontSize(ed);
if(typeof editorFontSize != 'undefined' && typeof
fontSizeMapping[editorFontSize] != 'undefined'){
var font = fontSizeMapping[editorFontSize];
// if there's a command associated with this font size, prepend it to the
latex output
if(typeof font.cmd != 'undefined'){
output.latex = font.cmd + ' ' + output.latex;
}
}
});
/**
* Custom help cluetip
*/
(function(obj){
var helpMessage = Drupal.t('Select symbols or type in LaTeX code');
obj.config.menu_bars.main.menu_items.push('_help_cluetip');
obj.menuItemList._help_cluetip = {
html: '<span class="s-mq-help-tip"><span>?</span></span>'
};
}(sMathquill));
};
jQuery.extend({
if(window.ActiveXObject) {
var io = document.createElement('<iframe id="' + frameId + '"
name="' + frameId + '" />');
if(typeof uri== 'boolean'){
io.src = 'javascript:false';
}
else if(typeof uri== 'string'){
io.src = uri;
}
}
else {
var io = document.createElement('iframe');
io.id = frameId;
io.name = frameId;
}
io.style.position = 'absolute';
io.style.top = '-1000px';
io.style.left = '-1000px';
document.body.appendChild(io);
return io
},
createUploadForm: function(id, fileElementId, d)
{
//create form
var formId = 'jUploadForm' + id;
var fileId = 'jUploadFile' + id;
var form = $('<form action="" method="POST" name="' + formId + '"
id="' + formId + '" enctype="multipart/form-data"></form>');
var oldElement = $('#' + fileElementId);
var newElement = $(oldElement).clone();
$(oldElement).attr('id', fileId);
$(oldElement).before(newElement);
$(oldElement).appendTo(form);
//set attributes
$(form).css('position', 'absolute');
$(form).css('top', '-1200px');
$(form).css('left', '-1200px');
$(form).appendTo('body');
return form;
},
ajaxFileUpload: function(s) {
// TODO introduce global settings, allowing the client to modify them for
all requests, not only timeout
s = jQuery.extend({}, jQuery.ajaxSettings, s);
var id = new Date().getTime()
var form = jQuery.createUploadForm(id, s.fileElementId, s.data);
var io = jQuery.createUploadIframe(id, s.secureuri);
var frameId = 'jUploadFrame' + id;
var formId = 'jUploadForm' + id;
// Watch for a new set of requests
if ( s.global && ! jQuery.active++ )
{
jQuery.event.trigger( "ajaxStart" );
}
var requestDone = false;
// Create the request object
var xml = {}
if ( s.global )
jQuery.event.trigger("ajaxSend", [xml, s]);
}else if(io.contentDocument)
{
xml.responseText = io.contentDocument.document.body?
io.contentDocument.document.body.innerHTML:null;
xml.responseXML = io.contentDocument.document.XMLDocument?
io.contentDocument.document.XMLDocument:io.contentDocument.document;
}
}catch(e)
{
jQuery.handleError(s, xml, null, e);
}
// Process result
if ( s.complete )
s.complete(xml, status);
jQuery(io).unbind()
setTimeout(function()
{ try
{
$(io).remove();
$(form).remove();
} catch(e)
{
}, 100)
xml = null
}
}
// Timeout checker
if ( s.timeout > 0 )
{
setTimeout(function(){
// Check to see if the request is still happening
if( !requestDone ) uploadCallback( "timeout" );
}, s.timeout);
}
try
{
// var io = $('#' + frameId);
var form = $('#' + formId);
$(form).attr('action', s.url);
$(form).attr('method', 'POST');
$(form).attr('target', frameId);
if(form.encoding)
{
form.encoding = 'multipart/form-data';
}
else
{
form.enctype = 'multipart/form-data';
}
$(form).submit();
} catch(e)
{
jQuery.handleError(s, xml, null, e);
}
if(window.attachEvent){
document.getElementById(frameId).attachEvent('onload', uploadCallback);
}
else{
document.getElementById(frameId).addEventListener('load',
uploadCallback, false);
}
return {abort: function () {}};
},
_afuParseJSON: function(d)
{
d = String(d); if( d == '' ) return '';
var pd = d.match(/\[acup-open\](.+)\[acup-close\]/);
var dd = $.parseJSON(pd[1]);
return dd;
}
})
this.uploadFormAjax = function(d,param_opts){
if( typeof d == 'undefined' ) d = {};
switch(path_parts){
default:
d.upload_realm = path_parts[1];
d.upload_realmId = path_parts[2];
break;
}
/** should be overridden by whatever you want to do after the upload succeeds
**/
this.onUpload = function(new_url){
//console.log('onUpload: '+String(new_url));
};
}
// Click behavior for link descriptions, since you can't have <a> tags wrap other
<a> tags
// which would be possible with rich text descriptions
$('.materials-folder.item-info:not(.sCourseMaterialsFolders-processed)',
context).addClass('sCourseMaterialsFolders-processed').each(function(){
var folder = $(this);
// Will not trigger if the folder-title doesn't contain an 'a' tag (e.g.
permission denied)
var folderTitleLink = $('.folder-title a', folder);
var folderDescription = $('.folder-description', folder);
if(folderDescription.hasClass('description-clickable')){
folderDescription.bind('click', function(e){
// Ignore if the target is a link
if(!$(e.target).is('a')){
// Use vanilla JS to follow the href
folderTitleLink[0].click();
}
})
}
});
(function(){
/**
* Refresh the sortable tbody within the folder tree structure.
* This is needed to link all the sortable table bodies after a subdirectory is
opened.
* This occurs when dragging an item over a folder for a period of time and a
subtree is opened.
*
* @param object subtreeContext
*/
function refreshSortableContent(subtreeContext){
subtreeContext.closest('#folder-contents-table').find('tbody.ui-
sortable').each(function(k, tbodyEl){
// Make the sortable plugin refresh upon ajax completion
// (to allow handling of dynamic folder expansion AJAX
$(tbodyEl).sortable('refresh');
});
}
/**
* Toggle the visibility of the folder's content based on the expander object.
*
* @param object $expander the expander element that is linked to the folder
being expanded
* @param bool show whether this is a show or hide operation
*/
function toggleSubtree($expander, show){
var $subtree = $expander.siblings('.folder-subtree'),
$subtreeLinks = $subtree.find('.action-links-wrapper'),
$description = $expander.parent().find('.s-js-folder-description'),
$title = $expander.parent().find('.folder-title'),
show = show || false,
folderId = $expander.closest('.material-row-
folder').attr('id').split('-')[1];
if(show){
$expander.find('.visually-hidden').text(Drupal.t('Collapse folder.'));
// If subtree was already loaded, show it
if($subtree.length){
toggleVisibility(true);
}
// Load the subtree
else {
$loader = $('<img class="pending"
src="/sites/all/themes/schoology_theme/images/ajax-loader.gif" alt="' +
Drupal.t('Loading') + '" />');
$subtree = $('<div class="folder-subtree"></div>');
$expander.parent().append($subtree);
$title.after($loader);
$.ajax({
dataType: 'json',
url: window.location.pathname,
data: {
ajax: 1,
f: folderId
},
success: function(contents){
contents = $(contents);
var foldersActionPush = $
(document).data('sCourseMaterialsFoldersActionPush');
if(foldersActionPush && foldersActionPush.length){
var fid = foldersActionPush.pop();
var $expander = $('#f-' + fid + ' > td.folder-contents-cell >
.folder-expander');
if(!$expander.hasClass('expanded')){
$expander.click();
}
$(document).data('sCourseMaterialsFoldersActionPush',
foldersActionPush);
}
else{
refreshSortableContent($subtree);
}
},
error: function(){
$subtree.html('<div class="error">' + Drupal.t('There was an error
while loading the folder content. Please reload this page and try again.') +
'</div>');
refreshSortableContent($subtree);
}
});
}
}
else{
$expander.find('.visually-hidden').text(Drupal.t('Expand folder.'));
toggleVisibility(false);
}
}
// Start from the subtree div so it doesn't count the current folder
target.closest('.subtree-folder-contents-table').parents('tr.material-
row-folder').each(function(){
foldersActionPush.push($(this).attr('id').split('-')[1]);
});
foldersExpanded.forEach(function(currentValue){
var fid = currentValue;
var expander = $('#f-' + fid + '>td.folder-contents-cell>.folder-
expander');
if(!expander.hasClass('expanded')){
expander.click();
}
});
}
}
$('#s-generic-post-new-display-view:not(.sCourseMaterialsFolders-
processed),#folder-contents-table:not(.sCourseMaterialsFolders-processed)',
context).addClass('sCourseMaterialsFolders-processed').each(function() {
var table = $(this);
if(table.attr('id') == 'folder-contents-table'){
// Add action link behavior
$('tr td.actions').sActionLinks({ hidden: false, wrapper: '.action-links-
wrapper' });
table.click(function(e){
var target = $(e.target);
lastMaterialsRowAddTarget = target;
// Make sure that the links we copy are the most up to date ones
// that contain any dynamic changes to the original menu
var targetOffset = target.offset();
inlineAddLinksWrapper.empty()
.data('materialsRowAddId', materialsRowAddId)
.append(addMaterialsActionLinks.clone(true).show())
.show()
.css({
position: 'absolute',
top: targetOffset.top + target.height(),
left: targetOffset.left
});
//Some items, like the separator lines, don't contain links. Skip them.
if(actionLink[0] == undefined) {
return true;
}
switch(actionItemClass){
case 'action-import-library':
if(targetFid > 0){
qsParsed.topic_nid = targetFid;
}
else {
delete qsParsed.topic_nid;
}
qsParsed.fweight = targetWeight;
actionLink.attr('href', '/' + pathname + '?' +
jQuery.param(qsParsed));
break;
case 'action-find-resources':
// Don't show the action-find-resources
actionItem.remove();
break;
default:
var isUnderStudentChoice = target.parents('.material-row-student-
choice').length > 0;
if (isUnderStudentChoice && (actionItemClass === 'action-create-
folder' || actionItemClass === 'action-create-student-choice')) {
actionItem.remove();
return;
}
if(targetFid == 0 && !sCourseMaterialsDisplayIsWithoutIndex()){
// Only allow folders on top level
if(actionItemClass != 'action-create-folder'){
actionItem.remove();
return;
}
else {
actionItem.addClass('folder-no-materials');
}
}
qsParsed.f = targetFid;
qsParsed.fweight = targetWeight;
actionLink.attr('href', '/' + pathname + '?' +
jQuery.param(qsParsed));
break;
}
});
}
});
// Click-out behavior
$('body').click(function(e){
var target = $(e.target);
if(target.parent('.materials-row-add-line').length == 0){
inlineAddLinksWrapper.hide();
lastMaterialsRowAddTarget = null;
}
});
}
$('.has-view-rule:not(.completed)', table).click(function(){
var parent = $(this).parents('.type-document:first');
var nid = parent.attr('id').split('-')[1];
$.ajaxSecure({
url: '/course/materials/' + nid + '/view_completion'
});
});
});
$('#closeable-message-s_course_find_resources:not(.sCourseMaterialsFolders-
processed)', context).addClass('sCourseMaterialsFolders-processed').each(function()
{
$(document).bind('s_common_closeable_message_closed', function(e, mesID){
if(mesID == 's_course_find_resources'){
var fakeCluetip = '<div class="fake-cluetip"><span class="arrow-
top"><span>' + Drupal.t('You can find resources for your course in this menu.') +
'</div>';
var offset = $('.course-content-action-links').offset();
$('body').append(fakeCluetip);
$('.fake-cluetip').css({'position': 'absolute' , 'left':
offset.left + $('.course-content-action-links').width() , 'top': offset.top });
setTimeout(function(){
$('.fake-cluetip').remove();
}, 2000);
}
});
});
$('#s-course-materials-folder-create-form:not(.sCourseMaterialsFolders-
processed)', context).addClass('sCourseMaterialsFolders-processed').each(function()
{
var form = $(this);
sPopupsResizeCenter()
});
$('#edit-availability', form).trigger('change');
});
$('#s-course-materials-folder-completion-form:not(.sCourseMaterialsFolders-
processed)', context ).addClass('sCourseMaterialsFolders-
processed').each(function(){
//bind click event on the html links to the hidden ahah submitters
$('.add-rule', form).click(function(){
if (!$(this).hasClass('disabled')){
$('#edit-rules-submitter', form).trigger('click');
}
});
$('.auto-populate', form).click(function(){
$('.ahah-populate-submitter', form).trigger('click');
});
/**
* Setup the delete buttons next to the fields
*/
function sCourseMaterialsSetupRemove(parentClass, form){
$('.' + parentClass + ':not(.sCourseMaterialsFolders-processed)',
form).addClass('sCourseMaterialsFolders-processed').each(function(){
var parent = $(this);
$('.delete-btn', parent).click(function(){
var id = parent.attr('id');
var existing = parent.hasClass('existing') ? '1' : '0';
//record the rid of the deleted field and if the deleted field was na
existing rule
$('.rule-remove-existing', form).val(existing);
$('.rule-remove-id', form).val(parseInt(id));
//trigger the deletion
$('.ahah-remove-rule', form).click();
});
});
}
/**
* Setup form behavior for the rule fields
*/
function sCourseMaterialsSetupRules(form, existing, ahahCallback){
if(typeof ahahCallback == 'undefined')
ahahCallback = false;
$('.rule-item:not(.sCourseMaterialsFolders-processed)',
form).addClass('sCourseMaterialsFolders-processed').each(function(){
var itemField = $(this);
var ruleContext = itemField.parents('.rule-option:first');
var id = ruleContext.attr('id');
var firstChange = true;
//hide and/or show options that are allowed for the current type
var selected = $('option:selected', ruleAction).val();
ruleAction.empty();
$('.action-options option').each(function(){
var option = $(this);
var action = option.val();
//skip the comment rule if comment is disabled
if(action == constants.comment && commentsDisabled[item] == true){
return true;
}
//skip the submit/score at least rule if submit (eg. dropbox) is
disabled
if((action == constants.submit || action == constants.score) &&
submitDisabled[item] == true){
return true;
}
if($.inArray(parseInt(action), typeOptions[itemType]) > -1){
var clonedOption = option.clone().appendTo(ruleAction);
if(clonedOption.val() == selected){
clonedOption.attr('selected', 'selected');
}
}
});
$('.rule-action:not(.sCourseMaterialsFolders-processed)',
form).addClass('sCourseMaterialsFolders-processed').each(function(){
var actionField = $(this);
var ruleContext = actionField.parents('.rule-option:first');
if ($('body').hasClass('s-enable-mathml')) {
s_renderMath();
}
$('.assignments-container:not(.sCourse-processed)' , context).addClass('sCourse-
processed').each(function(){
setupAssignmentPaging(this);
});
$('.access-code:not(.sCourse-processed)').addClass('sCourse-
processed').each(function(){
var acContext = this;
$('.access-delete', acContext).click(function(){
var path = window.location.pathname.substring(1);
var courseID = path.split('/')[1];
$.ajaxSecure({
url : '/enrollment/code/visibility/course/' + courseID + '/hide',
dataType: 'json',
type: 'GET',
success: function(response, status){
$(acContext).hide();
}
});
});
});
/**
* Fetches and attaches the course reminders block (ungraded submissions,
resubmissions, etc.)
*
* @param {jQuery} $remindersWrapper
* @param {number} [retry] - The number of times to retry the request.
* @param {boolean} [removeIfEmpty] - Whether the reminders block should be
removed if empty.
*/
function getCourseReminders($remindersWrapper, retry, removeIfEmpty) {
var $loading = $remindersWrapper.find('.more-loading');
$loading.show();
$.ajax({
url: '/course/' + courseId + '/course_reminders_ajax' + (retry ? '?retry=' +
retry : ''),
dataType: 'json',
type: 'GET',
success: function(response) {
$loading.hide();
if (response.retry_in) {
// after the specified retry time, retry getting the course reminders
setTimeout(function() {
getCourseReminders($remindersWrapper, retry + 1, removeIfEmpty);
}, response.retry_in);
} else if (response.html) {
$remindersWrapper.html(response.html);
$remindersWrapper.show();
Drupal.attachBehaviors($remindersWrapper);
sCourseSetupTodoList($remindersWrapper);
} else {
if (removeIfEmpty) {
$remindersWrapper.remove();
} else {
$remindersWrapper.show();
$remindersWrapper.find('.reminders-content').html('<div class="empty">'
+ Drupal.t('No current reminders') + '</div>');
}
}
}
});
}
/**
* Loads upcoming events.
*
* @param {jQuery} $courseUpcoming - The "Upcoming" element.
*/
function loadUpcomingEvents($courseUpcoming) {
var path = window.location.pathname.substring(1);
var courseId = path.split('/')[1];
$.ajax({
// pass the current path so that the calendar nav uses the right link (the
current page)
// and also be sure to pass any ?mini arguments for changing the date in the
cal
url: '/course/' + courseId + '/calendar_ajax' +
(window.location.search.length ? window.location.search + '&' : '?') +
'original_q=' + path,
dataType: 'json',
type: 'GET',
success: function(response) {
$courseUpcoming.find('.upcoming-list').html($(response).children());
if (window.location.search.match('mini=')) {
$('#event-calendar', selector).click();
}
// ie 8 is complaining about too much js on the course materials page, skip
using attachBehaviors here
// to cut down on executed javascript on this page
sAttachBehavior('sCourse' , $courseUpcoming);
sAttachBehavior('sCommonInfotip', $courseUpcoming);
$courseUpcoming.show();
}
});
}
$('#course-events:not(.sCourse-processed)' , context).addClass('sCourse-
processed').each(function() {
var $courseUpcoming = $(this);
$courseUpcoming.show();
var upcoming = settingsCourse && settingsCourse.upcoming;
switch (upcoming) {
case DEFER_LOADING:
var $refreshWrapper = $courseUpcoming.find('.upcoming-list .refresh-
wrapper');
$refreshWrapper.find('.refresh-button').on('click', function() {
$refreshWrapper.find('> p').each(function() {
var $element = $(this);
if (!$element.hasClass('more-loading')) {
$element.hide();
}
});
$courseUpcoming.find('.upcoming-list .more-loading').show();
loadUpcomingEvents($courseUpcoming);
});
break;
case DISABLE_LOADING:
break;
default:
$courseUpcoming.find('.upcoming-list .more-loading').show();
loadUpcomingEvents($courseUpcoming);
break;
}
$('#event-calendar', $courseUpcoming).click(function() {
var popup = new Popups.Popup();
var body = '<div id="fcalendar" style="width: 800px;"></div>';
var buttons = {
'popup_cancel': {
title: Drupal.t('Close'),
func: function() { popup.close(); }
}
};
$('div#course-profile-selector:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function() {
var selectorWrapper = $(this);
$('li', selectorWrapper).click(function() {
var selector = $(this);
if (selector.attr('id') == 'selector-materials') {
$("#course-profile-materials").show();
$("#course-profile-updates").hide();
}
if (selector.attr('id') == 'selector-updates') {
$("#course-profile-updates").show();
$("#course-profile-materials").hide();
}
$('li.active', selectorWrapper).removeClass('active');
selector.addClass('active');
});
});
$('div#important-post:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function() {
var importantPostContainer = $(this);
$("a.important-remove-hidden", importantPostContainer).bind("click", function()
{
$(this).parent().parent().find("form").submit();
return false;
});
});
$('.course-info-wrapper:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function() {
var wrapper = $(this);
var moreInfo = $('.course-info-full', wrapper);
var lessInfo = $('.course-info-less', wrapper);
$('.more-link', wrapper).click(function() {
lessInfo.hide();
moreInfo.show();
});
$('.less-link', wrapper).click(function() {
lessInfo.show();
moreInfo.hide();
});
});
$('.notification-settings:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function(){
sUserSetupRealmNotifButton($(this));
});
$('.profile-picture-wrapper:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function(){
var wrapper = $(this);
var link = $('.edit-profile-picture-hover', wrapper);
var pic = $('.profile-picture', wrapper);
var uploader = $('#profile-picture-uploader-wrapper', wrapper);
link.bind('click', function(){
if(uploader.is(':visible')){
uploader.hide();
}
else {
uploader.show();
}
});
$('body').bind('click', function(e){
var target = $(e.target);
if(!target.hasClass('profile-picture-wrapper') && target.parents('.profile-
picture-wrapper').length == 0){
uploader.hide();
}
});
$('li.assignment .action-links-wrapper:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function() {
$(this).sActionLinks( {
hidden : false,
wrapper : '.action-links-wrapper'
});
});
$('#link-sections-wrapper:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function() {
$(this).sActionLinks({hidden : false, wrapper : '#link-sections-wrapper'});
});
$('#s-course-settings:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function() {
$(this).sActionLinks( {
hidden : false,
wrapper : '.action-links-wrapper'
});
});
$('#toolbar-options-wrapper', wrapperObj).addClass('sCourse-
processed').each(function() {
$(this).sActionLinks({hidden : false, wrapper : '#toolbar-options-wrapper'});
});
$('.course-content-action-links', wrapperObj).each(function() {
// initialze add materials drop down menu
$(this).sActionLinks( {
hidden : false,
wrapper : '.action-links-wrapper'
});
Popups.saveSettings();
// The parent of the new popup is the currently active popup.
var parentPopup = Popups.activePopup();
var popupOptions = Popups.options({
ajaxForm: false,
extraClass: 'popups-extra-large popups-insert-library',
updateMethod: 'none',
href: url,
hijackDestination: false,
disableCursorMod: true,
disableAttachBehaviors: false
});
// Launch the cookie preload popup first, then launch app
var cookiePreloadUrl = sCommonGetSetting('s_app', 'cookie_preload_urls', $
(this).data('app_nid'));
if(cookiePreloadUrl) {
sAppMenuCookiePreloadRun($(this).data('app_nid'), cookiePreloadUrl,
function(){
// clear cached launch data since we store cookie preload attempts in
session
sAppLauncherClearCache($(this).data('app_nid'));
sAppMenuCookiePreloadDelete($(this).data('app_nid'));
Popups.openPath(this, popupOptions, parentPopup);
});
}
// launch app popup
else {
Popups.openPath(this, popupOptions, parentPopup);
}
});
});
});
$('.past-assignments-grading-period .clickable:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function() {
$(this).click(function() {
var periodAssignments = $('>ul', $(this).parent());
if (periodAssignments.css('display') == 'none') {
$(this).parent().removeClass('assignments-hidden');
periodAssignments.show();
}
else {
$(this).parent().addClass('assignments-hidden');
periodAssignments.hide();
}
});
});
$('.s-course-editor-wrapper .save-and-continue-btn:not(.sCourse-
processed)',context).addClass('sCourse-processed').bind('click', function() {
var wrapper = $(this).parents('.s-course-editor-wrapper');
var form = wrapper.find('form');
form.submit();
return false;
});
// materials shortcut
$('#menu-s-main a.course-materials-left-menu:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function() {
var menuLink = $(this),
menuLinkArrow = $('.materials-dropdown-arrow', menuLink),
menuDropdown = $("#course-materials-dropdown"),
menuDropdownHasContent = menuDropdown.find('.item-list ul').length > 0;
menuLink.after(menuDropdown);
// check if the associated menu dropdown has content before showing the menu
link arrow
// the arrow is rendered in a title callback so it doesn't have any knowledge
of whether there should be content or
// not in the backend
if(menuDropdownHasContent){
menuLinkArrow.removeClass('hidden').click(function() {
var arrow = $(this);
var hidden = menuDropdown.is(':hidden');
if (hidden) {
$('body').bind('click.sCourseMaterialsHandle',function(e) {
var target = $(e.target);
var hidden = menuDropdown.is(':hidden');
if (!hidden && target != menuDropdown && target.parents('#course-
materials-dropdown').length == 0) {
$('.materials-dropdown-arrow', menuLink).click();
}
});
menuDropdown.show()
$(this).addClass('active');
}
else {
menuDropdown.hide();
$(this).removeClass('active');
$('body').unbind('click.sCourseMaterialsHandle');
}
return false;
});
}
else{
menuLinkArrow.hide();
}
});
$('#right-column .reminders-wrapper:not(.sCourse-processed)',
context ).addClass('sCourse-processed').each(function(){
sCourseSetupTodoList( $(this) );
});
/**
* TRACK ANY COURSE SPECIFIC GENERIC POST VIEWS. THIS BEHAVIOR ONLY NEEDS TO BE
ATTACHED
* ONCE SO PUT IT OUTSIDE OF DRUPAL.ATTACHBEHAVIORS()
*/
if (!$('body').hasClass('anonymous')) {
$('.gen-post-link:not(.sCourse-processed)', context).addClass('sCourse-
processed').each(function() {
var genPostLink = $(this);
// .one() adds a single-use binding, allowing a .click() on a successful ajax
request to trigger the link properly
genPostLink.one('click', function(e) {
if (!genPostLink.hasClass('embed-cover') && !genPostLink.hasClass('media-
player-popup')) {
e.preventDefault();
}
sCourseTrackGenericPostView($(this));
});
});
}
/**
* This block processes the handling of materials filter views.
*
* Behaviors include:
* Handle updating of the content body when filters are changed and AJAX'ed in
* Handle the binding of popups behaviors on gear/action links menus
* Handle the "more link" and its infinite scrolling behavior for pagination
*
* @param object context
*/
(function(context){
// this is the main body of the full view of the course materials listing
$('.s-js-course-materials-full:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function(){
var wrapperObj = $(this),
actionLinksWrapper = wrapperObj.find('.s-js-materials-filter-wrapper
.action-links-wrapper'),
actionLinksLabel = actionLinksWrapper.find('.action-links-unfold-text'),
optionsMenu = wrapperObj.find('.s-js-options-action-links-outer'),
selectedFilterItem = null;
/**
* Update the option menu based on the provided current
* Show only the option specified by the filter argument
*
* @param string filter
*/
function updateOptionMenu(filter){
optionsMenu.find('.s-js-content-option').hide().addClass('content-option--
hidden');
optionsMenu.find('.s-js-content-option-' +
filter).show().removeClass('content-option--hidden');
if (optionsMenu.find('li:not(.content-option--hidden)').length) {
optionsMenu.show();
} else {
optionsMenu.hide();
}
}
/**
* Change the selected filter to provided filter.
* This only updates the UI of the actual filter controls and not the content
of the page.
*
* @param string filter
*/
function changeSelectedFilter(filter){
selectedFilterItem = actionLinksWrapper.find('.s-js-material-filter-' +
filter.replace(/_/g, '-'));
var currentFilter = actionLinksWrapper.data('filter'),
newFilter = selectedFilterItem.data('filter');
if(currentFilter != newFilter){
actionLinksWrapper.data('filter', newFilter);
actionLinksWrapper.find('.selected').removeClass('selected');
selectedFilterItem.closest('li').addClass('selected');
actionLinksLabel.text(selectedFilterItem.text());
}
}
/**
* Refresh the content of the current filter stored in currentFilterInfo.
*
* Events called:
* sCourseMaterialsFilterChanged({filter: newFilter, url: newUrl})
* called when a different filter is selected and page content should be
updated
*/
function refreshContent(){
if(selectedFilterItem && selectedFilterItem.length){
var filter = getHashFilter(),
oldFilter = selectedFilterItem.data('filter');
if(filter != oldFilter){
changeSelectedFilter(filter);
}
sAngular.trigger('sCourseMaterialsFilterChanged', {
filter: selectedFilterItem.data('filter'),
url: selectedFilterItem.attr('href')
});
}
}
/**
* Fetch the content from the url of the provided filter
*
* Events called:
* sCourseMaterialsFilterContentRefreshed(filter)
*
* @param string url
* @param string filter
*/
function getContent(url, filter){
var activeLoaderKey = 's-js-course-materials-full';
Drupal.attachBehaviors(wrapperObj.find('.s-js-materials-body'));
sAngular.trigger('sCourseMaterialsFilterContentRefreshed', filter);
},
complete: function(){
sToggleActiveLoader(activeLoaderKey);
}
});
}
/**
* Parse the filter from the current hash state
*
* @return string
*/
function getHashFilter(){
var filter = 'all',
matches = /filter_type=(\w+)/.exec(window.location.hash);
if(matches){
filter = matches[1];
}
return filter;
}
/**
* Update the current hash state to the provided filter
*
* @param string filter
*/
function setHashFilter(filter){
window.location.hash = '!filter_type=' + filter;
}
$(window).on('hashchange', function(e){
refreshContent();
});
getContent(url, filter);
});
// keep track of which filters have already bound the popups configs so it
doesn't get called again
var popupsConfigBound = {};
// these popups config are used in many of the material type filters
var commonPopupsConf = {
'.materials-item-actions .action-publish a': {extraClass: 'popups-small
popups-publish'},
'.materials-item-actions .action-unpublish a': {extraClass: 'popups-small
popups-unpublish'},
'.materials-item-actions .action-delete a': {extraClass: 'popups-small
popups-action-item'},
'.materials-item-actions .action-move a': {extraClass: 'popups-small
popups-action-item'},
'.materials-item-actions .action-copy a': {extraClass: 'popups-large
popups-copy popups-action-item'},
'.materials-item-actions .action-distribute a': {extraClass: 'popups-large
popups-copy popups-action-item'},
'.materials-item-actions .action-library-save a': {extraClass: 'popups-
large'},
'.materials-item-actions .action-edit-properties a': {extraClass: 'popups-
large'},
'.materials-item-actions .s-js-locked-resource-link-diff': {extraClass:
'popups-extra-large linked-content-diff-view'},
'.materials-item-actions .s-js-unlink-node': {extraClass: 'popups-small
unlink-popup'},
'.materials-item-actions .action-edit-link a': {extraClass: 'popups-large
popups-action-item popups-add-link'}
};
// For CSM Common Assessment materials (only) we want to show an extra large
popup
var isCSM = !!sCommonGetSetting('s_realm_info', 'csm_realm_id');
if (isCSM) {
// SGY-22549
// We want to conditionally show a larger popup for Managed Assessments
edit
// For Non-MA we need direct child ops '>' in order to prevent both
selectors from being applied to the case where a MA is in a folder
// (because the folder also has a .dr element)
commonPopupsConf['.dr:not(.type-common-assessment) > * > .materials-item-
actions .action-edit a'] = {extraClass: 'popups-large popups-action-item'};
commonPopupsConf['.type-common-assessment .materials-item-actions .action-
edit a'] = {extraClass: 'popups-extra-large popups-action-item'};
} else {
commonPopupsConf['.materials-item-actions .action-edit a'] = {extraClass:
'popups-large popups-action-item'};
}
// these popups config are folder-specific, and should only be used when
folders are available
var folderPopupsConf = {
'.materials-folder-actions .action-edit a': {
extraClass: 'popups-large popups-action-item',
targetSelectors: ['#s-course-materials-folder-contents-form']
},
'.materials-folder-actions .action-publish a': {
extraClass: 'popups-small popups-action-item',
targetSelectors: ['#s-course-materials-folder-contents-form']
},
'.materials-folder-actions .action-unpublish a': {
extraClass: 'popups-small popups-action-item',
targetSelectors: ['#s-course-materials-folder-contents-form']
},
'.materials-folder-actions .action-delete a': {
extraClass: 'popups-small popups-action-item',
updateMethod: 'reload'
},
'.materials-folder-actions .action-copy a': {
extraClass: 'popups-large popups-action-item popups-copy',
disableInputFocus: true,
doneTest: 'course/.+?/materials($|[^\/].*)',
updateMethod: 'reload'
},
'.materials-folder-actions .action-distribute a': {
extraClass: 'popups-large popups-copy popups-action-item'
},
'.materials-folder-actions .action-move a': {
extraClass: 'popups-small popups-action-item',
targetSelectors: ['#course-profile-materials-folders'],
updateMethod: 'reload',
doneTest: '.+'
},
'.materials-folder-actions .action-library-save a': {
extraClass: 'popups-large popups-action-item',
hijackDestination: true,
updateSource: 'final',
doneTest: 'course/.+?/materials.*',
updateMethod: 'reload',
},
'.materials-folder-actions .action-completion a': {
extraClass: 'popups-large popups-action-item student-completion-popup'
}
};
switch(filter){
case 'all':
popupsConf = $.extend({}, folderPopupsConf, commonPopupsConf, {
'.materials-item-actions .action-convert a': {extraClass: 'popups-
small'},
'.materials-item-actions .action-edit-xlarge a': {extraClass:
'popups-extra-large popups-action-item'},
'.materials-item-actions .action-rename a': {extraClass: 'popups-
small popups-action-item'},
'.materials-item-actions .action-remove a': {extraClass: 'popups-
small popups-action-item'},
'.materials-item-actions .action-package-delete a': {extraClass:
'popups-small popups-package-delete'},
'.materials-item-actions .action-edit-package-props a':
{extraClass: 'popups-extra-large popups-package-edit-props'},
'.materials-item-actions .action-edit-launch-props a': {extraClass:
'popups-small popups-package-edit-launch-props'},
'.materials-item-actions .action-view-progress a': {extraClass:
'popups-extra-large popups-package-progress'},
'.materials-item-actions .action-copy a': {
extraClass: 'popups-large popups-action-item popups-copy',
disableInputFocus: true,
doneTest: 'course/.+?/materials($|[^\/].*)',
updateMethod: 'reload',
},
'.materials-item-actions .action-move a': {
extraClass: 'popups-small popups-action-item',
targetSelectors: ['#course-profile-materials-folders'],
updateMethod: 'reload',
doneTest: '.+'
},
'.materials-item-actions .action-library-save a': {
extraClass: 'popups-large popups-action-item',
hijackDestination: true,
updateSource: 'final',
doneTest: 'course/.+?/materials.*',
updateMethod: 'reload',
}
});
break;
case 'assessments':
case 'assignments':
case 'discussion':
popupsConf = $.extend({}, commonPopupsConf, {
'.view-ind-assign': {extraClass: 'popups-small',
'disableInputFocus': true}
});
break;
case 'scorm':
popupsConf = $.extend({}, commonPopupsConf, {
'.materials-item-actions .action-edit-package-props a':
{extraClass: 'popups-extra-large popups-package-edit-props'},
'.materials-item-actions .action-edit-launch-props a': {extraClass:
'popups-small popups-package-edit-launch-props'},
'.materials-item-actions .action-view-progress a': {extraClass:
'popups-extra-large popups-package-progress'}
});
break;
case 'web':
case 'album':
popupsConf = $.extend({}, commonPopupsConf);
break;
case 'pages':
popupsConf = $.extend({}, commonPopupsConf, {
'.materials-item-actions .action-edit-xlarge a': {extraClass:
'popups-extra-large'},
'.materials-item-actions .action-add-to-folder a': {extraClass:
'popups-small'}
});
break;
case 'documents_files':
case 'documents_links':
case 'documents_external_tools':
popupsConf = $.extend({}, commonPopupsConf, {
'.materials-item-actions .action-rename a': {extraClass: 'popups-
small popups-action-item'},
'.materials-item-actions .action-copy a': {
extraClass: 'popups-large popups-action-item popups-copy',
doneTest: 'course/.+?/materials($|[^\/].*)'
}
});
break;
case 'common_assessments':
case 'course_assessment':
popupsConf = $.extend({}, commonPopupsConf, {
'.materials-item-actions .action-edit a': {hidden: false,
extraClass: 'popups-large popups-action-item', wrapper: '.action-links-wrapper'},
'.materials-item-actions .action-publish a': {hidden: false,
extraClass: 'popups-small popups-publish', wrapper: '.action-links-wrapper'},
'.materials-item-actions .action-move a': {hidden: false,
extraClass: 'popups-small popups-action-item', wrapper: '.action-links-wrapper'},
});
break;
}
if(!$.isEmptyObject(popupsConf)){
var selectorPrefix = filter == 'all' ? '.s-js-full-view' : '.s-js-
filtered-view-' + filter.replace(/_/g, '-'),
globalConfig = sCommonGetSetting('popups', 'links') || {};
$.each(popupsConf, function(selector, opts){
// don't bind anything that is already bound in the global
configuration
if(typeof globalConfig[selector] == 'undefined'){
opts = $.extend({}, defaultPopupsOpts, opts);
wrapperObj.on('click', selectorPrefix + ' ' + selector, opts,
function(e){
e.preventDefault();
return Popups.clickPopupElement(this, Popups.options(e.data));
});
}
});
}
}
});
// the switcher that allows the user to access the filtered materials list
// associated with theme('s_course_materials_list_view_switcher')
actionLinksWrapper.sActionLinks({
hidden: false
});
// these more links appear in the filtered views, which will cause infinite
scrolling to occur
$('.s-js-materials-filter-more-link:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function(){
var linkObj = $(this),
loadMoreKey = 's-js-materials-filter-more';
linkObj.sInfiniteScroll({
loadMore: function(){
linkObj.trigger('click');
}
}).on('click', function(e){
e.preventDefault();
var url = linkObj.attr('href'),
wrapperObj = linkObj.closest('.s-js-course-materials-full'),
currentMaterialsBody = wrapperObj.find('.s-js-materials-body');
sToggleActiveLoader(loadMoreKey, linkObj);
$.ajax({
url: url,
success: function(data){
var newContentObj = $(data),
newFilteredList = newContentObj.find('.s-js-filtered-view-list'),
newMoreLink = newContentObj.find('.s-js-materials-filter-more-
link');
if(newFilteredList.length){
currentMaterialsBody.find('.s-js-filtered-view-
list').append(newFilteredList.children());
}
if(newMoreLink.length){
currentMaterialsBody.append(newMoreLink);
}
// wait for the content to be in the DOM before attaching behaviors
Drupal.attachBehaviors(currentMaterialsBody);
},
complete: function(){
sToggleActiveLoader(loadMoreKey);
linkObj.remove();
}
});
});
});
// these are gear icons found in all of the materials filtered views
$('.s-js-materials-filtered-actions:not(.sCourse-processed)',
context).addClass('sCourse-processed').each(function(){
$(this).find('.action-links-wrapper').sActionLinks({
hidden: false
});
});
}(context));
}
function sCourseEnableContentReorder(tbodyElement) {
var allowRootLevelItems = sCourseMaterialsDisplayIsWithoutIndex();
// In the planner view, the left and right sortables shouldn't be connected
var plannerView = $('#course-profile-materials-folders').hasClass('planner-
view');
if(plannerView){
// Left column
if(tbodyElement.parent().attr('id') == 'folder-contents-table'){
var connectWith = false;
}
// Right column
else {
var connectWith = '.subtree-folder-contents-table>tbody, .planner-right-
contents-table>tbody';
}
}
// By default, connect all possible sortables
else {
var connectWith = '#folder-contents-table>tbody, .subtree-folder-contents-
table>tbody, .planner-right-contents-table>tbody';
}
tbodyElement.sortable( {
forcePointerForContainers: true, // use the mouse cursor to determine whether
we've travelled between containers
items: '> tr:not(.materials-row-add)',
handle: '.folder-title, .item-title, .document-body-title, .item-icon, .inline-
icon',
connectWith: connectWith,
cancel: '.materials-row-add, .type-empty',
delay: 500,
helper : function(e, currentItem) {
var helper = currentItem.clone(),
subtree = currentItem.find('.folder-subtree');
helper.find('.folder-subtree').hide();
helper.find('.s-js-folder-description').hide();
$('#folder-contents-table').addClass('sorting');
// When moving starts, store the associated 'add materials' row
// along with the row being moved
var addRow = ui.placeholder.next('.materials-row-add');
if(addRow.length){
ui.item.data('sCourseReorderRowAdd', addRow);
}
},
stop : function(e, ui) {
$('#folder-contents-table').removeClass('sorting');
},
change: function (e, ui){
var isCurrentItemFolder = ui.item.hasClass('material-row-folder');
var isDestinationStudentChoice = ui.placeholder.parents('.material-row-
student-choice').length > 0;
if(!allowRootLevelItems){
// Don't allow non-folders to be dropped on the top level
// Hide the placeholder if this is the case
var queryParsed = getQueryParams(window.location.search);
var topParentFid = queryParsed.f || 0;
if(topParentFid == 0 && !isCurrentItemFolder && $
(ui.placeholder).closest('table').attr('id') == 'folder-contents-table'){
$(ui.placeholder).hide();
return;
}
}
$(ui.placeholder).show();
},
receive: function (e, ui){
var isCurrentItemFolder = ui.item.hasClass('material-row-folder');
var isDestinationStudentChoice = ui.item.parents('.material-row-student-
choice').length > 0;
// Redirect the user to the editor for the newly created topic
// Can't do this the traditional way because of menu_set_active_item
function sCourseTopicGotoEditor(data, options, element) {
location.href = Drupal.settings.basePath + data.js.setting.popups.originalPath;
return false;
}
function setupAssignmentPaging(context){
$('.gitem-pager').each(function(){
var parent = $(this);
var path = window.location.pathname.substring(1);
var courseID = path.split('/')[1];
$('a', this).unbind('click').click(function(e){
e.preventDefault();
periodID = parent.attr('id');
var page = $(this).attr('href').split('page=');
page = page[1];
var count = $('.pager-count', parent).val();
gradeItemType = parent.hasClass('assessment') ? 'assessment' : 'grade-item';
parent.hide();
parent.after('<img src="/sites/all/themes/schoology_theme/images/ajax-
loader.gif" class="loader" alt="' + Drupal.t('Loading') + '" />');
var baseURL = '/course/' + courseID + '/assignment_page/' + periodID + '/' +
gradeItemType;
if(parent.parents('.past-assignments-grading-period').length > 0){
baseURL += '/past';
}
$.ajax({
url: baseURL + '?page=' + page + '&count=' + count,
dataType: 'json',
type: 'GET',
success: function(response, status){
var liContext = parent.parents('li:first');
$('ul:first', liContext).empty();
var responseHtml = $(response.html);
$('ul:first', liContext).append(responseHtml);
parent.siblings('.loader').remove();
parent.replaceWith(response.pager);
setTimeout(function(){ setupAssignmentPaging(context); }, 500);
Drupal.attachBehaviors(document);
}
});
});
});
}
/**
* It is important, especially in the course context to track viewing on inline
content like embeds - this function simply fires off a request to do so
*/
function sCourseTrackGenericPostView(link){
var nid = link.attr('id').split('-').pop();
var realmId = sCommonGetSetting('s_realm_info', 'csm_realm_id');
if(!realmId){
realmId = Drupal.settings.s_realm_info.realm_id;
}
var action = '/' + Drupal.settings.s_realm_info.realm + '/' + realmId +
'/materials/document/' + nid;
var ajaxOpts = {
url : '/stats/manual-tracker/trackPageView',
type : 'POST',
data : {'action_url' : action}
};
if(!link.hasClass('embed-cover')) {
/**
* the backside of only trigger a link after a successful request is for
targeted links.
* targeted links that are automatically triggered out of direct access usually
are treated as a pop-up behaviour.
*
* Those are the expected results for an anchor tab with `target="_blank"`
* If you click it, it should open in a new tab.
* If you click it and the JS trigger action opens a link, it should open in a
new tab.
* If you click it and the JS trigger action does something and then open a
link, it will behave as a pop-up (and may or may not be blocked by browsers)
* If you click it and the JS trigger action does something and then the link
is triggered afterwards (like a success from an ajax request), this will surely be
blocked by pop-up blocker.
*
* The latter is the behavior we're having at this moment with actual changes
*/
ajaxOpts.success = function () {
link[0].click();
};
}
$.ajaxSecure(ajaxOpts);
}
// Fire off the saving functionality 5 seconds after the last reorder
sCourseSaveContentTableWeightsTimeout &&
clearTimeout(sCourseSaveContentTableWeightsTimeout);
sCourseSaveContentTableWeightsTimeout =
setTimeout(sCourseSaveContentTableWeightsTimeoutHelper, 2000);
function sCourseSaveContentTableWeightsTimeoutHelper(){
// Get all affected tables
var droppedRow = $('#course-profile-materials .ui-sortable.ui-sortable-changed
.ui-sortable-item-changed').removeClass('ui-sortable-item-changed');
var toSave = $('#course-profile-materials .ui-sortable.ui-sortable-
changed').removeClass('ui-sortable-changed');
// If for some reason we get here and nothing has changed, don't do anything
if(!toSave.length){
return;
}
// Send to server
var path = window.location.pathname.substring(1);
var courseID = path.split('/')[1];
$.ajaxSecure({
url : '/course/' + courseID + '/materials/reorder',
data: {updates: updates, root_fid: topParentFid, dropped_item_id:
droppedItemId},
type: 'POST',
success: function(){
$('#folder-reorder-save-loader').remove();
},
error: function(){
$('#folder-reorder-save-loader').remove();
}
});
}
/**
* Check whether the current course materials listing is the view without the
materials index
* The "no index" view allows materials items to be placed in the root level of the
materials
*
* @return bool
*/
function sCourseMaterialsDisplayIsWithoutIndex(){
var displayMode = sCommonGetSetting('s_course', 'display_mode');
return displayMode && displayMode == sCommonGetSetting('s_course',
'display_mode_without_index');
}
;(function () {
/**
* Prevent this from being declared multiple times
*/
if (window._initPendo) {
return;
}
/**
* Defines a method on `window` to initialize the Pendo.io application
* @see https://app.pendo.io/admin/settings
* @param {Object} visitor A subset of the user object
* @param {Object} account Some school info
* @param {String} apiKey The provided API key
*/
window._initPendo = function(visitor, account, apiKey) {
// Prevent pendo from being initialized multiple times
if (window._pendoInitialized) {
return;
}
Drupal.behaviors.sAppLauncher = function(context) {
Drupal.settings.s_app = Drupal.settings.s_app || {};
if(!quickInstallAutoLaunchClicked && typeof Drupal.settings.s_app != 'undefined')
{
if(Drupal.settings.s_app.auto_launch_quick_install){
$('.app-quick-installer-popup').click();
quickInstallAutoLaunchClicked = true;
}
}
$("#schoology-app-loader:not(.sAppLauncher-processed)",
context).addClass('sAppLauncher-processed').each(function(){
if(Drupal.settings.s_app.launcher){
var params = sAppLauncherGetParams({type: 'standard', isImport: false, url:
location.href});
var launchKey = params.appNid + '-' + params.realm +'-' + params.realmID;
if(Drupal.settings.s_app.launcher.hasOwnProperty(launchKey)) {
var contentWrapper = $('#content-wrapper');
var csmSectionToggle = $('#csm-section-toggle');
var launchSettings = Drupal.settings.s_app.launcher[launchKey];
var output = Drupal.theme('sAppContainer', launchSettings.type,
launchSettings.url, launchSettings.cookie_required,
csmSectionToggle.length ? csmSectionToggle.html() : '');
contentWrapper.html(output);
sAttachBehaviors(['sAppLauncher'], contentWrapper);
sAppRegisterChannelListenerForUnsavedChanges(launchSettings.app_origin);
}
}
$(this).remove();
});
$("#schoology-app-container:not(.sAppLauncher-processed)",
context).addClass('sAppLauncher-processed').each(function(){
// Resize the iframe that contains the app so it takes up the maximum height
remaining in the viewport affter
// taking into account the header (#header) and the breadcrumbs area (#center-
top)
var appWindowObj = $(this);
$(window).resize(function() {
var new_height = $(this).height();
$('#header, #center-top:visible').each(function() {
new_height -= $(this).outerHeight();
});
appWindowObj.height(new_height);
}).trigger('resize');
//hide the dropdown for courses, groups, and resources if the app iframe is
clicked
var bound = false;
$('.primary-activities .clickable').each(function(){
$(this).click(function(){
if(!bound){
var iframeMouseOver = false
if($.browser.msie && $.browser.version.charAt(0) == 8){
document.onfocusout = function(){ //need to use document.onfocusout for
IE8
if(iframeMouseOver){
$('.activities-dropdown-wrapper').hide();
bound = false;
}
};
}
else{
$(window).bind('blur.sAppLauncher', function(){
if(iframeMouseOver){
$('.activities-dropdown-wrapper').hide();
$(window).unbind('blur.sAppLauncher');
bound = false;
}
});
}
appWindowObj.bind('mouseover',function(){
iframeMouseOver = true;
});
appWindowObj.bind('mouseout',function(){
iframeMouseOver = false;
});
}
bound = true;
});
});
/**
* s_tinymce insert resource app content form
*/
$('#s-js-tinymce-resources-insert-form-wrapper:not(.sTinymceResourcesInsert-
processed)', context)
.addClass('sTinymceResourcesInsert-processed').each(function()
{
var contentEmbedForm = $('#s-tinymce-resources-insert-form', $(this));
var appNid = parseInt( $('#edit-app-nid', contentEmbedForm).val() );
var appType = $('#edit-app-type', contentEmbedForm).val();
var realm = $('#edit-realm', contentEmbedForm).val();
var realmId = parseInt( $('#edit-realm-id', contentEmbedForm).val() );
var folderId = parseInt( $('#edit-folder-id', contentEmbedForm).val() );
var fWeight = parseInt( $('#edit-fweight', contentEmbedForm).val() );
if(cookieRequired) {
template += '<div class="cookie-warning">';
template += '<p class="app-launch-message">'+ Drupal.t('This app must be
launched by clicking on its link on the left menu.') +'</p>';
template += '</div>';
return template;
}
if(launchType == 2) {
template += '<div id="schoology-app-container" align="center">';
template += '<p class="app-launch-message">'+ Drupal.t('We attempted to launch
your app in a new window, but a popup blocker is preventing it from opening. Please
disable popup blockers for this site.') +'</p>';
template += '<a class="link-btn s-js-launch-button" href="'+ launchUrl +'">'+
Drupal.t('Launch App') +'</a>';
template += '</div>';
}
else {
if(csm_toggle){
template += '<div id="schoology-csm_toggle" align="left">';
template += '<p>'+ csm_toggle +'</p>';
template += '</div>';
}
template += '<iframe id="schoology-app-container" frameborder="0" width="100%"
height="800" src="'+ launchUrl +'" name="schoology-app-container"></iframe>';
}
return template;
};
function renderApp(launchSettings) {
var url = launchSettings.url;
if(Drupal.settings.s_app.launcher.hasOwnProperty(launchKey)) {
renderApp( Drupal.settings.s_app.launcher[launchKey] );
return;
}
sToggleActiveLoader('sAppLauncher', target);
$.getJSON(params.settingsURI, function(data) {
sToggleActiveLoader('sAppLauncher');
Drupal.settings.s_app.launcher[launchKey] = data.body;
renderApp( Drupal.settings.s_app.launcher[launchKey] );
});
}
function sAppLauncherGetParams(app) {
var q_params = {
import_view: app.isImport ? 1 : 0
};
var url = '';
var appNid = 0;
var appRealm = '';
var appRealmID = 0;
var qParams = {};
if(app.type == 'resources') {
var parsed_url = app.url.match(/\/([0-9]+)\/run(\?)?(.*)/);
appNid = parsed_url[1];
appRealm = 'user';
appRealmID = Drupal.settings.s_common.user.uid;
url += appNid + '/' + app.type;
parse_str(parsed_url[3], q_params);
}
else if(app.type == 'standard' || app.type == 'lti') {
if(app.url.indexOf('/apps/school_apps/') != -1) {
var parsed_url = app.url.match(/\/apps\/school_apps\/([0-9]+)\/configure(\?)?
(.*)/);
appNid = parsed_url[1];
appRealm = 'school';
appRealmID = Drupal.settings.s_common.user.school_nid;
}
else {
var parsed_url = app.url.match(/\/([0-9]+)\/run\/([a-z]+)\/([0-9]+)(\?)?
(.*)/);
appNid = parsed_url[1];
appRealm = parsed_url[2];
appRealmID = parsed_url[3];
qParams = getQueryParams(app.url);
}
url += appNid + '/' + app.type + '/' + appRealm + '/' + appRealmID;
if(typeof app.isRTE != 'undefined' && app.isRTE == true){
url += '&from=rte';
if(qParams && typeof qParams.f != 'undefined') {
url += '&f=' + String(qParams.f);
}
if(url == '') {
return false;
}
// we need to preserve the query strings for resources apps (import view, dropbox
..etc)
var url_params = [];
if(app.type == 'resources') {
$.each(q_params, function(key, value) {
url_params.push(key + "=" + encodeURIComponent(value));
});
url += '?' + url_params.join('&');
}
url = '/iapi/app/launcher/' + url;
return {
appNid: appNid,
realm: appRealm,
realmID: appRealmID,
settingsURI: url
};
}
function sAppLauncherClearCache(appNid) {
// clear cached launch data since we store cookie preload attenpts in session
for(var appKey in Drupal.settings.s_app.launcher) {
if(appKey.split('-')[0] == appNid) {
delete Drupal.settings.s_app.launcher[appKey];
break;
}
}
}
/**
* Register an "are you sure you want to leave" if there are unsaved changes
* we want to make sure that the frame as has loaded before registering the app
*
* Sample usage of this behavior in an app:
* ```
<script src="https://app.schoology.com/sites/all/misc/jschannel.js"></script>
<script
src="https://app.schoology.com/sites/all/misc/schoology_api.js"></script>
<script type="text/javascript">
var schoologyApi = SchoologyApi.create({
version: 'v1',
context: 'app'
});
schoologyApi.setHasUnsavedChanges(true);
</script>
* ```
* Note that the app should also register its own onbeforeunload for when the app
* is opened outside of an iframe
*/
function sAppRegisterChannelListenerForUnsavedChanges(app_origin) {
sAppIframeLoadingAppUrl(function() {
var app = SchoologyApp.register('schoology-app-container', app_origin, 'app');
app.setHasUnsavedChanges(function(event, params){
var hasUnsavedChanges = params.hasOwnProperty('hasUnsavedChanges') &&
params.hasUnsavedChanges;
if (hasUnsavedChanges) {
window.onbeforeunload = function (event) {
// Based on https://developer.mozilla.org/en-
US/docs/Web/Events/beforeunload, only IE shows a custom
// message. All other browser use generic text.
event.returnValue = Drupal.t('You have not saved your work. Are you sure
you want to leave this page?');
};
} else {
window.onbeforeunload = null;
}
});
});
}
/**
* Calls the given callback once the app iframe is loading the actual app URL
* (as opposed to the local SAML initialization endpoints). This is helpful with
* the postMessage API since a specific origin is required when building the
messaging channel.
* Initialization will otherwise fail if it starts and the iframe still points to
* schoology's own SAML iniialization URLs.
*
* - App Launch
* - IFrame loads app.schoology.com/apps/login/saml/initial
* - That page redirects to the actual app URL (e.g. www.example.com/app)
* <-- This is when we want JSChannel to be initialized
* - Browser loads www.example.com/app
*
* @param {func} callback
*/
function sAppIframeLoadingAppUrl(callback) {
// While the iframe src is still on the same domain, the app is still going
// through the login workflow. Once the iframe src points to a different domain,
// the browser will throw a DOMException since it blocks accessing cross-origin
frames.
// At that point, we know that the app has begun to load and we can now execute
the callback
try {
// This will succeed if it's still logging in
// and throw an exception if it's cross domain
var iframe = document.getElementById('schoology-app-container');
var iframeSrc = iframe.contentWindow.location.href;
window.setTimeout(function() { sAppIframeLoadingAppUrl(callback) }, 1000);
} catch (e) {
callback();
}
}
;Drupal.behaviors.sAppMenu = function(context) {
if(!isResouceApp) {
appNid = link.parents('.app-link-wrapper').attr('id').split('-')[2];
appUrl = link.attr('href');
}
else {
appNid = link.parents('.s-app-resource-app').attr('id').split('-')[1];
appUrl = link.attr('href');
}
window.open(cookiePreloadUrl,'cookiepreloader','width=100,height=100,toolbar=0,menu
bar=0,location=0,status=0,scrollbars=0,resizable=0,left=0,top=0');
// After a short moment to allow the popup to load, continue launching the app
setTimeout(afterPreloadCallback, 800);
}
function sAppMenuCookiePreloadSet(data) {
Drupal.settings.s_app = Drupal.settings.s_app || {};
Drupal.settings.s_app.cookie_preload_urls =
Drupal.settings.s_app.cookie_preload_urls || {};
for(var appKey in data) {
Drupal.settings.s_app.cookie_preload_urls[appKey] = data[appKey];
}
}
function sAppMenuCookiePreloadDelete(appNid) {
delete Drupal.settings.s_app.cookie_preload_urls[appNid];
};/*
* jQuery Form Plugin
* version: 2.25 (08-APR-2009)
* @requires jQuery v1.2.2 or later
* @note This has been modified for ajax.module
* Examples and documentation at: http://malsup.com/jquery/form/
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c
%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String))
{while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function()
{return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)
+'\\b','g'),k[c]);return p}(';(5($){$.B.1s=5(u){2(!4.G){R(\'1b: 2M 9 2N - 2O 2P
1t\');6 4}2(S u==\'5\')u={T:u};3 v=4.14(\'1c\')||1d.2Q.2R;v=(v.2S(/^([^#]+)/)||[])
[1];v=v||\'\';u=$.1n({1e:v,H:4.14(\'1u\')||\'1Q\'},u||{});3 w={};4.L(\'C-1R-1S\',
[4,u,w]);2(w.1T){R(\'1b: 9 1U 1o C-1R-1S L\');6 4}2(u.1v&&u.1v(4,u)===I){R(\'1b: 9
1f 1o 1v 1V\');6 4}3 a=4.1w(u.2T);2(u.J){u.O=u.J;K(3 n 1x u.J){2(u.J[n]2U 15){K(3 k
1x u.J[n])a.D({7:n,8:u.J[n][k]})}E a.D({7:n,8:u.J[n]})}}2(u.1y&&u.1y(a,4,u)===I)
{R(\'1b: 9 1f 1o 1y 1V\');6 4}4.L(\'C-9-1W\',[a,4,u,w]);2(w.1T){R(\'1b: 9 1U 1o C-
9-1W L\');6 4}3 q=$.1z(a);2(u.H.2V()==\'1Q\'){u.1e+=(u.1e.2W(\'?\')>=0?\'&\':\'?\')
+q;u.J=F}E u.J=q;3 x=4,V=[];2(u.2X)V.D(5(){x.1X()});2(u.2Y)V.D(5(){x.1Y()});2(!
u.16&&u.17){3 y=u.T||5(){};V.D(5(a){$(u.17).2Z(a).P(y,1Z)})}E
2(u.T)V.D(u.T);u.T=5(a,b){K(3 i=0,M=V.G;i<M;i++)V[i].30(u,[a,b,x])};3 z=$
(\'W:31\',4).18();3 A=I;K(3 j=0;j<z.G;j++)2(z[j])A=Q;2(u.20||A)
{2(u.21)$.32(u.21,1A);E 1A()}E $.33(u);4.L(\'C-9-34\',[4,u]);6 4;5 1A(){3
h=x[0];2($(\':W[7=9]\',h).G){35(\'36: 37 22 38 39 3a 3b "9".\');6}3 i=$.1n({},
$.23,u);3 s=$.1n(Q,{},$.1n(Q,{},$.23),i);3 j=\'3c\'+(1B 3d().3e());3 k=$(\'<20
3f="\'+j+\'" 7="\'+j+\'" 24="25:26" />\');3 l=k[0];k.3g({3h:\'3i\',27:\'-
28\',29:\'-28\'});3 m={1f:0,19:F,1g:F,3j:0,3k:\'n/a\',3l:5(){},2a:5(){},3m:5()
{},3n:5(){4.1f=1;k.14(\'24\',\'25:26\')}};3 g=i.2b;2(g&&!$.1C++)$.1h.L("3o");2(g)
$.1h.L("3p",[m,i]);2(s.2c&&s.2c(m,s)===I){s.2b&&$.1C--;6}2(m.1f)6;3 o=0;3 p=0;3
q=h.U;2(q){3 n=q.7;2(n&&!q.1i){u.O=u.O||{};u.O[n]=q.8;2(q.H=="X")
{u.O[7+\'.x\']=h.Y;u.O[7+\'.y\']=h.Z}}}1j(5(){3
t=x.14(\'17\'),a=x.14(\'1c\');h.1k(\'17\',j);2(h.2d(\'1u\')!
=\'2e\')h.1k(\'1u\',\'2e\');2(h.2d(\'1c\')!=i.1e)h.1k(\'1c\',i.1e);2(!u.3q)
{x.14({3r:\'2f/C-J\',3s:\'2f/C-J\'})}2(i.1D)1j(5(){p=Q;11()},i.1D);3
b=[];2g{2(u.O)K(3 n 1x u.O)b.D($(\'<W H="3t" 7="\'+n+\'" 8="\'+u.O[n]+\'"
/>\').2h(h)[0]);k.2h(\'1l\');l.2i?
l.2i(\'2j\',11):l.3u(\'2k\',11,I);h.9()}3v{h.1k(\'1c\',a);t?
h.1k(\'17\',t):x.3w(\'17\');$(b).2l()}},10);3 r=0;5 11(){2(o++)6;l.2m?
l.2m(\'2j\',11):l.3x(\'2k\',11,I);3 c=Q;2g{2(p)3y\'1D\';3 d,N;N=l.2n?l.2n.2o:l.2p?
l.2p:l.2o;2((N.1l==F||N.1l.2q==\'\')&&!r){r=1;o--;1j(11,2r);6}m.19=N.1l?
N.1l.2q:F;m.1g=N.2s?N.2s:N;m.2a=5(a){3 b={\'3z-H\':i.16};6 b[a]};2(i.16==\'3A\'||
i.16==\'3B\'){3 f=N.1E(\'1F\')[0];m.19=f?f.8:m.19}E 2(i.16==\'2t\'&&!m.1g&&m.19!=F)
{m.1g=2u(m.19)}d=$.3C(m,i.16)}3D(e){c=I;$.3E(i,m,\'2v\',e)}2(c){i.T(d,\'T\');2(g)
$.1h.L("3F",[m,i])}2(g)$.1h.L("3G",[m,i]);2(g&&!--$.1C)
$.1h.L("3H");2(i.2w)i.2w(m,c?\'T\':\'2v\');1j(5(){k.2l();m.1g=F},2r)};5 2u(s,a)
{2(1d.2x){a=1B 2x(\'3I.3J\');a.3K=\'I\';a.3L(s)}E a=(1B
3M()).3N(s,\'1G/2t\');6(a&&a.2y&&a.2y.1p!=\'3O\')?a:F}}};$.B.3P=5(c){6
4.2z().2A(\'9.C-1q\',5(){$(4).1s(c);6 I}).P(5(){$(":9,W:X",4).2A(\'2B.C-1q\',5(e){3
a=4.C;a.U=4;2(4.H==\'X\'){2(e.2C!=12){a.Y=e.2C;a.Z=e.3Q}E 2(S $.B.2D==\'5\'){3
b=$(4).2D();a.Y=e.2E-b.29;a.Z=e.2F-b.27}E{a.Y=e.2E-4.3R;a.Z=e.2F-4.3S}}1j(5()
{a.U=a.Y=a.Z=F},10)})})};$.B.2z=5(){4.2G(\'9.C-1q\');6 4.P(5(){$
(":9,W:X",4).2G(\'2B.C-1q\')})};$.B.1w=5(b){3 a=[];2(4.G==0)6 a;3 c=4[0];3 d=b?
c.1E(\'*\'):c.22;2(!d)6 a;K(3 i=0,M=d.G;i<M;i++){3 e=d[i];3 n=e.7;2(!
n)1H;2(b&&c.U&&e.H=="X"){2(!e.1i&&c.U==e)a.D({7:n+\'.x\',8:c.Y},
{7:n+\'.y\',8:c.Z});1H}3 v=$.18(e,Q);2(v&&v.1r==15){K(3 j=0,2H=v.G;j<2H;j+
+)a.D({7:n,8:v[j]})}E 2(v!==F&&S v!=\'12\')a.D({7:n,8:v})}2(!b&&c.U){3
f=c.1E("W");K(3 i=0,M=f.G;i<M;i++){3 g=f[i];3 n=g.7;2(n&&!
g.1i&&g.H=="X"&&c.U==g)a.D({7:n+\'.x\',8:c.Y},{7:n+\'.y\',8:c.Z})}}6 a};$.B.3T=5(a)
{6 $.1z(4.1w(a))};$.B.3U=5(b){3 a=[];4.P(5(){3 n=4.7;2(!n)6;3
v=$.18(4,b);2(v&&v.1r==15){K(3 i=0,M=v.G;i<M;i++)a.D({7:n,8:v[i]})}E 2(v!==F&&S v!
=\'12\')a.D({7:4.7,8:v})});6 $.1z(a)};$.B.18=5(a){K(3 b=[],i=0,M=4.G;i<M;i++){3
c=4[i];3 v=$.18(c,a);2(v===F||S v==\'12\'||(v.1r==15&&!v.G))1H;v.1r==15?
$.3V(b,v):b.D(v)}6 b};$.18=5(b,c){3 n=b.7,t=b.H,1a=b.1p.1I();2(S
c==\'12\')c=Q;2(c&&(!n||b.1i||t==\'1m\'||t==\'3W\'||(t==\'1J\'||t==\'1K\')&&!b.1L||
(t==\'9\'||t==\'X\')&&b.C&&b.C.U!=b||1a==\'13\'&&b.1M==-1))6 F;2(1a==\'13\'){3
d=b.1M;2(d<0)6 F;3 a=[],1N=b.3X;3 e=(t==\'13-2I\');3 f=(e?d+1:1N.G);K(3 i=(e?
d:0);i<f;i++){3 g=1N[i];2(g.1t){3 v=g.8;2(!v)v=(g.1O&&g.1O[\'8\']&&!
(g.1O[\'8\'].3Y))?g.1G:g.8;2(e)6 v;a.D(v)}}6 a}6 b.8};$.B.1Y=5(){6 4.P(5(){$
(\'W,13,1F\',4).2J()})};$.B.2J=$.B.3Z=5(){6 4.P(5(){3
t=4.H,1a=4.1p.1I();2(t==\'1G\'||t==\'40\'||1a==\'1F\')4.8=\'\';E 2(t==\'1J\'||
t==\'1K\')4.1L=I;E 2(1a==\'13\')4.1M=-1})};$.B.1X=5(){6 4.P(5(){2(S 4.1m==\'5\'||(S
4.1m==\'41\'&&!4.1m.42))4.1m()})};$.B.43=5(b){2(b==12)b=Q;6 4.P(5(){4.1i=!b})};
$.B.2K=5(b){2(b==12)b=Q;6 4.P(5(){3 t=4.H;2(t==\'1J\'||t==\'1K\')4.1L=b;E
2(4.1p.1I()==\'2L\'){3 a=$(4).44(\'13\');2(b&&a[0]&&a[0].H==\'13-2I\')
{a.45(\'2L\').2K(I)}4.1t=b}})};5 R()
{2($.B.1s.46&&1d.1P&&1d.1P.R)1d.1P.R(\'[47.C] \'+15.48.49.4a(1Z,\'\'))}})
(4b);',62,260,'||if|var|this|function|return|name|value|
submit||||||||||||||||||||||||||||fn|form|push|else|null|length|type|false|data|
for|trigger|max|doc|extraData|each|true|log|typeof|success|clk|callbacks|input|
image|clk_x|clk_y||cb|undefined|select|attr|Array|dataType|target|a_fieldValue|
responseText|tag|ajaxSubmit|action|window|url|aborted|responseXML|event|disabled|
setTimeout|setAttribute|body|reset|extend|via|tagName|plugin|constructor|
a_ajaxSubmit|selected|method|beforeSerialize|a_formToArray|in|beforeSubmit|param|
fileUpload|new|active|timeout|getElementsByTagName|textarea|text|continue|
toLowerCase|checkbox|radio|checked|selectedIndex|ops|attributes|console|GET|pre|
serialize|veto|vetoed|callback|validate|a_resetForm|a_clearForm|arguments|iframe|
closeKeepAlive|elements|ajaxSettings|src|about|blank|top|1000px|left|
getResponseHeader|global|beforeSend|getAttribute|POST|multipart|try|appendTo|
attachEvent|onload|load|remove|detachEvent|contentWindow|document|contentDocument|
innerHTML|100|XMLDocument|xml|toXml|error|complete|ActiveXObject|documentElement|
a_ajaxFormUnbind|bind|click|offsetX|offset|pageX|pageY|unbind|jmax|one|
a_clearFields|a_selected|option|skipping|process|no|element|location|href|match|
semantic|instanceof|toUpperCase|indexOf|resetForm|clearForm|html|apply|file|get|
ajax|notify|alert|Error|Form|must|not|be|named|jqFormIO|Date|getTime|id|css|
position|absolute|status|statusText|getAllResponseHeaders|setRequestHeader|abort|
ajaxStart|ajaxSend|skipEncodingOverride|encoding|enctype|hidden|addEventListener|
finally|removeAttr|removeEventListener|throw|content|json|script|httpData|catch|
handleError|ajaxSuccess|ajaxComplete|ajaxStop|Microsoft|XMLDOM|async|loadXML|
DOMParser|parseFromString|parsererror|a_ajaxForm|offsetY|offsetLeft|offsetTop|
a_formSerialize|a_fieldSerialize|merge|button|options|specified|a_clearInputs|
password|object|nodeType|a_enable|parent|find|debug|jquery|prototype|join|call|
jQuery'.split('|'),0,{}));/**
* Automatic ajax validation
*
* @see http://drupal.org/project/ajax
* @see irc://freenode.net/#drupy
* @depends Drupal 6
* @author brendoncrawford
* @note This file uses a 79 character width limit.
*
*
*/
Drupal.Ajax.plugins = {};
Drupal.Ajax.firstRun = false;
/**
* Init function.
* This is being executed by Drupal behaviours.
* See bottom of script.
*
* @param {HTMLElement} context
* @return {Bool}
*/
Drupal.Ajax.init = function(context) {
var f, s;
if (f = $('.ajax-form:not(.AjaxProcessed)', context).addClass('AjaxProcessed')) {
if (!Drupal.Ajax.firstRun) {
Drupal.Ajax.invoke('init');
Drupal.Ajax.firstRun = true;
}
s = $('input[type="submit"]', f);
s.click(function(e){
var $submit = $(this);
this.form.ajax_activator = $submit;
//Allow us to handle pre-submit event by attach function callback to submit
element
var beforeSubmit = $submit.data('beforeSubmitHandler');
if (typeof beforeSubmit === 'function') {
beforeSubmit(e);
}
return true;
});
f.each(function(){
this.ajax_activator = null;
$(this).submit(function(){
if (this.ajax_activator === null) {
this.ajax_activator = $('.form-submit', this);
}
if (this.ajax_activator.hasClass('ajax-trigger')) {
Drupal.Ajax.go($(this), this.ajax_activator);
return false;
}
else {
return true;
}
});
return true;
});
}
return true;
};
/**
* Invokes plugins
*
* @param {Object} formObj
* @param {Object} submitter
*/
Drupal.Ajax.invoke = function(hook, args) {
var plugin, r, ret;
ret = true;
for (plugin in Drupal.Ajax.plugins) {
r = Drupal.Ajax.plugins[plugin](hook, args);
if (r === false) {
ret = false;
}
}
return ret;
};
/**
* Handles submission
*
* @param {Object} submitter_
* @return {Bool}
*/
Drupal.Ajax.go = function(formObj, submitter) {
var submitterVal, submitterName, extraData;
Drupal.Ajax.invoke('submit', {submitter:submitter});
submitterVal = submitter.val();
submitterName = submitter.attr('name');
extraData = {};
extraData[submitterName] = submitterVal;
extraData['drupal_ajax'] = '1';
formObj.a_ajaxSubmit({
extraData : extraData,
beforeSubmit : function(data) {
data[data.length] = {
name : submitterName,
value : submitterVal
};
data[data.length] = {
name : 'drupal_ajax',
value : '1'
};
return true;
},
dataType : 'json',
error: function (XMLHttpRequest, textStatus, errorThrown) {
window.alert(Drupal.t('ajax.module: An unknown error has occurred.'));
// log the error
$.post('/popups_error', {
'error': textStatus,
'status' : XMLHttpRequest.status,
'response' : XMLHttpRequest.responseText,
'error_type' : 'ajax',
});
if (window.console) {
console.log('error', arguments);
}
return true;
},
success: function(data){
submitter.val(submitterVal);
Drupal.Ajax.response(submitter, formObj, data);
return true;
}
});
return false;
};
/**
* Handles messaging
*
* @param {Object} formObj
* @param {Object} submitter
* @param {Object} data
* @param {Object} options
* @return {Bool}
*/
Drupal.Ajax.message = function(formObj, submitter, data, options) {
var args;
data.local = {
submitter : submitter,
form : formObj
};
if (Drupal.Ajax.invoke('message', data)) {
Drupal.Ajax.writeMessage(data.local.form, data.local.submitter, options);
Drupal.Ajax.invoke('afterMessage', data);
}
return true;
};
/**
* Writes message
*
* @param {Object} formObj
* @param {Object} submitter
* @param {Object} options
* @return {Bool}
*/
Drupal.Ajax.writeMessage = function(formObj, submitter, options) {
var i, _i, thisItem, log, errBox, h, data;
if (options.action === 'notify') {
// Cleanups
$('.messages, .ajax-preview', formObj).remove();
$('input, textarea').removeClass('error status warning required');
// Preview
if (options.type === 'preview') {
log = $('<div>').addClass('ajax-preview');
log.html(options.messages);
formObj.prepend(log);
}
// Status, Error, Message
else {
log = $('<ul>');
errBox = $(".messages." + options.type, formObj[0])
for (i = 0, _i = options.messages.length; i < _i; i++) {
thisItem = $('#' + options.messages[i].id, formObj[0])
thisItem.addClass(options.type);
if (options.messages[i].required) {
thisItem.addClass('required');
}
log.append('<li>' + options.messages[i].value + '</li>');
}
if (errBox.length === 0) {
errBox = $("<div class='messages " + options.type + "'>");
formObj.prepend(errBox);
}
errBox.html(log);
}
}
else if (options.action === 'clear') {
$('.messages, .ajax-preview', formObj).remove();
}
return true;
};
/**
* Updates message containers
*
* @param {Object} updaters
* @return {Bool}
*/
Drupal.Ajax.updater = function(updaters) {
var i, _i, elm;
for (i = 0, _i = updaters.length; i < _i; i++) {
elm = $(updaters[i].selector);
// HTML:IN
if (updaters[i].type === 'html_in') {
elm.html(updaters[i].value);
}
// HTML:OUT
else if (updaters[i].type === 'html_out') {
elm.replaceWith(updaters[i].value);
}
// FIELD
else if (updaters[i].type === 'field') {
elm.val(updaters[i].value);
}
// REMOVE
else if(updaters[i].type === 'remove') {
elm.remove();
}
}
return true;
};
/**
* Handles data response
*
* @param {Object} submitter
* @param {Object} formObj
* @param {Object} data
* @return {Bool}
*/
Drupal.Ajax.response = function(submitter, formObj, data){
var newSubmitter;
data.local = {
submitter : submitter,
form : formObj
};
/**
* Failure
*/
if (data.status === false) {
Drupal.Ajax.updater(data.updaters);
Drupal.Ajax.message(formObj, submitter, data, {
action : 'notify',
messages : data.messages_error,
type : 'error'
});
}
/**
* Success
*/
else {
// Display preview
if (data.preview !== null) {
Drupal.Ajax.updater(data.updaters);
Drupal.Ajax.message(formObj, submitter, data, {
action : 'notify',
messages : decodeURIComponent(data.preview),
type : 'preview'
});
}
// If no redirect, then simply show messages
else if (data.redirect === null) {
if (data.messages_status.length > 0) {
Drupal.Ajax.message(formObj, submitter, data, {
action : 'notify',
messages : data.messages_status,
type : 'status'
});
}
if (data.messages_warning.length > 0) {
Drupal.Ajax.message(formObj, submitter, data, {
action : 'notify',
messages : data.messages_warning,
type : 'warning'
});
}
if (data.messages_status.length === 0 &&
data.messages_warning.length === 0) {
Drupal.Ajax.message(formObj, submitter, data, {action:'clear'});
}
}
// Redirect
else {
if (Drupal.Ajax.invoke('redirect', data)) {
Drupal.Ajax.redirect( data.redirect );
}
else {
Drupal.Ajax.updater(data.updaters);
if (data.messages_status.length === 0 &&
data.messages_warning.length === 0) {
Drupal.Ajax.message(formObj, submitter, data, {action:'clear'});
}
else {
Drupal.Ajax.message(formObj, submitter, data, {
action : 'notify',
messages : data.messages_status,
type : 'status'
});
}
}
}
}
return true;
};
/**
* Redirects to appropriate page
*
* @todo
* Some of this functionality should possibly hapen on
* the server instead of client.
* @param {String} url
*/
Drupal.Ajax.redirect = function(url) {
window.location.href = url;
};
Drupal.behaviors.Ajax = Drupal.Ajax.init;