el-finder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +11 -0
  3. data/LICENSE +19 -0
  4. data/README.md +66 -0
  5. data/Rakefile +21 -0
  6. data/assets/api.js +273 -0
  7. data/assets/bootstrap-contextmenu.js +147 -0
  8. data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
  9. data/assets/bootstrap/css/bootstrap.min.css +9 -0
  10. data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
  11. data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
  12. data/assets/bootstrap/js/bootstrap.min.js +6 -0
  13. data/assets/colorbox/colorbox.css +44 -0
  14. data/assets/colorbox/images/controls.png +0 -0
  15. data/assets/colorbox/images/loading.gif +0 -0
  16. data/assets/colorbox/jquery.colorbox-min.js +6 -0
  17. data/assets/fileupload.js +31 -0
  18. data/assets/icons/Oxygen/preview.png +0 -0
  19. data/assets/icons/Oxygen/video.png +0 -0
  20. data/assets/icons/delete.png +0 -0
  21. data/assets/icons/file.png +0 -0
  22. data/assets/icons/folder.png +0 -0
  23. data/assets/jQuery-File-Upload/css/jquery.fileupload-ui-noscript.css +27 -0
  24. data/assets/jQuery-File-Upload/css/jquery.fileupload-ui.css +83 -0
  25. data/assets/jQuery-File-Upload/css/style.css +15 -0
  26. data/assets/jQuery-File-Upload/img/loading.gif +0 -0
  27. data/assets/jQuery-File-Upload/img/progressbar.gif +0 -0
  28. data/assets/jQuery-File-Upload/js/cors/jquery.postmessage-transport.js +117 -0
  29. data/assets/jQuery-File-Upload/js/cors/jquery.xdr-transport.js +87 -0
  30. data/assets/jQuery-File-Upload/js/jquery.fileupload-fp.js +227 -0
  31. data/assets/jQuery-File-Upload/js/jquery.fileupload-ui.js +807 -0
  32. data/assets/jQuery-File-Upload/js/jquery.fileupload.js +1201 -0
  33. data/assets/jQuery-File-Upload/js/jquery.iframe-transport.js +185 -0
  34. data/assets/jQuery-File-Upload/js/main.js +83 -0
  35. data/assets/jQuery-File-Upload/js/vendor/jquery.ui.widget.js +530 -0
  36. data/assets/jquery-ui/css/ui-lightness/images/animated-overlay.gif +0 -0
  37. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  38. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  39. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png +0 -0
  40. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  41. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  42. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  43. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  44. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  45. data/assets/jquery-ui/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  46. data/assets/jquery-ui/css/ui-lightness/images/ui-icons_222222_256x240.png +0 -0
  47. data/assets/jquery-ui/css/ui-lightness/images/ui-icons_228ef1_256x240.png +0 -0
  48. data/assets/jquery-ui/css/ui-lightness/images/ui-icons_ef8c08_256x240.png +0 -0
  49. data/assets/jquery-ui/css/ui-lightness/images/ui-icons_ffd27a_256x240.png +0 -0
  50. data/assets/jquery-ui/css/ui-lightness/images/ui-icons_ffffff_256x240.png +0 -0
  51. data/assets/jquery-ui/css/ui-lightness/jquery-ui.css +5 -0
  52. data/assets/jquery-ui/js/jquery-ui.js +6 -0
  53. data/assets/jquery.js +5 -0
  54. data/assets/noty/jquery.noty.js +520 -0
  55. data/assets/noty/layouts/top.js +34 -0
  56. data/assets/noty/layouts/topRight.js +43 -0
  57. data/assets/noty/themes/default.js +156 -0
  58. data/assets/select2-bootstrap.css +86 -0
  59. data/assets/select2/select2-spinner.gif +0 -0
  60. data/assets/select2/select2.css +615 -0
  61. data/assets/select2/select2.min.js +22 -0
  62. data/assets/select2/select2.png +0 -0
  63. data/assets/select2/select2x2.png +0 -0
  64. data/assets/ui.css +43 -0
  65. data/assets/ui.js +34 -0
  66. data/assets/xhr.js +4 -0
  67. data/el-finder.gemspec +25 -0
  68. data/examples/basic/Gemfile +15 -0
  69. data/examples/basic/app.rb +21 -0
  70. data/lib/el-finder.rb +6 -0
  71. data/lib/el-finder/el-finder.rb +200 -0
  72. data/lib/el-finder/templates/column.slim +64 -0
  73. data/lib/el-finder/templates/index.slim +235 -0
  74. metadata +158 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OTJkMzg2ZGMzMzFmNWI2YTQ1ZDE5NjMyYmNmODQ1OTUyMzU2MzQ5Mg==
