{I" class:ETI"BundledAsset;�FI"logical_path;�TI""think_feel_do_engine/users.js;�TI" pathname;�TI"u/Users/usabilitymonitor/Desktop/Github/think_feel_do_engine/app/assets/javascripts/think_feel_do_engine/users.js;�FI"content_type;�TI"application/javascript;�TI" mtime;�Tl+�TI"length;�Ti��I"digest;�TI"%ec80b69b2f079c022ea6a8dfc3dfc72e;�FI"source;�TI"��/*! * jQuery UI Core 1.10.4 * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/category/ui-core/ */ (function( $, undefined ) { var uuid = 0, runiqueId = /^ui-id-\d+$/; // $.ui might exist from components with no dependencies, e.g., $.ui.position $.ui = $.ui || {}; $.extend( $.ui, { version: "1.10.4", keyCode: { BACKSPACE: 8, COMMA: 188, DELETE: 46, DOWN: 40, END: 35, ENTER: 13, ESCAPE: 27, HOME: 36, LEFT: 37, NUMPAD_ADD: 107, NUMPAD_DECIMAL: 110, NUMPAD_DIVIDE: 111, NUMPAD_ENTER: 108, NUMPAD_MULTIPLY: 106, NUMPAD_SUBTRACT: 109, PAGE_DOWN: 34, PAGE_UP: 33, PERIOD: 190, RIGHT: 39, SPACE: 32, TAB: 9, UP: 38 } }); // plugins $.fn.extend({ focus: (function( orig ) { return function( delay, fn ) { return typeof delay === "number" ? this.each(function() { var elem = this; setTimeout(function() { $( elem ).focus(); if ( fn ) { fn.call( elem ); } }, delay ); }) : orig.apply( this, arguments ); }; })( $.fn.focus ), scrollParent: function() { var scrollParent; if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) { scrollParent = this.parents().filter(function() { return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); }).eq(0); } else { scrollParent = this.parents().filter(function() { return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x")); }).eq(0); } return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent; }, zIndex: function( zIndex ) { if ( zIndex !== undefined ) { return this.css( "zIndex", zIndex ); } if ( this.length ) { var elem = $( this[ 0 ] ), position, value; while ( elem.length && elem[ 0 ] !== document ) { // Ignore z-index if position is set to a value where z-index is ignored by the browser // This makes behavior of this function consistent across browsers // WebKit always returns auto if the element is positioned position = elem.css( "position" ); if ( position === "absolute" || position === "relative" || position === "fixed" ) { // IE returns 0 when zIndex is not specified // other browsers return a string // we ignore the case of nested elements with an explicit value of 0 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> value = parseInt( elem.css( "zIndex" ), 10 ); if ( !isNaN( value ) && value !== 0 ) { return value; } } elem = elem.parent(); } } return 0; }, uniqueId: function() { return this.each(function() { if ( !this.id ) { this.id = "ui-id-" + (++uuid); } }); }, removeUniqueId: function() { return this.each(function() { if ( runiqueId.test( this.id ) ) { $( this ).removeAttr( "id" ); } }); } }); // selectors function focusable( element, isTabIndexNotNaN ) { var map, mapName, img, nodeName = element.nodeName.toLowerCase(); if ( "area" === nodeName ) { map = element.parentNode; mapName = map.name; if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { return false; } img = $( "img[usemap=#" + mapName + "]" )[0]; return !!img && visible( img ); } return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) && // the element and all of its ancestors must be visible visible( element ); } function visible( element ) { return $.expr.filters.visible( element ) && !$( element ).parents().addBack().filter(function() { return $.css( this, "visibility" ) === "hidden"; }).length; } $.extend( $.expr[ ":" ], { data: $.expr.createPseudo ? $.expr.createPseudo(function( dataName ) { return function( elem ) { return !!$.data( elem, dataName ); }; }) : // support: jQuery <1.8 function( elem, i, match ) { return !!$.data( elem, match[ 3 ] ); }, focusable: function( element ) { return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); }, tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), isTabIndexNaN = isNaN( tabIndex ); return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); } }); // support: jQuery <1.8 if ( !$( "<a>" ).outerWidth( 1 ).jquery ) { $.each( [ "Width", "Height" ], function( i, name ) { var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], type = name.toLowerCase(), orig = { innerWidth: $.fn.innerWidth, innerHeight: $.fn.innerHeight, outerWidth: $.fn.outerWidth, outerHeight: $.fn.outerHeight }; function reduce( elem, size, border, margin ) { $.each( side, function() { size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; if ( border ) { size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; } if ( margin ) { size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; } }); return size; } $.fn[ "inner" + name ] = function( size ) { if ( size === undefined ) { return orig[ "inner" + name ].call( this ); } return this.each(function() { $( this ).css( type, reduce( this, size ) + "px" ); }); }; $.fn[ "outer" + name] = function( size, margin ) { if ( typeof size !== "number" ) { return orig[ "outer" + name ].call( this, size ); } return this.each(function() { $( this).css( type, reduce( this, size, true, margin ) + "px" ); }); }; }); } // support: jQuery <1.8 if ( !$.fn.addBack ) { $.fn.addBack = function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); }; } // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413) if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) { $.fn.removeData = (function( removeData ) { return function( key ) { if ( arguments.length ) { return removeData.call( this, $.camelCase( key ) ); } else { return removeData.call( this ); } }; })( $.fn.removeData ); } // deprecated $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); $.support.selectstart = "onselectstart" in document.createElement( "div" ); $.fn.extend({ disableSelection: function() { return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) + ".ui-disableSelection", function( event ) { event.preventDefault(); }); }, enableSelection: function() { return this.unbind( ".ui-disableSelection" ); } }); $.extend( $.ui, { // $.ui.plugin is deprecated. Use $.widget() extensions instead. plugin: { add: function( module, option, set ) { var i, proto = $.ui[ module ].prototype; for ( i in set ) { proto.plugins[ i ] = proto.plugins[ i ] || []; proto.plugins[ i ].push( [ option, set[ i ] ] ); } }, call: function( instance, name, args ) { var i, set = instance.plugins[ name ]; if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) { return; } for ( i = 0; i < set.length; i++ ) { if ( instance.options[ set[ i ][ 0 ] ] ) { set[ i ][ 1 ].apply( instance.element, args ); } } } }, // only used by resizable hasScroll: function( el, a ) { //If overflow is hidden, the element might have extra content, but the user wants to hide it if ( $( el ).css( "overflow" ) === "hidden") { return false; } var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop", has = false; if ( el[ scroll ] > 0 ) { return true; } // TODO: determine which cases actually cause this to happen // if the element doesn't have the scroll set, see if it's possible to // set the scroll el[ scroll ] = 1; has = ( el[ scroll ] > 0 ); el[ scroll ] = 0; return has; } }); })( jQuery ); /*! * jQuery UI Widget 1.10.4 * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/jQuery.widget/ */ (function( $, undefined ) { var uuid = 0, slice = Array.prototype.slice, _cleanData = $.cleanData; $.cleanData = function( elems ) { for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { try { $( elem ).triggerHandler( "remove" ); // http://bugs.jquery.com/ticket/8235 } catch( e ) {} } _cleanData( elems ); }; $.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) proxiedPrototype = {}, namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; if ( !prototype ) { prototype = base; base = $.Widget; } // create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { // allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; // extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] }); basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } proxiedPrototype[ prop ] = (function() { var _super = function() { return base.prototype[ prop ].apply( this, arguments ); }, _superApply = function( args ) { return base.prototype[ prop ].apply( this, args ); }; return function() { var __super = this._super, __superApply = this._superApply, returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; })(); }); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name }, proxiedPrototype, { constructor: constructor, namespace: namespace, widgetName: name, widgetFullName: fullName }); // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); }); // remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } $.widget.bridge( name, constructor ); }; $.widget.extend = function( target ) { var input = slice.call( arguments, 1 ), inputIndex = 0, inputLength = input.length, key, value; for ( ; inputIndex < inputLength; inputIndex++ ) { for ( key in input[ inputIndex ] ) { value = input[ inputIndex ][ key ]; if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { // Clone objects if ( $.isPlainObject( value ) ) { target[ key ] = $.isPlainObject( target[ key ] ) ? $.widget.extend( {}, target[ key ], value ) : // Don't extend strings, arrays, etc. with objects $.widget.extend( {}, value ); // Copy everything else by reference } else { target[ key ] = value; } } } } return target; }; $.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? $.widget.extend.apply( null, [ options ].concat(args) ) : options; if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? returnValue.pushStack( methodValue.get() ) : methodValue; return false; } }); } else { this.each(function() { var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} )._init(); } else { $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; }; $.Widget = function( /* options, element */ ) {}; $.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "<div>", options: { disabled: false, // callbacks create: null }, _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) // 1.9 BC for #7810 // TODO remove dual storage .removeData( this.widgetName ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { var options = key, parts, curOption, i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } return this; }, enable: function() { return this._setOption( "disabled", false ); }, disable: function() { return this._setOption( "disabled", true ); }, _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { // accept selectors, DOM elements element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } var match = event.match( /^(\w+)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); }, _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { if ( typeof options === "string" ) { options = { effect: options }; } var hasOptions, effectName = !options ? method : options === true || typeof options === "number" ? defaultEffect : options.effect || defaultEffect; options = options || {}; if ( typeof options === "number" ) { options = { duration: options }; } hasOptions = !$.isEmptyObject( options ); options.complete = callback; if ( options.delay ) { element.delay( options.delay ); } if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { element[ method ]( options ); } else if ( effectName !== method && element[ effectName ] ) { element[ effectName ]( options.duration, options.easing, callback ); } else { element.queue(function( next ) { $( this )[ method ](); if ( callback ) { callback.call( element[ 0 ] ); } next(); }); } }; }); })( jQuery ); /*! * jQuery UI Mouse 1.10.4 * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/mouse/ * * Depends: * jquery.ui.widget.js */ (function( $, undefined ) { var mouseHandled = false; $( document ).mouseup( function() { mouseHandled = false; }); $.widget("ui.mouse", { version: "1.10.4", options: { cancel: "input,textarea,button,select,option", distance: 1, delay: 0 }, _mouseInit: function() { var that = this; this.element .bind("mousedown."+this.widgetName, function(event) { return that._mouseDown(event); }) .bind("click."+this.widgetName, function(event) { if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) { $.removeData(event.target, that.widgetName + ".preventClickEvent"); event.stopImmediatePropagation(); return false; } }); this.started = false; }, // TODO: make sure destroying one instance of mouse doesn't mess with // other instances of mouse _mouseDestroy: function() { this.element.unbind("."+this.widgetName); if ( this._mouseMoveDelegate ) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); } }, _mouseDown: function(event) { // don't let more than one widget handle mouseStart if( mouseHandled ) { return; } // we may have missed mouseup (out of window) (this._mouseStarted && this._mouseUp(event)); this._mouseDownEvent = event; var that = this, btnIsLeft = (event.which === 1), // event.target.nodeName works around a bug in IE 8 with // disabled inputs (#7620) elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false); if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) { return true; } this.mouseDelayMet = !this.options.delay; if (!this.mouseDelayMet) { this._mouseDelayTimer = setTimeout(function() { that.mouseDelayMet = true; }, this.options.delay); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(event) !== false); if (!this._mouseStarted) { event.preventDefault(); return true; } } // Click event may never have fired (Gecko & Opera) if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) { $.removeData(event.target, this.widgetName + ".preventClickEvent"); } // these delegates are required to keep context this._mouseMoveDelegate = function(event) { return that._mouseMove(event); }; this._mouseUpDelegate = function(event) { return that._mouseUp(event); }; $(document) .bind("mousemove."+this.widgetName, this._mouseMoveDelegate) .bind("mouseup."+this.widgetName, this._mouseUpDelegate); event.preventDefault(); mouseHandled = true; return true; }, _mouseMove: function(event) { // IE mouseup check - mouseup happened when mouse was out of window if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) { return this._mouseUp(event); } if (this._mouseStarted) { this._mouseDrag(event); return event.preventDefault(); } if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) { this._mouseStarted = (this._mouseStart(this._mouseDownEvent, event) !== false); (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event)); } return !this._mouseStarted; }, _mouseUp: function(event) { $(document) .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate) .unbind("mouseup."+this.widgetName, this._mouseUpDelegate); if (this._mouseStarted) { this._mouseStarted = false; if (event.target === this._mouseDownEvent.target) { $.data(event.target, this.widgetName + ".preventClickEvent", true); } this._mouseStop(event); } return false; }, _mouseDistanceMet: function(event) { return (Math.max( Math.abs(this._mouseDownEvent.pageX - event.pageX), Math.abs(this._mouseDownEvent.pageY - event.pageY) ) >= this.options.distance ); }, _mouseDelayMet: function(/* event */) { return this.mouseDelayMet; }, // These are placeholder methods, to be overriden by extending plugin _mouseStart: function(/* event */) {}, _mouseDrag: function(/* event */) {}, _mouseStop: function(/* event */) {}, _mouseCapture: function(/* event */) { return true; } }); })(jQuery); /*! * jQuery UI Sortable 1.10.4 * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/sortable/ * * Depends: * jquery.ui.core.js * jquery.ui.mouse.js * jquery.ui.widget.js */ (function( $, undefined ) { function isOverAxis( x, reference, size ) { return ( x > reference ) && ( x < ( reference + size ) ); } function isFloating(item) { return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display")); } $.widget("ui.sortable", $.ui.mouse, { version: "1.10.4", widgetEventPrefix: "sort", ready: false, options: { appendTo: "parent", axis: false, connectWith: false, containment: false, cursor: "auto", cursorAt: false, dropOnEmpty: true, forcePlaceholderSize: false, forceHelperSize: false, grid: false, handle: false, helper: "original", items: "> *", opacity: false, placeholder: false, revert: false, scroll: true, scrollSensitivity: 20, scrollSpeed: 20, scope: "default", tolerance: "intersect", zIndex: 1000, // callbacks activate: null, beforeStop: null, change: null, deactivate: null, out: null, over: null, receive: null, remove: null, sort: null, start: null, stop: null, update: null }, _create: function() { var o = this.options; this.containerCache = {}; this.element.addClass("ui-sortable"); //Get the items this.refresh(); //Let's determine if the items are being displayed horizontally this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false; //Let's determine the parent's offset this.offset = this.element.offset(); //Initialize mouse events for interaction this._mouseInit(); //We're ready to go this.ready = true; }, _destroy: function() { this.element .removeClass("ui-sortable ui-sortable-disabled"); this._mouseDestroy(); for ( var i = this.items.length - 1; i >= 0; i-- ) { this.items[i].item.removeData(this.widgetName + "-item"); } return this; }, _setOption: function(key, value){ if ( key === "disabled" ) { this.options[ key ] = value; this.widget().toggleClass( "ui-sortable-disabled", !!value ); } else { // Don't call widget base _setOption for disable as it adds ui-state-disabled class $.Widget.prototype._setOption.apply(this, arguments); } }, _mouseCapture: function(event, overrideHandle) { var currentItem = null, validHandle = false, that = this; if (this.reverting) { return false; } if(this.options.disabled || this.options.type === "static") { return false; } //We have to refresh the items data once first this._refreshItems(event); //Find out if the clicked node (or one of its parents) is a actual item in this.items $(event.target).parents().each(function() { if($.data(this, that.widgetName + "-item") === that) { currentItem = $(this); return false; } }); if($.data(event.target, that.widgetName + "-item") === that) { currentItem = $(event.target); } if(!currentItem) { return false; } if(this.options.handle && !overrideHandle) { $(this.options.handle, currentItem).find("*").addBack().each(function() { if(this === event.target) { validHandle = true; } }); if(!validHandle) { return false; } } this.currentItem = currentItem; this._removeCurrentsFromItems(); return true; }, _mouseStart: function(event, overrideHandle, noActivation) { var i, body, o = this.options; this.currentContainer = this; //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture this.refreshPositions(); //Create and append the visible helper this.helper = this._createHelper(event); //Cache the helper size this._cacheHelperProportions(); /* * - Position generation - * This block generates everything position related - it's the core of draggables. */ //Cache the margins of the original element this._cacheMargins(); //Get the next scrolling parent this.scrollParent = this.helper.scrollParent(); //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { top: this.offset.top - this.margins.top, left: this.offset.left - this.margins.left }; $.extend(this.offset, { click: { //Where the click happened, relative to the element left: event.pageX - this.offset.left, top: event.pageY - this.offset.top }, parent: this._getParentOffset(), relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper }); // Only after we got the offset, we can change the helper's position to absolute // TODO: Still need to figure out a way to make relative sorting possible this.helper.css("position", "absolute"); this.cssPosition = this.helper.css("position"); //Generate the original position this.originalPosition = this._generatePosition(event); this.originalPageX = event.pageX; this.originalPageY = event.pageY; //Adjust the mouse offset relative to the helper if "cursorAt" is supplied (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)); //Cache the former DOM position this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] }; //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way if(this.helper[0] !== this.currentItem[0]) { this.currentItem.hide(); } //Create the placeholder this._createPlaceholder(); //Set a containment if given in the options if(o.containment) { this._setContainment(); } if( o.cursor && o.cursor !== "auto" ) { // cursor option body = this.document.find( "body" ); // support: IE this.storedCursor = body.css( "cursor" ); body.css( "cursor", o.cursor ); this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body ); } if(o.opacity) { // opacity option if (this.helper.css("opacity")) { this._storedOpacity = this.helper.css("opacity"); } this.helper.css("opacity", o.opacity); } if(o.zIndex) { // zIndex option if (this.helper.css("zIndex")) { this._storedZIndex = this.helper.css("zIndex"); } this.helper.css("zIndex", o.zIndex); } //Prepare scrolling if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { this.overflowOffset = this.scrollParent.offset(); } //Call callbacks this._trigger("start", event, this._uiHash()); //Recache the helper size if(!this._preserveHelperProportions) { this._cacheHelperProportions(); } //Post "activate" events to possible containers if( !noActivation ) { for ( i = this.containers.length - 1; i >= 0; i-- ) { this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); } } //Prepare possible droppables if($.ui.ddmanager) { $.ui.ddmanager.current = this; } if ($.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } this.dragging = true; this.helper.addClass("ui-sortable-helper"); this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position return true; }, _mouseDrag: function(event) { var i, item, itemElement, intersection, o = this.options, scrolled = false; //Compute the helpers position this.position = this._generatePosition(event); this.positionAbs = this._convertPositionTo("absolute"); if (!this.lastPositionAbs) { this.lastPositionAbs = this.positionAbs; } //Do scrolling if(this.options.scroll) { if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") { if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; } if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; } } else { if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) { scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); } if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) { scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); } } if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { $.ui.ddmanager.prepareOffsets(this, event); } } //Regenerate the absolute position used for position checks this.positionAbs = this._convertPositionTo("absolute"); //Set the helper position if(!this.options.axis || this.options.axis !== "y") { this.helper[0].style.left = this.position.left+"px"; } if(!this.options.axis || this.options.axis !== "x") { this.helper[0].style.top = this.position.top+"px"; } //Rearrange for (i = this.items.length - 1; i >= 0; i--) { //Cache variables and intersection, continue if no intersection item = this.items[i]; itemElement = item.item[0]; intersection = this._intersectsWithPointer(item); if (!intersection) { continue; } // Only put the placeholder inside the current Container, skip all // items from other containers. This works because when moving // an item from one container to another the // currentContainer is switched before the placeholder is moved. // // Without this, moving items in "sub-sortables" can cause // the placeholder to jitter beetween the outer and inner container. if (item.instance !== this.currentContainer) { continue; } // cannot intersect with itself // no useless actions that have been done before // no action if the item moved is the parent of the item checked if (itemElement !== this.currentItem[0] && this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && !$.contains(this.placeholder[0], itemElement) && (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) ) { this.direction = intersection === 1 ? "down" : "up"; if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { this._rearrange(event, item); } else { break; } this._trigger("change", event, this._uiHash()); break; } } //Post events to containers this._contactContainers(event); //Interconnect with droppables if($.ui.ddmanager) { $.ui.ddmanager.drag(this, event); } //Call callbacks this._trigger("sort", event, this._uiHash()); this.lastPositionAbs = this.positionAbs; return false; }, _mouseStop: function(event, noPropagation) { if(!event) { return; } //If we are using droppables, inform the manager about the drop if ($.ui.ddmanager && !this.options.dropBehaviour) { $.ui.ddmanager.drop(this, event); } if(this.options.revert) { var that = this, cur = this.placeholder.offset(), axis = this.options.axis, animation = {}; if ( !axis || axis === "x" ) { animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft); } if ( !axis || axis === "y" ) { animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop); } this.reverting = true; $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { that._clear(event); }); } else { this._clear(event, noPropagation); } return false; }, cancel: function() { if(this.dragging) { this._mouseUp({ target: null }); if(this.options.helper === "original") { this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); } else { this.currentItem.show(); } //Post deactivating events to containers for (var i = this.containers.length - 1; i >= 0; i--){ this.containers[i]._trigger("deactivate", null, this._uiHash(this)); if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", null, this._uiHash(this)); this.containers[i].containerCache.over = 0; } } } if (this.placeholder) { //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! if(this.placeholder[0].parentNode) { this.placeholder[0].parentNode.removeChild(this.placeholder[0]); } if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { this.helper.remove(); } $.extend(this, { helper: null, dragging: false, reverting: false, _noFinalSort: null }); if(this.domPosition.prev) { $(this.domPosition.prev).after(this.currentItem); } else { $(this.domPosition.parent).prepend(this.currentItem); } } return this; }, serialize: function(o) { var items = this._getItemsAsjQuery(o && o.connected), str = []; o = o || {}; $(items).each(function() { var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); if (res) { str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); } }); if(!str.length && o.key) { str.push(o.key + "="); } return str.join("&"); }, toArray: function(o) { var items = this._getItemsAsjQuery(o && o.connected), ret = []; o = o || {}; items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); return ret; }, /* Be careful with the following core functions */ _intersectsWith: function(item) { var x1 = this.positionAbs.left, x2 = x1 + this.helperProportions.width, y1 = this.positionAbs.top, y2 = y1 + this.helperProportions.height, l = item.left, r = l + item.width, t = item.top, b = t + item.height, dyClick = this.offset.click.top, dxClick = this.offset.click.left, isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), isOverElement = isOverElementHeight && isOverElementWidth; if ( this.options.tolerance === "pointer" || this.options.forcePointerForContainers || (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) ) { return isOverElement; } else { return (l < x1 + (this.helperProportions.width / 2) && // Right Half x2 - (this.helperProportions.width / 2) < r && // Left Half t < y1 + (this.helperProportions.height / 2) && // Bottom Half y2 - (this.helperProportions.height / 2) < b ); // Top Half } }, _intersectsWithPointer: function(item) { var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), isOverElement = isOverElementHeight && isOverElementWidth, verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (!isOverElement) { return false; } return this.floating ? ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); }, _intersectsWithSides: function(item) { var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), verticalDirection = this._getDragVerticalDirection(), horizontalDirection = this._getDragHorizontalDirection(); if (this.floating && horizontalDirection) { return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); } else { return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); } }, _getDragVerticalDirection: function() { var delta = this.positionAbs.top - this.lastPositionAbs.top; return delta !== 0 && (delta > 0 ? "down" : "up"); }, _getDragHorizontalDirection: function() { var delta = this.positionAbs.left - this.lastPositionAbs.left; return delta !== 0 && (delta > 0 ? "right" : "left"); }, refresh: function(event) { this._refreshItems(event); this.refreshPositions(); return this; }, _connectWith: function() { var options = this.options; return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; }, _getItemsAsjQuery: function(connected) { var i, j, cur, inst, items = [], queries = [], connectWith = this._connectWith(); if(connectWith && connected) { for (i = connectWith.length - 1; i >= 0; i--){ cur = $(connectWith[i]); for ( j = cur.length - 1; j >= 0; j--){ inst = $.data(cur[j], this.widgetFullName); if(inst && inst !== this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); } } } } queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); function addItems() { items.push( this ); } for (i = queries.length - 1; i >= 0; i--){ queries[i][0].each( addItems ); } return $(items); }, _removeCurrentsFromItems: function() { var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); this.items = $.grep(this.items, function (item) { for (var j=0; j < list.length; j++) { if(list[j] === item.item[0]) { return false; } } return true; }); }, _refreshItems: function(event) { this.items = []; this.containers = [this]; var i, j, cur, inst, targetData, _queries, item, queriesLength, items = this.items, queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], connectWith = this._connectWith(); if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down for (i = connectWith.length - 1; i >= 0; i--){ cur = $(connectWith[i]); for (j = cur.length - 1; j >= 0; j--){ inst = $.data(cur[j], this.widgetFullName); if(inst && inst !== this && !inst.options.disabled) { queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); this.containers.push(inst); } } } } for (i = queries.length - 1; i >= 0; i--) { targetData = queries[i][1]; _queries = queries[i][0]; for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { item = $(_queries[j]); item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) items.push({ item: item, instance: targetData, width: 0, height: 0, left: 0, top: 0 }); } } }, refreshPositions: function(fast) { //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change if(this.offsetParent && this.helper) { this.offset.parent = this._getParentOffset(); } var i, item, t, p; for (i = this.items.length - 1; i >= 0; i--){ item = this.items[i]; //We ignore calculating positions of all connected containers when we're not over them if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { continue; } t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; if (!fast) { item.width = t.outerWidth(); item.height = t.outerHeight(); } p = t.offset(); item.left = p.left; item.top = p.top; } if(this.options.custom && this.options.custom.refreshContainers) { this.options.custom.refreshContainers.call(this); } else { for (i = this.containers.length - 1; i >= 0; i--){ p = this.containers[i].element.offset(); this.containers[i].containerCache.left = p.left; this.containers[i].containerCache.top = p.top; this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); } } return this; }, _createPlaceholder: function(that) { that = that || this; var className, o = that.options; if(!o.placeholder || o.placeholder.constructor === String) { className = o.placeholder; o.placeholder = { element: function() { var nodeName = that.currentItem[0].nodeName.toLowerCase(), element = $( "<" + nodeName + ">", that.document[0] ) .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") .removeClass("ui-sortable-helper"); if ( nodeName === "tr" ) { that.currentItem.children().each(function() { $( "<td> </td>", that.document[0] ) .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) .appendTo( element ); }); } else if ( nodeName === "img" ) { element.attr( "src", that.currentItem.attr( "src" ) ); } if ( !className ) { element.css( "visibility", "hidden" ); } return element; }, update: function(container, p) { // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified if(className && !o.forcePlaceholderSize) { return; } //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } } }; } //Create the placeholder that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); //Append it after the actual current item that.currentItem.after(that.placeholder); //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) o.placeholder.update(that, that.placeholder); }, _contactContainers: function(event) { var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating, innermostContainer = null, innermostIndex = null; // get innermost container that intersects with item for (i = this.containers.length - 1; i >= 0; i--) { // never consider a container that's located within the item itself if($.contains(this.currentItem[0], this.containers[i].element[0])) { continue; } if(this._intersectsWith(this.containers[i].containerCache)) { // if we've already found a container and it's more "inner" than this, then continue if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { continue; } innermostContainer = this.containers[i]; innermostIndex = i; } else { // container doesn't intersect. trigger "out" event if necessary if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", event, this._uiHash(this)); this.containers[i].containerCache.over = 0; } } } // if no intersecting containers found, return if(!innermostContainer) { return; } // move the item into the container if it's not there already if(this.containers.length === 1) { if (!this.containers[innermostIndex].containerCache.over) { this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } } else { //When entering a new container, we will find the item with the least distance and append our item near it dist = 10000; itemWithLeastDistance = null; floating = innermostContainer.floating || isFloating(this.currentItem); posProperty = floating ? "left" : "top"; sizeProperty = floating ? "width" : "height"; base = this.positionAbs[posProperty] + this.offset.click[posProperty]; for (j = this.items.length - 1; j >= 0; j--) { if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { continue; } if(this.items[j].item[0] === this.currentItem[0]) { continue; } if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) { continue; } cur = this.items[j].item.offset()[posProperty]; nearBottom = false; if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){ nearBottom = true; cur += this.items[j][sizeProperty]; } if(Math.abs(cur - base) < dist) { dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; this.direction = nearBottom ? "up": "down"; } } //Check if dropOnEmpty is enabled if(!itemWithLeastDistance && !this.options.dropOnEmpty) { return; } if(this.currentContainer === this.containers[innermostIndex]) { return; } itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); this._trigger("change", event, this._uiHash()); this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); this.currentContainer = this.containers[innermostIndex]; //Update the placeholder this.options.placeholder.update(this.currentContainer, this.placeholder); this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } }, _createHelper: function(event) { var o = this.options, helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); //Add the helper to the DOM if that didn't happen already if(!helper.parents("body").length) { $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); } if(helper[0] === this.currentItem[0]) { this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; } if(!helper[0].style.width || o.forceHelperSize) { helper.width(this.currentItem.width()); } if(!helper[0].style.height || o.forceHelperSize) { helper.height(this.currentItem.height()); } return helper; }, _adjustOffsetFromHelper: function(obj) { if (typeof obj === "string") { obj = obj.split(" "); } if ($.isArray(obj)) { obj = {left: +obj[0], top: +obj[1] || 0}; } if ("left" in obj) { this.offset.click.left = obj.left + this.margins.left; } if ("right" in obj) { this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; } if ("top" in obj) { this.offset.click.top = obj.top + this.margins.top; } if ("bottom" in obj) { this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; } }, _getParentOffset: function() { //Get the offsetParent and cache its position this.offsetParent = this.helper.offsetParent(); var po = this.offsetParent.offset(); // This is a special case where we need to modify a offset calculated on start, since the following happened: // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) { po.left += this.scrollParent.scrollLeft(); po.top += this.scrollParent.scrollTop(); } // This needs to be actually done for all browsers, since pageX/pageY includes this information // with an ugly IE fix if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { po = { top: 0, left: 0 }; } return { top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) }; }, _getRelativeOffset: function() { if(this.cssPosition === "relative") { var p = this.currentItem.position(); return { top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() }; } else { return { top: 0, left: 0 }; } }, _cacheMargins: function() { this.margins = { left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), top: (parseInt(this.currentItem.css("marginTop"),10) || 0) }; }, _cacheHelperProportions: function() { this.helperProportions = { width: this.helper.outerWidth(), height: this.helper.outerHeight() }; }, _setContainment: function() { var ce, co, over, o = this.options; if(o.containment === "parent") { o.containment = this.helper[0].parentNode; } if(o.containment === "document" || o.containment === "window") { this.containment = [ 0 - this.offset.relative.left - this.offset.parent.left, 0 - this.offset.relative.top - this.offset.parent.top, $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left, ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top ]; } if(!(/^(document|window|parent)$/).test(o.containment)) { ce = $(o.containment)[0]; co = $(o.containment).offset(); over = ($(ce).css("overflow") !== "hidden"); this.containment = [ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top ]; } }, _convertPositionTo: function(d, pos) { if(!pos) { pos = this.position; } var mod = d === "absolute" ? 1 : -1, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); return { top: ( pos.top + // The absolute mouse position this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) ), left: ( pos.left + // The absolute mouse position this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) ) }; }, _generatePosition: function(event) { var top, left, o = this.options, pageX = event.pageX, pageY = event.pageY, scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); // This is another very weird special case that only happens for relative elements: // 1. If the css position is relative // 2. and the scroll parent is the document or similar to the offset parent // we have to refresh the relative offset during the scroll so there are no jumps if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) { this.offset.relative = this._getRelativeOffset(); } /* * - Position constraining - * Constrain the position to a mix of grid, containment. */ if(this.originalPosition) { //If we are not dragging yet, we won't check for options if(this.containment) { if(event.pageX - this.offset.click.left < this.containment[0]) { pageX = this.containment[0] + this.offset.click.left; } if(event.pageY - this.offset.click.top < this.containment[1]) { pageY = this.containment[1] + this.offset.click.top; } if(event.pageX - this.offset.click.left > this.containment[2]) { pageX = this.containment[2] + this.offset.click.left; } if(event.pageY - this.offset.click.top > this.containment[3]) { pageY = this.containment[3] + this.offset.click.top; } } if(o.grid) { top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; } } return { top: ( pageY - // The absolute mouse position this.offset.click.top - // Click offset (relative to the element) this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.top + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) ), left: ( pageX - // The absolute mouse position this.offset.click.left - // Click offset (relative to the element) this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent this.offset.parent.left + // The offsetParent's offset without borders (offset + border) ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) ) }; }, _rearrange: function(event, i, a, hardRefresh) { a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); //Various things done here to improve the performance: // 1. we create a setTimeout, that calls refreshPositions // 2. on the instance, we have a counter variable, that get's higher after every append // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same // 4. this lets only the last addition to the timeout stack through this.counter = this.counter ? ++this.counter : 1; var counter = this.counter; this._delay(function() { if(counter === this.counter) { this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove } }); }, _clear: function(event, noPropagation) { this.reverting = false; // We delay all events that have to be triggered to after the point where the placeholder has been removed and // everything else normalized again var i, delayedTriggers = []; // We first have to update the dom position of the actual currentItem // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) if(!this._noFinalSort && this.currentItem.parent().length) { this.placeholder.before(this.currentItem); } this._noFinalSort = null; if(this.helper[0] === this.currentItem[0]) { for(i in this._storedCSS) { if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { this._storedCSS[i] = ""; } } this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); } else { this.currentItem.show(); } if(this.fromOutside && !noPropagation) { delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); } if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed } // Check if the items Container has Changed and trigger appropriate // events. if (this !== this.currentContainer) { if(!noPropagation) { delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); } } //Post events to containers function delayEvent( type, instance, container ) { return function( event ) { container._trigger( type, event, instance._uiHash( instance ) ); }; } for (i = this.containers.length - 1; i >= 0; i--){ if (!noPropagation) { delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) ); } if(this.containers[i].containerCache.over) { delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) ); this.containers[i].containerCache.over = 0; } } //Do what was originally in plugins if ( this.storedCursor ) { this.document.find( "body" ).css( "cursor", this.storedCursor ); this.storedStylesheet.remove(); } if(this._storedOpacity) { this.helper.css("opacity", this._storedOpacity); } if(this._storedZIndex) { this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); } this.dragging = false; if(this.cancelHelperRemoval) { if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); for (i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); } //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } this.fromOutside = false; return false; } if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); } //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! this.placeholder[0].parentNode.removeChild(this.placeholder[0]); if(this.helper[0] !== this.currentItem[0]) { this.helper.remove(); } this.helper = null; if(!noPropagation) { for (i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); } //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } this.fromOutside = false; return true; }, _trigger: function() { if ($.Widget.prototype._trigger.apply(this, arguments) === false) { this.cancel(); } }, _uiHash: function(_inst) { var inst = _inst || this; return { helper: inst.helper, placeholder: inst.placeholder || $([]), position: inst.position, originalPosition: inst.originalPosition, offset: inst.positionAbs, item: inst.currentItem, sender: _inst ? _inst.element : null }; } }); })(jQuery); /* ======================================================================== * Bootstrap: tooltip.js v3.3.1 * http://getbootstrap.com/javascript/#tooltip * Inspired by the original jQuery.tipsy by Jason Frame * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // TOOLTIP PUBLIC CLASS DEFINITION // =============================== var Tooltip = function (element, options) { this.type = this.options = this.enabled = this.timeout = this.hoverState = this.$element = null this.init('tooltip', element, options) } Tooltip.VERSION = '3.3.1' Tooltip.TRANSITION_DURATION = 150 Tooltip.DEFAULTS = { animation: true, placement: 'top', selector: false, template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', trigger: 'hover focus', title: '', delay: 0, html: false, container: false, viewport: { selector: 'body', padding: 0 } } Tooltip.prototype.init = function (type, element, options) { this.enabled = true this.type = type this.$element = $(element) this.options = this.getOptions(options) this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport) var triggers = this.options.trigger.split(' ') for (var i = triggers.length; i--;) { var trigger = triggers[i] if (trigger == 'click') { this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) } else if (trigger != 'manual') { var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) } } this.options.selector ? (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : this.fixTitle() } Tooltip.prototype.getDefaults = function () { return Tooltip.DEFAULTS } Tooltip.prototype.getOptions = function (options) { options = $.extend({}, this.getDefaults(), this.$element.data(), options) if (options.delay && typeof options.delay == 'number') { options.delay = { show: options.delay, hide: options.delay } } return options } Tooltip.prototype.getDelegateOptions = function () { var options = {} var defaults = this.getDefaults() this._options && $.each(this._options, function (key, value) { if (defaults[key] != value) options[key] = value }) return options } Tooltip.prototype.enter = function (obj) { var self = obj instanceof this.constructor ? obj : $(obj.currentTarget).data('bs.' + this.type) if (self && self.$tip && self.$tip.is(':visible')) { self.hoverState = 'in' return } if (!self) { self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) $(obj.currentTarget).data('bs.' + this.type, self) } clearTimeout(self.timeout) self.hoverState = 'in' if (!self.options.delay || !self.options.delay.show) return self.show() self.timeout = setTimeout(function () { if (self.hoverState == 'in') self.show() }, self.options.delay.show) } Tooltip.prototype.leave = function (obj) { var self = obj instanceof this.constructor ? obj : $(obj.currentTarget).data('bs.' + this.type) if (!self) { self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) $(obj.currentTarget).data('bs.' + this.type, self) } clearTimeout(self.timeout) self.hoverState = 'out' if (!self.options.delay || !self.options.delay.hide) return self.hide() self.timeout = setTimeout(function () { if (self.hoverState == 'out') self.hide() }, self.options.delay.hide) } Tooltip.prototype.show = function () { var e = $.Event('show.bs.' + this.type) if (this.hasContent() && this.enabled) { this.$element.trigger(e) var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) if (e.isDefaultPrevented() || !inDom) return var that = this var $tip = this.tip() var tipId = this.getUID(this.type) this.setContent() $tip.attr('id', tipId) this.$element.attr('aria-describedby', tipId) if (this.options.animation) $tip.addClass('fade') var placement = typeof this.options.placement == 'function' ? this.options.placement.call(this, $tip[0], this.$element[0]) : this.options.placement var autoToken = /\s?auto?\s?/i var autoPlace = autoToken.test(placement) if (autoPlace) placement = placement.replace(autoToken, '') || 'top' $tip .detach() .css({ top: 0, left: 0, display: 'block' }) .addClass(placement) .data('bs.' + this.type, this) this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) var pos = this.getPosition() var actualWidth = $tip[0].offsetWidth var actualHeight = $tip[0].offsetHeight if (autoPlace) { var orgPlacement = placement var $container = this.options.container ? $(this.options.container) : this.$element.parent() var containerDim = this.getPosition($container) placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' : placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' : placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' : placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' : placement $tip .removeClass(orgPlacement) .addClass(placement) } var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) this.applyPlacement(calculatedOffset, placement) var complete = function () { var prevHoverState = that.hoverState that.$element.trigger('shown.bs.' + that.type) that.hoverState = null if (prevHoverState == 'out') that.leave(that) } $.support.transition && this.$tip.hasClass('fade') ? $tip .one('bsTransitionEnd', complete) .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : complete() } } Tooltip.prototype.applyPlacement = function (offset, placement) { var $tip = this.tip() var width = $tip[0].offsetWidth var height = $tip[0].offsetHeight // manually read margins because getBoundingClientRect includes difference var marginTop = parseInt($tip.css('margin-top'), 10) var marginLeft = parseInt($tip.css('margin-left'), 10) // we must check for NaN for ie 8/9 if (isNaN(marginTop)) marginTop = 0 if (isNaN(marginLeft)) marginLeft = 0 offset.top = offset.top + marginTop offset.left = offset.left + marginLeft // $.fn.offset doesn't round pixel values // so we use setOffset directly with our own function B-0 $.offset.setOffset($tip[0], $.extend({ using: function (props) { $tip.css({ top: Math.round(props.top), left: Math.round(props.left) }) } }, offset), 0) $tip.addClass('in') // check to see if placing tip in new offset caused the tip to resize itself var actualWidth = $tip[0].offsetWidth var actualHeight = $tip[0].offsetHeight if (placement == 'top' && actualHeight != height) { offset.top = offset.top + height - actualHeight } var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) if (delta.left) offset.left += delta.left else offset.top += delta.top var isVertical = /top|bottom/.test(placement) var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' $tip.offset(offset) this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) } Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) { this.arrow() .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') .css(isHorizontal ? 'top' : 'left', '') } Tooltip.prototype.setContent = function () { var $tip = this.tip() var title = this.getTitle() $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) $tip.removeClass('fade in top bottom left right') } Tooltip.prototype.hide = function (callback) { var that = this var $tip = this.tip() var e = $.Event('hide.bs.' + this.type) function complete() { if (that.hoverState != 'in') $tip.detach() that.$element .removeAttr('aria-describedby') .trigger('hidden.bs.' + that.type) callback && callback() } this.$element.trigger(e) if (e.isDefaultPrevented()) return $tip.removeClass('in') $.support.transition && this.$tip.hasClass('fade') ? $tip .one('bsTransitionEnd', complete) .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : complete() this.hoverState = null return this } Tooltip.prototype.fixTitle = function () { var $e = this.$element if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') { $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') } } Tooltip.prototype.hasContent = function () { return this.getTitle() } Tooltip.prototype.getPosition = function ($element) { $element = $element || this.$element var el = $element[0] var isBody = el.tagName == 'BODY' var elRect = el.getBoundingClientRect() if (elRect.width == null) { // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) } var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null return $.extend({}, elRect, scroll, outerDims, elOffset) } Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } } Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { var delta = { top: 0, left: 0 } if (!this.$viewport) return delta var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 var viewportDimensions = this.getPosition(this.$viewport) if (/right|left/.test(placement)) { var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight if (topEdgeOffset < viewportDimensions.top) { // top overflow delta.top = viewportDimensions.top - topEdgeOffset } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset } } else { var leftEdgeOffset = pos.left - viewportPadding var rightEdgeOffset = pos.left + viewportPadding + actualWidth if (leftEdgeOffset < viewportDimensions.left) { // left overflow delta.left = viewportDimensions.left - leftEdgeOffset } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset } } return delta } Tooltip.prototype.getTitle = function () { var title var $e = this.$element var o = this.options title = $e.attr('data-original-title') || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) return title } Tooltip.prototype.getUID = function (prefix) { do prefix += ~~(Math.random() * 1000000) while (document.getElementById(prefix)) return prefix } Tooltip.prototype.tip = function () { return (this.$tip = this.$tip || $(this.options.template)) } Tooltip.prototype.arrow = function () { return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) } Tooltip.prototype.enable = function () { this.enabled = true } Tooltip.prototype.disable = function () { this.enabled = false } Tooltip.prototype.toggleEnabled = function () { this.enabled = !this.enabled } Tooltip.prototype.toggle = function (e) { var self = this if (e) { self = $(e.currentTarget).data('bs.' + this.type) if (!self) { self = new this.constructor(e.currentTarget, this.getDelegateOptions()) $(e.currentTarget).data('bs.' + this.type, self) } } self.tip().hasClass('in') ? self.leave(self) : self.enter(self) } Tooltip.prototype.destroy = function () { var that = this clearTimeout(this.timeout) this.hide(function () { that.$element.off('.' + that.type).removeData('bs.' + that.type) }) } // TOOLTIP PLUGIN DEFINITION // ========================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.tooltip') var options = typeof option == 'object' && option var selector = options && options.selector if (!data && option == 'destroy') return if (selector) { if (!data) $this.data('bs.tooltip', (data = {})) if (!data[selector]) data[selector] = new Tooltip(this, options) } else { if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) } if (typeof option == 'string') data[option]() }) } var old = $.fn.tooltip $.fn.tooltip = Plugin $.fn.tooltip.Constructor = Tooltip // TOOLTIP NO CONFLICT // =================== $.fn.tooltip.noConflict = function () { $.fn.tooltip = old return this } }(jQuery); /* ======================================================================== * Bootstrap: popover.js v3.3.1 * http://getbootstrap.com/javascript/#popovers * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // POPOVER PUBLIC CLASS DEFINITION // =============================== var Popover = function (element, options) { this.init('popover', element, options) } if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') Popover.VERSION = '3.3.1' Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { placement: 'right', trigger: 'click', content: '', template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' }) // NOTE: POPOVER EXTENDS tooltip.js // ================================ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) Popover.prototype.constructor = Popover Popover.prototype.getDefaults = function () { return Popover.DEFAULTS } Popover.prototype.setContent = function () { var $tip = this.tip() var title = this.getTitle() var content = this.getContent() $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' ](content) $tip.removeClass('fade top bottom left right in') // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do // this manually by checking the contents. if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() } Popover.prototype.hasContent = function () { return this.getTitle() || this.getContent() } Popover.prototype.getContent = function () { var $e = this.$element var o = this.options return $e.attr('data-content') || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) } Popover.prototype.arrow = function () { return (this.$arrow = this.$arrow || this.tip().find('.arrow')) } Popover.prototype.tip = function () { if (!this.$tip) this.$tip = $(this.options.template) return this.$tip } // POPOVER PLUGIN DEFINITION // ========================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.popover') var options = typeof option == 'object' && option var selector = options && options.selector if (!data && option == 'destroy') return if (selector) { if (!data) $this.data('bs.popover', (data = {})) if (!data[selector]) data[selector] = new Popover(this, options) } else { if (!data) $this.data('bs.popover', (data = new Popover(this, options))) } if (typeof option == 'string') data[option]() }) } var old = $.fn.popover $.fn.popover = Plugin $.fn.popover.Constructor = Popover // POPOVER NO CONFLICT // =================== $.fn.popover.noConflict = function () { $.fn.popover = old return this } }(jQuery); /* ======================================================================== * Bootstrap: collapse.js v3.3.1 * http://getbootstrap.com/javascript/#collapse * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // COLLAPSE PUBLIC CLASS DEFINITION // ================================ var Collapse = function (element, options) { this.$element = $(element) this.options = $.extend({}, Collapse.DEFAULTS, options) this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]') this.transitioning = null if (this.options.parent) { this.$parent = this.getParent() } else { this.addAriaAndCollapsedClass(this.$element, this.$trigger) } if (this.options.toggle) this.toggle() } Collapse.VERSION = '3.3.1' Collapse.TRANSITION_DURATION = 350 Collapse.DEFAULTS = { toggle: true, trigger: '[data-toggle="collapse"]' } Collapse.prototype.dimension = function () { var hasWidth = this.$element.hasClass('width') return hasWidth ? 'width' : 'height' } Collapse.prototype.show = function () { if (this.transitioning || this.$element.hasClass('in')) return var activesData var actives = this.$parent && this.$parent.find('> .panel').children('.in, .collapsing') if (actives && actives.length) { activesData = actives.data('bs.collapse') if (activesData && activesData.transitioning) return } var startEvent = $.Event('show.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return if (actives && actives.length) { Plugin.call(actives, 'hide') activesData || actives.data('bs.collapse', null) } var dimension = this.dimension() this.$element .removeClass('collapse') .addClass('collapsing')[dimension](0) .attr('aria-expanded', true) this.$trigger .removeClass('collapsed') .attr('aria-expanded', true) this.transitioning = 1 var complete = function () { this.$element .removeClass('collapsing') .addClass('collapse in')[dimension]('') this.transitioning = 0 this.$element .trigger('shown.bs.collapse') } if (!$.support.transition) return complete.call(this) var scrollSize = $.camelCase(['scroll', dimension].join('-')) this.$element .one('bsTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) } Collapse.prototype.hide = function () { if (this.transitioning || !this.$element.hasClass('in')) return var startEvent = $.Event('hide.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return var dimension = this.dimension() this.$element[dimension](this.$element[dimension]())[0].offsetHeight this.$element .addClass('collapsing') .removeClass('collapse in') .attr('aria-expanded', false) this.$trigger .addClass('collapsed') .attr('aria-expanded', false) this.transitioning = 1 var complete = function () { this.transitioning = 0 this.$element .removeClass('collapsing') .addClass('collapse') .trigger('hidden.bs.collapse') } if (!$.support.transition) return complete.call(this) this.$element [dimension](0) .one('bsTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(Collapse.TRANSITION_DURATION) } Collapse.prototype.toggle = function () { this[this.$element.hasClass('in') ? 'hide' : 'show']() } Collapse.prototype.getParent = function () { return $(this.options.parent) .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') .each($.proxy(function (i, element) { var $element = $(element) this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) }, this)) .end() } Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { var isOpen = $element.hasClass('in') $element.attr('aria-expanded', isOpen) $trigger .toggleClass('collapsed', !isOpen) .attr('aria-expanded', isOpen) } function getTargetFromTrigger($trigger) { var href var target = $trigger.attr('data-target') || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 return $(target) } // COLLAPSE PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.collapse') var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) if (!data && options.toggle && option == 'show') options.toggle = false if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.collapse $.fn.collapse = Plugin $.fn.collapse.Constructor = Collapse // COLLAPSE NO CONFLICT // ==================== $.fn.collapse.noConflict = function () { $.fn.collapse = old return this } // COLLAPSE DATA-API // ================= $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { var $this = $(this) if (!$this.attr('data-target')) e.preventDefault() var $target = getTargetFromTrigger($this) var data = $target.data('bs.collapse') var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this }) Plugin.call($target, option) }) }(jQuery); /* =================================================== * bootstrap-markdown.js v2.6.0 * http://github.com/toopay/bootstrap-markdown * =================================================== * Copyright 2013-2014 Taufan Aditya * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================== */ !function ($) { "use strict"; // jshint ;_; /* MARKDOWN CLASS DEFINITION * ========================== */ var Markdown = function (element, options) { // Class Properties this.$ns = 'bootstrap-markdown' this.$element = $(element) this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null} this.$options = $.extend(true, {}, $.fn.markdown.defaults, options, this.$element.data(), this.$element.data('options')) this.$oldContent = null this.$isPreview = false this.$editor = null this.$textarea = null this.$handler = [] this.$callback = [] this.$nextTab = [] this.showEditor() } Markdown.prototype = { constructor: Markdown , __alterButtons: function(name,alter) { var handler = this.$handler, isAll = (name == 'all'),that = this $.each(handler,function(k,v) { var halt = true if (isAll) { halt = false } else { halt = v.indexOf(name) < 0 } if (halt == false) { alter(that.$editor.find('button[data-handler="'+v+'"]')) } }) } , __buildButtons: function(buttonsArray, container) { var i, ns = this.$ns, handler = this.$handler, callback = this.$callback for (i=0;i<buttonsArray.length;i++) { // Build each group container var y, btnGroups = buttonsArray[i] for (y=0;y<btnGroups.length;y++) { // Build each button group var z, buttons = btnGroups[y].data, btnGroupContainer = $('<div/>', { 'class': 'btn-group' }) for (z=0;z<buttons.length;z++) { var button = buttons[z], buttonToggle = '', buttonHandler = ns+'-'+button.name, buttonIcon = button.icon instanceof Object ? button.icon[this.$options.iconlibrary] : button.icon, btnText = button.btnText ? button.btnText : '', btnClass = button.btnClass ? button.btnClass : 'btn', tabIndex = button.tabIndex ? button.tabIndex : '-1', hotkey = typeof button.hotkey !== 'undefined' ? button.hotkey : '', hotkeyCaption = typeof jQuery.hotkeys !== 'undefined' && hotkey !== '' ? ' ('+hotkey+')' : '' if (button.toggle == true) { buttonToggle = ' data-toggle="button"' } // Attach the button object btnGroupContainer.append('<button type="button" class="' +btnClass +' btn-default btn-sm" title="' +this.__localize(button.title) +hotkeyCaption +'" tabindex="' +tabIndex +'" data-provider="' +ns +'" data-handler="' +buttonHandler +'" data-hotkey="' +hotkey +'"' +buttonToggle +'><span class="' +buttonIcon +'"></span> ' +this.__localize(btnText) +'</button>') // Register handler and callback handler.push(buttonHandler) callback.push(button.callback) } // Attach the button group into container dom container.append(btnGroupContainer) } } return container } , __setListener: function() { // Set size and resizable Properties var hasRows = typeof this.$textarea.attr('rows') != 'undefined', maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5', rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows this.$textarea.attr('rows',rowsVal) if (this.$options.resize) { this.$textarea.css('resize',this.$options.resize) } this.$textarea .on('focus', $.proxy(this.focus, this)) .on('keypress', $.proxy(this.keypress, this)) .on('keyup', $.proxy(this.keyup, this)) .on('change', $.proxy(this.change, this)) if (this.eventSupported('keydown')) { this.$textarea.on('keydown', $.proxy(this.keydown, this)) } // Re-attach markdown data this.$textarea.data('markdown',this) } , __handle: function(e) { var target = $(e.currentTarget), handler = this.$handler, callback = this.$callback, handlerName = target.attr('data-handler'), callbackIndex = handler.indexOf(handlerName), callbackHandler = callback[callbackIndex] // Trigger the focusin $(e.currentTarget).focus() callbackHandler(this) // Trigger onChange for each button handle this.change(this); // Unless it was the save handler, // focusin the textarea if (handlerName.indexOf('cmdSave') < 0) { this.$textarea.focus() } e.preventDefault() } , __localize: function(string) { var messages = $.fn.markdown.messages, language = this.$options.language if ( typeof messages !== 'undefined' && typeof messages[language] !== 'undefined' && typeof messages[language][string] !== 'undefined' ) { return messages[language][string]; } return string; } , showEditor: function() { var instance = this, textarea, ns = this.$ns, container = this.$element, originalHeigth = container.css('height'), originalWidth = container.css('width'), editable = this.$editable, handler = this.$handler, callback = this.$callback, options = this.$options, editor = $( '<div/>', { 'class': 'md-editor', click: function() { instance.focus() } }) // Prepare the editor if (this.$editor == null) { // Create the panel var editorHeader = $('<div/>', { 'class': 'md-header btn-toolbar' }) // Merge the main & additional button groups together var allBtnGroups = [] if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0]) if (options.additionalButtons.length > 0) allBtnGroups = allBtnGroups.concat(options.additionalButtons[0]) // Reduce and/or reorder the button groups if (options.reorderButtonGroups.length > 0) { allBtnGroups = allBtnGroups .filter(function(btnGroup) { return options.reorderButtonGroups.indexOf(btnGroup.name) > -1 }) .sort(function(a, b) { if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1 if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1 return 0 }) } // Build the buttons if (allBtnGroups.length > 0) { editorHeader = this.__buildButtons([allBtnGroups], editorHeader) } editor.append(editorHeader) // Wrap the textarea if (container.is('textarea')) { container.before(editor) textarea = container textarea.addClass('md-input') editor.append(textarea) } else { var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(), currentContent = $.trim(rawContent) // This is some arbitrary content that could be edited textarea = $('<textarea/>', { 'class': 'md-input', 'val' : currentContent }) editor.append(textarea) // Save the editable editable.el = container editable.type = container.prop('tagName').toLowerCase() editable.content = container.html() $(container[0].attributes).each(function(){ editable.attrKeys.push(this.nodeName) editable.attrValues.push(this.nodeValue) }) // Set editor to blocked the original container container.replaceWith(editor) } var editorFooter = $('<div/>', { 'class': 'md-footer' }), createFooter = false, footer = '' // Create the footer if savable if (options.savable) { createFooter = true; var saveHandler = 'cmdSave' // Register handler and callback handler.push(saveHandler) callback.push(options.onSave) editorFooter.append('<button class="btn btn-success" data-provider="' +ns +'" data-handler="' +saveHandler +'"><i class="icon icon-white icon-ok"></i> ' +this.__localize('Save') +'</button>') } footer = typeof options.footer === 'function' ? options.footer(this) : options.footer if ($.trim(footer) !== '') { createFooter = true; editorFooter.append(footer); } if (createFooter) editor.append(editorFooter) // Set width if (options.width && options.width !== 'inherit') { if (jQuery.isNumeric(options.width)) { editor.css('display', 'table') textarea.css('width', options.width + 'px') } else { editor.addClass(options.width) } } // Set height if (options.height && options.height !== 'inherit') { if (jQuery.isNumeric(options.height)) { var height = options.height if (editorHeader) height = Math.max(0, height - editorHeader.outerHeight()) if (editorFooter) height = Math.max(0, height - editorFooter.outerHeight()) textarea.css('height', height + 'px') } else { editor.addClass(options.height) } } // Reference this.$editor = editor this.$textarea = textarea this.$editable = editable this.$oldContent = this.getContent() this.__setListener() // Set editor attributes, data short-hand API and listener this.$editor.attr('id',(new Date).getTime()) this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this)) if (this.$element.is(':disabled') || this.$element.is('[readonly]')) { this.disableButtons('all'); } if (this.eventSupported('keydown') && typeof jQuery.hotkeys === 'object') { editorHeader.find('[data-provider="bootstrap-markdown"]').each(function() { var $button = $(this), hotkey = $button.attr('data-hotkey') if (hotkey.toLowerCase() !== '') { textarea.bind('keydown', hotkey, function() { $button.trigger('click') return false; }) } }) } } else { this.$editor.show() } if (options.autofocus) { this.$textarea.focus() this.$editor.addClass('active') } if (options.initialstate === 'preview') { this.showPreview(); } // hide hidden buttons from options this.hideButtons(options.hiddenButtons) // disable disabled buttons from options this.disableButtons(options.disabledButtons) // Trigger the onShow hook options.onShow(this) return this } , parseContent: function() { var content, callbackContent = this.$options.onPreview(this) // Try to get the content from callback if (typeof callbackContent == 'string') { // Set the content based by callback content content = callbackContent } else { // Set the content var val = this.$textarea.val(); if(typeof markdown == 'object') { content = markdown.toHTML(val); }else if(typeof marked == 'function') { content = marked(val); } else { content = val; } } return content; } , showPreview: function() { var options = this.$options, container = this.$textarea, afterContainer = container.next(), replacementContainer = $('<div/>',{'class':'md-preview','data-provider':'markdown-preview'}), content // Give flag that tell the editor enter preview mode this.$isPreview = true // Disable all buttons this.disableButtons('all').enableButtons('cmdPreview') content = this.parseContent() // Build preview element replacementContainer.html(content) if (afterContainer && afterContainer.attr('class') == 'md-footer') { // If there is footer element, insert the preview container before it replacementContainer.insertBefore(afterContainer) } else { // Otherwise, just append it after textarea container.parent().append(replacementContainer) } // Set the preview element dimensions replacementContainer.css({ width: container.outerWidth() + 'px', height: container.outerHeight() + 'px' }) if (this.$options.resize) { replacementContainer.css('resize',this.$options.resize) } // Hide the last-active textarea container.hide() // Attach the editor instances replacementContainer.data('markdown',this) return this } , hidePreview: function() { // Give flag that tell the editor quit preview mode this.$isPreview = false // Obtain the preview container var container = this.$editor.find('div[data-provider="markdown-preview"]') // Remove the preview container container.remove() // Enable all buttons this.enableButtons('all') // Disable configured disabled buttons this.disableButtons(this.$options.disabledButtons) // Back to the editor this.$textarea.show() this.__setListener() return this } , isDirty: function() { return this.$oldContent != this.getContent() } , getContent: function() { return this.$textarea.val() } , setContent: function(content) { this.$textarea.val(content) return this } , findSelection: function(chunk) { var content = this.getContent(), startChunkPosition if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) { var oldSelection = this.getSelection(), selection this.setSelection(startChunkPosition,startChunkPosition+chunk.length) selection = this.getSelection() this.setSelection(oldSelection.start,oldSelection.end) return selection } else { return null } } , getSelection: function() { var e = this.$textarea[0] return ( ('selectionStart' in e && function() { var l = e.selectionEnd - e.selectionStart return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) } }) || /* browser not supported */ function() { return null } )() } , setSelection: function(start,end) { var e = this.$textarea[0] return ( ('selectionStart' in e && function() { e.selectionStart = start e.selectionEnd = end return }) || /* browser not supported */ function() { return null } )() } , replaceSelection: function(text) { var e = this.$textarea[0] return ( ('selectionStart' in e && function() { e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length) // Set cursor to the last replacement end e.selectionStart = e.value.length return this }) || /* browser not supported */ function() { e.value += text return jQuery(e) } )() } , getNextTab: function() { // Shift the nextTab if (this.$nextTab.length == 0) { return null } else { var nextTab, tab = this.$nextTab.shift() if (typeof tab == 'function') { nextTab = tab() } else if (typeof tab == 'object' && tab.length > 0) { nextTab = tab } return nextTab } } , setNextTab: function(start,end) { // Push new selection into nextTab collections if (typeof start == 'string') { var that = this this.$nextTab.push(function(){ return that.findSelection(start) }) } else if (typeof start == 'number' && typeof end == 'number') { var oldSelection = this.getSelection() this.setSelection(start,end) this.$nextTab.push(this.getSelection()) this.setSelection(oldSelection.start,oldSelection.end) } return } , __parseButtonNameParam: function(nameParam) { var buttons = [] if (typeof nameParam == 'string') { buttons.push(nameParam) } else { buttons = nameParam } return buttons } , enableButtons: function(name) { var buttons = this.__parseButtonNameParam(name), that = this $.each(buttons, function(i, v) { that.__alterButtons(buttons[i], function (el) { el.removeAttr('disabled') }); }) return this; } , disableButtons: function(name) { var buttons = this.__parseButtonNameParam(name), that = this $.each(buttons, function(i, v) { that.__alterButtons(buttons[i], function (el) { el.attr('disabled','disabled') }); }) return this; } , hideButtons: function(name) { var buttons = this.__parseButtonNameParam(name), that = this $.each(buttons, function(i, v) { that.__alterButtons(buttons[i], function (el) { el.addClass('hidden'); }); }) return this; } , showButtons: function(name) { var buttons = this.__parseButtonNameParam(name), that = this $.each(buttons, function(i, v) { that.__alterButtons(buttons[i], function (el) { el.removeClass('hidden'); }); }) return this; } , eventSupported: function(eventName) { var isSupported = eventName in this.$element if (!isSupported) { this.$element.setAttribute(eventName, 'return;') isSupported = typeof this.$element[eventName] === 'function' } return isSupported } , keyup: function (e) { var blocked = false switch(e.keyCode) { case 40: // down arrow case 38: // up arrow case 16: // shift case 17: // ctrl case 18: // alt break case 9: // tab var nextTab if (nextTab = this.getNextTab(),nextTab != null) { // Get the nextTab if exists var that = this setTimeout(function(){ that.setSelection(nextTab.start,nextTab.end) },500) blocked = true } else { // The next tab memory contains nothing... // check the cursor position to determine tab action var cursor = this.getSelection() if (cursor.start == cursor.end && cursor.end == this.getContent().length) { // The cursor already reach the end of the content blocked = false } else { // Put the cursor to the end this.setSelection(this.getContent().length,this.getContent().length) blocked = true } } break case 13: // enter case 27: // escape blocked = false break default: blocked = false } if (blocked) { e.stopPropagation() e.preventDefault() } this.$options.onChange(this) } , change: function(e) { this.$options.onChange(this); return this; } , focus: function (e) { var options = this.$options, isHideable = options.hideable, editor = this.$editor editor.addClass('active') // Blur other markdown(s) $(document).find('.md-editor').each(function(){ if ($(this).attr('id') != editor.attr('id')) { var attachedMarkdown if (attachedMarkdown = $(this).find('textarea').data('markdown'), attachedMarkdown == null) { attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown') } if (attachedMarkdown) { attachedMarkdown.blur() } } }) // Trigger the onFocus hook options.onFocus(this); return this } , blur: function (e) { var options = this.$options, isHideable = options.hideable, editor = this.$editor, editable = this.$editable if (editor.hasClass('active') || this.$element.parent().length == 0) { editor.removeClass('active') if (isHideable) { // Check for editable elements if (editable.el != null) { // Build the original element var oldElement = $('<'+editable.type+'/>'), content = this.getContent(), currentContent = (typeof markdown == 'object') ? markdown.toHTML(content) : content $(editable.attrKeys).each(function(k,v) { oldElement.attr(editable.attrKeys[k],editable.attrValues[k]) }) // Get the editor content oldElement.html(currentContent) editor.replaceWith(oldElement) } else { editor.hide() } } // Trigger the onBlur hook options.onBlur(this) } return this } } /* MARKDOWN PLUGIN DEFINITION * ========================== */ var old = $.fn.markdown $.fn.markdown = function (option) { return this.each(function () { var $this = $(this) , data = $this.data('markdown') , options = typeof option == 'object' && option if (!data) $this.data('markdown', (data = new Markdown(this, options))) }) } $.fn.markdown.messages = {} $.fn.markdown.defaults = { /* Editor Properties */ autofocus: false, hideable: false, savable:false, width: 'inherit', height: 'inherit', resize: 'none', iconlibrary: 'glyph', language: 'en', initialstate: 'editor', /* Buttons Properties */ buttons: [ [{ name: 'groupFont', data: [{ name: 'cmdBold', hotkey: 'Ctrl+B', title: 'Bold', icon: { glyph: 'glyphicon glyphicon-bold', fa: 'fa fa-bold', 'fa-3': 'icon-bold' }, callback: function(e){ // Give/remove ** surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent() if (selected.length == 0) { // Give extra word chunk = e.__localize('strong text') } else { chunk = selected.text } // transform selection and set the cursor into chunked text if (content.substr(selected.start-2,2) == '**' && content.substr(selected.end,2) == '**' ) { e.setSelection(selected.start-2,selected.end+2) e.replaceSelection(chunk) cursor = selected.start-2 } else { e.replaceSelection('**'+chunk+'**') cursor = selected.start+2 } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } },{ name: 'cmdItalic', title: 'Italic', hotkey: 'Ctrl+I', icon: { glyph: 'glyphicon glyphicon-italic', fa: 'fa fa-italic', 'fa-3': 'icon-italic' }, callback: function(e){ // Give/remove * surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent() if (selected.length == 0) { // Give extra word chunk = e.__localize('emphasized text') } else { chunk = selected.text } // transform selection and set the cursor into chunked text if (content.substr(selected.start-1,1) == '*' && content.substr(selected.end,1) == '*' ) { e.setSelection(selected.start-1,selected.end+1) e.replaceSelection(chunk) cursor = selected.start-1 } else { e.replaceSelection('*'+chunk+'*') cursor = selected.start+1 } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } },{ name: 'cmdHeading', title: 'Heading', hotkey: 'Ctrl+H', icon: { glyph: 'glyphicon glyphicon-header', fa: 'fa fa-font', 'fa-3': 'icon-font' }, callback: function(e){ // Append/remove ### surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent(), pointer, prevChar if (selected.length == 0) { // Give extra word chunk = e.__localize('heading text') } else { chunk = selected.text + '\n'; } // transform selection and set the cursor into chunked text if ((pointer = 4, content.substr(selected.start-pointer,pointer) == '### ') || (pointer = 3, content.substr(selected.start-pointer,pointer) == '###')) { e.setSelection(selected.start-pointer,selected.end) e.replaceSelection(chunk) cursor = selected.start-pointer } else if (selected.start > 0 && (prevChar = content.substr(selected.start-1,1), !!prevChar && prevChar != '\n')) { e.replaceSelection('\n\n### '+chunk) cursor = selected.start+6 } else { // Empty string before element e.replaceSelection('### '+chunk) cursor = selected.start+4 } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } }] },{ name: 'groupLink', data: [{ name: 'cmdUrl', title: 'URL/Link', hotkey: 'Ctrl+L', icon: { glyph: 'glyphicon glyphicon-link', fa: 'fa fa-link', 'fa-3': 'icon-link' }, callback: function(e){ // Give [] surround the selection and prepend the link var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link if (selected.length == 0) { // Give extra word chunk = e.__localize('enter link description here') } else { chunk = selected.text } link = prompt(e.__localize('Insert Hyperlink'),'http://') if (link != null && link != '' && link != 'http://') { // transform selection and set the cursor into chunked text e.replaceSelection('['+chunk+']('+link+')') cursor = selected.start+1 // Set the cursor e.setSelection(cursor,cursor+chunk.length) } } },{ name: 'cmdImage', title: 'Image', hotkey: 'Ctrl+G', icon: { glyph: 'glyphicon glyphicon-picture', fa: 'fa fa-picture-o', 'fa-3': 'icon-picture' }, callback: function(e){ // Give ![] surround the selection and prepend the image link var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link if (selected.length == 0) { // Give extra word chunk = e.__localize('enter image description here') } else { chunk = selected.text } link = prompt(e.__localize('Insert Image Hyperlink'),'http://') if (link != null) { // transform selection and set the cursor into chunked text e.replaceSelection('+'")') cursor = selected.start+2 // Set the next tab e.setNextTab(e.__localize('enter image title here')) // Set the cursor e.setSelection(cursor,cursor+chunk.length) } } }] },{ name: 'groupMisc', data: [{ name: 'cmdList', hotkey: 'Ctrl+U', title: 'Unordered List', icon: { glyph: 'glyphicon glyphicon-list', fa: 'fa fa-list', 'fa-3': 'icon-list-ul' }, callback: function(e){ // Prepend/Give - surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent() // transform selection and set the cursor into chunked text if (selected.length == 0) { // Give extra word chunk = e.__localize('list text here') e.replaceSelection('- '+chunk) // Set the cursor cursor = selected.start+2 } else { if (selected.text.indexOf('\n') < 0) { chunk = selected.text e.replaceSelection('- '+chunk) // Set the cursor cursor = selected.start+2 } else { var list = [] list = selected.text.split('\n') chunk = list[0] $.each(list,function(k,v) { list[k] = '- '+v }) e.replaceSelection('\n\n'+list.join('\n')) // Set the cursor cursor = selected.start+4 } } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } }, { name: 'cmdListO', hotkey: 'Ctrl+O', title: 'Ordered List', icon: { glyph: 'glyphicon glyphicon-th-list', fa: 'fa fa-list-ol', 'fa-3': 'icon-list-ol' }, callback: function(e) { // Prepend/Give - surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent() // transform selection and set the cursor into chunked text if (selected.length == 0) { // Give extra word chunk = e.__localize('list text here') e.replaceSelection('1. '+chunk) // Set the cursor cursor = selected.start+3 } else { if (selected.text.indexOf('\n') < 0) { chunk = selected.text e.replaceSelection('1. '+chunk) // Set the cursor cursor = selected.start+3 } else { var list = [] list = selected.text.split('\n') chunk = list[0] $.each(list,function(k,v) { list[k] = '1. '+v }) e.replaceSelection('\n\n'+list.join('\n')) // Set the cursor cursor = selected.start+5 } } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } }, { name: 'cmdCode', hotkey: 'Ctrl+K', title: 'Code', icon: { glyph: 'glyphicon glyphicon-asterisk', fa: 'fa fa-code', 'fa-3': 'icon-code' }, callback: function(e) { // Give/remove ** surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent() if (selected.length == 0) { // Give extra word chunk = e.__localize('code text here') } else { chunk = selected.text } // transform selection and set the cursor into chunked text if (content.substr(selected.start-1,1) == '`' && content.substr(selected.end,1) == '`' ) { e.setSelection(selected.start-1,selected.end+1) e.replaceSelection(chunk) cursor = selected.start-1 } else { e.replaceSelection('`'+chunk+'`') cursor = selected.start+1 } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } }, { name: 'cmdQuote', hotkey: 'Ctrl+Q', title: 'Quote', icon: { glyph: 'glyphicon glyphicon-comment', fa: 'fa fa-quote-left', 'fa-3': 'icon-quote-left' }, callback: function(e) { // Prepend/Give - surround the selection var chunk, cursor, selected = e.getSelection(), content = e.getContent() // transform selection and set the cursor into chunked text if (selected.length == 0) { // Give extra word chunk = e.__localize('quote here') e.replaceSelection('> '+chunk) // Set the cursor cursor = selected.start+2 } else { if (selected.text.indexOf('\n') < 0) { chunk = selected.text e.replaceSelection('> '+chunk) // Set the cursor cursor = selected.start+2 } else { var list = [] list = selected.text.split('\n') chunk = list[0] $.each(list,function(k,v) { list[k] = '> '+v }) e.replaceSelection('\n\n'+list.join('\n')) // Set the cursor cursor = selected.start+4 } } // Set the cursor e.setSelection(cursor,cursor+chunk.length) } }] },{ name: 'groupUtil', data: [{ name: 'cmdPreview', toggle: true, hotkey: 'Ctrl+P', title: 'Preview', btnText: 'Preview', btnClass: 'btn btn-primary btn-sm', icon: { glyph: 'glyphicon glyphicon-search', fa: 'fa fa-search', 'fa-3': 'icon-search' }, callback: function(e){ // Check the preview mode and toggle based on this flag var isPreview = e.$isPreview,content if (isPreview == false) { // Give flag that tell the editor enter preview mode e.showPreview() } else { e.hidePreview() } } }] }] ], additionalButtons:[], // Place to hook more buttons by code reorderButtonGroups:[], hiddenButtons:[], // Default hidden buttons disabledButtons:[], // Default disabled buttons footer: '', /* Events hook */ onShow: function (e) {}, onPreview: function (e) {}, onSave: function (e) {}, onBlur: function (e) {}, onFocus: function (e) {}, onChange: function(e) {} } $.fn.markdown.Constructor = Markdown /* MARKDOWN NO CONFLICT * ==================== */ $.fn.markdown.noConflict = function () { $.fn.markdown = old return this } /* MARKDOWN GLOBAL FUNCTION & DATA-API * ==================================== */ var initMarkdown = function(el) { var $this = el if ($this.data('markdown')) { $this.data('markdown').showEditor() return } $this.markdown() } var analyzeMarkdown = function(e) { var blurred = false, el, $docEditor = $(e.currentTarget) // Check whether it was editor childs or not if ((e.type == 'focusin' || e.type == 'click') && $docEditor.length == 1 && typeof $docEditor[0] == 'object'){ el = $docEditor[0].activeElement if ( ! $(el).data('markdown')) { if (typeof $(el).parent().parent().parent().attr('class') == "undefined" || $(el).parent().parent().parent().attr('class').indexOf('md-editor') < 0) { if ( typeof $(el).parent().parent().attr('class') == "undefined" || $(el).parent().parent().attr('class').indexOf('md-editor') < 0) { blurred = true } } else { blurred = false } } if (blurred) { // Blur event $(document).find('.md-editor').each(function(){ var parentMd = $(el).parent() if ($(this).attr('id') != parentMd.attr('id')) { var attachedMarkdown if (attachedMarkdown = $(this).find('textarea').data('markdown'), attachedMarkdown == null) { attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown') } if (attachedMarkdown) { attachedMarkdown.blur() } } }) } e.stopPropagation() } } $(document) .on('click.markdown.data-api', '[data-provide="markdown-editable"]', function (e) { initMarkdown($(this)) e.preventDefault() }) .on('click', function (e) { analyzeMarkdown(e) }) .on('focusin', function (e) { analyzeMarkdown(e) }) .ready(function(){ $('textarea[data-provide="markdown"]').each(function(){ initMarkdown($(this)) }) }) }(window.jQuery); // Released under MIT license // Copyright (c) 2009-2010 Dominic Baggott // Copyright (c) 2009-2010 Ash Berlin // Copyright (c) 2011 Christoph Dorn <christoph@christophdorn.com> (http://www.christophdorn.com) /*jshint browser:true, devel:true */ (function( expose ) { /** * class Markdown * * Markdown processing in Javascript done right. We have very particular views * on what constitutes 'right' which include: * * - produces well-formed HTML (this means that em and strong nesting is * important) * * - has an intermediate representation to allow processing of parsed data (We * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). * * - is easily extensible to add new dialects without having to rewrite the * entire parsing mechanics * * - has a good test suite * * This implementation fulfills all of these (except that the test suite could * do with expanding to automatically run all the fixtures from other Markdown * implementations.) * * ##### Intermediate Representation * * *TODO* Talk about this :) Its JsonML, but document the node names we use. * * [JsonML]: http://jsonml.org/ "JSON Markup Language" **/ var Markdown = expose.Markdown = function(dialect) { switch (typeof dialect) { case "undefined": this.dialect = Markdown.dialects.Gruber; break; case "object": this.dialect = dialect; break; default: if ( dialect in Markdown.dialects ) { this.dialect = Markdown.dialects[dialect]; } else { throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); } break; } this.em_state = []; this.strong_state = []; this.debug_indent = ""; }; /** * parse( markdown, [dialect] ) -> JsonML * - markdown (String): markdown string to parse * - dialect (String | Dialect): the dialect to use, defaults to gruber * * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. **/ expose.parse = function( source, dialect ) { // dialect will default if undefined var md = new Markdown( dialect ); return md.toTree( source ); }; /** * toHTML( markdown, [dialect] ) -> String * toHTML( md_tree ) -> String * - markdown (String): markdown string to parse * - md_tree (Markdown.JsonML): parsed markdown tree * * Take markdown (either as a string or as a JsonML tree) and run it through * [[toHTMLTree]] then turn it into a well-formated HTML fragment. **/ expose.toHTML = function toHTML( source , dialect , options ) { var input = expose.toHTMLTree( source , dialect , options ); return expose.renderJsonML( input ); }; /** * toHTMLTree( markdown, [dialect] ) -> JsonML * toHTMLTree( md_tree ) -> JsonML * - markdown (String): markdown string to parse * - dialect (String | Dialect): the dialect to use, defaults to gruber * - md_tree (Markdown.JsonML): parsed markdown tree * * Turn markdown into HTML, represented as a JsonML tree. If a string is given * to this function, it is first parsed into a markdown tree by calling * [[parse]]. **/ expose.toHTMLTree = function toHTMLTree( input, dialect , options ) { // convert string input to an MD tree if ( typeof input ==="string" ) input = this.parse( input, dialect ); // Now convert the MD tree to an HTML tree // remove references from the tree var attrs = extract_attr( input ), refs = {}; if ( attrs && attrs.references ) { refs = attrs.references; } var html = convert_tree_to_html( input, refs , options ); merge_text_nodes( html ); return html; }; // For Spidermonkey based engines function mk_block_toSource() { return "Markdown.mk_block( " + uneval(this.toString()) + ", " + uneval(this.trailing) + ", " + uneval(this.lineNumber) + " )"; } // node function mk_block_inspect() { var util = require("util"); return "Markdown.mk_block( " + util.inspect(this.toString()) + ", " + util.inspect(this.trailing) + ", " + util.inspect(this.lineNumber) + " )"; } var mk_block = Markdown.mk_block = function(block, trail, line) { // Be helpful for default case in tests. if ( arguments.length == 1 ) trail = "\n\n"; var s = new String(block); s.trailing = trail; // To make it clear its not just a string s.inspect = mk_block_inspect; s.toSource = mk_block_toSource; if ( line != undefined ) s.lineNumber = line; return s; }; function count_lines( str ) { var n = 0, i = -1; while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) n++; return n; } // Internal - split source into rough blocks Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) { input = input.replace(/(\r\n|\n|\r)/g, "\n"); // [\s\S] matches _anything_ (newline or space) // [^] is equivalent but doesn't work in IEs. var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, blocks = [], m; var line_no = 1; if ( ( m = /^(\s*\n)/.exec(input) ) != null ) { // skip (but count) leading blank lines line_no += count_lines( m[0] ); re.lastIndex = m[0].length; } while ( ( m = re.exec(input) ) !== null ) { if (m[2] == "\n#") { m[2] = "\n"; re.lastIndex--; } blocks.push( mk_block( m[1], m[2], line_no ) ); line_no += count_lines( m[0] ); } return blocks; }; /** * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] * - block (String): the block to process * - next (Array): the following blocks * * Process `block` and return an array of JsonML nodes representing `block`. * * It does this by asking each block level function in the dialect to process * the block until one can. Succesful handling is indicated by returning an * array (with zero or more JsonML nodes), failure by a false value. * * Blocks handlers are responsible for calling [[Markdown#processInline]] * themselves as appropriate. * * If the blocks were split incorrectly or adjacent blocks need collapsing you * can adjust `next` in place using shift/splice etc. * * If any of this default behaviour is not right for the dialect, you can * define a `__call__` method on the dialect that will get invoked to handle * the block processing. */ Markdown.prototype.processBlock = function processBlock( block, next ) { var cbs = this.dialect.block, ord = cbs.__order__; if ( "__call__" in cbs ) { return cbs.__call__.call(this, block, next); } for ( var i = 0; i < ord.length; i++ ) { //D:this.debug( "Testing", ord[i] ); var res = cbs[ ord[i] ].call( this, block, next ); if ( res ) { //D:this.debug(" matched"); if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) this.debug(ord[i], "didn't return a proper array"); //D:this.debug( "" ); return res; } } // Uhoh! no match! Should we throw an error? return []; }; Markdown.prototype.processInline = function processInline( block ) { return this.dialect.inline.__call__.call( this, String( block ) ); }; /** * Markdown#toTree( source ) -> JsonML * - source (String): markdown source to parse * * Parse `source` into a JsonML tree representing the markdown document. **/ // custom_tree means set this.tree to `custom_tree` and restore old value on return Markdown.prototype.toTree = function toTree( source, custom_root ) { var blocks = source instanceof Array ? source : this.split_blocks( source ); // Make tree a member variable so its easier to mess with in extensions var old_tree = this.tree; try { this.tree = custom_root || this.tree || [ "markdown" ]; blocks: while ( blocks.length ) { var b = this.processBlock( blocks.shift(), blocks ); // Reference blocks and the like won't return any content if ( !b.length ) continue blocks; this.tree.push.apply( this.tree, b ); } return this.tree; } finally { if ( custom_root ) { this.tree = old_tree; } } }; // Noop by default Markdown.prototype.debug = function () { var args = Array.prototype.slice.call( arguments); args.unshift(this.debug_indent); if ( typeof print !== "undefined" ) print.apply( print, args ); if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) console.log.apply( null, args ); } Markdown.prototype.loop_re_over_block = function( re, block, cb ) { // Dont use /g regexps with this var m, b = block.valueOf(); while ( b.length && (m = re.exec(b) ) != null ) { b = b.substr( m[0].length ); cb.call(this, m); } return b; }; /** * Markdown.dialects * * Namespace of built-in dialects. **/ Markdown.dialects = {}; /** * Markdown.dialects.Gruber * * The default dialect that follows the rules set out by John Gruber's * markdown.pl as closely as possible. Well actually we follow the behaviour of * that script which in some places is not exactly what the syntax web page * says. **/ Markdown.dialects.Gruber = { block: { atxHeader: function atxHeader( block, next ) { var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); if ( !m ) return undefined; var header = [ "header", { level: m[ 1 ].length } ]; Array.prototype.push.apply(header, this.processInline(m[ 2 ])); if ( m[0].length < block.length ) next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); return [ header ]; }, setextHeader: function setextHeader( block, next ) { var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); if ( !m ) return undefined; var level = ( m[ 2 ] === "=" ) ? 1 : 2; var header = [ "header", { level : level }, m[ 1 ] ]; if ( m[0].length < block.length ) next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); return [ header ]; }, code: function code( block, next ) { // | Foo // |bar // should be a code block followed by a paragraph. Fun // // There might also be adjacent code block to merge. var ret = [], re = /^(?: {0,3}\t| {4})(.*)\n?/, lines; // 4 spaces + content if ( !block.match( re ) ) return undefined; block_search: do { // Now pull out the rest of the lines var b = this.loop_re_over_block( re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); if ( b.length ) { // Case alluded to in first comment. push it back on as a new block next.unshift( mk_block(b, block.trailing) ); break block_search; } else if ( next.length ) { // Check the next block - it might be code too if ( !next[0].match( re ) ) break block_search; // Pull how how many blanks lines follow - minus two to account for .join ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); block = next.shift(); } else { break block_search; } } while ( true ); return [ [ "code_block", ret.join("\n") ] ]; }, horizRule: function horizRule( block, next ) { // this needs to find any hr in the block to handle abutting blocks var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); if ( !m ) { return undefined; } var jsonml = [ [ "hr" ] ]; // if there's a leading abutting block, process it if ( m[ 1 ] ) { jsonml.unshift.apply( jsonml, this.processBlock( m[ 1 ], [] ) ); } // if there's a trailing abutting block, stick it into next if ( m[ 3 ] ) { next.unshift( mk_block( m[ 3 ] ) ); } return jsonml; }, // There are two types of lists. Tight and loose. Tight lists have no whitespace // between the items (and result in text just in the <li>) and loose lists, // which have an empty line between list items, resulting in (one or more) // paragraphs inside the <li>. // // There are all sorts weird edge cases about the original markdown.pl's // handling of lists: // // * Nested lists are supposed to be indented by four chars per level. But // if they aren't, you can get a nested list by indenting by less than // four so long as the indent doesn't match an indent of an existing list // item in the 'nest stack'. // // * The type of the list (bullet or number) is controlled just by the // first item at the indent. Subsequent changes are ignored unless they // are for nested lists // lists: (function( ) { // Use a closure to hide a few variables. var any_list = "[*+-]|\\d+\\.", bullet_list = /[*+-]/, number_list = /\d+\./, // Capture leading indent as it matters for determining nested lists. is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), indent_re = "(?: {0,3}\\t| {4})"; // TODO: Cache this regexp for certain depths. // Create a regexp suitable for matching an li for a given stack depth function regex_for_depth( depth ) { return new RegExp( // m[1] = indent, m[2] = list_type "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + // m[3] = cont "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" ); } function expand_tab( input ) { return input.replace( / {0,3}\t/g, " " ); } // Add inline content `inline` to `li`. inline comes from processInline // so is an array of content function add(li, loose, inline, nl) { if ( loose ) { li.push( [ "para" ].concat(inline) ); return; } // Hmmm, should this be any block level element or just paras? var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para" ? li[li.length -1] : li; // If there is already some content in this list, add the new line in if ( nl && li.length > 1 ) inline.unshift(nl); for ( var i = 0; i < inline.length; i++ ) { var what = inline[i], is_str = typeof what == "string"; if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) { add_to[ add_to.length-1 ] += what; } else { add_to.push( what ); } } } // contained means have an indent greater than the current one. On // *every* line in the block function get_contained_blocks( depth, blocks ) { var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), ret = []; while ( blocks.length > 0 ) { if ( re.exec( blocks[0] ) ) { var b = blocks.shift(), // Now remove that indent x = b.replace( replace, ""); ret.push( mk_block( x, b.trailing, b.lineNumber ) ); } else { break; } } return ret; } // passed to stack.forEach to turn list items up the stack into paras function paragraphify(s, i, stack) { var list = s.list; var last_li = list[list.length-1]; if ( last_li[1] instanceof Array && last_li[1][0] == "para" ) { return; } if ( i + 1 == stack.length ) { // Last stack frame // Keep the same array, but replace the contents last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); } else { var sublist = last_li.pop(); last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); } } // The matcher function return function( block, next ) { var m = block.match( is_list_re ); if ( !m ) return undefined; function make_list( m ) { var list = bullet_list.exec( m[2] ) ? ["bulletlist"] : ["numberlist"]; stack.push( { list: list, indent: m[1] } ); return list; } var stack = [], // Stack of lists for nesting. list = make_list( m ), last_li, loose = false, ret = [ stack[0].list ], i; // Loop to search over block looking for inner block elements and loose lists loose_search: while ( true ) { // Split into lines preserving new lines at end of line var lines = block.split( /(?=\n)/ ); // We have to grab all lines for a li and call processInline on them // once as there are some inline things that can span lines. var li_accumulate = ""; // Loop over the lines in this block looking for tight lists. tight_search: for ( var line_no = 0; line_no < lines.length; line_no++ ) { var nl = "", l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); // TODO: really should cache this var line_re = regex_for_depth( stack.length ); m = l.match( line_re ); //print( "line:", uneval(l), "\nline match:", uneval(m) ); // We have a list item if ( m[1] !== undefined ) { // Process the previous list item, if any if ( li_accumulate.length ) { add( last_li, loose, this.processInline( li_accumulate ), nl ); // Loose mode will have been dealt with. Reset it loose = false; li_accumulate = ""; } m[1] = expand_tab( m[1] ); var wanted_depth = Math.floor(m[1].length/4)+1; //print( "want:", wanted_depth, "stack:", stack.length); if ( wanted_depth > stack.length ) { // Deep enough for a nested list outright //print ( "new nested list" ); list = make_list( m ); last_li.push( list ); last_li = list[1] = [ "listitem" ]; } else { // We aren't deep enough to be strictly a new level. This is // where Md.pl goes nuts. If the indent matches a level in the // stack, put it there, else put it one deeper then the // wanted_depth deserves. var found = false; for ( i = 0; i < stack.length; i++ ) { if ( stack[ i ].indent != m[1] ) continue; list = stack[ i ].list; stack.splice( i+1, stack.length - (i+1) ); found = true; break; } if (!found) { //print("not found. l:", uneval(l)); wanted_depth++; if ( wanted_depth <= stack.length ) { stack.splice(wanted_depth, stack.length - wanted_depth); //print("Desired depth now", wanted_depth, "stack:", stack.length); list = stack[wanted_depth-1].list; //print("list:", uneval(list) ); } else { //print ("made new stack for messy indent"); list = make_list(m); last_li.push(list); } } //print( uneval(list), "last", list === stack[stack.length-1].list ); last_li = [ "listitem" ]; list.push(last_li); } // end depth of shenegains nl = ""; } // Add content if ( l.length > m[0].length ) { li_accumulate += nl + l.substr( m[0].length ); } } // tight_search if ( li_accumulate.length ) { add( last_li, loose, this.processInline( li_accumulate ), nl ); // Loose mode will have been dealt with. Reset it loose = false; li_accumulate = ""; } // Look at the next block - we might have a loose list. Or an extra // paragraph for the current li var contained = get_contained_blocks( stack.length, next ); // Deal with code blocks or properly nested lists if ( contained.length > 0 ) { // Make sure all listitems up the stack are paragraphs forEach( stack, paragraphify, this); last_li.push.apply( last_li, this.toTree( contained, [] ) ); } var next_block = next[0] && next[0].valueOf() || ""; if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { block = next.shift(); // Check for an HR following a list: features/lists/hr_abutting var hr = this.dialect.block.horizRule( block, next ); if ( hr ) { ret.push.apply(ret, hr); break; } // Make sure all listitems up the stack are paragraphs forEach( stack, paragraphify, this); loose = true; continue loose_search; } break; } // loose_search return ret; }; })(), blockquote: function blockquote( block, next ) { if ( !block.match( /^>/m ) ) return undefined; var jsonml = []; // separate out the leading abutting block, if any. I.e. in this case: // // a // > b // if ( block[ 0 ] != ">" ) { var lines = block.split( /\n/ ), prev = [], line_no = block.lineNumber; // keep shifting lines until you find a crotchet while ( lines.length && lines[ 0 ][ 0 ] != ">" ) { prev.push( lines.shift() ); line_no++; } var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); // reassemble new block of just block quotes! block = mk_block( lines.join( "\n" ), block.trailing, line_no ); } // if the next block is also a blockquote merge it in while ( next.length && next[ 0 ][ 0 ] == ">" ) { var b = next.shift(); block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); } // Strip off the leading "> " and re-process as a block. var input = block.replace( /^> ?/gm, "" ), old_tree = this.tree, processedBlock = this.toTree( input, [ "blockquote" ] ), attr = extract_attr( processedBlock ); // If any link references were found get rid of them if ( attr && attr.references ) { delete attr.references; // And then remove the attribute object if it's empty if ( isEmpty( attr ) ) { processedBlock.splice( 1, 1 ); } } jsonml.push( processedBlock ); return jsonml; }, referenceDefn: function referenceDefn( block, next) { var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; // interesting matches are [ , ref_id, url, , title, title ] if ( !block.match(re) ) return undefined; // make an attribute node if it doesn't exist if ( !extract_attr( this.tree ) ) { this.tree.splice( 1, 0, {} ); } var attrs = extract_attr( this.tree ); // make a references hash if it doesn't exist if ( attrs.references === undefined ) { attrs.references = {}; } var b = this.loop_re_over_block(re, block, function( m ) { if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" ) m[2] = m[2].substring( 1, m[2].length - 1 ); var ref = attrs.references[ m[1].toLowerCase() ] = { href: m[2] }; if ( m[4] !== undefined ) ref.title = m[4]; else if ( m[5] !== undefined ) ref.title = m[5]; } ); if ( b.length ) next.unshift( mk_block( b, block.trailing ) ); return []; }, para: function para( block, next ) { // everything's a para! return [ ["para"].concat( this.processInline( block ) ) ]; } } }; Markdown.dialects.Gruber.inline = { __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { var m, res, lastIndex = 0; patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); m = re.exec( text ); if (!m) { // Just boring text return [ text.length, text ]; } else if ( m[1] ) { // Some un-interesting text matched. Return that first return [ m[1].length, m[1] ]; } var res; if ( m[2] in this.dialect.inline ) { res = this.dialect.inline[ m[2] ].call( this, text.substr( m.index ), m, previous_nodes || [] ); } // Default for now to make dev easier. just slurp special and output it. res = res || [ m[2].length, m[2] ]; return res; }, __call__: function inline( text, patterns ) { var out = [], res; function add(x) { //D:self.debug(" adding output", uneval(x)); if ( typeof x == "string" && typeof out[out.length-1] == "string" ) out[ out.length-1 ] += x; else out.push(x); } while ( text.length > 0 ) { res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); text = text.substr( res.shift() ); forEach(res, add ) } return out; }, // These characters are intersting elsewhere, so have rules for them so that // chunks of plain text blocks don't include them "]": function () {}, "}": function () {}, __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, "\\": function escaped( text ) { // [ length of input processed, node/children to add... ] // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! if ( this.dialect.inline.__escape__.exec( text ) ) return [ 2, text.charAt( 1 ) ]; else // Not an esacpe return [ 1, "\\" ]; }, " // 1 2 3 4 <--- captures var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); if ( m ) { if ( m[2] && m[2][0] == "<" && m[2][m[2].length-1] == ">" ) m[2] = m[2].substring( 1, m[2].length - 1 ); m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; var attrs = { alt: m[1], href: m[2] || "" }; if ( m[4] !== undefined) attrs.title = m[4]; return [ m[0].length, [ "img", attrs ] ]; } // ![Alt text][id] m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); if ( m ) { // We can't check if the reference is known here as it likely wont be // found till after. Check it in md tree->hmtl tree conversion return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; } // Just consume the '![' return [ 2, "![" ]; }, "[": function link( text ) { var orig = String(text); // Inline content is possible inside `link text` var res = Markdown.DialectHelpers.inline_until_char.call( this, text.substr(1), "]" ); // No closing ']' found. Just consume the [ if ( !res ) return [ 1, "[" ]; var consumed = 1 + res[ 0 ], children = res[ 1 ], link, attrs; // At this point the first [...] has been parsed. See what follows to find // out which kind of link we are (reference or direct url) text = text.substr( consumed ); // [link text](/path/to/img.jpg "Optional title") // 1 2 3 <--- captures // This will capture up to the last paren in the block. We then pull // back based on if there a matching ones in the url // ([here](/url/(test)) // The parens have to be balanced var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); if ( m ) { var url = m[1]; consumed += m[0].length; if ( url && url[0] == "<" && url[url.length-1] == ">" ) url = url.substring( 1, url.length - 1 ); // If there is a title we don't have to worry about parens in the url if ( !m[3] ) { var open_parens = 1; // One open that isn't in the capture for ( var len = 0; len < url.length; len++ ) { switch ( url[len] ) { case "(": open_parens++; break; case ")": if ( --open_parens == 0) { consumed -= url.length - len; url = url.substring(0, len); } break; } } } // Process escapes only url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; attrs = { href: url || "" }; if ( m[3] !== undefined) attrs.title = m[3]; link = [ "link", attrs ].concat( children ); return [ consumed, link ]; } // [Alt text][id] // [Alt text] [id] m = text.match( /^\s*\[(.*?)\]/ ); if ( m ) { consumed += m[ 0 ].length; // [links][] uses links as its reference attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; link = [ "link_ref", attrs ].concat( children ); // We can't check if the reference is known here as it likely wont be // found till after. Check it in md tree->hmtl tree conversion. // Store the original so that conversion can revert if the ref isn't found. return [ consumed, link ]; } // [id] // Only if id is plain (no formatting.) if ( children.length == 1 && typeof children[0] == "string" ) { attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; link = [ "link_ref", attrs, children[0] ]; return [ consumed, link ]; } // Just consume the "[" return [ 1, "[" ]; }, "<": function autoLink( text ) { var m; if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) { if ( m[3] ) { return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; } else if ( m[2] == "mailto" ) { return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; } else return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; } return [ 1, "<" ]; }, "`": function inlineCode( text ) { // Inline code block. as many backticks as you like to start it // Always skip over the opening ticks. var m = text.match( /(`+)(([\s\S]*?)\1)/ ); if ( m && m[2] ) return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; else { // TODO: No matching end code found - warn! return [ 1, "`" ]; } }, " \n": function lineBreak( text ) { return [ 3, [ "linebreak" ] ]; } }; // Meta Helper/generator method for em and strong handling function strong_em( tag, md ) { var state_slot = tag + "_state", other_slot = tag == "strong" ? "em_state" : "strong_state"; function CloseTag(len) { this.len_after = len; this.name = "close_" + md; } return function ( text, orig_match ) { if ( this[state_slot][0] == md ) { // Most recent em is of this type //D:this.debug("closing", md); this[state_slot].shift(); // "Consume" everything to go back to the recrusion in the else-block below return[ text.length, new CloseTag(text.length-md.length) ]; } else { // Store a clone of the em/strong states var other = this[other_slot].slice(), state = this[state_slot].slice(); this[state_slot].unshift(md); //D:this.debug_indent += " "; // Recurse var res = this.processInline( text.substr( md.length ) ); //D:this.debug_indent = this.debug_indent.substr(2); var last = res[res.length - 1]; //D:this.debug("processInline from", tag + ": ", uneval( res ) ); var check = this[state_slot].shift(); if ( last instanceof CloseTag ) { res.pop(); // We matched! Huzzah. var consumed = text.length - last.len_after; return [ consumed, [ tag ].concat(res) ]; } else { // Restore the state of the other kind. We might have mistakenly closed it. this[other_slot] = other; this[state_slot] = state; // We can't reuse the processed result as it could have wrong parsing contexts in it. return [ md.length, md ]; } } }; // End returned function } Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**"); Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__"); Markdown.dialects.Gruber.inline["*"] = strong_em("em", "*"); Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_"); // Build default order from insertion order. Markdown.buildBlockOrder = function(d) { var ord = []; for ( var i in d ) { if ( i == "__order__" || i == "__call__" ) continue; ord.push( i ); } d.__order__ = ord; }; // Build patterns for inline matcher Markdown.buildInlinePatterns = function(d) { var patterns = []; for ( var i in d ) { // __foo__ is reserved and not a pattern if ( i.match( /^__.*__$/) ) continue; var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) .replace( /\n/, "\\n" ); patterns.push( i.length == 1 ? l : "(?:" + l + ")" ); } patterns = patterns.join("|"); d.__patterns__ = patterns; //print("patterns:", uneval( patterns ) ); var fn = d.__call__; d.__call__ = function(text, pattern) { if ( pattern != undefined ) { return fn.call(this, text, pattern); } else { return fn.call(this, text, patterns); } }; }; Markdown.DialectHelpers = {}; Markdown.DialectHelpers.inline_until_char = function( text, want ) { var consumed = 0, nodes = []; while ( true ) { if ( text.charAt( consumed ) == want ) { // Found the character we were looking for consumed++; return [ consumed, nodes ]; } if ( consumed >= text.length ) { // No closing char found. Abort. return null; } var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); consumed += res[ 0 ]; // Add any returned nodes. nodes.push.apply( nodes, res.slice( 1 ) ); } } // Helper function to make sub-classing a dialect easier Markdown.subclassDialect = function( d ) { function Block() {} Block.prototype = d.block; function Inline() {} Inline.prototype = d.inline; return { block: new Block(), inline: new Inline() }; }; Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); Markdown.dialects.Maruku = Markdown.subclassDialect( Markdown.dialects.Gruber ); Markdown.dialects.Maruku.processMetaHash = function processMetaHash( meta_string ) { var meta = split_meta_hash( meta_string ), attr = {}; for ( var i = 0; i < meta.length; ++i ) { // id: #foo if ( /^#/.test( meta[ i ] ) ) { attr.id = meta[ i ].substring( 1 ); } // class: .foo else if ( /^\./.test( meta[ i ] ) ) { // if class already exists, append the new one if ( attr["class"] ) { attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); } else { attr["class"] = meta[ i ].substring( 1 ); } } // attribute: foo=bar else if ( /\=/.test( meta[ i ] ) ) { var s = meta[ i ].split( /\=/ ); attr[ s[ 0 ] ] = s[ 1 ]; } } return attr; } function split_meta_hash( meta_string ) { var meta = meta_string.split( "" ), parts = [ "" ], in_quotes = false; while ( meta.length ) { var letter = meta.shift(); switch ( letter ) { case " " : // if we're in a quoted section, keep it if ( in_quotes ) { parts[ parts.length - 1 ] += letter; } // otherwise make a new part else { parts.push( "" ); } break; case "'" : case '"' : // reverse the quotes and move straight on in_quotes = !in_quotes; break; case "\\" : // shift off the next letter to be used straight away. // it was escaped so we'll keep it whatever it is letter = meta.shift(); default : parts[ parts.length - 1 ] += letter; break; } } return parts; } Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) { // we're only interested in the first block if ( block.lineNumber > 1 ) return undefined; // document_meta blocks consist of one or more lines of `Key: Value\n` if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined; // make an attribute node if it doesn't exist if ( !extract_attr( this.tree ) ) { this.tree.splice( 1, 0, {} ); } var pairs = block.split( /\n/ ); for ( p in pairs ) { var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), key = m[ 1 ].toLowerCase(), value = m[ 2 ]; this.tree[ 1 ][ key ] = value; } // document_meta produces no content! return []; }; Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { // check if the last line of the block is an meta hash var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); if ( !m ) return undefined; // process the meta hash var attr = this.dialect.processMetaHash( m[ 2 ] ); var hash; // if we matched ^ then we need to apply meta to the previous block if ( m[ 1 ] === "" ) { var node = this.tree[ this.tree.length - 1 ]; hash = extract_attr( node ); // if the node is a string (rather than JsonML), bail if ( typeof node === "string" ) return undefined; // create the attribute hash if it doesn't exist if ( !hash ) { hash = {}; node.splice( 1, 0, hash ); } // add the attributes in for ( a in attr ) { hash[ a ] = attr[ a ]; } // return nothing so the meta hash is removed return []; } // pull the meta hash off the block and process what's left var b = block.replace( /\n.*$/, "" ), result = this.processBlock( b, [] ); // get or make the attributes hash hash = extract_attr( result[ 0 ] ); if ( !hash ) { hash = {}; result[ 0 ].splice( 1, 0, hash ); } // attach the attributes to the block for ( a in attr ) { hash[ a ] = attr[ a ]; } return result; }; Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) { // one or more terms followed by one or more definitions, in a single block var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, list = [ "dl" ], i, m; // see if we're dealing with a tight or loose block if ( ( m = block.match( tight ) ) ) { // pull subsequent tight DL blocks out of `next` var blocks = [ block ]; while ( next.length && tight.exec( next[ 0 ] ) ) { blocks.push( next.shift() ); } for ( var b = 0; b < blocks.length; ++b ) { var m = blocks[ b ].match( tight ), terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), defns = m[ 2 ].split( /\n:\s+/ ); // print( uneval( m ) ); for ( i = 0; i < terms.length; ++i ) { list.push( [ "dt", terms[ i ] ] ); } for ( i = 0; i < defns.length; ++i ) { // run inline processing over the definition list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); } } } else { return undefined; } return [ list ]; }; // splits on unescaped instances of @ch. If @ch is not a character the result // can be unpredictable Markdown.dialects.Maruku.block.table = function table (block, next) { var _split_on_unescaped = function(s, ch) { ch = ch || '\\s'; if (ch.match(/^[\\|\[\]{}?*.+^$]$/)) { ch = '\\' + ch; } var res = [ ], r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), m; while(m = s.match(r)) { res.push(m[1]); s = m[2]; } res.push(s); return res; } var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, // find at least an unescaped pipe in each line no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, i, m; if (m = block.match(leading_pipe)) { // remove leading pipes in contents // (header and horizontal rule already have the leading pipe left out) m[3] = m[3].replace(/^\s*\|/gm, ''); } else if (! ( m = block.match(no_leading_pipe))) { return undefined; } var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; // remove trailing pipes, then split on pipes // (no escaped pipes are allowed in horizontal rule) m[2] = m[2].replace(/\|\s*$/, '').split('|'); // process alignment var html_attrs = [ ]; forEach (m[2], function (s) { if (s.match(/^\s*-+:\s*$/)) html_attrs.push({align: "right"}); else if (s.match(/^\s*:-+\s*$/)) html_attrs.push({align: "left"}); else if (s.match(/^\s*:-+:\s*$/)) html_attrs.push({align: "center"}); else html_attrs.push({}); }); // now for the header, avoid escaped pipes m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); for (i = 0; i < m[1].length; i++) { table[1][1].push(['th', html_attrs[i] || {}].concat( this.processInline(m[1][i].trim()))); } // now for body contents forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { var html_row = ['tr']; row = _split_on_unescaped(row, '|'); for (i = 0; i < row.length; i++) { html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); } table[2].push(html_row); }, this); return [table]; } Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { if ( !out.length ) { return [ 2, "{:" ]; } // get the preceeding element var before = out[ out.length - 1 ]; if ( typeof before === "string" ) { return [ 2, "{:" ]; } // match a meta hash var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); // no match, false alarm if ( !m ) { return [ 2, "{:" ]; } // attach the attributes to the preceeding element var meta = this.dialect.processMetaHash( m[ 1 ] ), attr = extract_attr( before ); if ( !attr ) { attr = {}; before.splice( 1, 0, attr ); } for ( var k in meta ) { attr[ k ] = meta[ k ]; } // cut out the string and replace it with nothing return [ m[ 0 ].length, "" ]; }; Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); var isArray = Array.isArray || function(obj) { return Object.prototype.toString.call(obj) == "[object Array]"; }; var forEach; // Don't mess with Array.prototype. Its not friendly if ( Array.prototype.forEach ) { forEach = function( arr, cb, thisp ) { return arr.forEach( cb, thisp ); }; } else { forEach = function(arr, cb, thisp) { for (var i = 0; i < arr.length; i++) { cb.call(thisp || arr, arr[i], i, arr); } } } var isEmpty = function( obj ) { for ( var key in obj ) { if ( hasOwnProperty.call( obj, key ) ) { return false; } } return true; } function extract_attr( jsonml ) { return isArray(jsonml) && jsonml.length > 1 && typeof jsonml[ 1 ] === "object" && !( isArray(jsonml[ 1 ]) ) ? jsonml[ 1 ] : undefined; } /** * renderJsonML( jsonml[, options] ) -> String * - jsonml (Array): JsonML array to render to XML * - options (Object): options * * Converts the given JsonML into well-formed XML. * * The options currently understood are: * * - root (Boolean): wether or not the root node should be included in the * output, or just its children. The default `false` is to not include the * root itself. */ expose.renderJsonML = function( jsonml, options ) { options = options || {}; // include the root element in the rendered output? options.root = options.root || false; var content = []; if ( options.root ) { content.push( render_tree( jsonml ) ); } else { jsonml.shift(); // get rid of the tag if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { jsonml.shift(); // get rid of the attributes } while ( jsonml.length ) { content.push( render_tree( jsonml.shift() ) ); } } return content.join( "\n\n" ); }; function escapeHTML( text ) { return text.replace( /&/g, "&" ) .replace( /</g, "<" ) .replace( />/g, ">" ) .replace( /"/g, """ ) .replace( /'/g, "'" ); } function render_tree( jsonml ) { // basic case if ( typeof jsonml === "string" ) { return escapeHTML( jsonml ); } var tag = jsonml.shift(), attributes = {}, content = []; if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { attributes = jsonml.shift(); } while ( jsonml.length ) { content.push( render_tree( jsonml.shift() ) ); } var tag_attrs = ""; for ( var a in attributes ) { tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; } // be careful about adding whitespace here for inline elements if ( tag == "img" || tag == "br" || tag == "hr" ) { return "<"+ tag + tag_attrs + "/>"; } else { return "<"+ tag + tag_attrs + ">" + content.join( "" ) + "</" + tag + ">"; } } function convert_tree_to_html( tree, references, options ) { var i; options = options || {}; // shallow clone var jsonml = tree.slice( 0 ); if ( typeof options.preprocessTreeNode === "function" ) { jsonml = options.preprocessTreeNode(jsonml, references); } // Clone attributes if they exist var attrs = extract_attr( jsonml ); if ( attrs ) { jsonml[ 1 ] = {}; for ( i in attrs ) { jsonml[ 1 ][ i ] = attrs[ i ]; } attrs = jsonml[ 1 ]; } // basic case if ( typeof jsonml === "string" ) { return jsonml; } // convert this node switch ( jsonml[ 0 ] ) { case "header": jsonml[ 0 ] = "h" + jsonml[ 1 ].level; delete jsonml[ 1 ].level; break; case "bulletlist": jsonml[ 0 ] = "ul"; break; case "numberlist": jsonml[ 0 ] = "ol"; break; case "listitem": jsonml[ 0 ] = "li"; break; case "para": jsonml[ 0 ] = "p"; break; case "markdown": jsonml[ 0 ] = "html"; if ( attrs ) delete attrs.references; break; case "code_block": jsonml[ 0 ] = "pre"; i = attrs ? 2 : 1; var code = [ "code" ]; code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); jsonml[ i ] = code; break; case "inlinecode": jsonml[ 0 ] = "code"; break; case "img": jsonml[ 1 ].src = jsonml[ 1 ].href; delete jsonml[ 1 ].href; break; case "linebreak": jsonml[ 0 ] = "br"; break; case "link": jsonml[ 0 ] = "a"; break; case "link_ref": jsonml[ 0 ] = "a"; // grab this ref and clean up the attribute node var ref = references[ attrs.ref ]; // if the reference exists, make the link if ( ref ) { delete attrs.ref; // add in the href and title, if present attrs.href = ref.href; if ( ref.title ) { attrs.title = ref.title; } // get rid of the unneeded original text delete attrs.original; } // the reference doesn't exist, so revert to plain text else { return attrs.original; } break; case "img_ref": jsonml[ 0 ] = "img"; // grab this ref and clean up the attribute node var ref = references[ attrs.ref ]; // if the reference exists, make the link if ( ref ) { delete attrs.ref; // add in the href and title, if present attrs.src = ref.href; if ( ref.title ) { attrs.title = ref.title; } // get rid of the unneeded original text delete attrs.original; } // the reference doesn't exist, so revert to plain text else { return attrs.original; } break; } // convert all the children i = 1; // deal with the attribute node, if it exists if ( attrs ) { // if there are keys, skip over it for ( var key in jsonml[ 1 ] ) { i = 2; break; } // if there aren't, remove it if ( i === 1 ) { jsonml.splice( i, 1 ); } } for ( ; i < jsonml.length; ++i ) { jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); } return jsonml; } // merges adjacent text nodes into a single node function merge_text_nodes( jsonml ) { // skip the tag name and attribute hash var i = extract_attr( jsonml ) ? 2 : 1; while ( i < jsonml.length ) { // if it's a string check the next item too if ( typeof jsonml[ i ] === "string" ) { if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { // merge the second string into the first and remove it jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; } else { ++i; } } // if it's not a string recurse else { merge_text_nodes( jsonml[ i ] ); ++i; } } } } )( (function() { if ( typeof exports === "undefined" ) { window.markdown = {}; return window.markdown; } else { return exports; } } )() ); /* * to-markdown - an HTML to Markdown converter * * Copyright 2011, Dom Christie * Licenced under the MIT licence * */ if (typeof he !== 'object' && typeof require === 'function') { var he = require('he'); } var toMarkdown = function(string) { var ELEMENTS = [ { patterns: 'p', replacement: function(str, attrs, innerHTML) { return innerHTML ? '\n\n' + innerHTML + '\n' : ''; } }, { patterns: 'br', type: 'void', replacement: '\n' }, { patterns: 'h([1-6])', replacement: function(str, hLevel, attrs, innerHTML) { var hPrefix = ''; for(var i = 0; i < hLevel; i++) { hPrefix += '#'; } return '\n\n' + hPrefix + ' ' + innerHTML + '\n'; } }, { patterns: 'hr', type: 'void', replacement: '\n\n* * *\n' }, { patterns: 'a', replacement: function(str, attrs, innerHTML) { var href = attrs.match(attrRegExp('href')), title = attrs.match(attrRegExp('title')); return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str; } }, { patterns: ['b', 'strong'], replacement: function(str, attrs, innerHTML) { return innerHTML ? '**' + innerHTML + '**' : ''; } }, { patterns: ['i', 'em'], replacement: function(str, attrs, innerHTML) { return innerHTML ? '_' + innerHTML + '_' : ''; } }, { patterns: 'code', replacement: function(str, attrs, innerHTML) { return innerHTML ? '`' + he.decode(innerHTML) + '`' : ''; } }, { patterns: 'img', type: 'void', replacement: function(str, attrs, innerHTML) { var src = attrs.match(attrRegExp('src')), alt = attrs.match(attrRegExp('alt')), title = attrs.match(attrRegExp('title')); return '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')'; } } ]; for(var i = 0, len = ELEMENTS.length; i < len; i++) { if(typeof ELEMENTS[i].patterns === 'string') { string = replaceEls(string, { tag: ELEMENTS[i].patterns, replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type }); } else { for(var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) { string = replaceEls(string, { tag: ELEMENTS[i].patterns[j], replacement: ELEMENTS[i].replacement, type: ELEMENTS[i].type }); } } } function replaceEls(html, elProperties) { var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>', regex = new RegExp(pattern, 'gi'), markdown = ''; if(typeof elProperties.replacement === 'string') { markdown = html.replace(regex, elProperties.replacement); } else { markdown = html.replace(regex, function(str, p1, p2, p3) { return elProperties.replacement.call(this, str, p1, p2, p3); }); } return markdown; } function attrRegExp(attr) { return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i'); } // Pre code blocks string = string.replace(/<pre\b[^>]*>`([\s\S]*)`<\/pre>/gi, function(str, innerHTML) { var text = he.decode(innerHTML); text = text.replace(/^\t+/g, ' '); // convert tabs to spaces (you know it makes sense) text = text.replace(/\n/g, '\n '); return '\n\n ' + text + '\n'; }); // Lists // Escape numbers that could trigger an ol // If there are more than three spaces before the code, it would be in a pre tag // Make sure we are escaping the period not matching any character string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. '); // Converts lists that have no child lists (of same type) first, then works its way up var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi; while(string.match(noChildrenRegex)) { string = string.replace(noChildrenRegex, function(str) { return replaceLists(str); }); } function replaceLists(html) { html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) { var lis = innerHTML.split('</li>'); lis.splice(lis.length - 1, 1); for(i = 0, len = lis.length; i < len; i++) { if(lis[i]) { var prefix = (listType === 'ol') ? (i + 1) + ". " : "* "; lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) { innerHTML = innerHTML.replace(/^\s+/, ''); innerHTML = innerHTML.replace(/\n\n/g, '\n\n '); // indent nested lists innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1 $2 '); return prefix + innerHTML; }); } } return lis.join('\n'); }); return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, ''); } // Blockquotes var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi; while(string.match(deepest)) { string = string.replace(deepest, function(str) { return replaceBlockquotes(str); }); } function replaceBlockquotes(html) { html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) { inner = inner.replace(/^\s+|\s+$/g, ''); inner = cleanUp(inner); inner = inner.replace(/^/gm, '> '); inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >'); return inner; }); return html; } function cleanUp(string) { string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace string = string.replace(/\n\s+\n/g, '\n\n'); string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2 return string; } return cleanUp(string); }; if (typeof exports === 'object') { exports.toMarkdown = toMarkdown; } ; (function() { function sort() { $('#sortable-slides').sortable({ axis: 'y', handle: '.handle', update: function() { $.post($(this).data('update-url'), $(this).sortable('serialize')); } }); }; $(document).ready(sort); $(document).on('page:load', sort); })(); showWarningInfo = function(rows, span, flag){ if(rows.hasClass(flag)) span.addClass("show_suffix_info"); else span.removeClass("show_suffix_info"); }; enablePHQExplanationTable = function(){ $(".label-phq").off("click touchstart").on("click touchstart", function(){ //Variables var studyID, patientID, conditionalRows; //Constants var PATIENT_ID, EXPLANATION_DIV, SEPARATORS; PATIENT_ID = $("#phq_patientID"); EXPLANATION_DIV = $("div#phq_suggestion_explanation_div"); SEPARATORS = $("#phq_suggestion_explanation_table, #sep_after_explanation_table"); patientID = $(this).parents("tr").data("studyId"); studyID = $(this).parents("tr").attr("id"); if(PATIENT_ID.attr("data-last_opened") == studyID){ EXPLANATION_DIV.removeClass("show_suggestion"); PATIENT_ID.attr("data-last_opened", "PATIENT"); return; } if($(this).text() == "No Completed Assessments"){ EXPLANATION_DIV.removeClass("show_suggestion"); return; } else { $("#phq_patientSuggestion").text($("#phq_summary_" + studyID).attr("data-detailed_suggestion")); } if($(this).text() == "No; Too Early") SEPARATORS.addClass("no-explanation"); else SEPARATORS.removeClass("no-explanation"); PATIENT_ID.text(patientID); PATIENT_ID.attr("data-last_opened", studyID); $("#phq_weekInStudy span").text($("#phq_summary_" + studyID).attr("data-week")); $("#phq_suggestion_explanation_table tbody").html($("#phq_summary_" + studyID).clone().find("tr")); $("#phq_suggestion_test_results tbody").html($("#test_summary_" + studyID).clone().find("tr")); conditionalRows = EXPLANATION_DIV.find("tr"); showWarningInfo(conditionalRows,$("#phq_copy_notice"), "copied_row"); showWarningInfo(conditionalRows,$("#phq_missing_notice"), "lost_row"); showWarningInfo(conditionalRows,$("#phq_answers_missing_notice"), "missing_answers_row"); showWarningInfo(conditionalRows,$("#phq_unreliable_notice"), "unreliable_row"); EXPLANATION_DIV.addClass("show_suggestion"); }); $("#phq_suggestion_explanation_div").off("click touchstart").on("click touchstart", function(){ $(this).removeClass("show_suggestion"); $("#phq_patientID").attr("data-last_opened", "PATIENT"); }); $(document).off("patient:rendered", enablePHQExplanationTable).on("patient:rendered", enablePHQExplanationTable); }; (function() { // add the Markdown for a link to the selection $(document).on("change", "#coach-message-link-selection", function(event) { var intialContent, path, title; title = $(event.target.selectedOptions).text(); path = $(event.target.selectedOptions).val(); if (path !== "") { intialContent = $("#message_body").val(); return $("#message_body").val(intialContent + " [" + title + "](" + path + ")"); } }); })(); (function() { $(document).on('page:change patient:rendered', function() { $('#patients .datepicker').each(function() { $(this).datepicker({ dateFormat: 'M d, yy', altField: $(this).prev(), altFormat: 'yy-mm-dd', showButtonPanel: true }); $(this).on('change', function() { $(this).closest('form').submit(); }); }); }); })(); // vendor // // internal ; ;�TI"required_assets_digest;�TI"%f0a90c0801fdf98850ddcb05d6267a87;�FI" _version;�TI"%e9ce4940b8cff776edd3f1531dfea685;�F