rails_medium_editor_insert_plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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');