/*! * Mercury Editor is a CoffeeScript and jQuery based WYSIWYG editor. Documentation and other useful information can be * found at https://github.com/jejacks0n/mercury * * Minimum jQuery requirements are 1.7 * * You can include the Rails jQuery ujs script here to get some nicer behaviors in modals, panels and lightviews when * using :remote => true within the contents rendered in them. * require jquery_ujs * * Add any requires for the support libraries that integrate nicely with Mercury Editor. * require mercury/support/history * * Require Mercury Editor itself. * * Require any localizations you wish to support * Example: es.locale, or fr.locale -- regional dialects are in each language file so never en_US for instance. * Make sure you enable the localization feature in the configuration. * require mercury/locales/swedish_chef.locale * * Add all requires for plugins that extend or change the behavior of Mercury Editor. * require mercury/plugins/save_as_xml/plugin.js * * Require any files you want to use that either extend, or change the default Mercury behavior. * require mercury_overrides */ window.Mercury = { // # Mercury Configuration config: { // ## Toolbars // // This is where you can customize the toolbars by adding or removing buttons, or changing them and their // behaviors. Any top level object put here will create a new toolbar. Buttons are simply nested inside the // toolbars, along with button groups. // // Some toolbars are custom (the snippets toolbar for instance), and to denote that use _custom: true. You can then // build the toolbar yourself with it's own behavior. // // Buttons can be grouped, and a button group is simply a way to wrap buttons for styling -- they can also handle // enabling or disabling all the buttons within it by using a context. The table button group is a good example of // this. // // It's important to note that each of the button names (keys), in each toolbar object must be unique, regardless of // if it's in a button group, or nested, etc. This is because styling is applied to them by name, and because their // name is used in the event that's fired when you click on them. // // Button format: `[label, description, {type: action, type: action, etc}]` // // ### The available button types are: // // - toggle: toggles on or off when clicked, otherwise behaves like a button // - modal: opens a modal window, expects the action to be one of: // 1. a string url // 2. a function that returns a string url // - lightview: opens a lightview window (like modal, but different UI), expects the action to be one of: // 1. a string url // 2. a function that returns a string url // - panel: opens a panel dialog, expects the action to be one of: // 1. a string url // 2. a function that returns a string url // - palette: opens a palette window, expects the action to be one of: // 1. a string url // 2. a function that returns a string url // - select: opens a pulldown style window, expects the action to be one of: // 1. a string url // 2. a function that returns a string url // - context: calls a callback function, expects the action to be: // 1. a function that returns a boolean to highlight the button // note: if a function isn't provided, the key will be passed to the contextHandler, in which case a default // context will be used (for more info read the Contexts section below) // - mode: toggle a given mode in the editor, expects the action to be: // 1. a string, denoting the name of the mode // note: it's assumed that when a specific mode is turned on, all other modes will be turned off, which happens // automatically, thus putting the editor into a specific "state" // - regions: allows buttons to be enabled/disabled based on what region type has focus, expects: // 1. an array of region types (eg. ['full', 'markdown']) // - preload: allows some dialog views to be loaded when the button is created instead of on first open, expects: // 1. a boolean true / false // note: this is only used by panels, selects, and palettes // // Separators are any "button" that's not an array, and are expected to be a string. You can use two different // separator styles: line ('-'), and spacer (' '). // // ### Adding Contexts // // Contexts are used callback functions used for highlighting and disabling/enabling buttons and buttongroups. When // the cursor enters an element within an html region for instance we want to disable or highlight buttons based on // the properties of the given node. You can see examples of contexts in, and add your own to: // `Mercury.Toolbar.Button.contexts` and `Mercury.Toolbar.ButtonGroup.contexts` toolbars: { primary: { save: ['Save', 'Save this page'], preview: ['Preview', 'Preview this page', { toggle: true, mode: true }], sep1: ' ', undoredo: { undo: ['Undo', 'Undo your last action'], redo: ['Redo', 'Redo your last action'], sep: ' ' }, insertLink: ['Link', 'Insert Link', { modal: '/mercury/modals/link.html', regions: ['full', 'markdown'] }], insertMedia: ['Media', 'Insert Media (images and videos)', { modal: '/mercury/modals/media.html', regions: ['full', 'markdown'] }], insertTable: ['Table', 'Insert Table', { modal: '/mercury/modals/table.html', regions: ['full', 'markdown'] }], insertCharacter: ['Character', 'Special Characters', { modal: '/mercury/modals/character.html', regions: ['full', 'markdown'] }], snippetPanel: ['Snippet', 'Snippet Panel', { panel: '/mercury/panels/snippets.html' }], sep2: ' ', historyPanel: ['History', 'Page Version History', { panel: '/mercury/panels/history.html' }], sep3: ' ', notesPanel: ['Notes', 'Page Notes', { panel: '/mercury/panels/notes.html' }] }, editable: { _regions: ['full', 'markdown'], predefined: { style: ['Style', null, { select: '/mercury/selects/style.html', preload: true }], sep1: ' ', formatblock: ['Block Format', null, { select: '/mercury/selects/formatblock.html', preload: true }], sep2: '-' }, colors: { backColor: ['Background Color', null, { palette: '/mercury/palettes/backcolor.html', context: true, preload: true, regions: ['full'] }], sep1: ' ', foreColor: ['Text Color', null, { palette: '/mercury/palettes/forecolor.html', context: true, preload: true, regions: ['full'] }], sep2: '-' }, decoration: { bold: ['Bold', null, { context: true }], italic: ['Italicize', null, { context: true }], overline: ['Overline', null, { context: true, regions: ['full'] }], strikethrough: ['Strikethrough', null, { context: true, regions: ['full'] }], underline: ['Underline', null, { context: true, regions: ['full'] }], sep: '-' }, script: { subscript: ['Subscript', null, { context: true }], superscript: ['Superscript', null, { context: true }], sep: '-' }, justify: { justifyLeft: ['Align Left', null, { context: true, regions: ['full'] }], justifyCenter: ['Center', null, { context: true, regions: ['full'] }], justifyRight: ['Align Right', null, { context: true, regions: ['full'] }], justifyFull: ['Justify Full', null, { context: true, regions: ['full'] }], sep: '-' }, list: { insertUnorderedList: ['Unordered List', null, { context: true }], insertOrderedList: ['Numbered List', null, { context: true }], sep: '-' }, indent: { outdent: ['Decrease Indentation'], indent: ['Increase Indentation'], sep: '-' }, table: { _context: true, insertRowBefore: ['Insert Table Row', 'Insert a table row before the cursor', { regions: ['full'] }], insertRowAfter: ['Insert Table Row', 'Insert a table row after the cursor', { regions: ['full'] }], deleteRow: ['Delete Table Row', 'Delete this table row', { regions: ['full'] }], insertColumnBefore: ['Insert Table Column', 'Insert a table column before the cursor', { regions: ['full'] }], insertColumnAfter: ['Insert Table Column', 'Insert a table column after the cursor', { regions: ['full'] }], deleteColumn: ['Delete Table Column', 'Delete this table column', { regions: ['full'] }], sep1: ' ', increaseColspan: ['Increase Cell Columns', 'Increase the cells colspan'], decreaseColspan: ['Decrease Cell Columns', 'Decrease the cells colspan and add a new cell'], increaseRowspan: ['Increase Cell Rows', 'Increase the cells rowspan'], decreaseRowspan: ['Decrease Cell Rows', 'Decrease the cells rowspan and add a new cell'], sep2: '-' }, rules: { horizontalRule: ['Horizontal Rule', 'Insert a horizontal rule'], sep1: '-' }, formatting: { removeFormatting: ['Remove Formatting', 'Remove formatting for the selection', { regions: ['full'] }], sep2: ' ' }, editors: { htmlEditor: ['Edit HTML', 'Edit the HTML content', { regions: ['full'] }] } }, snippets: { _custom: true, actions: { editSnippet: ['Edit Snippet Settings'], sep1: ' ', removeSnippet: ['Remove Snippet'] } } }, // ## Region Options // // You can customize some aspects of how regions are found, identified, and saved. // // attribute: Mercury identifies editable regions by a data-mercury attribute. This attribute has to be added in // your HTML in advance, and is the only real code/naming exposed in the implementation of Mercury. To allow this // to be as configurable as possible, you can set the name of this attribute. If you change this, you should adjust // the injected styles as well. // // identifier: This is used as a unique identifier for any given region (and thus should be unique to the page). // By default this is the id attribute but can be changed to a data attribute should you want to use something // custom instead. // // dataAttributes: The dataAttributes is an array of data attributes that will be serialized and returned to the // server upon saving. These attributes, when applied to a Mercury region element, will be automatically serialized // and submitted with the AJAX request sent when a page is saved. These are expected to be HTML5 data attributes, // and 'data-' will automatically be prepended to each item in this directive. (ex. ['scope', 'version']) // // determineType: This function is called after checking the data-type attribute for the correct field type. Use // it if you want to dynamically set the type based on inspection of the region. regions: { attribute: 'data-mercury', identifier: 'id', dataAttributes: [] // determineType: function(region){}, }, // ## Snippet Options / Preview // // When a user drags a snippet onto the page they'll be prompted to enter options for the given snippet. The server // is expected to respond with a form. Once the user submits this form, an Ajax request is sent to the server with // the options provided; this preview request is expected to respond with the rendered markup for the snippet. // // method: The HTTP method used when submitting both the options and the preview. We use POST by default because a // snippet options form may contain large text inputs and we don't want that to be truncated when sent to the // server. // // optionsUrl: The url that the options form will be loaded from. // // previewUrl: The url that the options will be submitted to, and will return the rendered snippet markup. // // **Note:** `:name` will be replaced with the snippet name in the urls (eg. /mercury/snippets/example/options.html) snippets: { method: 'POST', optionsUrl: '/mercury/snippets/:name/options.html', previewUrl: '/mercury/snippets/:name/preview.html' }, // ## Image Uploading // // If you drag images from your desktop into regions that support it, it will be uploaded to the server and inserted // into the region. You can disable or enable this feature, the accepted mime-types, file size restrictions, and // other things related to uploading. // // **Note:** Image uploading is only supported in some region types, and some browsers. // // enabled: You can set this to true, or false if you want to disable the feature entirely. // // allowedMimeTypes: You can restrict the types of files that can be uploaded by providing a list of allowed mime // types. // // maxFileSize: You can restrict large files by setting the maxFileSize (in bytes). // // inputName: When uploading, a form is generated and submitted to the server via Ajax. If your server would prefer // a different name for how the image comes through, you can change the inputName. // // url: The url that the image upload will be submitted to. // // handler: You can use false to let Mercury handle it for you, or you can provide a handler function that can // modify the response from the server. This can be useful if your server doesn't respond the way Mercury expects. // The handler function should take the response from the server and return an object that matches: // `{image: {url: '[your provided url]'}` uploading: { enabled: true, allowedMimeTypes: ['image/jpeg', 'image/gif', 'image/png'], maxFileSize: 1235242880, inputName: 'image[image]', url: '/mercury/images', handler: false }, // ## Localization / I18n // // Include the .locale files you want to support when loading Mercury. The files are always named by the language, // and not the regional dialect (eg. en.locale.js) because the regional dialects are nested within the primary // locale files. // // The client locale will be used first, and if no proper locale file is found for their language then the fallback // preferredLocale configuration will be used. If one isn't provided, and the client locale isn't included, the // strings will remain untranslated. // // enabled: Set to false to disable, true to enable. // // preferredLocale: If a client doesn't support the locales you've included, this is used as a fallback. localization: { enabled: false, preferredLocale: 'swedish_chef-BORK' }, // ## Behaviors // // Behaviors are used to change the default behaviors of a given region type when a given button is clicked. For // example, you may prefer to add HR tags using an HR wrapped within a div with a classname (for styling). You // can add your own complex behaviors here and they'll be shared across all regions. // // If you want to add behaviors to specific region types, you can mix them into the actions property of any region // type. // // Mercury.Regions.Full.actions.htmlEditor = function() {} // // You can see how the behavior matches up directly with the button names. It's also important to note that the // callback functions are executed within the scope of the given region, so you have access to all it's methods. behaviors: { //foreColor: function(selection, options) { selection.wrap('', true) }, htmlEditor: function() { Mercury.modal('/mercury/modals/htmleditor.html', { title: 'HTML Editor', fullHeight: true, handler: 'htmlEditor' }); } }, // ## Global Behaviors // // Global behaviors are much like behaviors, but are more "global". Things like save, exit, etc. can be included // here. They'll only be called once, and execute within the scope of whatever editor is instantiated (eg. // PageEditor). // // An example of changing how saving works: // // save: function() { // var data = top.JSON.stringify(this.serialize(), null, ' '); // var content = ''; // Mercury.modal(null, {title: 'Saving', closeButton: true, content: content}) // } // // This is a nice way to add functionality, when the behaviors aren't region specific. These can be triggered by a // button, or manually with `Mercury.trigger('action', {action: 'barrelRoll'})` globalBehaviors: { exit: function() { window.location.href = this.iframeSrc() }, barrelRoll: function() { $('body').css({webkitTransform: 'rotate(360deg)'}) } }, // ## Ajax and CSRF Headers // // Some server frameworks require that you provide a specific header for Ajax requests. The values for these CSRF // tokens are typically stored in the rendered DOM. By default, Mercury will look for the Rails specific meta tag, // and provide the X-CSRF-Token header on Ajax requests, but you can modify this configuration if the system you're // using doesn't follow the same standard. csrfSelector: 'meta[name="csrf-token"]', csrfHeader: 'X-CSRF-Token', // ## Editor URLs // // When loading a given page, you may want to tweak this regex. It's to allow the url to differ from the page // you're editing, and the url at which you access it. editorUrlRegEx: /([http|https]:\/\/.[^\/]*)\/editor\/?(.*)/i, // ## Hijacking Links & Forms // // Mercury will hijack links and forms that don't have a target set, or the target is set to _self and will set it // to _parent. This is because the target must be set properly for Mercury to not get in the way of some // functionality, like proper page loads on form submissions etc. Mercury doesn't do this to links or forms that // are within editable regions because it doesn't want to impact the html that's saved. With that being explained, // you can add classes to links or forms that you don't want this behavior added to. Let's say you have links that // open a lightbox style window, and you don't want the targets of these to be set to _parent. You can add classes // to this array, and they will be ignored when the hijacking is applied. nonHijackableClasses: [], // ## Pasting & Sanitizing // // When pasting content into Mercury it may sometimes contain HTML tags and attributes. This markup is used to // style the content and makes the pasted content look (and behave) the same as the original content. This can be a // desired feature or an annoyance, so you can enable various sanitizing methods to clean the content when it's // pasted. // // sanitize: Can be any of the following: // - false: no sanitizing is done, the content is pasted the exact same as it was copied by the user // - 'whitelist': content is cleaned using the settings specified in the tag white list (described below) // - 'text': all html is stripped before pasting, leaving only the raw text // // whitelist: The white list allows you to specify tags and attributes that are allowed when pasting content. Each // item in this object should contain the allowed tag, and an array of attributes that are allowed on that tag. If // the allowed attributes array is empty, all attributes will be removed. If a tag is not present in this list, it // will be removed, but without removing any of the text or tags inside it. // // **Note:** Content is *always* sanitized if looks like it's from MS Word or similar editors regardless of this // configuration. pasting: { sanitize: 'whitelist', whitelist: { h1: [], h2: [], h3: [], h4: [], h5: [], h6: [], table: [], thead: [], tbody: [], tfoot: [], tr: [], th: ['colspan', 'rowspan'], td: ['colspan', 'rowspan'], div: ['class'], span: ['class'], ul: [], ol: [], li: [], b: [], strong: [], i: [], em: [], u: [], strike: [], br: [], p: [], hr: [], a: ['href', 'target', 'title', 'name'], img: ['src', 'title', 'alt'] } }, // ## Injected Styles // // Mercury tries to stay as much out of your code as possible, but because regions appear within your document we // need to include a few styles to indicate regions, as well as the different states of them (eg. focused). These // styles are injected into your document, and as simple as they might be, you may want to change them. injectedStyles: '' + '[data-mercury] { min-height: 10px; outline: 1px dotted #09F } ' + '[data-mercury]:focus { outline: none; -webkit-box-shadow: 0 0 10px #09F, 0 0 1px #045; box-shadow: 0 0 10px #09F, 0 0 1px #045 }' + '[data-mercury].focus { outline: none; -webkit-box-shadow: 0 0 10px #09F, 0 0 1px #045; box-shadow: 0 0 10px #09F, 0 0 1px #045 }' + '[data-mercury]:after { content: "."; display: block; visibility: hidden; clear: both; height: 0; overflow: hidden; }' + '[data-mercury] table { border: 1px dotted red; min-width: 6px; }' + '[data-mercury] th { border: 1px dotted red; min-width: 6px; }' + '[data-mercury] td { border: 1px dotted red; min-width: 6px; }' + '[data-mercury] .mercury-textarea { border: 0; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; resize: none; }' + '[data-mercury] .mercury-textarea:focus { outline: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }' }, // ## Silent Mode // // Turning silent mode on will disable asking about unsaved changes before leaving the page. silent: false, // ## Debug Mode // // Turning debug mode on will log events and other various things (using console.debug if available). debug: false }; /*! * jQuery UI 1.8.13 * * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI */ (function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.13", keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,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,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, "overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= 0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a= 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) { $(this.document || 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 Draggable 1.8.13 * * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Draggables * * Depends: * jquery.ui.core.js * jquery.ui.mouse.js * jquery.ui.widget.js */ (function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== "original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;this.helper= this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}); this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);return true},_mouseDrag:function(a,b){this.position=this._generatePosition(a); this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.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";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b= d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop", a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this== a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a= {left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&& d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a= this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions= {width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[(a.containment=="document"?0:d(window).scrollLeft())-this.offset.relative.left-this.offset.parent.left,(a.containment=="document"?0:d(window).scrollTop())-this.offset.relative.top-this.offset.parent.top,(a.containment=="document"?0:d(window).scrollLeft())+ d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!="hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"), 10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height- this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&& d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0], this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.leftg[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1];h=g?!(h-this.offset.click.topg[3])?h:!(h-this.offset.click.topg[2])?e:!(e-this.offset.click.left< g[0])?e-b.grid[0]:e+b.grid[0]:e}}return{top:h-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop()),left:e-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"); this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(a,b,c){c=c||this._uiHash();d.ui.plugin.call(this,a,[b,c]);if(a=="drag")this.positionAbs=this._convertPositionTo("absolute");return d.Widget.prototype._trigger.call(this,a,b,c)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});d.extend(d.ui.draggable,{version:"1.8.13"}); d.ui.plugin.add("draggable","connectToSortable",{start:function(a,b){var c=d(this).data("draggable"),f=c.options,e=d.extend({},b,{item:c.element});c.sortables=[];d(f.connectToSortable).each(function(){var h=d.data(this,"sortable");if(h&&!h.options.disabled){c.sortables.push({instance:h,shouldRevert:h.options.revert});h.refreshPositions();h._trigger("activate",a,e)}})},stop:function(a,b){var c=d(this).data("draggable"),f=d.extend({},b,{item:c.element});d.each(c.sortables,function(){if(this.instance.isOver){this.instance.isOver= 0;c.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(a);this.instance.options.helper=this.instance.options._helper;c.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",a,f)}})},drag:function(a,b){var c=d(this).data("draggable"),f=this;d.each(c.sortables,function(){this.instance.positionAbs= c.positionAbs;this.instance.helperProportions=c.helperProportions;this.instance.offset.click=c.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=d(f).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return b.helper[0]};a.target=this.instance.currentItem[0];this.instance._mouseCapture(a, true);this.instance._mouseStart(a,true,true);this.instance.offset.click.top=c.offset.click.top;this.instance.offset.click.left=c.offset.click.left;this.instance.offset.parent.left-=c.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=c.offset.parent.top-this.instance.offset.parent.top;c._trigger("toSortable",a);c.dropped=this.instance.element;c.currentItem=c.element;this.instance.fromOutside=c}this.instance.currentItem&&this.instance._mouseDrag(a)}else if(this.instance.isOver){this.instance.isOver= 0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",a,this.instance._uiHash(this.instance));this.instance._mouseStop(a,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&this.instance.placeholder.remove();c._trigger("fromSortable",a);c.dropped=false}})}});d.ui.plugin.add("draggable","cursor",{start:function(){var a=d("body"),b=d(this).data("draggable").options;if(a.css("cursor"))b._cursor= a.css("cursor");a.css("cursor",b.cursor)},stop:function(){var a=d(this).data("draggable").options;a._cursor&&d("body").css("cursor",a._cursor)}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("opacity"))b._opacity=a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable"); if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){if(!c.axis||c.axis!="x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e *', opacity: false, placeholder: false, revert: false, scroll: true, scrollSensitivity: 20, scrollSpeed: 20, scope: "default", tolerance: "intersect", zIndex: 1000 }, _create: function() { var o = this.options; this.document = o.document; 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' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false; //Let's determine the parent's offset this.offset = this.element.offset(); //Initialize mouse events for interaction this._mouseInit(); }, destroy: function() { this.element .removeClass("ui-sortable ui-sortable-disabled") .removeData("sortable") .unbind(".sortable"); this._mouseDestroy(); for ( var i = this.items.length - 1; i >= 0; i-- ) this.items[i].item.removeData("sortable-item"); return this; }, _setOption: function(key, value){ if ( key === "disabled" ) { this.options[ key ] = value; this.widget() [ value ? "addClass" : "removeClass"]( "ui-sortable-disabled" ); } 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) { 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 var currentItem = null, self = this, nodes = $(event.target).parents().each(function() { if($.data(this, 'sortable-item') == self) { currentItem = $(this); return false; } }); if($.data(event.target, 'sortable-item') == self) currentItem = $(event.target); if(!currentItem) return false; if(this.options.handle && !overrideHandle) { var validHandle = false; $(this.options.handle, currentItem).find("*").andSelf().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 o = this.options, self = this; 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 }; // 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"); $.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 }); //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) { // cursor option if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor"); $('body').css("cursor", o.cursor); } 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] != (this.document || 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 (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._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) { //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) { var o = this.options, scrolled = false; if(this.scrollParent[0] != (this.document || 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 - $((this.document || document)).scrollTop() < o.scrollSensitivity) scrolled = $((this.document || document)).scrollTop($((this.document || document)).scrollTop() - o.scrollSpeed); else if($(window).height() - (event.pageY - $((this.document || document)).scrollTop()) < o.scrollSensitivity) scrolled = $((this.document || document)).scrollTop($((this.document || document)).scrollTop() + o.scrollSpeed); if(event.pageX - $((this.document || document)).scrollLeft() < o.scrollSensitivity) scrolled = $((this.document || document)).scrollLeft($((this.document || document)).scrollLeft() - o.scrollSpeed); else if($(window).width() - (event.pageX - $((this.document || document)).scrollLeft()) < o.scrollSensitivity) scrolled = $((this.document || document)).scrollLeft($((this.document || 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 (var i = this.items.length - 1; i >= 0; i--) { //Cache variables and intersection, continue if no intersection var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); if (!intersection) continue; if(itemElement != this.currentItem[0] //cannot intersect with itself && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before && !$.ui.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked && (this.options.type == 'semi-dynamic' ? !$.ui.contains(this.element[0], itemElement) : true) //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container ) { 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 self = this; var cur = self.placeholder.offset(); self.reverting = true; $(this.helper).animate({ left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == (this.document || document).body ? 0 : this.offsetParent[0].scrollLeft), top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == (this.document || document).body ? 0 : this.offsetParent[0].scrollTop) }, parseInt(this.options.revert, 10) || 500, function() { self._clear(event); }); } else { this._clear(event, noPropagation); } return false; }, cancel: function() { var self = this; 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, self._uiHash(this)); if(this.containers[i].containerCache.over) { this.containers[i]._trigger("out", null, self._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); var 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); var 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; var l = item.left, r = l + item.width, t = item.top, b = t + item.height; var dyClick = this.offset.click.top, dxClick = this.offset.click.left; var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r; 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 = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), isOverElementWidth = $.ui.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 = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), isOverRightHalf = $.ui.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 self = this; var items = []; var queries = []; var connectWith = this._connectWith(); if(connectWith && connected) { for (var i = connectWith.length - 1; i >= 0; i--){ var cur = $(connectWith[i]); for (var j = cur.length - 1; j >= 0; j--){ var inst = $.data(cur[j], 'sortable'); 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]); for (var i = queries.length - 1; i >= 0; i--){ queries[i][0].each(function() { items.push(this); }); }; return $(items); }, _removeCurrentsFromItems: function() { var list = this.currentItem.find(":data(sortable-item)"); for (var i=0; i < this.items.length; i++) { for (var j=0; j < list.length; j++) { if(list[j] == this.items[i].item[0]) this.items.splice(i,1); }; }; }, _refreshItems: function(event) { this.items = []; this.containers = [this]; var items = this.items; var self = this; var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]]; var connectWith = this._connectWith(); if(connectWith) { for (var i = connectWith.length - 1; i >= 0; i--){ var cur = $(connectWith[i]); for (var j = cur.length - 1; j >= 0; j--){ var inst = $.data(cur[j], 'sortable'); 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 (var i = queries.length - 1; i >= 0; i--) { var targetData = queries[i][1]; var _queries = queries[i][0]; for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) { var item = $(_queries[j]); item.data('sortable-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(); } for (var i = this.items.length - 1; i >= 0; i--){ var 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; var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; if (!fast) { item.width = t.outerWidth(); item.height = t.outerHeight(); } var 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 (var i = this.containers.length - 1; i >= 0; i--){ var 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) { var self = that || this, o = self.options; if(!o.placeholder || o.placeholder.constructor == String) { var className = o.placeholder; o.placeholder = { element: function() { var el = $((this.document || document).createElement(self.currentItem[0].nodeName)) .addClass(className || self.currentItem[0].className+" ui-sortable-placeholder") .removeClass("ui-sortable-helper")[0]; if(!className) el.style.visibility = "hidden"; return el; }, 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(self.currentItem.innerHeight() - parseInt(self.currentItem.css('paddingTop')||0, 10) - parseInt(self.currentItem.css('paddingBottom')||0, 10)); }; if(!p.width()) { p.width(self.currentItem.innerWidth() - parseInt(self.currentItem.css('paddingLeft')||0, 10) - parseInt(self.currentItem.css('paddingRight')||0, 10)); }; } }; } //Create the placeholder self.placeholder = $(o.placeholder.element.call(self.element, self.currentItem)); //Append it after the actual current item self.currentItem.after(self.placeholder); //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) o.placeholder.update(self, self.placeholder); }, _contactContainers: function(event) { // get innermost container that intersects with item var innermostContainer = null, innermostIndex = null; for (var i = this.containers.length - 1; i >= 0; i--){ // never consider a container that's located within the item itself if($.ui.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 && $.ui.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) { this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); this.containers[innermostIndex].containerCache.over = 1; } else if(this.currentContainer != this.containers[innermostIndex]) { //When entering a new container, we will find the item with the least distance and append our item near it var dist = 10000; var itemWithLeastDistance = null; var base = this.positionAbs[this.containers[innermostIndex].floating ? 'left' : 'top']; for (var j = this.items.length - 1; j >= 0; j--) { if(!$.ui.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue; var cur = this.items[j][this.containers[innermostIndex].floating ? 'left' : 'top']; if(Math.abs(cur - base) < dist) { dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j]; } } if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled return; this.currentContainer = this.containers[innermostIndex]; 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)); //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; var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem); if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already $(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] != (this.document || document) && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) { po.left += this.scrollParent.scrollLeft(); po.top += this.scrollParent.scrollTop(); } if((this.offsetParent[0] == (this.document || document).body) //This needs to be actually done for all browsers, since pageX/pageY includes this information || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix 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 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' ? (this.document || document) : window).width() - this.helperProportions.width - this.margins.left, ($(o.containment == 'document' ? (this.document || document) : window).height() || (this.document || document).body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top ]; if(!(/^(document|window|parent)$/).test(o.containment)) { var ce = $(o.containment)[0]; var co = $(o.containment).offset(); var 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; var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != (this.document || document) && $.ui.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) - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( 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) - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) ) }; }, _generatePosition: function(event) { var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != (this.document || document) && $.ui.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] != (this.document || document) && this.scrollParent[0] != this.offsetParent[0])) { this.offset.relative = this._getRelativeOffset(); } var pageX = event.pageX; var pageY = event.pageY; /* * - 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) { var 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; var 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) + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( 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) + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( 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 self = this, counter = this.counter; window.setTimeout(function() { if(counter == self.counter) self.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove },0); }, _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 delayedTriggers = [], self = this; // 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[0].parentNode) this.placeholder.before(this.currentItem); this._noFinalSort = null; if(this.helper[0] == this.currentItem[0]) { for(var 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 if(!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element if(!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); for (var i = this.containers.length - 1; i >= 0; i--){ if($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) { delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i])); delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i])); } }; }; //Post events to containers for (var i = this.containers.length - 1; i >= 0; i--){ if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i])); if(this.containers[i].containerCache.over) { delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i])); this.containers[i].containerCache.over = 0; } } //Do what was originally in plugins if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index this.dragging = false; if(this.cancelHelperRemoval) { if(!noPropagation) { this._trigger("beforeStop", event, this._uiHash()); for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events this._trigger("stop", event, this._uiHash()); } 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 (var 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 self = inst || this; return { helper: self.helper, placeholder: self.placeholder || $([]), position: self.position, originalPosition: self.originalPosition, offset: self.positionAbs, item: self.currentItem, sender: inst ? inst.element : null }; } }); $.extend($.ui.sortable, { version: "1.8.13" }); })(jQuery); /* * jQuery serializeObject Plugin: https://github.com/fojas/jQuery-serializeObject * */ !function($){ $.serializeObject = function(obj){ var o={},lookup=o,a = obj; $.each(a,function(){ var named = this.name.replace(/\[([^\]]+)?\]/g,',$1').split(','), cap = named.length - 1, i = 0; for(;i. The name of * the University may not be used to endorse or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ (function($) { $.fn.localize = function(locale) { this.find('*').contents().each(function() { var translated = false; var source = ''; if (typeof(this.data) == 'string') { source = $.trim(this.data); if (source && (translated = (locale.sub[source] || locale.top[source]))) { this.data = translated; } } if (this.nodeName == 'IMG' && this.attributes['src']) { source = this.attributes['src'].nodeValue; if (source && (translated = (locale.sub[source] || locale.top[source]))) { $(this).attr('src', translated); } } if (this.nodeName == "A" && this.attributes['href']) { source = $.trim(this.attributes['href'].nodeValue); if (source && (translated = (locale.sub[source] || locale.top[source]))) { $(this).attr('href', translated); } } if (this.nodeName == "INPUT" && this.attributes['type']) { if (this.attributes['value'] && ['submit', 'reset', 'button'].indexOf(this.attributes['type'].nodeValue.toLowerCase()) > -1) { source = $.trim(this.attributes['value'].nodeValue); if (source && (translated = (locale.sub[source] || locale.top[source]))) { $(this).attr('value', translated); } } } return this; }); }; })(jQuery); /* HTML Clean for jQuery Anthony Johnston http://www.antix.co.uk version 1.2.3 $Revision: 51 $ requires jQuery http://jquery.com Use and distibution http://www.opensource.org/licenses/bsd-license.php 2010-04-02 allowedTags/removeTags added (white/black list) thanks to David Wartian (Dwartian) 2010-06-30 replaceStyles added for replacement of bold, italic, super and sub styles on a tag 2010-07-01 notRenderedTags added, where tags are to be removed but their contents are kept */ (function ($) { $.fn.htmlClean = function (options) { // iterate and html clean each matched element return this.each(function () { if (this.value) { this.value = $.htmlClean(this.value, options); } else { this.innerHTML = $.htmlClean(this.innerHTML, options); } }); }; // clean the passed html $.htmlClean = function (html, options) { options = $.extend({}, $.htmlClean.defaults, options); var tagsRE = /<(\/)?(\w+:)?([\w]+)([^>]*)>/gi; var attrsRE = /(\w+)=(".*?"|'.*?'|[^\s>]*)/gi; var tagMatch; var root = new Element(); var stack = [root]; var container = root; if (options.bodyOnly) { // check for body tag if (tagMatch = /]*>((\n|.)*)<\/body>/i.exec(html)) { html = tagMatch[1]; } } html = html.concat(""); // ensure last element/text is found var lastIndex; while (tagMatch = tagsRE.exec(html)) { var tag = new Tag(tagMatch[3], tagMatch[1], tagMatch[4], options); // add the text var text = html.substring(lastIndex, tagMatch.index); if (text.length > 0) { var child = container.children[container.children.length - 1]; if (container.children.length > 0 && isText(child = container.children[container.children.length - 1])) { // merge text container.children[container.children.length - 1] = child.concat(text); } else { container.children.push(text); } } lastIndex = tagsRE.lastIndex; if (tag.isClosing) { // find matching container if (pop(stack, [tag.name])) { stack.pop(); container = stack[stack.length - 1]; } } else { // create a new element var element = new Element(tag); // add attributes var attrMatch; while (attrMatch = attrsRE.exec(tag.rawAttributes)) { // check style attribute and do replacements if (attrMatch[1].toLowerCase() == "style" && options.replaceStyles) { var renderParent = !tag.isInline; for (var i = 0; i < options.replaceStyles.length; i++) { if (options.replaceStyles[i][0].test(attrMatch[2])) { if (!renderParent) { tag.render = false; renderParent = true; } container.children.push(element); // assumes not replaced stack.push(element); container = element; // assumes replacement is a container // create new tag and element tag = new Tag(options.replaceStyles[i][1], "", "", options); element = new Element(tag); } } } if (tag.allowedAttributes != null && (tag.allowedAttributes.length == 0 || $.inArray(attrMatch[1], tag.allowedAttributes) > -1)) { element.attributes.push(new Attribute(attrMatch[1], attrMatch[2])); } } // add required empty ones $.each(tag.requiredAttributes, function () { var name = this.toString(); if (!element.hasAttribute(name)) element.attributes.push(new Attribute(name, "")); }); // check for replacements for (var repIndex = 0; repIndex < options.replace.length; repIndex++) { for (var tagIndex = 0; tagIndex < options.replace[repIndex][0].length; tagIndex++) { var byName = typeof (options.replace[repIndex][0][tagIndex]) == "string"; if ((byName && options.replace[repIndex][0][tagIndex] == tag.name) || (!byName && options.replace[repIndex][0][tagIndex].test(tagMatch))) { // don't render this tag tag.render = false; container.children.push(element); stack.push(element); container = element; // render new tag, keep attributes tag = new Tag(options.replace[repIndex][1], tagMatch[1], tagMatch[4], options); element = new Element(tag); element.attributes = container.attributes; repIndex = options.replace.length; // break out of both loops break; } } } // check container rules var add = true; if (!container.isRoot) { if (container.tag.isInline && !tag.isInline) { add = false; } else if (container.tag.disallowNest && tag.disallowNest && !tag.requiredParent) { add = false; } else if (tag.requiredParent) { if (add = pop(stack, tag.requiredParent)) { container = stack[stack.length - 1]; } } } if (add) { container.children.push(element); if (tag.toProtect) { // skip to closing tag var tagMatch2 = null; while (tagMatch2 = tagsRE.exec(html)) { var tag2 = new Tag(tagMatch2[3], tagMatch2[1], tagMatch2[4], options); if (tag2.isClosing && tag2.name == tag.name) { element.children.push(RegExp.leftContext.substring(lastIndex)); lastIndex = tagsRE.lastIndex; break; } } } else { // set as current container element if (!tag.isSelfClosing && !tag.isNonClosing) { stack.push(element); container = element; } } } } } // render doc return render(root, options).join(""); }; // defaults $.htmlClean.defaults = { // only clean the body tagbody bodyOnly: true, // only allow tags in this array, (white list), contents still rendered allowedTags: [], // remove tags in this array, (black list), contents still rendered removeTags: ["basefont", "center", "dir", "font", "frame", "frameset", "iframe", "isindex", "menu", "noframes", "s", "strike", "u"], // array of attribute names to remove on all elements in addition to those not in tagAttributes e.g ["width", "height"] removeAttrs: [], // array of [className], [optional array of allowed on elements] e.g. [["class"], ["anotherClass", ["p", "dl"]]] allowedClasses: [], // tags not rendered, contents remain notRenderedTags: [], // format the result format: false, // format indent to start on formatIndent: 0, // tags to replace, and what to replace with, tag name or regex to match the tag and attributes replace: [ [ ["b", "big"], "strong" ], [ ["i"], "em" ] ], // styles to replace with tags, multiple style matches supported, inline tags are replaced by the first match blocks are retained replaceStyles: [ [/font-weight:\s*bold/i, "strong"], [/font-style:\s*italic/i, "em"], [/vertical-align:\s*super/i, "sup"], [/vertical-align:\s*sub/i, "sub"] ] }; function applyFormat(element, options, output, indent) { if (!element.tag.isInline && output.length > 0) { output.push("\n"); for (var i = 0; i < indent; i++) output.push("\t"); } } function render(element, options) { var output = [], empty = element.attributes.length == 0, indent = 0, outputChildren = null; // don't render if not in allowedTags or in removeTags var renderTag = element.tag.render && (options.allowedTags.length == 0 || $.inArray(element.tag.name, options.allowedTags) > -1) && (options.removeTags.length == 0 || $.inArray(element.tag.name, options.removeTags) == -1); if (!element.isRoot && renderTag) { // render opening tag output.push("<"); output.push(element.tag.name); $.each(element.attributes, function () { if ($.inArray(this.name, options.removeAttrs) == -1) { var m = new RegExp(/^(['"]?)(.*?)['"]?$/).exec(this.value); var value = m[2]; var valueQuote = m[1] || "'"; // check for classes allowed if (this.name == "class") { value = $.grep(value.split(" "), function (c) { return $.grep(options.allowedClasses, function (a) { return a[0] == c && (a.length == 1 || $.inArray(element.tag.name, a[1]) > -1); }).length > 0; }) .join(" "); valueQuote = "'"; } if (value != null && (value.length > 0 || $.inArray(this.name, element.tag.requiredAttributes) > -1)) { output.push(" "); output.push(this.name); output.push("="); output.push(valueQuote); output.push(value); output.push(valueQuote); } } }); } if (element.tag.isSelfClosing) { // self closing if (renderTag) output.push(" />"); empty = false; } else if (element.tag.isNonClosing) { empty = false; } else { if (!element.isRoot && renderTag) { // close output.push(">"); } indent = options.formatIndent++; // render children if (element.tag.toProtect) { outputChildren = $.htmlClean.trim(element.children.join("")).replace(/
/ig, "\n"); output.push(outputChildren); empty = outputChildren.length == 0; options.formatIndent--; } else { outputChildren = []; for (var i = 0; i < element.children.length; i++) { var child = element.children[i]; var text = $.htmlClean.trim(textClean(isText(child) ? child : child.childrenToString())); if (isInline(child)) { if (i > 0 && text.length > 0 && (startsWithWhitespace(child) || endsWithWhitespace(element.children[i - 1]))) { outputChildren.push(" "); } } if (isText(child)) { if (text.length > 0) { outputChildren.push(text); } } else { // don't allow a break to be the last child if (i != element.children.length - 1 || child.tag.name != "br") { if (options.format) applyFormat(child, options, outputChildren, indent); outputChildren = outputChildren.concat(render(child, options)); } } } options.formatIndent--; if (outputChildren.length > 0) { if (options.format && outputChildren[0] != "\n") applyFormat(element, options, output, indent); output = output.concat(outputChildren); empty = false; } } if (!element.isRoot && renderTag) { // render the closing tag if (options.format) applyFormat(element, options, output, indent - 1); output.push(""); } } // check for empty tags if (!element.tag.allowEmpty && empty) { return []; } return output; } // find a matching tag, and pop to it, if not do nothing function pop(stack, tagNameArray, index) { index = index || 1; if ($.inArray(stack[stack.length - index].tag.name, tagNameArray) > -1) { return true; } else if (stack.length - (index + 1) > 0 && pop(stack, tagNameArray, index + 1)) { stack.pop(); return true; } return false; } // Element Object function Element(tag) { if (tag) { this.tag = tag; this.isRoot = false; } else { this.tag = new Tag("root"); this.isRoot = true; } this.attributes = []; this.children = []; this.hasAttribute = function (name) { for (var i = 0; i < this.attributes.length; i++) { if (this.attributes[i].name == name) return true; } return false; }; this.childrenToString = function () { return this.children.join(""); }; return this; } // Attribute Object function Attribute(name, value) { this.name = name; this.value = value; return this; } // Tag object function Tag(name, close, rawAttributes, options) { this.name = name.toLowerCase(); this.isSelfClosing = $.inArray(this.name, tagSelfClosing) > -1; this.isNonClosing = $.inArray(this.name, tagNonClosing) > -1; this.isClosing = (close != undefined && close.length > 0); this.isInline = $.inArray(this.name, tagInline) > -1; this.disallowNest = $.inArray(this.name, tagDisallowNest) > -1; this.requiredParent = tagRequiredParent[$.inArray(this.name, tagRequiredParent) + 1]; this.allowEmpty = $.inArray(this.name, tagAllowEmpty) > -1; this.toProtect = $.inArray(this.name, tagProtect) > -1; this.rawAttributes = rawAttributes; this.allowedAttributes = tagAttributes[$.inArray(this.name, tagAttributes) + 1]; this.requiredAttributes = tagAttributesRequired[$.inArray(this.name, tagAttributesRequired) + 1]; this.render = options && $.inArray(this.name, options.notRenderedTags) == -1; return this; } function startsWithWhitespace(item) { while (isElement(item) && item.children.length > 0) { item = item.children[0] } return isText(item) && item.length > 0 && $.htmlClean.isWhitespace(item.charAt(0)); } function endsWithWhitespace(item) { while (isElement(item) && item.children.length > 0) { item = item.children[item.children.length - 1] } return isText(item) && item.length > 0 && $.htmlClean.isWhitespace(item.charAt(item.length - 1)); } function isText(item) { return item.constructor == String; } function isInline(item) { return isText(item) || item.tag.isInline; } function isElement(item) { return item.constructor == Element; } function textClean(text) { return text.replace(/ |\n/g, " ").replace(/\s\s+/g, " "); } // trim off white space, doesn't use regex $.htmlClean.trim = function (text) { return $.htmlClean.trimStart($.htmlClean.trimEnd(text)); }; $.htmlClean.trimStart = function (text) { return text.substring($.htmlClean.trimStartIndex(text)); }; $.htmlClean.trimStartIndex = function (text) { for (var start = 0; start < text.length - 1 && $.htmlClean.isWhitespace(text.charAt(start)); start++); return start; }; $.htmlClean.trimEnd = function (text) { return text.substring(0, $.htmlClean.trimEndIndex(text)); }; $.htmlClean.trimEndIndex = function (text) { for (var end = text.length - 1; end >= 0 && $.htmlClean.isWhitespace(text.charAt(end)); end--); return end + 1; }; // checks a char is white space or not $.htmlClean.isWhitespace = function (c) { return $.inArray(c, whitespace) != -1; }; // tags which are inline var tagInline = [ "a", "abbr", "acronym", "address", "b", "big", "br", "button", "caption", "cite", "code", "del", "em", "font", "hr", "i", "input", "img", "ins", "label", "legend", "map", "q", "samp", "select", "small", "span", "strong", "sub", "sup", "tt", "var"]; var tagDisallowNest = ["h1", "h2", "h3", "h4", "h5", "h6", "p", "th", "td"]; var tagAllowEmpty = ["th", "td"]; var tagRequiredParent = [ null, "li", ["ul", "ol"], "dt", ["dl"], "dd", ["dl"], "td", ["tr"], "th", ["tr"], "tr", ["table", "thead", "tbody", "tfoot"], "thead", ["table"], "tbody", ["table"], "tfoot", ["table"] ]; var tagProtect = ["script", "style", "pre", "code"]; // tags which self close e.g.
var tagSelfClosing = ["br", "hr", "img", "link", "meta"]; // tags which do not close var tagNonClosing = ["!doctype", "?xml"]; // attributes allowed on tags var tagAttributes = [ ["class"], // default, for all tags not mentioned "?xml", [], "!doctype", [], "a", ["accesskey", "class", "href", "name", "title", "rel", "rev", "type", "tabindex"], "abbr", ["class", "title"], "acronym", ["class", "title"], "blockquote", ["cite", "class"], "button", ["class", "disabled", "name", "type", "value"], "del", ["cite", "class", "datetime"], "form", ["accept", "action", "class", "enctype", "method", "name"], "input", ["accept", "accesskey", "alt", "checked", "class", "disabled", "ismap", "maxlength", "name", "size", "readonly", "src", "tabindex", "type", "usemap", "value"], "img", ["alt", "class", "height", "src", "width"], "ins", ["cite", "class", "datetime"], "label", ["accesskey", "class", "for"], "legend", ["accesskey", "class"], "link", ["href", "rel", "type"], "meta", ["content", "http-equiv", "name", "scheme"], "map", ["name"], "optgroup", ["class", "disabled", "label"], "option", ["class", "disabled", "label", "selected", "value"], "q", ["class", "cite"], "td", ["colspan", "rowspan"], "th", ["colspan", "rowspan"], "script", ["src", "type"], "select", ["class", "disabled", "multiple", "name", "size", "tabindex"], "style", ["type"], "table", ["class", "summary"], "textarea", ["accesskey", "class", "cols", "disabled", "name", "readonly", "rows", "tabindex"] ]; var tagAttributesRequired = [[], "img", ["alt"]]; // white space chars var whitespace = [" ", " ", "\t", "\n", "\r", "\f"]; })(jQuery); /* * LiquidMetal, version: 0.1 (2009-02-05) * * A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially * LiquidMetal. * * For usage and examples, visit: * http://github.com/rmm5t/liquidmetal * * Licensed under the MIT: * http://www.opensource.org/licenses/mit-license.php * * Copyright (c) 2009, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org) */ var LiquidMetal = function() { var SCORE_NO_MATCH = 0.0; var SCORE_MATCH = 1.0; var SCORE_TRAILING = 0.8; var SCORE_TRAILING_BUT_STARTED = 0.9; var SCORE_BUFFER = 0.85; return { score: function(string, abbreviation) { // Short circuits if (abbreviation.length == 0) return SCORE_TRAILING; if (abbreviation.length > string.length) return SCORE_NO_MATCH; var scores = this.buildScoreArray(string, abbreviation); var sum = 0.0; for (var i =0; i < scores.length; i++) { sum += scores[i]; } return (sum / scores.length); }, buildScoreArray: function(string, abbreviation) { var scores = new Array(string.length); var lower = string.toLowerCase(); var chars = abbreviation.toLowerCase().split(""); var lastIndex = -1; var started = false; for (var i =0; i < chars.length; i++) { var c = chars[i]; var index = lower.indexOf(c, lastIndex+1); if (index < 0) return fillArray(scores, SCORE_NO_MATCH); if (index == 0) started = true; if (isNewWord(string, index)) { scores[index-1] = 1; fillArray(scores, SCORE_BUFFER, lastIndex+1, index-1); } else if (isUpperCase(string, index)) { fillArray(scores, SCORE_BUFFER, lastIndex+1, index); } else { fillArray(scores, SCORE_NO_MATCH, lastIndex+1, index); } scores[index] = SCORE_MATCH; lastIndex = index; } var trailingScore = started ? SCORE_TRAILING_BUT_STARTED : SCORE_TRAILING; fillArray(scores, trailingScore, lastIndex+1); return scores; } }; function isUpperCase(string, index) { var c = string.charAt(index); return ("A" <= c && c <= "Z"); } function isNewWord(string, index) { var c = string.charAt(index-1); return (c == " " || c == "\t"); } function fillArray(array, value, from, to) { from = Math.max(from || 0, 0); to = Math.min(to || array.length, array.length); for (var i = from; i < to; i++) { array[i] = value; } return array; } }(); // // showdown.js -- A javascript port of Markdown. // // Copyright (c) 2007 John Fraser. // // Original Markdown Copyright (c) 2004-2005 John Gruber // // // Redistributable under a BSD-style open source license. // See license.txt for more information. // // The full source distribution is at: // // A A L // T C A // T K B // // // // // Wherever possible, Showdown is a straight, line-by-line port // of the Perl version of Markdown. // // This is not a normal parser design; it's basically just a // series of string substitutions. It's hard to read and // maintain this way, but keeping Showdown close to the original // design makes it easier to port new features. // // More importantly, Showdown behaves like markdown.pl in most // edge cases. So web applications can do client-side preview // in Javascript, and then build identical HTML on the server. // // This port needs the new RegExp functionality of ECMA 262, // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers // should do fine. Even with the new regular expression features, // We do a lot of work to emulate Perl's regex functionality. // The tricky changes in this file mostly have the "attacklab:" // label. Major or self-explanatory changes don't. // // Smart diff tools like Araxis Merge will be able to match up // this file with markdown.pl in a useful way. A little tweaking // helps: in a copy of markdown.pl, replace "#" with "//" and // replace "$text" with "text". Be sure to ignore whitespace // and line endings. // // // Showdown usage: // // var text = "Markdown *rocks*."; // // var converter = new Showdown.converter(); // var html = converter.makeHtml(text); // // alert(html); // // Note: move the sample code to the bottom of this // file before uncommenting it. // // ************************************************** // GitHub Flavored Markdown modifications by Tekkub // http://github.github.com/github-flavored-markdown/ // // Modifications are tagged with "GFM" // ************************************************** // // Showdown namespace // var Showdown = {}; // // converter // // Wraps all "globals" so that the only thing // exposed is makeHtml(). // Showdown.converter = function() { // Global hashes, used by various utility routines var g_urls; var g_titles; var g_html_blocks; // Used to track when we're inside an ordered or unordered list // (see _ProcessListItems() for details): var g_list_level = 0; // Main function. The order in which other subs are called here is // essential. Link and image substitutions need to happen before // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the // and tags get encoded. this.makeHtml = function(text) { // Clear the global hashes. If we don't clear these, you get conflicts // from other articles when generating a page which contains more than // one article (e.g. an index page that shows the N most recent // articles): g_urls = new Array(); g_titles = new Array(); g_html_blocks = new Array(); // attacklab: Replace ~ with ~T // This lets us use tilde as an escape char to avoid md5 hashes // The choice of character is arbitray; anything that isn't // magic in Markdown will work. text = text.replace(/~/g, "~T"); // attacklab: Replace $ with ~D // RegExp interprets $ as a special character // when it's in a replacement string text = text.replace(/\$/g, "~D"); // Standardize line endings text = text.replace(/\r\n/g, "\n"); // DOS to Unix text = text.replace(/\r/g, "\n"); // Mac to Unix // Make sure text begins and ends with a couple of newlines: text = "\n\n" + text + "\n\n"; // Convert all tabs to spaces. text = _Detab(text); // Strip any lines consisting only of spaces and tabs. // This makes subsequent regexen easier to write, because we can // match consecutive blank lines with /\n+/ instead of something // contorted like /[ \t]*\n+/ . text = text.replace(/^[ \t]+$/mg, ""); // Turn block-level HTML blocks into hash entries text = _HashHTMLBlocks(text); // Strip link definitions, store in hashes. text = _StripLinkDefinitions(text); text = _RunBlockGamut(text); text = _UnescapeSpecialChars(text); // attacklab: Restore dollar signs text = text.replace(/~D/g, "$$"); // attacklab: Restore tildes text = text.replace(/~T/g, "~"); // ** GFM ** Auto-link URLs and emails text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch, matchIndex) { var left = text.slice(0, matchIndex), right = text.slice(matchIndex); if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) { return wholeMatch } var href = wholeMatch.replace(/^http:\/\/github.com\//, "https://github.com/"); return "" + wholeMatch + ""; }); text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/ig, function(wholeMatch) { return "" + wholeMatch + ""; }); // ** GFM ** Auto-link sha1 if GitHub.nameWithOwner is defined text = text.replace(/[a-f0-9]{40}/ig, function(wholeMatch, matchIndex) { if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { return wholeMatch; } var left = text.slice(0, matchIndex), right = text.slice(matchIndex); if (left.match(/@$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { return wholeMatch; } return "" + wholeMatch.substring(0, 7) + ""; }); // ** GFM ** Auto-link user@sha1 if GitHub.nameWithOwner is defined text = text.replace(/([a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch, username, sha, matchIndex) { if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { return wholeMatch; } GitHub.repoName = GitHub.repoName || _GetRepoName(); var left = text.slice(0, matchIndex), right = text.slice(matchIndex); if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { return wholeMatch; } return "" + username + "@" + sha.substring(0, 7) + ""; }); // ** GFM ** Auto-link user/repo@sha1 text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch, repo, sha) { return "" + repo + "@" + sha.substring(0, 7) + ""; }); // ** GFM ** Auto-link #issue if GitHub.nameWithOwner is defined text = text.replace(/#([0-9]+)/ig, function(wholeMatch, issue, matchIndex) { if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { return wholeMatch; } var left = text.slice(0, matchIndex), right = text.slice(matchIndex); if (left == "" || left.match(/[a-z0-9_\-+=.]$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { return wholeMatch; } return "" + wholeMatch + ""; }); // ** GFM ** Auto-link user#issue if GitHub.nameWithOwner is defined text = text.replace(/([a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch, username, issue, matchIndex) { if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") { return wholeMatch; } GitHub.repoName = GitHub.repoName || _GetRepoName(); var left = text.slice(0, matchIndex), right = text.slice(matchIndex); if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) { return wholeMatch; } return "" + wholeMatch + ""; }); // ** GFM ** Auto-link user/repo#issue text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch, repo, issue) { return "" + wholeMatch + ""; }); return text; }; var _GetRepoName = function() { return GitHub.nameWithOwner.match(/^.+\/(.+)$/)[1] }; // // Strips link definitions from text, stores the URLs and titles in // hash references. // var _StripLinkDefinitions = function(text) { // Link defs are in the form: ^[id]: url "optional title" /* var text = text.replace(/ ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 [ \t]* \n? // maybe *one* newline [ \t]* ? // url = $2 [ \t]* \n? // maybe one newline [ \t]* (?: (\n*) // any lines skipped = $3 attacklab: lookbehind removed ["(] (.+?) // title = $4 [")] [ \t]* )? // title is optional (?:\n+|$) /gm, function(){...}); */ var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, function (wholeMatch, m1, m2, m3, m4) { m1 = m1.toLowerCase(); g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive if (m3) { // Oops, found blank lines, so it's not a title. // Put back the parenthetical statement we stole. return m3 + m4; } else if (m4) { g_titles[m1] = m4.replace(/"/g, """); } // Completely remove the definition from the text return ""; }); return text; }; var _HashHTMLBlocks = function(text) { // attacklab: Double up blank lines to reduce lookaround text = text.replace(/\n/g, "\n\n"); // Hashify HTML blocks: // We only want to do this for block-level HTML tags, such as headers, // lists, and tables. That's because we still want to wrap

s around // "paragraphs" that are wrapped in non-block-level tags, such as anchors, // phrase emphasis, and spans. The list of tags we're looking for is // hard-coded: var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"; var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"; // First, look for nested blocks, e.g.: //

//
// tags for inner block must be indented. //
//
// // The outermost tags must start at the left margin for this to match, and // the inner nested divs must be indented. // We need to do this before the next, more liberal match, because the next // match will start at the first `
` and stop at the first `
`. // attacklab: This regex can be expensive when it fails. /* var text = text.replace(/ ( // save in $1 ^ // start of line (with /m) <($block_tags_a) // start tag = $2 \b // word break // attacklab: hack around khtml/pcre bug... [^\r]*?\n // any number of lines, minimally matching // the matching end tag [ \t]* // trailing spaces/tabs (?=\n+) // followed by a newline ) // attacklab: there are sentinel newlines at end of document /gm,function(){...}}; */ text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement); // // Now match more liberally, simply from `\n` to `\n` // /* var text = text.replace(/ ( // save in $1 ^ // start of line (with /m) <($block_tags_b) // start tag = $2 \b // word break // attacklab: hack around khtml/pcre bug... [^\r]*? // any number of lines, minimally matching .* // the matching end tag [ \t]* // trailing spaces/tabs (?=\n+) // followed by a newline ) // attacklab: there are sentinel newlines at end of document /gm,function(){...}}; */ text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement); // Special case just for
. It was easier to make a special case than // to make the other regex more complicated. /* text = text.replace(/ ( // save in $1 \n\n // Starting after a blank line [ ]{0,3} (<(hr) // start tag = $2 \b // word break ([^<>])*? // \/?>) // the matching end tag [ \t]* (?=\n{2,}) // followed by a blank line ) /g,hashElement); */ text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); // Special case for standalone HTML comments: /* text = text.replace(/ ( // save in $1 \n\n // Starting after a blank line [ ]{0,3} // attacklab: g_tab_width - 1 [ \t]* (?=\n{2,}) // followed by a blank line ) /g,hashElement); */ text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g, hashElement); // PHP and ASP-style processor instructions ( and <%...%>) /* text = text.replace(/ (?: \n\n // Starting after a blank line ) ( // save in $1 [ ]{0,3} // attacklab: g_tab_width - 1 (?: <([?%]) // $2 [^\r]*? \2> ) [ \t]* (?=\n{2,}) // followed by a blank line ) /g,hashElement); */ text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); // attacklab: Undo double lines (see comment at top of this function) text = text.replace(/\n\n/g, "\n"); return text; }; var hashElement = function(wholeMatch, m1) { var blockText = m1; // Undo double lines blockText = blockText.replace(/\n\n/g, "\n"); blockText = blockText.replace(/^\n/, ""); // strip trailing blank lines blockText = blockText.replace(/\n+$/g, ""); // Replace the element text with a marker ("~KxK" where x is its key) blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; return blockText; }; // // These are all the transformations that form block-level // tags like paragraphs, headers, and list items. // var _RunBlockGamut = function(text) { text = _DoHeaders(text); // Do Horizontal Rules: var key = hashBlock("
"); text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key); text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key); text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm, key); text = _DoLists(text); text = _DoCodeBlocks(text); text = _DoBlockQuotes(text); // We already ran _HashHTMLBlocks() before, in Markdown(), but that // was to escape raw HTML in the original Markdown source. This time, // we're escaping the markup we've just created, so that we don't wrap //

tags around block-level tags. text = _HashHTMLBlocks(text); text = _FormParagraphs(text); return text; }; // // These are all the transformations that occur *within* block-level // tags like paragraphs, headers, and list items. // var _RunSpanGamut = function(text) { text = _DoCodeSpans(text); text = _EscapeSpecialCharsWithinTagAttributes(text); text = _EncodeBackslashEscapes(text); // Process anchor and image tags. Images must come first, // because ![foo][f] looks like an anchor. text = _DoImages(text); text = _DoAnchors(text); // Make links out of things like `` // Must come after _DoAnchors(), because you can use < and > // delimiters in inline links like [this](). text = _DoAutoLinks(text); text = _EncodeAmpsAndAngles(text); text = _DoItalicsAndBold(text); // Do hard breaks: text = text.replace(/ +\n/g, "
\n"); return text; }; // // Within tags -- meaning between < and > -- encode [\ ` * _] so they // don't conflict with their use in Markdown for code, italics and strong. // var _EscapeSpecialCharsWithinTagAttributes = function(text) { // Build a regex to find HTML tags and comments. See Friedl's // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; text = text.replace(regex, function(wholeMatch) { var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); tag = escapeCharacters(tag, "\\`*_"); return tag; }); return text; }; // // Turn Markdown link shortcuts into XHTML tags. // var _DoAnchors = function(text) { // // First, handle reference-style links: [link text] [id] // /* text = text.replace(/ ( // wrap whole match in $1 \[ ( (?: \[[^\]]*\] // allow brackets nested one level | [^\[] // or anything else )* ) \] [ ]? // one optional space (?:\n[ ]*)? // one optional newline followed by spaces \[ (.*?) // id = $3 \] )()()()() // pad remaining backreferences /g,_DoAnchors_callback); */ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); // // Next, inline-style links: [link text](url "optional title") // /* text = text.replace(/ ( // wrap whole match in $1 \[ ( (?: \[[^\]]*\] // allow brackets nested one level | [^\[\]] // or anything else ) ) \] \( // literal paren [ \t]* () // no id, so leave $3 empty ? // href = $4 [ \t]* ( // $5 (['"]) // quote char = $6 (.*?) // Title = $7 \6 // matching quote [ \t]* // ignore any spaces/tabs between closing quote and ) )? // title is optional \) ) /g,writeAnchorTag); */ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); // // Last, handle reference-style shortcuts: [link text] // These must come last in case you've also got [link test][1] // or [link test](/foo) // /* text = text.replace(/ ( // wrap whole match in $1 \[ ([^\[\]]+) // link text = $2; can't contain '[' or ']' \] )()()()()() // pad rest of backreferences /g, writeAnchorTag); */ text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); return text; }; var writeAnchorTag = function(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { if (m7 == undefined) m7 = ""; var whole_match = m1; var link_text = m2; var link_id = m3.toLowerCase(); var url = m4; var title = m7; if (url == "") { if (link_id == "") { // lower-case and turn embedded newlines into spaces link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); } url = "#" + link_id; if (g_urls[link_id] != undefined) { url = g_urls[link_id]; if (g_titles[link_id] != undefined) { title = g_titles[link_id]; } } else { if (whole_match.search(/\(\s*\)$/m) > -1) { // Special case for explicit empty url url = ""; } else { return whole_match; } } } url = escapeCharacters(url, "*_"); var result = ""; return result; }; // // Turn Markdown image shortcuts into tags. // var _DoImages = function(text) { // // First, handle reference-style labeled images: ![alt text][id] // /* text = text.replace(/ ( // wrap whole match in $1 !\[ (.*?) // alt text = $2 \] [ ]? // one optional space (?:\n[ ]*)? // one optional newline followed by spaces \[ (.*?) // id = $3 \] )()()()() // pad rest of backreferences /g,writeImageTag); */ text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); // // Next, handle inline images: ![alt text](url "optional title") // Don't forget: encode * and _ /* text = text.replace(/ ( // wrap whole match in $1 !\[ (.*?) // alt text = $2 \] \s? // One optional whitespace character \( // literal paren [ \t]* () // no id, so leave $3 empty ? // src url = $4 [ \t]* ( // $5 (['"]) // quote char = $6 (.*?) // title = $7 \6 // matching quote [ \t]* )? // title is optional \) ) /g,writeImageTag); */ text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); return text; }; var writeImageTag = function(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { var whole_match = m1; var alt_text = m2; var link_id = m3.toLowerCase(); var url = m4; var title = m7; if (!title) title = ""; if (url == "") { if (link_id == "") { // lower-case and turn embedded newlines into spaces link_id = alt_text.toLowerCase().replace(/ ?\n/g, " "); } url = "#" + link_id; if (g_urls[link_id] != undefined) { url = g_urls[link_id]; if (g_titles[link_id] != undefined) { title = g_titles[link_id]; } } else { return whole_match; } } alt_text = alt_text.replace(/"/g, """); url = escapeCharacters(url, "*_"); var result = "\""" + _RunSpanGamut(m1) + ""); }); text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, function(matchFound, m1) { return hashBlock("

" + _RunSpanGamut(m1) + "

"); }); // atx-style headers: // # Header 1 // ## Header 2 // ## Header 2 with closing hashes ## // ... // ###### Header 6 // /* text = text.replace(/ ^(\#{1,6}) // $1 = string of #'s [ \t]* (.+?) // $2 = Header text [ \t]* \#* // optional closing #'s (not counted) \n+ /gm, function() {...}); */ text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, function(wholeMatch, m1, m2) { var h_level = m1.length; return hashBlock("" + _RunSpanGamut(m2) + ""); }); return text; }; // This declaration keeps Dojo compressor from outputting garbage: var _ProcessListItems; // // Form HTML ordered (numbered) and unordered (bulleted) lists. // var _DoLists = function(text) { // attacklab: add sentinel to hack around khtml/safari bug: // http://bugs.webkit.org/show_bug.cgi?id=11231 text += "~0"; // Re-usable pattern to match any entirel ul or ol list: /* var whole_list = / ( // $1 = whole list ( // $2 [ ]{0,3} // attacklab: g_tab_width - 1 ([*+-]|\d+[.]) // $3 = first list item marker [ \t]+ ) [^\r]+? ( // $4 ~0 // sentinel for workaround; should be $ | \n{2,} (?=\S) (?! // Negative lookahead for another list item marker [ \t]* (?:[*+-]|\d+[.])[ \t]+ ) ) )/g */ var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; if (g_list_level) { text = text.replace(whole_list, function(wholeMatch, m1, m2) { var list = m1; var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; // Turn double returns into triple returns, so that we can make a // paragraph for the last item in a list, if necessary: list = list.replace(/\n{2,}/g, "\n\n\n"); var result = _ProcessListItems(list); // Trim any trailing whitespace, to put the closing `` // up on the preceding line, to get it past the current stupid // HTML block parser. This is a hack to work around the terrible // hack that is the HTML block parser. result = result.replace(/\s+$/, ""); result = "<" + list_type + ">" + result + "\n"; return result; }); } else { whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; text = text.replace(whole_list, function(wholeMatch, m1, m2, m3) { var runup = m1; var list = m2; var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; // Turn double returns into triple returns, so that we can make a // paragraph for the last item in a list, if necessary: var list = list.replace(/\n{2,}/g, "\n\n\n"); var result = _ProcessListItems(list); result = runup + "<" + list_type + ">\n" + result + "\n"; return result; }); } // attacklab: strip sentinel text = text.replace(/~0/, ""); return text; }; // // Process the contents of a single ordered or unordered list, splitting it // into individual list items. // _ProcessListItems = function(list_str) { // The $g_list_level global keeps track of when we're inside a list. // Each time we enter a list, we increment it; when we leave a list, // we decrement. If it's zero, we're not in a list anymore. // // We do this because when we're not inside a list, we want to treat // something like this: // // I recommend upgrading to version // 8. Oops, now this line is treated // as a sub-list. // // As a single paragraph, despite the fact that the second line starts // with a digit-period-space sequence. // // Whereas when we're inside a list (or sub-list), that line will be // treated as the start of a sub-list. What a kludge, huh? This is // an aspect of Markdown's syntax that's hard to parse perfectly // without resorting to mind-reading. Perhaps the solution is to // change the syntax rules such that sub-lists must start with a // starting cardinal number; e.g. "1." or "a.". g_list_level++; // trim trailing blank lines: list_str = list_str.replace(/\n{2,}$/, "\n"); // attacklab: add sentinel to emulate \z list_str += "~0"; /* list_str = list_str.replace(/ (\n)? // leading line = $1 (^[ \t]*) // leading whitespace = $2 ([*+-]|\d+[.]) [ \t]+ // list marker = $3 ([^\r]+? // list item text = $4 (\n{1,2})) (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) /gm, function(){...}); */ list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, function(wholeMatch, m1, m2, m3, m4) { var item = m4; var leading_line = m1; var leading_space = m2; if (leading_line || (item.search(/\n{2,}/) > -1)) { item = _RunBlockGamut(_Outdent(item)); } else { // Recursion for sub-lists: item = _DoLists(_Outdent(item)); item = item.replace(/\n$/, ""); // chomp(item) item = _RunSpanGamut(item); } return "
  • " + item + "
  • \n"; } ); // attacklab: strip sentinel list_str = list_str.replace(/~0/g, ""); g_list_level--; return list_str; }; // // Process Markdown `
    ` blocks.
      //
      var _DoCodeBlocks = function(text) {
    
        /*
         text = text.replace(text,
         /(?:\n\n|^)
         (								// $1 = the code block -- one or more lines, starting with a space/tab
         (?:
         (?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
         .*\n+
         )+
         )
         (\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
         /g,function(){...});
         */
    
        // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
        text += "~0";
    
        text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g, function(wholeMatch, m1, m2) {
          var codeblock = m1;
          var nextChar = m2;
    
          codeblock = _EncodeCode(_Outdent(codeblock));
          codeblock = _Detab(codeblock);
          codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
          codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    
          codeblock = "
    " + codeblock + "\n
    "; return hashBlock(codeblock) + nextChar; }); // attacklab: strip sentinel text = text.replace(/~0/, ""); return text; }; var hashBlock = function(text) { text = text.replace(/(^\n+|\n+$)/g, ""); return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; }; // // * Backtick quotes are used for spans. // // * You can use multiple backticks as the delimiters if you want to // include literal backticks in the code span. So, this input: // // Just type ``foo `bar` baz`` at the prompt. // // Will translate to: // //

    Just type foo `bar` baz at the prompt.

    // // There's no arbitrary limit to the number of backticks you // can use as delimters. If you need three consecutive backticks // in your code, use four for delimiters, etc. // // * You can use spaces to get literal backticks at the edges: // // ... type `` `bar` `` ... // // Turns to: // // ... type `bar` ... // var _DoCodeSpans = function(text) { /* text = text.replace(/ (^|[^\\]) // Character before opening ` can't be a backslash (`+) // $2 = Opening run of ` ( // $3 = The code block [^\r]*? [^`] // attacklab: work around lack of lookbehind ) \2 // Matching closer (?!`) /gm, function(){...}); */ text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, function(wholeMatch, m1, m2, m3, m4) { var c = m3; c = c.replace(/^([ \t]*)/g, ""); // leading whitespace c = c.replace(/[ \t]*$/g, ""); // trailing whitespace c = _EncodeCode(c); return m1 + "" + c + ""; }); return text; }; // // Encode/escape certain characters inside Markdown code runs. // The point is that in code, these characters are literals, // and lose their special Markdown meanings. // var _EncodeCode = function(text) { // Encode all ampersands; HTML entities are not // entities within a Markdown code span. text = text.replace(/&/g, "&"); // Do the angle bracket song and dance: text = text.replace(//g, ">"); // Now, escape characters that are magic in Markdown: text = escapeCharacters(text, "\*_{}[]\\", false); return text; }; var _DoItalicsAndBold = function(text) { // must go first: text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, "$2"); text = text.replace(/(\w)_(\w)/g, "$1~E95E$2"); // ** GFM ** "~E95E" == escaped "_" text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, "$2"); return text; }; var _DoBlockQuotes = function(text) { /* text = text.replace(/ ( // Wrap whole match in $1 ( ^[ \t]*>[ \t]? // '>' at the start of a line .+\n // rest of the first line (.+\n)* // subsequent consecutive lines \n* // blanks )+ ) /gm, function(){...}); */ text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, function(wholeMatch, m1) { var bq = m1; // attacklab: hack around Konqueror 3.5.4 bug: // "----------bug".replace(/^-/g,"") == "bug" bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting // attacklab: clean up hack bq = bq.replace(/~0/g, ""); bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines bq = _RunBlockGamut(bq); // recurse bq = bq.replace(/(^|\n)/g, "$1 "); // These leading spaces screw with
     content, so we need to fix that:
                  bq = bq.replace(
                          /(\s*
    [^\r]+?<\/pre>)/gm,
                          function(wholeMatch, m1) {
                            var pre = m1;
                            // attacklab: hack around Konqueror 3.5.4 bug:
                            pre = pre.replace(/^  /mg, "~0");
                            pre = pre.replace(/~0/g, "");
                            return pre;
                          });
    
                  return hashBlock("
    \n" + bq + "\n
    "); }); return text; }; // // Params: // $text - string to process with html

    tags // var _FormParagraphs = function(text) { // Strip leading and trailing lines: text = text.replace(/^\n+/g, ""); text = text.replace(/\n+$/g, ""); var grafs = text.split(/\n{2,}/g); var grafsOut = new Array(); // // Wrap

    tags. // var end = grafs.length; for (var i = 0; i < end; i++) { var str = grafs[i]; // if this is an HTML marker, copy it if (str.search(/~K(\d+)K/g) >= 0) { grafsOut.push(str); } else if (str.search(/\S/) >= 0) { str = _RunSpanGamut(str); str = str.replace(/\n/g, "
    "); // ** GFM ** str = str.replace(/^([ \t]*)/g, "

    "); str += "

    "; grafsOut.push(str); } } // // Unhashify HTML blocks // end = grafsOut.length; for (var i = 0; i < end; i++) { // if this is a marker for an html block... while (grafsOut[i].search(/~K(\d+)K/) >= 0) { var blockText = g_html_blocks[RegExp.$1]; blockText = blockText.replace(/\$/g, "$$$$"); // Escape any dollar signs grafsOut[i] = grafsOut[i].replace(/~K\d+K/, blockText); } } return grafsOut.join("\n\n"); }; // Smart processing for ampersands and angle brackets that need to be encoded. var _EncodeAmpsAndAngles = function(text) { // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: // http://bumppo.net/projects/amputator/ text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); // Encode naked <'s text = text.replace(/<(?![a-z\/?\$!])/gi, "<"); return text; }; // // Parameter: String. // Returns: The string, with after processing the following backslash // escape sequences. // var _EncodeBackslashEscapes = function(text) { // attacklab: The polite way to do this is with the new // escapeCharacters() function: // // text = escapeCharacters(text,"\\",true); // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); // // ...but we're sidestepping its use of the (slow) RegExp constructor // as an optimization for Firefox. This function gets called a LOT. text = text.replace(/\\(\\)/g, escapeCharacters_callback); text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); return text; }; var _DoAutoLinks = function(text) { text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi, "
    $1"); // Email addresses: /* text = text.replace(/ < (?:mailto:)? ( [-.\w]+ \@ [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ ) > /gi, _DoAutoLinks_callback()); */ text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, function(wholeMatch, m1) { return _EncodeEmailAddress(_UnescapeSpecialChars(m1)); }); return text; }; // // Input: an email address, e.g. "foo@example.com" // // Output: the email address as a mailto link, with each character // of the address encoded as either a decimal or hex entity, in // the hopes of foiling most address harvesting spam bots. E.g.: // // foo // @example.com // // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk // mailing list: // var _EncodeEmailAddress = function(addr) { // attacklab: why can't javascript speak hex? function char2hex(ch) { var hexDigits = '0123456789ABCDEF'; var dec = ch.charCodeAt(0); return(hexDigits.charAt(dec >> 4) + hexDigits.charAt(dec & 15)); } var encode = [ function(ch) { return "&#" + ch.charCodeAt(0) + ";" }, function(ch) { return "&#x" + char2hex(ch) + ";" }, function(ch) { return ch } ]; addr = "mailto:" + addr; addr = addr.replace(/./g, function(ch) { if (ch == "@") { // this *must* be encoded. I insist. ch = encode[Math.floor(Math.random() * 2)](ch); } else if (ch != ":") { // leave ':' alone (to spot mailto: later) var r = Math.random(); // roughly 10% raw, 45% hex, 45% dec ch = (r > .9 ? encode[2](ch) : r > .45 ? encode[1](ch) : encode[0](ch)); } return ch; }); addr = "" + addr + ""; addr = addr.replace(/">.+:/g, "\">"); // strip the mailto: from the visible part return addr; }; // // Swap back in all the special characters we've hidden. // var _UnescapeSpecialChars = function(text) { text = text.replace(/~E(\d+)E/g, function(wholeMatch, m1) { var charCodeToReplace = parseInt(m1); return String.fromCharCode(charCodeToReplace); }); return text; }; // // Remove one level of line-leading tabs or spaces // var _Outdent = function(text) { // attacklab: hack around Konqueror 3.5.4 bug: // "----------bug".replace(/^-/g,"") == "bug" text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width // attacklab: clean up hack text = text.replace(/~0/g, ""); return text; }; // attacklab: Detab's completely rewritten for speed. // In perl we could fix it by anchoring the regexp with \G. // In javascript we're less fortunate. var _Detab = function(text) { // expand first n-1 tabs text = text.replace(/\t(?=\t)/g, " "); // attacklab: g_tab_width // replace the nth with two sentinels text = text.replace(/\t/g, "~A~B"); // use the sentinel to anchor our regex so it doesn't explode text = text.replace(/~B(.+?)~A/g, function(wholeMatch, m1, m2) { var leadingText = m1; var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width // there *must* be a better way to do this: for (var i = 0; i < numSpaces; i++) leadingText += " "; return leadingText; }); // clean up sentinels text = text.replace(/~A/g, " "); // attacklab: g_tab_width text = text.replace(/~B/g, ""); return text; }; // attacklab: Utility functions var escapeCharacters = function(text, charsToEscape, afterBackslash) { // First we have to escape the escape characters so that // we can build a character class out of them var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; if (afterBackslash) { regexString = "\\\\" + regexString; } var regex = new RegExp(regexString, "g"); text = text.replace(regex, escapeCharacters_callback); return text; }; var escapeCharacters_callback = function(wholeMatch, m1) { var charCodeToEscape = m1.charCodeAt(0); return "~E" + charCodeToEscape + "E"; }; }; (function() { var __slice = [].slice; this.Mercury || (this.Mercury = {}); jQuery.extend(this.Mercury, { version: '0.9.0', Regions: Mercury.Regions || {}, modalHandlers: Mercury.modalHandlers || {}, lightviewHandlers: Mercury.lightviewHandlers || {}, dialogHandlers: Mercury.dialogHandlers || {}, preloadedViews: Mercury.preloadedViews || {}, ajaxHeaders: function() { var headers; headers = {}; headers[Mercury.config.csrfHeader] = Mercury.csrfToken; return headers; }, on: function(eventName, callback) { return jQuery(window).on("mercury:" + eventName, callback); }, off: function(eventName, callback) { return jQuery(window).off("mercury:" + eventName, callback); }, one: function(eventName, callback) { return jQuery(window).one("mercury:" + eventName, callback); }, trigger: function(eventName, options) { Mercury.log(eventName, options); return jQuery(window).trigger("mercury:" + eventName, options); }, notify: function() { var args; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; return window.alert(Mercury.I18n.apply(this, args)); }, warn: function(message, severity) { if (severity == null) { severity = 0; } if (console) { try { return console.warn(message); } catch (e1) { if (severity >= 1) { try { return console.debug(message); } catch (e2) { } } } } else if (severity >= 1) { return Mercury.notify(message); } }, deprecated: function(message) { if (console && console.trace) { message = "" + message + " -- " + (console.trace()); } return Mercury.warn("deprecated: " + message, 1); }, log: function() { if (Mercury.debug && console) { if (arguments[0] === 'hide:toolbar' || arguments[0] === 'show:toolbar') { return; } try { return console.debug(arguments); } catch (e) { } } }, locale: function() { var locale, subLocale, topLocale; if (Mercury.determinedLocale) { return Mercury.determinedLocale; } if (Mercury.config.localization.enabled) { locale = []; if (navigator.language && (locale = navigator.language.toString().split('-')).length) { topLocale = Mercury.I18n[locale[0]] || {}; subLocale = locale.length > 1 ? topLocale["_" + (locale[1].toUpperCase()) + "_"] : void 0; } if (!Mercury.I18n[locale[0]]) { locale = Mercury.config.localization.preferredLocale.split('-'); topLocale = Mercury.I18n[locale[0]] || {}; subLocale = locale.length > 1 ? topLocale["_" + (locale[1].toUpperCase()) + "_"] : void 0; } } return Mercury.determinedLocale = { top: topLocale || {}, sub: subLocale || {} }; }, I18n: function() { var args, locale, sourceString, translation; sourceString = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; locale = Mercury.locale(); translation = (locale.sub[sourceString] || locale.top[sourceString] || sourceString || '').toString(); if (args.length) { return translation.printf.apply(translation, args); } else { return translation; } } }); }).call(this); (function() { String.prototype.titleize = function() { return this[0].toUpperCase() + this.slice(1); }; String.prototype.toHex = function() { if (this[0] === '#') { return this; } return this.replace(/rgb(a)?\(([0-9|%]+)[\s|,]?\s?([0-9|%]+)[\s|,]?\s?([0-9|%]+)[\s|,]?\s?([0-9|.|%]+\s?)?\)/gi, function(x, alpha, r, g, b, a) { return "#" + (parseInt(r).toHex()) + (parseInt(g).toHex()) + (parseInt(b).toHex()); }); }; String.prototype.regExpEscape = function() { var escaped, specials; specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']; escaped = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); return this.replace(escaped, '\\$1'); }; String.prototype.printf = function() { var arg, chunk, chunks, index, offset, p, re, result, _i, _len; chunks = this.split('%'); result = chunks[0]; re = /^([sdf])([\s\S%]*)$/; offset = 0; for (index = _i = 0, _len = chunks.length; _i < _len; index = ++_i) { chunk = chunks[index]; p = re.exec(chunk); if (index === 0 || !p || arguments[index] === null) { if (index > 1) { offset += 2; result += "%" + chunk; } continue; } arg = arguments[(index - 1) - offset]; switch (p[1]) { case 's': result += arg; break; case 'd': case 'i': result += parseInt(arg.toString(), 10); break; case 'f': result += parseFloat(arg); } result += p[2]; } return result; }; Number.prototype.toHex = function() { var result; result = this.toString(16).toUpperCase(); if (result[1]) { return result; } else { return "0" + result; } }; Number.prototype.toBytes = function() { var bytes, i; bytes = parseInt(this); i = 0; while (1023 < bytes) { bytes /= 1024; i += 1; } if (i) { return "" + (bytes.toFixed(2)) + ['', ' kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb'][i]; } else { return "" + bytes + " bytes"; } }; }).call(this); (function() { this.Mercury.PageEditor = (function() { function PageEditor(saveUrl, options) { var token; this.saveUrl = saveUrl != null ? saveUrl : null; this.options = options != null ? options : {}; if (window.mercuryInstance) { throw Mercury.I18n('Mercury.PageEditor can only be instantiated once.'); } if (!(this.options.visible === false || this.options.visible === 'false')) { this.options.visible = true; } this.visible = this.options.visible; if (!(this.options.saveDataType === false || this.options.saveDataType)) { this.options.saveDataType = 'json'; } window.mercuryInstance = this; this.regions = []; this.initializeInterface(); if (token = jQuery(Mercury.config.csrfSelector).attr('content')) { Mercury.csrfToken = token; } } PageEditor.prototype.initializeInterface = function() { var _ref, _ref1, _this = this; this.focusableElement = jQuery('', { "class": 'mercury-focusable', type: 'text' }).appendTo((_ref = this.options.appendTo) != null ? _ref : 'body'); this.iframe = jQuery('