5
+ data.tar.gz: !binary |-
6
+ MjhiOTFjZDNiODdiZjEyMWJkNzVlMTkzMGQzNmJhYzAzZDM4NGI5Yg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NjYzNjNkYzdkOGRkY2QyNjM5MzYzNzgyNmY0N2U1YTRkNGY2Njk5MzVlNjY2
10
+ MDI4MmMyNDZhNGJjMzM2NmM2ZmRhM2VjNTY2NDJiNjA2OGU4YTRmYzczMWI4
11
+ NTZiMTYzN2Y2YmFiNDY5NGM5ZGExZDQzZjEzM2EzNzg3ZjkyMDk=
12
+ data.tar.gz: !binary |-
13
+ MGRjN2ZkOGY5NWQwN2VjMjk3ZGRmMjg2ZDAwYzFkZmY1YTkxMGEzM2JhZDZm
14
+ MWVlYjdkYjYzODMyM2MwYTIyNWY3YjUzZTdjYmQyYWFhY2MzYjI4ODk0MGFl
15
+ NjA3NDQwNjgwMzdkMWE2NWJiYTAxMGE1OGNjNGIzYTYwM2VlZjA=
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+
4
+ if ENV['E_DEV']
5
+ %w[e el].each do |g|
6
+ gem g, path: File.expand_path('../../' + g, __FILE__)
7
+ end
8
+ else
9
+ gem 'el'
10
+ end
11
+ gem 'rake', '~> 10'
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+
2
+ Copyright (c) 2013 Silviu Rusu <slivuz@gmail.com>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"),
6
+ to deal in the Software without restriction, including without limitation
7
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ and/or sell copies of the Software, and to permit persons to whom the Software
9
+ is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,66 @@
1
+
2
+ ## [Espresso Lungo](https://github.com/espresso/espresso-lungo) / Finder
3
+
4
+ ### Web-based File Manager for [Espresso](https://github.com/espresso/espresso) applications
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ $ gem install el-finder
10
+ ```
11
+
12
+ or add `gem 'el-finder'` to your `Gemfile`
13
+
14
+ ## Load
15
+
16
+ Ignore this if `el-finder` added to `Gemfile` and `Bundler.require` used.
17
+
18
+ Otherwise you'll have to load `el-finder` gem explicitly.
19
+
20
+ Just add `require 'el-finder'` at the top of your app.
21
+
22
+ ## Use
23
+
24
+ First of all include `EL::Finder` into controllers you need a file manager.
25
+
26
+ Then use `finder` into your views passing path to managed directory via first argument:
27
+
28
+ ```ruby
29
+ finder 'path/to/files'
30
+ ```
31
+
32
+ ## Editors
33
+
34
+ By default, `el-finder` will use a textarea to edit files.
35
+
36
+ To use `Ace` editor, install and load `el-ace` gem.
37
+
38
+ See [github.com/espresso/el-ace](https://github.com/espresso/el-ace) for instructions.
39
+
40
+ Then simply set editor via `:editor` option:
41
+
42
+ ```ruby
43
+ finder 'path/to/files', editor: :ace
44
+ ```
45
+
46
+ Same for `CKEditor`, see [github.com/espresso/el-ckeditor](https://github.com/espresso/el-ckeditor) for instructions.
47
+
48
+ Feel free to pass any editor options:
49
+
50
+ ```ruby
51
+ finder 'path/to/files', editor: :ace, mode: 'html'
52
+ ```
53
+
54
+ ```ruby
55
+ finder 'path/to/files', editor: :ckeditor, lang: 'fr'
56
+ ```
57
+
58
+ <hr>
59
+
60
+ **Issues/Bugs:** [github.com/espresso/espresso-lungo/issues](https://github.com/espresso/espresso-lungo/issues)
61
+
62
+ **Mailing List:** [groups.google.com/.../espresso-framework](https://groups.google.com/forum/?fromgroups#!forum/espresso-framework)
63
+
64
+ **IRC channel:** #espressorb on irc.freenode.net
65
+
66
+ **Author** - [Silviu Rusu](https://github.com/slivu)
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ namespace :assets do
5
+ desc 'Update css files to correctly load background images'
6
+ task :css do
7
+ puts "Looking for css files containing background(-image)?:url ..."
8
+ src = /background(\-image)?[\s+]?\:(.*?)url\((\W+)?([^\.]*)\.(\w+)(\W+)?\)/
9
+ dst = 'background\1:\2url(\3\4.\5%s\6)' % EL::FinderHelpers::ASSETS_EXT
10
+ Dir[File.expand_path('../assets/**/*.css', __FILE__)].each do |file|
11
+ css = File.read(file)
12
+ if css =~ src
13
+ puts "Updating #{file}"
14
+ File.open(file, 'w') {|f| f << css.gsub(src, dst)}
15
+ end
16
+ end
17
+ puts "Done"
18
+ end
19
+ end
20
+
21
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,273 @@
1
+ ELFinderAPI = function(baseURL, params) {
2
+
3
+ var baseURL = (baseURL||'').match(/\/$/) ? baseURL : baseURL + '/',
4
+ params = (params || {});
5
+
6
+ this.delete = function(type, path, name) {
7
+ var path = guess_path(path),
8
+ name = guess_name(name);
9
+
10
+ var warning = "You are going to delete \"" + name + "\" " + type + ". ";
11
+ warning += "This action can NOT be undone! Continue?";
12
+ if (!confirm(warning)) return false;
13
+
14
+ $.ajax({
15
+ type: 'DELETE',
16
+ url: baseURL,
17
+ data: {
18
+ action: type,
19
+ path: path,
20
+ name: name
21
+ },
22
+ complete: function(xhr, txtResponse) {
23
+ if(txtResponse == 'success') {
24
+ if (type == 'file') {
25
+ redirect({location: params.location.URL});
26
+ } else {
27
+ if(params.location.URL.match(new RegExp("^" + path + "/" + name)) ||
28
+ (path == params.location.path && name == params.location.name))
29
+ redirect({location: path});
30
+ else
31
+ reload();
32
+ }
33
+ } else displayError(xhr);
34
+ }
35
+ });
36
+ }
37
+
38
+ this.download = function(path, name) {
39
+ var url = baseURL + build_query_string({
40
+ action: 'download',
41
+ path: guess_path(path),
42
+ name: guess_name(name)
43
+ });
44
+ $('#ELFinderDownloadIframe').attr('src', url);
45
+ }
46
+
47
+ this.rename = function(selector) {
48
+ if (selector) {
49
+ var element = $(selector);
50
+ var path = element.attr('data-path'),
51
+ name = element.attr('data-name'),
52
+ new_name = element.val();
53
+ } else
54
+ var path, name, new_name = $('#input-rename').val();
55
+
56
+ $.ajax({
57
+ type: 'POST',
58
+ url: baseURL,
59
+ data: {
60
+ action: 'rename',
61
+ path: guess_path(path),
62
+ name: guess_name(name),
63
+ new_name: new_name
64
+ },
65
+ complete: function(xhr, txtResponse) {
66
+ if(txtResponse == 'success') {
67
+ if (params.file) {
68
+ redirect({location: params.location.URL, file: [params.file.path, new_name].join('/')});
69
+ } else {
70
+ if (selector) window.location.reload();
71
+ else redirect({location: [params.location.path, new_name].join('/')});
72
+ }
73
+ } else displayError(xhr);
74
+ }
75
+ });
76
+ }
77
+
78
+ this.save_file = function() {
79
+ $.ajax({
80
+ type: 'POST',
81
+ url: baseURL,
82
+ data: {
83
+ action: 'update',
84
+ path: params.file.path,
85
+ name: params.file.name,
86
+ content: $('#ELFinderEditor').val()
87
+ },
88
+ complete: function(xhr, txtResponse) {
89
+ if(txtResponse == 'success')
90
+ ELFinder.alert(params.file.name + ' Successfully Updated');
91
+ else displayError(xhr);
92
+ }
93
+ });
94
+ }
95
+
96
+ this.create = function(type) {
97
+ var new_name = $('#input-create').val();
98
+ $.ajax({
99
+ type: 'POST',
100
+ url: baseURL,
101
+ data: {
102
+ action: type,
103
+ path: params.location.path,
104
+ name: params.location.name,
105
+ new_name: new_name
106
+ },
107
+ complete: function(xhr, txtResponse) {
108
+ if(txtResponse == 'success') reload();
109
+ else displayError(xhr);
110
+ }
111
+ });
112
+ }
113
+
114
+ this.move = function(source, target) {
115
+ $.ajax({
116
+ type: 'POST',
117
+ url: baseURL,
118
+ data: {
119
+ action: 'move',
120
+ source: source,
121
+ target: target
122
+ },
123
+ complete: function(xhr, txtResponse) {
124
+ if(txtResponse == 'success') {
125
+ var actual = params.location.path + '/' + params.location.name;
126
+ if(actual.match(new RegExp("^" + source)))
127
+ redirect({location: target});
128
+ else
129
+ reload();
130
+ } else displayError(xhr);
131
+ }
132
+ });
133
+ }
134
+
135
+ this.loadScript = function(url, checkLoaded, callback) {
136
+ if (checkLoaded && checkLoaded())
137
+ return callback && callback();
138
+
139
+ var script = document.createElement("script");
140
+ script.type = "text/javascript";
141
+ script.async = true;
142
+
143
+ if (script.readyState) {
144
+ script.onreadystatechange = function () {
145
+ if (script.readyState == "loaded" || script.readyState == "complete") {
146
+ script.onreadystatechange = null;
147
+ callback && callback();
148
+ }
149
+ }
150
+ } else {
151
+ script.onload = function() {
152
+ callback && callback();
153
+ }
154
+ }
155
+
156
+ script.src = url;
157
+ document.head.appendChild(script);
158
+ }
159
+
160
+ this.loadStylesheet = function(url, checkForClasses) {
161
+ if (checkForClasses) {
162
+ if (typeof checkForClasses == "string")
163
+ checkForClasses = checkForClasses.split(/\s+/g);
164
+ var hasClass = false, styleSheets = document.styleSheets;
165
+ for (var x = 0; x < styleSheets.length; x++) {
166
+ var sheetClasses = styleSheets[x].rules || document.styleSheets[x].cssRules;
167
+ for (var y = 0; y < sheetClasses.length; y++) {
168
+ if ( checkForClasses.indexOf(sheetClasses[y].selectorText) > -1 ) {
169
+ hasClass = true; break;
170
+ }
171
+ }
172
+ }
173
+ if (hasClass) return true;
174
+ }
175
+ var link = document.createElement("link");
176
+ link.media = "all";
177
+ link.type = "text/css"
178
+ link.rel = "stylesheet"
179
+ link.async = true;
180
+ link.href = url;
181
+ document.head.appendChild(link);
182
+ }
183
+
184
+ this.alert = function(msg, timeout) {
185
+ noty({
186
+ text: msg,
187
+ type: 'information',
188
+ layout: 'topRight',
189
+ timeout: timeout || 2000
190
+ });
191
+ }
192
+
193
+ this.sticky_alert = function(msg) {
194
+ noty({
195
+ text: msg,
196
+ type: 'information',
197
+ layout: 'topRight'
198
+ });
199
+ }
200
+
201
+ this.warn = function(msg, timeout) {
202
+ noty({
203
+ text: msg,
204
+ type: 'warning',
205
+ layout: 'topRight',
206
+ timeout: timeout || 3000
207
+ });
208
+ }
209
+
210
+ this.sticky_warn = function(msg) {
211
+ noty({
212
+ text: msg,
213
+ type: 'warning',
214
+ layout: 'topRight'
215
+ });
216
+ }
217
+
218
+ this.error = function(msg) {
219
+ msg = msg.replace(/</gm, '&lt;').replace(/>/gm, '&gt;').
220
+ replace(/\n|\r/gm, '<br>').replace(/\t/gm, '&nbsp;&nbsp;');
221
+ noty({
222
+ text: '<div style="text-align: left; max-height: 280px; overflow: auto;">' + msg + '</div>',
223
+ type: 'warning',
224
+ layout: 'top',
225
+ modal: true,
226
+ closeWith: ['button'],
227
+ buttons: [
228
+ {addClass: 'btn btn-primary', text: 'Close', onClick: function($noty) {
229
+ $noty.close();
230
+ }
231
+ }
232
+ ]
233
+ });
234
+ }
235
+
236
+ var redirect = function(_url, _params) {
237
+ var requestURI = $(location).attr('pathname');
238
+ if((typeof _url) == 'object') {
239
+ params = _url;
240
+ url = requestURI;
241
+ } else {
242
+ params = _params || params;
243
+ url = _url || requestURI;
244
+ }
245
+ window.location = url + build_query_string(params);
246
+ }
247
+
248
+ var reload = function() {
249
+ window.location.reload(true);
250
+ }
251
+
252
+ var build_query_string = function(_params) {
253
+ var params = _params || params;
254
+ if(jQuery.isEmptyObject(params)) return '';
255
+ var query_string = [];
256
+ $.each(params, function(k,v) { query_string.push(k + '=' + v) });
257
+ return '?' + query_string.join('&');
258
+ }
259
+
260
+ var guess_path = function(path) {
261
+ return (path || (params.file ? params.file.path : params.location.path));
262
+ }
263
+ var guess_name = function(name) {
264
+ return (name || (params.file ? params.file.name : params.location.name));
265
+ }
266
+
267
+ var displayError = function(xhr) {
268
+ if(xhr.status == 400)
269
+ ELFinder.warn(xhr.responseText);
270
+ else if(xhr.status == 500)
271
+ ELFinder.error(xhr.responseText);
272
+ }
273
+ }
@@ -0,0 +1,147 @@
1
+ /*!
2
+ * Bootstrap Context Menu
3
+ * Version: 2.0
4
+ * A small variation of the dropdown plugin by @sydcanem
5
+ * https://github.com/sydcanem/bootstrap-contextmenu
6
+ *
7
+ * Twitter Bootstrap (http://twitter.github.com/bootstrap).
8
+ */
9
+
10
+ /* =========================================================
11
+ * bootstrap-dropdown.js
12
+ * http://twitter.github.com/bootstrap/
13
+ * =========================================================
14
+ * Copyright 2012 Twitter, Inc.
15
+ *
16
+ * Licensed under the Apache License, Version 2.0 (the "License");
17
+ * you may not use this file except in compliance with the License.
18
+ * You may obtain a copy of the License at
19
+ *
20
+ * http://www.apache.org/licenses/LICENSE-2.0
21
+ *
22
+ * Unless required by applicable law or agreed to in writing, software
23
+ * distributed under the License is distributed on an "AS IS" BASIS,
24
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
+ * See the License for the specific language governing permissions and
26
+ * limitations under the License.
27
+ * ========================================================= */
28
+
29
+ !(function($) {
30
+
31
+ "use strict"; // jshint ;_;
32
+
33
+ /* CONTEXTMENU CLASS DEFINITION
34
+ * ============================ */
35
+
36
+ var toggle = '[data-toggle=context]'
37
+ , ContextMenu = function (element) {
38
+ var $el = $(element).on('contextmenu.context.data-api', this.toggle);
39
+ var $target = $($el.attr('data-target'));
40
+ $('html').on('click.context.data-api', function (e) {
41
+ if (!e.ctrlKey) {
42
+ $target.removeClass('open');
43
+ }
44
+ });
45
+ }
46
+
47
+ ContextMenu.prototype = {
48
+
49
+ constructor: ContextMenu
50
+ ,toggle: function(e) {
51
+
52
+ var $this = $(this)
53
+ , $menu
54
+ , $contextmenu
55
+ , evt;
56
+
57
+ if ($this.is('.disabled, :disabled')) return;
58
+
59
+ evt = $.Event('context');
60
+ $this.trigger(evt);
61
+
62
+ $menu = getMenu($this);
63
+ $menu.removeClass('open');
64
+
65
+ var tp = getPosition(e, $menu);
66
+ $menu.attr('style', '')
67
+ .css(tp)
68
+ .toggleClass('open');
69
+
70
+ return false;
71
+ }
72
+
73
+ }
74
+
75
+ function getMenu($this) {
76
+ var selector = $this.attr('data-target')
77
+ , $menu;
78
+
79
+ if (!selector) {
80
+ selector = $this.attr('href')
81
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
82
+ }
83
+
84
+ $menu = $(selector);
85
+
86
+ return $menu;
87
+ }
88
+
89
+ function getPosition(e, $menu) {
90
+ var mouseX = e.clientX
91
+ , mouseY = e.clientY
92
+ , boundsX = $(window).width()
93
+ , boundsY = $(window).height()
94
+ , menuWidth = $menu.find('.dropdown-menu').outerWidth()
95
+ , menuHeight = $menu.find('.dropdown-menu').outerHeight()
96
+ , tp = {"position":"fixed"}
97
+ , Y, X;
98
+
99
+ if (mouseY + menuHeight > boundsY) {
100
+ Y = {"top": mouseY - menuHeight};
101
+ } else {
102
+ Y = {"top": mouseY};
103
+ }
104
+
105
+ if (mouseX + menuWidth > boundsX) {
106
+ X = {"left": mouseX - menuWidth};
107
+ } else {
108
+ X = {"left": mouseX};
109
+ }
110
+
111
+ return $.extend(tp, Y, X);
112
+ }
113
+
114
+ function clearMenus(e) {
115
+ if (!e.ctrlKey) {
116
+ $(toggle).each(function() {
117
+ getMenu($(this))
118
+ .removeClass('open');
119
+ });
120
+ }
121
+ }
122
+
123
+ /* CONTEXT MENU PLUGIN DEFINITION
124
+ * ========================== */
125
+
126
+ $.fn.contextmenu = function (option) {
127
+ return this.each(function () {
128
+ var $this = $(this)
129
+ , data = $this.data('context');
130
+ if (!data) $this.data('context', (data = new ContextMenu(this)));
131
+ if (typeof option == 'string') data[option].call($this);
132
+ });
133
+ }
134
+
135
+ $.fn.contextmenu.Constructor = ContextMenu;
136
+
137
+ /* APPLY TO STANDARD CONTEXT MENU ELEMENTS
138
+ * =================================== */
139
+
140
+ $(function () {
141
+ $('html')
142
+ .on('click.context.data-api', clearMenus)
143
+ $('body')
144
+ .on('contextmenu.context.data-api', toggle, ContextMenu.prototype.toggle);
145
+ });
146
+
147
+ }(window.jQuery));