rails_medium_editor_insert_plugin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +86 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +139 -0
  10. data/Rakefile +6 -0
  11. data/app/assets/javascripts/medium-editor-js.js +1 -0
  12. data/app/assets/stylesheets/medium-editor-style.scss +3 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/rails_medium_editor_insert_plugin.rb +7 -0
  16. data/lib/rails_medium_editor_insert_plugin/version.rb +3 -0
  17. data/rails_medium_editor_insert_plugin.gemspec +31 -0
  18. data/vendor/assets/javascripts/medium_editor_js/handlebars.js +29 -0
  19. data/vendor/assets/javascripts/medium_editor_js/jquery-fileupload.js +1477 -0
  20. data/vendor/assets/javascripts/medium_editor_js/jquery-iframe-transport.js +217 -0
  21. data/vendor/assets/javascripts/medium_editor_js/jquery-sortable.js +693 -0
  22. data/vendor/assets/javascripts/medium_editor_js/jquery-ui-widget.js +572 -0
  23. data/vendor/assets/javascripts/medium_editor_js/medium-editor-insert-plugin.js +2091 -0
  24. data/vendor/assets/javascripts/medium_editor_js/medium-editor.js +7054 -0
  25. data/vendor/assets/javascripts/rails-medium-editor-insert-plugin.js +7 -0
  26. data/vendor/assets/stylesheets/medium-editor.scss +3 -0
  27. data/vendor/assets/stylesheets/medium_editor_style/_flat.scss +62 -0
  28. data/vendor/assets/stylesheets/medium_editor_style/_medium-editor.scss +182 -0
  29. data/vendor/assets/stylesheets/medium_editor_style/medium-editor-insert-plugin-frontend.scss +72 -0
  30. data/vendor/assets/stylesheets/medium_editor_style/medium-editor-insert-plugin.scss +209 -0
  31. data/vendor/assets/stylesheets/medium_editor_style/medium-editor-style.scss +4 -0
  32. metadata +130 -0
@@ -0,0 +1,217 @@
1
+ /*
2
+ * jQuery Iframe Transport Plugin
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2011, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* global define, require, window, document */
13
+
14
+ (function (factory) {
15
+ 'use strict';
16
+ if (typeof define === 'function' && define.amd) {
17
+ // Register as an anonymous AMD module:
18
+ define(['jquery'], factory);
19
+ } else if (typeof exports === 'object') {
20
+ // Node/CommonJS:
21
+ factory(require('jquery'));
22
+ } else {
23
+ // Browser globals:
24
+ factory(window.jQuery);
25
+ }
26
+ }(function ($) {
27
+ 'use strict';
28
+
29
+ // Helper variable to create unique names for the transport iframes:
30
+ var counter = 0;
31
+
32
+ // The iframe transport accepts four additional options:
33
+ // options.fileInput: a jQuery collection of file input fields
34
+ // options.paramName: the parameter name for the file form data,
35
+ // overrides the name property of the file input field(s),
36
+ // can be a string or an array of strings.
37
+ // options.formData: an array of objects with name and value properties,
38
+ // equivalent to the return data of .serializeArray(), e.g.:
39
+ // [{name: 'a', value: 1}, {name: 'b', value: 2}]
40
+ // options.initialIframeSrc: the URL of the initial iframe src,
41
+ // by default set to "javascript:false;"
42
+ $.ajaxTransport('iframe', function (options) {
43
+ if (options.async) {
44
+ // javascript:false as initial iframe src
45
+ // prevents warning popups on HTTPS in IE6:
46
+ /*jshint scripturl: true */
47
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
48
+ /*jshint scripturl: false */
49
+ form,
50
+ iframe,
51
+ addParamChar;
52
+ return {
53
+ send: function (_, completeCallback) {
54
+ form = $('<form style="display:none;"></form>');
55
+ form.attr('accept-charset', options.formAcceptCharset);
56
+ addParamChar = /\?/.test(options.url) ? '&' : '?';
57
+ // XDomainRequest only supports GET and POST:
58
+ if (options.type === 'DELETE') {
59
+ options.url = options.url + addParamChar + '_method=DELETE';
60
+ options.type = 'POST';
61
+ } else if (options.type === 'PUT') {
62
+ options.url = options.url + addParamChar + '_method=PUT';
63
+ options.type = 'POST';
64
+ } else if (options.type === 'PATCH') {
65
+ options.url = options.url + addParamChar + '_method=PATCH';
66
+ options.type = 'POST';
67
+ }
68
+ // IE versions below IE8 cannot set the name property of
69
+ // elements that have already been added to the DOM,
70
+ // so we set the name along with the iframe HTML markup:
71
+ counter += 1;
72
+ iframe = $(
73
+ '<iframe src="' + initialIframeSrc +
74
+ '" name="iframe-transport-' + counter + '"></iframe>'
75
+ ).bind('load', function () {
76
+ var fileInputClones,
77
+ paramNames = $.isArray(options.paramName) ?
78
+ options.paramName : [options.paramName];
79
+ iframe
80
+ .unbind('load')
81
+ .bind('load', function () {
82
+ var response;
83
+ // Wrap in a try/catch block to catch exceptions thrown
84
+ // when trying to access cross-domain iframe contents:
85
+ try {
86
+ response = iframe.contents();
87
+ // Google Chrome and Firefox do not throw an
88
+ // exception when calling iframe.contents() on
89
+ // cross-domain requests, so we unify the response:
90
+ if (!response.length || !response[0].firstChild) {
91
+ throw new Error();
92
+ }
93
+ } catch (e) {
94
+ response = undefined;
95
+ }
96
+ // The complete callback returns the
97
+ // iframe content document as response object:
98
+ completeCallback(
99
+ 200,
100
+ 'success',
101
+ {'iframe': response}
102
+ );
103
+ // Fix for IE endless progress bar activity bug
104
+ // (happens on form submits to iframe targets):
105
+ $('<iframe src="' + initialIframeSrc + '"></iframe>')
106
+ .appendTo(form);
107
+ window.setTimeout(function () {
108
+ // Removing the form in a setTimeout call
109
+ // allows Chrome's developer tools to display
110
+ // the response result
111
+ form.remove();
112
+ }, 0);
113
+ });
114
+ form
115
+ .prop('target', iframe.prop('name'))
116
+ .prop('action', options.url)
117
+ .prop('method', options.type);
118
+ if (options.formData) {
119
+ $.each(options.formData, function (index, field) {
120
+ $('<input type="hidden"/>')
121
+ .prop('name', field.name)
122
+ .val(field.value)
123
+ .appendTo(form);
124
+ });
125
+ }
126
+ if (options.fileInput && options.fileInput.length &&
127
+ options.type === 'POST') {
128
+ fileInputClones = options.fileInput.clone();
129
+ // Insert a clone for each file input field:
130
+ options.fileInput.after(function (index) {
131
+ return fileInputClones[index];
132
+ });
133
+ if (options.paramName) {
134
+ options.fileInput.each(function (index) {
135
+ $(this).prop(
136
+ 'name',
137
+ paramNames[index] || options.paramName
138
+ );
139
+ });
140
+ }
141
+ // Appending the file input fields to the hidden form
142
+ // removes them from their original location:
143
+ form
144
+ .append(options.fileInput)
145
+ .prop('enctype', 'multipart/form-data')
146
+ // enctype must be set as encoding for IE:
147
+ .prop('encoding', 'multipart/form-data');
148
+ // Remove the HTML5 form attribute from the input(s):
149
+ options.fileInput.removeAttr('form');
150
+ }
151
+ form.submit();
152
+ // Insert the file input fields at their original location
153
+ // by replacing the clones with the originals:
154
+ if (fileInputClones && fileInputClones.length) {
155
+ options.fileInput.each(function (index, input) {
156
+ var clone = $(fileInputClones[index]);
157
+ // Restore the original name and form properties:
158
+ $(input)
159
+ .prop('name', clone.prop('name'))
160
+ .attr('form', clone.attr('form'));
161
+ clone.replaceWith(input);
162
+ });
163
+ }
164
+ });
165
+ form.append(iframe).appendTo(document.body);
166
+ },
167
+ abort: function () {
168
+ if (iframe) {
169
+ // javascript:false as iframe src aborts the request
170
+ // and prevents warning popups on HTTPS in IE6.
171
+ // concat is used to avoid the "Script URL" JSLint error:
172
+ iframe
173
+ .unbind('load')
174
+ .prop('src', initialIframeSrc);
175
+ }
176
+ if (form) {
177
+ form.remove();
178
+ }
179
+ }
180
+ };
181
+ }
182
+ });
183
+
184
+ // The iframe transport returns the iframe content document as response.
185
+ // The following adds converters from iframe to text, json, html, xml
186
+ // and script.
187
+ // Please note that the Content-Type for JSON responses has to be text/plain
188
+ // or text/html, if the browser doesn't include application/json in the
189
+ // Accept header, else IE will show a download dialog.
190
+ // The Content-Type for XML responses on the other hand has to be always
191
+ // application/xml or text/xml, so IE properly parses the XML response.
192
+ // See also
193
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
194
+ $.ajaxSetup({
195
+ converters: {
196
+ 'iframe text': function (iframe) {
197
+ return iframe && $(iframe[0].body).text();
198
+ },
199
+ 'iframe json': function (iframe) {
200
+ return iframe && $.parseJSON($(iframe[0].body).text());
201
+ },
202
+ 'iframe html': function (iframe) {
203
+ return iframe && $(iframe[0].body).html();
204
+ },
205
+ 'iframe xml': function (iframe) {
206
+ var xmlDoc = iframe && iframe[0];
207
+ return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
208
+ $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
209
+ $(xmlDoc.body).html());
210
+ },
211
+ 'iframe script': function (iframe) {
212
+ return iframe && $.globalEval($(iframe[0].body).text());
213
+ }
214
+ }
215
+ });
216
+
217
+ }));
@@ -0,0 +1,693 @@
1
+ /* ===================================================
2
+ * jquery-sortable.js v0.9.13
3
+ * http://johnny.github.com/jquery-sortable/
4
+ * ===================================================
5
+ * Copyright (c) 2012 Jonas von Andrian
6
+ * All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without
9
+ * modification, are permitted provided that the following conditions are met:
10
+ * * Redistributions of source code must retain the above copyright
11
+ * notice, this list of conditions and the following disclaimer.
12
+ * * Redistributions in binary form must reproduce the above copyright
13
+ * notice, this list of conditions and the following disclaimer in the
14
+ * documentation and/or other materials provided with the distribution.
15
+ * * The name of the author may not be used to endorse or promote products
16
+ * derived from this software without specific prior written permission.
17
+ *
18
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+ * ========================================================== */
29
+
30
+
31
+ !function ( $, window, pluginName, undefined){
32
+ var containerDefaults = {
33
+ // If true, items can be dragged from this container
34
+ drag: true,
35
+ // If true, items can be droped onto this container
36
+ drop: true,
37
+ // Exclude items from being draggable, if the
38
+ // selector matches the item
39
+ exclude: "",
40
+ // If true, search for nested containers within an item.If you nest containers,
41
+ // either the original selector with which you call the plugin must only match the top containers,
42
+ // or you need to specify a group (see the bootstrap nav example)
43
+ nested: true,
44
+ // If true, the items are assumed to be arranged vertically
45
+ vertical: true
46
+ }, // end container defaults
47
+ groupDefaults = {
48
+ // This is executed after the placeholder has been moved.
49
+ // $closestItemOrContainer contains the closest item, the placeholder
50
+ // has been put at or the closest empty Container, the placeholder has
51
+ // been appended to.
52
+ afterMove: function ($placeholder, container, $closestItemOrContainer) {
53
+ },
54
+ // The exact css path between the container and its items, e.g. "> tbody"
55
+ containerPath: "",
56
+ // The css selector of the containers
57
+ containerSelector: "ol, ul",
58
+ // Distance the mouse has to travel to start dragging
59
+ distance: 0,
60
+ // Time in milliseconds after mousedown until dragging should start.
61
+ // This option can be used to prevent unwanted drags when clicking on an element.
62
+ delay: 0,
63
+ // The css selector of the drag handle
64
+ handle: "",
65
+ // The exact css path between the item and its subcontainers.
66
+ // It should only match the immediate items of a container.
67
+ // No item of a subcontainer should be matched. E.g. for ol>div>li the itemPath is "> div"
68
+ itemPath: "",
69
+ // The css selector of the items
70
+ itemSelector: "li",
71
+ // The class given to "body" while an item is being dragged
72
+ bodyClass: "dragging",
73
+ // The class giving to an item while being dragged
74
+ draggedClass: "dragged",
75
+ // Check if the dragged item may be inside the container.
76
+ // Use with care, since the search for a valid container entails a depth first search
77
+ // and may be quite expensive.
78
+ isValidTarget: function ($item, container) {
79
+ return true
80
+ },
81
+ // Executed before onDrop if placeholder is detached.
82
+ // This happens if pullPlaceholder is set to false and the drop occurs outside a container.
83
+ onCancel: function ($item, container, _super, event) {
84
+ },
85
+ // Executed at the beginning of a mouse move event.
86
+ // The Placeholder has not been moved yet.
87
+ onDrag: function ($item, position, _super, event) {
88
+ $item.css(position)
89
+ },
90
+ // Called after the drag has been started,
91
+ // that is the mouse button is being held down and
92
+ // the mouse is moving.
93
+ // The container is the closest initialized container.
94
+ // Therefore it might not be the container, that actually contains the item.
95
+ onDragStart: function ($item, container, _super, event) {
96
+ $item.css({
97
+ height: $item.outerHeight(),
98
+ width: $item.outerWidth()
99
+ })
100
+ $item.addClass(container.group.options.draggedClass)
101
+ $("body").addClass(container.group.options.bodyClass)
102
+ },
103
+ // Called when the mouse button is being released
104
+ onDrop: function ($item, container, _super, event) {
105
+ $item.removeClass(container.group.options.draggedClass).removeAttr("style")
106
+ $("body").removeClass(container.group.options.bodyClass)
107
+ },
108
+ // Called on mousedown. If falsy value is returned, the dragging will not start.
109
+ // Ignore if element clicked is input, select or textarea
110
+ onMousedown: function ($item, _super, event) {
111
+ if (!event.target.nodeName.match(/^(input|select|textarea)$/i)) {
112
+ event.preventDefault()
113
+ return true
114
+ }
115
+ },
116
+ // The class of the placeholder (must match placeholder option markup)
117
+ placeholderClass: "placeholder",
118
+ // Template for the placeholder. Can be any valid jQuery input
119
+ // e.g. a string, a DOM element.
120
+ // The placeholder must have the class "placeholder"
121
+ placeholder: '<li class="placeholder"></li>',
122
+ // If true, the position of the placeholder is calculated on every mousemove.
123
+ // If false, it is only calculated when the mouse is above a container.
124
+ pullPlaceholder: true,
125
+ // Specifies serialization of the container group.
126
+ // The pair $parent/$children is either container/items or item/subcontainers.
127
+ serialize: function ($parent, $children, parentIsContainer) {
128
+ var result = $.extend({}, $parent.data())
129
+
130
+ if(parentIsContainer)
131
+ return [$children]
132
+ else if ($children[0]){
133
+ result.children = $children
134
+ }
135
+
136
+ delete result.subContainers
137
+ delete result.sortable
138
+
139
+ return result
140
+ },
141
+ // Set tolerance while dragging. Positive values decrease sensitivity,
142
+ // negative values increase it.
143
+ tolerance: 0
144
+ }, // end group defaults
145
+ containerGroups = {},
146
+ groupCounter = 0,
147
+ emptyBox = {
148
+ left: 0,
149
+ top: 0,
150
+ bottom: 0,
151
+ right:0
152
+ },
153
+ eventNames = {
154
+ start: "touchstart.sortable mousedown.sortable",
155
+ drop: "touchend.sortable touchcancel.sortable mouseup.sortable",
156
+ drag: "touchmove.sortable mousemove.sortable",
157
+ scroll: "scroll.sortable"
158
+ },
159
+ subContainerKey = "subContainers"
160
+
161
+ /*
162
+ * a is Array [left, right, top, bottom]
163
+ * b is array [left, top]
164
+ */
165
+ function d(a,b) {
166
+ var x = Math.max(0, a[0] - b[0], b[0] - a[1]),
167
+ y = Math.max(0, a[2] - b[1], b[1] - a[3])
168
+ return x+y;
169
+ }
170
+
171
+ function setDimensions(array, dimensions, tolerance, useOffset) {
172
+ var i = array.length,
173
+ offsetMethod = useOffset ? "offset" : "position"
174
+ tolerance = tolerance || 0
175
+
176
+ while(i--){
177
+ var el = array[i].el ? array[i].el : $(array[i]),
178
+ // use fitting method
179
+ pos = el[offsetMethod]()
180
+ pos.left += parseInt(el.css('margin-left'), 10)
181
+ pos.top += parseInt(el.css('margin-top'),10)
182
+ dimensions[i] = [
183
+ pos.left - tolerance,
184
+ pos.left + el.outerWidth() + tolerance,
185
+ pos.top - tolerance,
186
+ pos.top + el.outerHeight() + tolerance
187
+ ]
188
+ }
189
+ }
190
+
191
+ function getRelativePosition(pointer, element) {
192
+ var offset = element.offset()
193
+ return {
194
+ left: pointer.left - offset.left,
195
+ top: pointer.top - offset.top
196
+ }
197
+ }
198
+
199
+ function sortByDistanceDesc(dimensions, pointer, lastPointer) {
200
+ pointer = [pointer.left, pointer.top]
201
+ lastPointer = lastPointer && [lastPointer.left, lastPointer.top]
202
+
203
+ var dim,
204
+ i = dimensions.length,
205
+ distances = []
206
+
207
+ while(i--){
208
+ dim = dimensions[i]
209
+ distances[i] = [i,d(dim,pointer), lastPointer && d(dim, lastPointer)]
210
+ }
211
+ distances = distances.sort(function (a,b) {
212
+ return b[1] - a[1] || b[2] - a[2] || b[0] - a[0]
213
+ })
214
+
215
+ // last entry is the closest
216
+ return distances
217
+ }
218
+
219
+ function ContainerGroup(options) {
220
+ this.options = $.extend({}, groupDefaults, options)
221
+ this.containers = []
222
+
223
+ if(!this.options.rootGroup){
224
+ this.scrollProxy = $.proxy(this.scroll, this)
225
+ this.dragProxy = $.proxy(this.drag, this)
226
+ this.dropProxy = $.proxy(this.drop, this)
227
+ this.placeholder = $(this.options.placeholder)
228
+
229
+ if(!options.isValidTarget)
230
+ this.options.isValidTarget = undefined
231
+ }
232
+ }
233
+
234
+ ContainerGroup.get = function (options) {
235
+ if(!containerGroups[options.group]) {
236
+ if(options.group === undefined)
237
+ options.group = groupCounter ++
238
+
239
+ containerGroups[options.group] = new ContainerGroup(options)
240
+ }
241
+
242
+ return containerGroups[options.group]
243
+ }
244
+
245
+ ContainerGroup.prototype = {
246
+ dragInit: function (e, itemContainer) {
247
+ this.$document = $(itemContainer.el[0].ownerDocument)
248
+
249
+ // get item to drag
250
+ var closestItem = $(e.target).closest(this.options.itemSelector);
251
+ // using the length of this item, prevents the plugin from being started if there is no handle being clicked on.
252
+ // this may also be helpful in instantiating multidrag.
253
+ if (closestItem.length) {
254
+ this.item = closestItem;
255
+ this.itemContainer = itemContainer;
256
+ if (this.item.is(this.options.exclude) || !this.options.onMousedown(this.item, groupDefaults.onMousedown, e)) {
257
+ return;
258
+ }
259
+ this.setPointer(e);
260
+ this.toggleListeners('on');
261
+ this.setupDelayTimer();
262
+ this.dragInitDone = true;
263
+ }
264
+ },
265
+ drag: function (e) {
266
+ if(!this.dragging){
267
+ if(!this.distanceMet(e) || !this.delayMet)
268
+ return
269
+
270
+ this.options.onDragStart(this.item, this.itemContainer, groupDefaults.onDragStart, e)
271
+ this.item.before(this.placeholder)
272
+ this.dragging = true
273
+ }
274
+
275
+ this.setPointer(e)
276
+ // place item under the cursor
277
+ this.options.onDrag(this.item,
278
+ getRelativePosition(this.pointer, this.item.offsetParent()),
279
+ groupDefaults.onDrag,
280
+ e)
281
+
282
+ var p = this.getPointer(e),
283
+ box = this.sameResultBox,
284
+ t = this.options.tolerance
285
+
286
+ if(!box || box.top - t > p.top || box.bottom + t < p.top || box.left - t > p.left || box.right + t < p.left)
287
+ if(!this.searchValidTarget()){
288
+ this.placeholder.detach()
289
+ this.lastAppendedItem = undefined
290
+ }
291
+ },
292
+ drop: function (e) {
293
+ this.toggleListeners('off')
294
+
295
+ this.dragInitDone = false
296
+
297
+ if(this.dragging){
298
+ // processing Drop, check if placeholder is detached
299
+ if(this.placeholder.closest("html")[0]){
300
+ this.placeholder.before(this.item).detach()
301
+ } else {
302
+ this.options.onCancel(this.item, this.itemContainer, groupDefaults.onCancel, e)
303
+ }
304
+ this.options.onDrop(this.item, this.getContainer(this.item), groupDefaults.onDrop, e)
305
+
306
+ // cleanup
307
+ this.clearDimensions()
308
+ this.clearOffsetParent()
309
+ this.lastAppendedItem = this.sameResultBox = undefined
310
+ this.dragging = false
311
+ }
312
+ },
313
+ searchValidTarget: function (pointer, lastPointer) {
314
+ if(!pointer){
315
+ pointer = this.relativePointer || this.pointer
316
+ lastPointer = this.lastRelativePointer || this.lastPointer
317
+ }
318
+
319
+ var distances = sortByDistanceDesc(this.getContainerDimensions(),
320
+ pointer,
321
+ lastPointer),
322
+ i = distances.length
323
+
324
+ while(i--){
325
+ var index = distances[i][0],
326
+ distance = distances[i][1]
327
+
328
+ if(!distance || this.options.pullPlaceholder){
329
+ var container = this.containers[index]
330
+ if(!container.disabled){
331
+ if(!this.$getOffsetParent()){
332
+ var offsetParent = container.getItemOffsetParent()
333
+ pointer = getRelativePosition(pointer, offsetParent)
334
+ lastPointer = getRelativePosition(lastPointer, offsetParent)
335
+ }
336
+ if(container.searchValidTarget(pointer, lastPointer))
337
+ return true
338
+ }
339
+ }
340
+ }
341
+ if(this.sameResultBox)
342
+ this.sameResultBox = undefined
343
+ },
344
+ movePlaceholder: function (container, item, method, sameResultBox) {
345
+ var lastAppendedItem = this.lastAppendedItem
346
+ if(!sameResultBox && lastAppendedItem && lastAppendedItem[0] === item[0])
347
+ return;
348
+
349
+ item[method](this.placeholder)
350
+ this.lastAppendedItem = item
351
+ this.sameResultBox = sameResultBox
352
+ this.options.afterMove(this.placeholder, container, item)
353
+ },
354
+ getContainerDimensions: function () {
355
+ if(!this.containerDimensions)
356
+ setDimensions(this.containers, this.containerDimensions = [], this.options.tolerance, !this.$getOffsetParent())
357
+ return this.containerDimensions
358
+ },
359
+ getContainer: function (element) {
360
+ return element.closest(this.options.containerSelector).data(pluginName)
361
+ },
362
+ $getOffsetParent: function () {
363
+ if(this.offsetParent === undefined){
364
+ var i = this.containers.length - 1,
365
+ offsetParent = this.containers[i].getItemOffsetParent()
366
+
367
+ if(!this.options.rootGroup){
368
+ while(i--){
369
+ if(offsetParent[0] != this.containers[i].getItemOffsetParent()[0]){
370
+ // If every container has the same offset parent,
371
+ // use position() which is relative to this parent,
372
+ // otherwise use offset()
373
+ // compare #setDimensions
374
+ offsetParent = false
375
+ break;
376
+ }
377
+ }
378
+ }
379
+
380
+ this.offsetParent = offsetParent
381
+ }
382
+ return this.offsetParent
383
+ },
384
+ setPointer: function (e) {
385
+ var pointer = this.getPointer(e)
386
+
387
+ if(this.$getOffsetParent()){
388
+ var relativePointer = getRelativePosition(pointer, this.$getOffsetParent())
389
+ this.lastRelativePointer = this.relativePointer
390
+ this.relativePointer = relativePointer
391
+ }
392
+
393
+ this.lastPointer = this.pointer
394
+ this.pointer = pointer
395
+ },
396
+ distanceMet: function (e) {
397
+ var currentPointer = this.getPointer(e)
398
+ return (Math.max(
399
+ Math.abs(this.pointer.left - currentPointer.left),
400
+ Math.abs(this.pointer.top - currentPointer.top)
401
+ ) >= this.options.distance)
402
+ },
403
+ getPointer: function(e) {
404
+ var o = e.originalEvent || e.originalEvent.touches && e.originalEvent.touches[0]
405
+ return {
406
+ left: e.pageX || o.pageX,
407
+ top: e.pageY || o.pageY
408
+ }
409
+ },
410
+ setupDelayTimer: function () {
411
+ var that = this
412
+ this.delayMet = !this.options.delay
413
+
414
+ // init delay timer if needed
415
+ if (!this.delayMet) {
416
+ clearTimeout(this._mouseDelayTimer);
417
+ this._mouseDelayTimer = setTimeout(function() {
418
+ that.delayMet = true
419
+ }, this.options.delay)
420
+ }
421
+ },
422
+ scroll: function (e) {
423
+ this.clearDimensions()
424
+ this.clearOffsetParent() // TODO is this needed?
425
+ },
426
+ toggleListeners: function (method) {
427
+ var that = this,
428
+ events = ['drag','drop','scroll']
429
+
430
+ $.each(events,function (i,event) {
431
+ that.$document[method](eventNames[event], that[event + 'Proxy'])
432
+ })
433
+ },
434
+ clearOffsetParent: function () {
435
+ this.offsetParent = undefined
436
+ },
437
+ // Recursively clear container and item dimensions
438
+ clearDimensions: function () {
439
+ this.traverse(function(object){
440
+ object._clearDimensions()
441
+ })
442
+ },
443
+ traverse: function(callback) {
444
+ callback(this)
445
+ var i = this.containers.length
446
+ while(i--){
447
+ this.containers[i].traverse(callback)
448
+ }
449
+ },
450
+ _clearDimensions: function(){
451
+ this.containerDimensions = undefined
452
+ },
453
+ _destroy: function () {
454
+ containerGroups[this.options.group] = undefined
455
+ }
456
+ }
457
+
458
+ function Container(element, options) {
459
+ this.el = element
460
+ this.options = $.extend( {}, containerDefaults, options)
461
+
462
+ this.group = ContainerGroup.get(this.options)
463
+ this.rootGroup = this.options.rootGroup || this.group
464
+ this.handle = this.rootGroup.options.handle || this.rootGroup.options.itemSelector
465
+
466
+ var itemPath = this.rootGroup.options.itemPath
467
+ this.target = itemPath ? this.el.find(itemPath) : this.el
468
+
469
+ this.target.on(eventNames.start, this.handle, $.proxy(this.dragInit, this))
470
+
471
+ if(this.options.drop)
472
+ this.group.containers.push(this)
473
+ }
474
+
475
+ Container.prototype = {
476
+ dragInit: function (e) {
477
+ var rootGroup = this.rootGroup
478
+
479
+ if( !this.disabled &&
480
+ !rootGroup.dragInitDone &&
481
+ this.options.drag &&
482
+ this.isValidDrag(e)) {
483
+ rootGroup.dragInit(e, this)
484
+ }
485
+ },
486
+ isValidDrag: function(e) {
487
+ return e.which == 1 ||
488
+ e.type == "touchstart" && e.originalEvent.touches.length == 1
489
+ },
490
+ searchValidTarget: function (pointer, lastPointer) {
491
+ var distances = sortByDistanceDesc(this.getItemDimensions(),
492
+ pointer,
493
+ lastPointer),
494
+ i = distances.length,
495
+ rootGroup = this.rootGroup,
496
+ validTarget = !rootGroup.options.isValidTarget ||
497
+ rootGroup.options.isValidTarget(rootGroup.item, this)
498
+
499
+ if(!i && validTarget){
500
+ rootGroup.movePlaceholder(this, this.target, "append")
501
+ return true
502
+ } else
503
+ while(i--){
504
+ var index = distances[i][0],
505
+ distance = distances[i][1]
506
+ if(!distance && this.hasChildGroup(index)){
507
+ var found = this.getContainerGroup(index).searchValidTarget(pointer, lastPointer)
508
+ if(found)
509
+ return true
510
+ }
511
+ else if(validTarget){
512
+ this.movePlaceholder(index, pointer)
513
+ return true
514
+ }
515
+ }
516
+ },
517
+ movePlaceholder: function (index, pointer) {
518
+ var item = $(this.items[index]),
519
+ dim = this.itemDimensions[index],
520
+ method = "after",
521
+ width = item.outerWidth(),
522
+ height = item.outerHeight(),
523
+ offset = item.offset(),
524
+ sameResultBox = {
525
+ left: offset.left,
526
+ right: offset.left + width,
527
+ top: offset.top,
528
+ bottom: offset.top + height
529
+ }
530
+ if(this.options.vertical){
531
+ var yCenter = (dim[2] + dim[3]) / 2,
532
+ inUpperHalf = pointer.top <= yCenter
533
+ if(inUpperHalf){
534
+ method = "before"
535
+ sameResultBox.bottom -= height / 2
536
+ } else
537
+ sameResultBox.top += height / 2
538
+ } else {
539
+ var xCenter = (dim[0] + dim[1]) / 2,
540
+ inLeftHalf = pointer.left <= xCenter
541
+ if(inLeftHalf){
542
+ method = "before"
543
+ sameResultBox.right -= width / 2
544
+ } else
545
+ sameResultBox.left += width / 2
546
+ }
547
+ if(this.hasChildGroup(index))
548
+ sameResultBox = emptyBox
549
+ this.rootGroup.movePlaceholder(this, item, method, sameResultBox)
550
+ },
551
+ getItemDimensions: function () {
552
+ if(!this.itemDimensions){
553
+ this.items = this.$getChildren(this.el, "item").filter(
554
+ ":not(." + this.group.options.placeholderClass + ", ." + this.group.options.draggedClass + ")"
555
+ ).get()
556
+ setDimensions(this.items, this.itemDimensions = [], this.options.tolerance)
557
+ }
558
+ return this.itemDimensions
559
+ },
560
+ getItemOffsetParent: function () {
561
+ var offsetParent,
562
+ el = this.el
563
+ // Since el might be empty we have to check el itself and
564
+ // can not do something like el.children().first().offsetParent()
565
+ if(el.css("position") === "relative" || el.css("position") === "absolute" || el.css("position") === "fixed")
566
+ offsetParent = el
567
+ else
568
+ offsetParent = el.offsetParent()
569
+ return offsetParent
570
+ },
571
+ hasChildGroup: function (index) {
572
+ return this.options.nested && this.getContainerGroup(index)
573
+ },
574
+ getContainerGroup: function (index) {
575
+ var childGroup = $.data(this.items[index], subContainerKey)
576
+ if( childGroup === undefined){
577
+ var childContainers = this.$getChildren(this.items[index], "container")
578
+ childGroup = false
579
+
580
+ if(childContainers[0]){
581
+ var options = $.extend({}, this.options, {
582
+ rootGroup: this.rootGroup,
583
+ group: groupCounter ++
584
+ })
585
+ childGroup = childContainers[pluginName](options).data(pluginName).group
586
+ }
587
+ $.data(this.items[index], subContainerKey, childGroup)
588
+ }
589
+ return childGroup
590
+ },
591
+ $getChildren: function (parent, type) {
592
+ var options = this.rootGroup.options,
593
+ path = options[type + "Path"],
594
+ selector = options[type + "Selector"]
595
+
596
+ parent = $(parent)
597
+ if(path)
598
+ parent = parent.find(path)
599
+
600
+ return parent.children(selector)
601
+ },
602
+ _serialize: function (parent, isContainer) {
603
+ var that = this,
604
+ childType = isContainer ? "item" : "container",
605
+
606
+ children = this.$getChildren(parent, childType).not(this.options.exclude).map(function () {
607
+ return that._serialize($(this), !isContainer)
608
+ }).get()
609
+
610
+ return this.rootGroup.options.serialize(parent, children, isContainer)
611
+ },
612
+ traverse: function(callback) {
613
+ $.each(this.items || [], function(item){
614
+ var group = $.data(this, subContainerKey)
615
+ if(group)
616
+ group.traverse(callback)
617
+ });
618
+
619
+ callback(this)
620
+ },
621
+ _clearDimensions: function () {
622
+ this.itemDimensions = undefined
623
+ },
624
+ _destroy: function() {
625
+ var that = this;
626
+
627
+ this.target.off(eventNames.start, this.handle);
628
+ this.el.removeData(pluginName)
629
+
630
+ if(this.options.drop)
631
+ this.group.containers = $.grep(this.group.containers, function(val){
632
+ return val != that
633
+ })
634
+
635
+ $.each(this.items || [], function(){
636
+ $.removeData(this, subContainerKey)
637
+ })
638
+ }
639
+ }
640
+
641
+ var API = {
642
+ enable: function() {
643
+ this.traverse(function(object){
644
+ object.disabled = false
645
+ })
646
+ },
647
+ disable: function (){
648
+ this.traverse(function(object){
649
+ object.disabled = true
650
+ })
651
+ },
652
+ serialize: function () {
653
+ return this._serialize(this.el, true)
654
+ },
655
+ refresh: function() {
656
+ this.traverse(function(object){
657
+ object._clearDimensions()
658
+ })
659
+ },
660
+ destroy: function () {
661
+ this.traverse(function(object){
662
+ object._destroy();
663
+ })
664
+ }
665
+ }
666
+
667
+ $.extend(Container.prototype, API)
668
+
669
+ /**
670
+ * jQuery API
671
+ *
672
+ * Parameters are
673
+ * either options on init
674
+ * or a method name followed by arguments to pass to the method
675
+ */
676
+ $.fn[pluginName] = function(methodOrOptions) {
677
+ var args = Array.prototype.slice.call(arguments, 1)
678
+
679
+ return this.map(function(){
680
+ var $t = $(this),
681
+ object = $t.data(pluginName)
682
+
683
+ if(object && API[methodOrOptions])
684
+ return API[methodOrOptions].apply(object, args) || this
685
+ else if(!object && (methodOrOptions === undefined ||
686
+ typeof methodOrOptions === "object"))
687
+ $t.data(pluginName, new Container($t, methodOrOptions))
688
+
689
+ return this
690
+ });
691
+ };
692
+
693
+ }(jQuery, window, 'sortable');