neofiles 1.0.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/LICENSE +1 -0
  3. data/README.md +417 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/images/neofiles/loading.gif +0 -0
  6. data/app/assets/images/neofiles/swf-thumb-100x100.png +0 -0
  7. data/app/assets/images/neofiles/watermark.png +0 -0
  8. data/app/assets/javascripts/neofiles/index.js.coffee +3 -0
  9. data/app/assets/javascripts/neofiles/jquery.fileupload.js +1128 -0
  10. data/app/assets/javascripts/neofiles/jquery.iframe-transport.js +172 -0
  11. data/app/assets/javascripts/neofiles/jquery.neofiles.js.coffee +191 -0
  12. data/app/assets/stylesheets/neofiles/index.css.scss +3 -0
  13. data/app/assets/stylesheets/neofiles/neofiles.css.scss +149 -0
  14. data/app/controllers/concerns/neofiles/not_found.rb +21 -0
  15. data/app/controllers/neofiles/admin_controller.rb +228 -0
  16. data/app/controllers/neofiles/admin_test_controller.rb +5 -0
  17. data/app/controllers/neofiles/files_controller.rb +28 -0
  18. data/app/controllers/neofiles/images_controller.rb +130 -0
  19. data/app/helpers/neofiles/neofiles_helper.rb +188 -0
  20. data/app/models/neofiles/file.rb +319 -0
  21. data/app/models/neofiles/file_chunk.rb +18 -0
  22. data/app/models/neofiles/image.rb +119 -0
  23. data/app/models/neofiles/swf.rb +45 -0
  24. data/app/views/neofiles/admin/_file_compact.html.haml +85 -0
  25. data/app/views/neofiles/admin/file_compact.html.haml +1 -0
  26. data/app/views/neofiles/admin_test/file_compact.erb +7 -0
  27. data/config/locales/ru.yml +21 -0
  28. data/config/routes.rb +1 -0
  29. data/lib/neofiles.rb +94 -0
  30. data/lib/neofiles/engine.rb +33 -0
  31. data/lib/neofiles/version.rb +3 -0
  32. metadata +131 -0
@@ -0,0 +1,172 @@
1
+ /*
2
+ * jQuery Iframe Transport Plugin 1.5
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
+ /*jslint unparam: true, nomen: true */
13
+ /*global define, window, document */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function' && define.amd) {
18
+ // Register as an anonymous AMD module:
19
+ define(['jquery'], factory);
20
+ } else {
21
+ // Browser globals:
22
+ factory(window.jQuery);
23
+ }
24
+ }(function ($) {
25
+ 'use strict';
26
+
27
+ // Helper variable to create unique names for the transport iframes:
28
+ var counter = 0;
29
+
30
+ // The iframe transport accepts three additional options:
31
+ // options.fileInput: a jQuery collection of file input fields
32
+ // options.paramName: the parameter name for the file form data,
33
+ // overrides the name property of the file input field(s),
34
+ // can be a string or an array of strings.
35
+ // options.formData: an array of objects with name and value properties,
36
+ // equivalent to the return data of .serializeArray(), e.g.:
37
+ // [{name: 'a', value: 1}, {name: 'b', value: 2}]
38
+ $.ajaxTransport('iframe', function (options) {
39
+ if (options.async && (options.type === 'POST' || options.type === 'GET')) {
40
+ var form,
41
+ iframe;
42
+ return {
43
+ send: function (_, completeCallback) {
44
+ form = $('<form style="display:none;"></form>');
45
+ form.attr('accept-charset', options.formAcceptCharset);
46
+ // javascript:false as initial iframe src
47
+ // prevents warning popups on HTTPS in IE6.
48
+ // IE versions below IE8 cannot set the name property of
49
+ // elements that have already been added to the DOM,
50
+ // so we set the name along with the iframe HTML markup:
51
+ iframe = $(
52
+ '<iframe src="javascript:false;" name="iframe-transport-' +
53
+ (counter += 1) + '"></iframe>'
54
+ ).bind('load', function () {
55
+ var fileInputClones,
56
+ paramNames = $.isArray(options.paramName) ?
57
+ options.paramName : [options.paramName];
58
+ iframe
59
+ .unbind('load')
60
+ .bind('load', function () {
61
+ var response;
62
+ // Wrap in a try/catch block to catch exceptions thrown
63
+ // when trying to access cross-domain iframe contents:
64
+ try {
65
+ response = iframe.contents();
66
+ // Google Chrome and Firefox do not throw an
67
+ // exception when calling iframe.contents() on
68
+ // cross-domain requests, so we unify the response:
69
+ if (!response.length || !response[0].firstChild) {
70
+ throw new Error();
71
+ }
72
+ } catch (e) {
73
+ response = undefined;
74
+ }
75
+ // The complete callback returns the
76
+ // iframe content document as response object:
77
+ completeCallback(
78
+ 200,
79
+ 'success',
80
+ {'iframe': response}
81
+ );
82
+ // Fix for IE endless progress bar activity bug
83
+ // (happens on form submits to iframe targets):
84
+ $('<iframe src="javascript:false;"></iframe>')
85
+ .appendTo(form);
86
+ form.remove();
87
+ });
88
+ form
89
+ .prop('target', iframe.prop('name'))
90
+ .prop('action', options.url)
91
+ .prop('method', options.type);
92
+ if (options.formData) {
93
+ $.each(options.formData, function (index, field) {
94
+ $('<input type="hidden"/>')
95
+ .prop('name', field.name)
96
+ .val(field.value)
97
+ .appendTo(form);
98
+ });
99
+ }
100
+ if (options.fileInput && options.fileInput.length &&
101
+ options.type === 'POST') {
102
+ fileInputClones = options.fileInput.clone();
103
+ // Insert a clone for each file input field:
104
+ options.fileInput.after(function (index) {
105
+ return fileInputClones[index];
106
+ });
107
+ if (options.paramName) {
108
+ options.fileInput.each(function (index) {
109
+ $(this).prop(
110
+ 'name',
111
+ paramNames[index] || options.paramName
112
+ );
113
+ });
114
+ }
115
+ // Appending the file input fields to the hidden form
116
+ // removes them from their original location:
117
+ form
118
+ .append(options.fileInput)
119
+ .prop('enctype', 'multipart/form-data')
120
+ // enctype must be set as encoding for IE:
121
+ .prop('encoding', 'multipart/form-data');
122
+ }
123
+ form.submit();
124
+ // Insert the file input fields at their original location
125
+ // by replacing the clones with the originals:
126
+ if (fileInputClones && fileInputClones.length) {
127
+ options.fileInput.each(function (index, input) {
128
+ var clone = $(fileInputClones[index]);
129
+ $(input).prop('name', clone.prop('name'));
130
+ clone.replaceWith(input);
131
+ });
132
+ }
133
+ });
134
+ form.append(iframe).appendTo(document.body);
135
+ },
136
+ abort: function () {
137
+ if (iframe) {
138
+ // javascript:false as iframe src aborts the request
139
+ // and prevents warning popups on HTTPS in IE6.
140
+ // concat is used to avoid the "Script URL" JSLint error:
141
+ iframe
142
+ .unbind('load')
143
+ .prop('src', 'javascript'.concat(':false;'));
144
+ }
145
+ if (form) {
146
+ form.remove();
147
+ }
148
+ }
149
+ };
150
+ }
151
+ });
152
+
153
+ // The iframe transport returns the iframe content document as response.
154
+ // The following adds converters from iframe to text, json, html, and script:
155
+ $.ajaxSetup({
156
+ converters: {
157
+ 'iframe text': function (iframe) {
158
+ return $(iframe[0].body).text();
159
+ },
160
+ 'iframe json': function (iframe) {
161
+ return $.parseJSON($(iframe[0].body).text());
162
+ },
163
+ 'iframe html': function (iframe) {
164
+ return $(iframe[0].body).html();
165
+ },
166
+ 'iframe script': function (iframe) {
167
+ return $.globalEval($(iframe[0].body).text());
168
+ }
169
+ }
170
+ });
171
+
172
+ }));
@@ -0,0 +1,191 @@
1
+ $ ->
2
+ $.widget "neofiles.image",
3
+ options: {},
4
+
5
+ _$transferInput: null,
6
+ _$fileInput: null,
7
+ _$removeButton: null,
8
+ _$optionsButton: null,
9
+ _$descriptionHandle: null,
10
+
11
+ _savedNowmState: null,
12
+ _savedDescription: null,
13
+
14
+ _create: ->
15
+
16
+ $form = @element
17
+
18
+ @_$transferInput = $form.find(".neofiles-image-transfer-input")
19
+ @_$fileInput = $form.find(".neofiles-image-compact-file")
20
+ @_$removeButton = $form.find(".neofiles-image-compact-remove")
21
+ @_$optionsButton = $form.find(".neofiles-image-compact-options")
22
+ @_$descriptionHandle = $form.find(".neofiles-image-compact-description-handle")
23
+
24
+ $form.fileupload
25
+ dropZone: $form,
26
+ pasteZone: $form,
27
+ singleFileUploads: false,
28
+ limitMultiFileUploads: if @_$fileInput.attr("multiple") == "multiple" then 999 else 1,
29
+
30
+ formData: ->
31
+ $form.find("input, select").serializeArray()
32
+
33
+ start: =>
34
+ @loading()
35
+
36
+ success: (response, textStatus, jqXhr)=>
37
+ $form.replaceWith(response)
38
+ @destroy()
39
+
40
+ error: =>
41
+ alert("Ошибка при загрузке файла, попробуйте обновить страницу.\nТакже, проверьте тип файла, загружать можно только картинки.")
42
+ @notloading()
43
+ false
44
+
45
+ @_$removeButton.click (e)=>
46
+ e.preventDefault()
47
+ @remove()
48
+
49
+ @_$optionsButton.popover().on "shown.bs.popover", =>
50
+ @hideOtherActivePopovers()
51
+ @restoreSavedNowmState()
52
+
53
+ @_$optionsButton.click (e)=>
54
+ e.preventDefault()
55
+
56
+ @_$descriptionHandle.popover().on "shown.bs.popover", =>
57
+ @hideOtherActivePopovers()
58
+ @restoreSavedDescription()
59
+ @element.find(".neofiles-image-compact-description-input").redactor
60
+ toolbarFixed: false
61
+ minHeight: 250
62
+ buttons: ['bold', 'italic', 'orderedlist', 'unorderedlist', 'alignment']
63
+
64
+ @_$descriptionHandle.click (e)=>
65
+ e.preventDefault()
66
+
67
+ $form.find(".neofiles-image-compact-upload-icon").click =>
68
+ @_$fileInput.click()
69
+
70
+ $form.on "change", ".neofiles-image-compact-nowm", (e)=>
71
+ @saveNowmState($(e.target))
72
+
73
+ $form.on "click", ".neofiles-image-compact-description-save", (e)=>
74
+ @saveDescription()
75
+
76
+ imageId: ->
77
+ @_$transferInput.val()
78
+
79
+ loading: ->
80
+ @element[0].className += " neofiles-image-compact-loading"
81
+
82
+ notloading: ->
83
+ @element[0].className = @element[0].className.replace(/(^|\s+)neofiles-image-compact-loading/, "")
84
+
85
+ remove: ->
86
+ @loading()
87
+
88
+ data = {}
89
+ @element.find("input[type!=file][name^=neofiles], select[name^=neofiles]").serializeArray().forEach (o)->
90
+ data[o.name] = o.value
91
+
92
+ removeUrl = if @_$removeButton.is("a") then @_$removeButton.attr("href") else @_$removeButton.data("url")
93
+ $.ajax removeUrl,
94
+ type: "POST",
95
+ data: data,
96
+ success: (response)=>
97
+ @element.replaceWith(response)
98
+ @destroy()
99
+
100
+ @_$removeButton.trigger("neofiles.click.remove")
101
+
102
+ hideOtherActivePopovers: ->
103
+ self = @_$optionsButton[0]
104
+ $(".neofiles-image-compact .neofiles-image-compact-options").each ->
105
+ if @ != self
106
+ $(@).popover("hide")
107
+
108
+ restoreSavedNowmState: ->
109
+ if @_savedNowmState != null
110
+ @element.find(".neofiles-image-compact-nowm").prop("checked", @_savedNowmState)
111
+
112
+ restoreSavedDescription: ->
113
+ if @_savedDescription != null
114
+ @element.find(".neofiles-image-compact-description-input").val(@_savedDescription)
115
+
116
+ saveNowmState: ($checkbox)->
117
+ $checkbox.prop("disabled", true)
118
+ formData = @element.find("input, select").serializeArray()
119
+ formData.push name: "neofiles[no_wm]", value: if $checkbox.is(":checked") then "1" else "0"
120
+
121
+ $.ajax($checkbox.data("update-url"), type: "post", data: formData)
122
+ .done =>
123
+ @_savedNowmState = $checkbox.is(":checked")
124
+ .fail ->
125
+ $checkbox.prop("checked", !$checkbox.is(":checked"))
126
+ alert("Ошибка при сохранении, попробуйте еще раз позднее")
127
+ .always ->
128
+ # ответ приходит быстро, бывает не успеваешь заметить моргание disabled/enabled
129
+ setTimeout ->
130
+ $checkbox.prop("disabled", false)
131
+ , 300
132
+
133
+ saveDescription: ->
134
+ $textarea = @element.find(".neofiles-image-compact-description-input")
135
+ $button = @element.find(".neofiles-image-compact-description-save")
136
+
137
+ $textarea.prop("disabled", true)
138
+ formData = @element.find("input, select").serializeArray()
139
+ formData.push name: "neofiles[description]", value: $textarea.val()
140
+
141
+ $button.prop("disabled", true)
142
+
143
+ $.ajax($textarea.data("update-url"), type: "post", data: formData)
144
+
145
+ .done =>
146
+ setTimeout =>
147
+ @_savedDescription = $textarea.val()
148
+ text = $.trim(@_savedDescription).substr(0, 15)
149
+ @_$descriptionHandle.text(text || @_$descriptionHandle.data("empty"))
150
+ @_$descriptionHandle[if text then "removeClass" else "addClass"]("neofiles-image-compact-description-empty")
151
+ @_$descriptionHandle.popover("hide")
152
+ , 300
153
+
154
+ .fail ->
155
+ alert("Ошибка при сохранении, попробуйте еще раз позднее")
156
+
157
+ .always ->
158
+ # ответ приходит быстро, бывает не успеваешь заметить моргание disabled/enabled
159
+ setTimeout ->
160
+ $textarea.prop("disabled", false)
161
+ $button.prop("disabled", false)
162
+ , 300
163
+
164
+
165
+
166
+ $.widget "neofiles.album",
167
+ options: {},
168
+ limit: null,
169
+
170
+ _create: ->
171
+ $form = @element
172
+
173
+ # повесим сортировку на файлы, они сохранятся в том порядке, в котором input:hidden придут в контроллер
174
+ neofilesImageContainersSelector = ".neofiles-image-compact:not(.neofiles-image-compact-empty)"
175
+ $form.sortable
176
+ items: neofilesImageContainersSelector
177
+ handle: ".neofiles-image-compact-view"
178
+
179
+ @limit = @options.limit
180
+
181
+ if @limit
182
+ limit = @limit;
183
+ trigger = (e)->
184
+ ourLimit = (e.data && e.data.limit) || limit
185
+ if $(neofilesImageContainersSelector).length >= ourLimit
186
+ $(".neofiles-image-compact-empty").hide()
187
+ else
188
+ $(".neofiles-image-compact-empty").show()
189
+
190
+ $("body").on("neofiles.newimage", trigger)
191
+ $("body").on("neofiles.click.remove", {limit: limit + 1}, trigger)
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require ./neofiles
3
+ */
@@ -0,0 +1,149 @@
1
+ .neofiles-image-compact {
2
+ box-sizing: border-box;
3
+ display: inline-block;
4
+ position: relative;
5
+ border: 1px solid #ccc;
6
+ background-color: #fff;
7
+ margin-bottom: 10px;
8
+ vertical-align: top;
9
+ padding: 3px;
10
+ line-height: 1em;
11
+
12
+ .popover-content {
13
+ width: 250px;
14
+ }
15
+ }
16
+ .neofiles-image-compact-with-description {
17
+ margin-bottom: 40px;
18
+ min-width: 100px;
19
+ text-align: center;
20
+ }
21
+ .neofiles-image-compact-empty {
22
+ width: 108px;
23
+ height: 108px;
24
+ }
25
+ .neofiles-image-compact-upload-icon {
26
+ position: absolute;
27
+ box-sizing: border-box;
28
+ bottom: -7px;
29
+ left: 50%;
30
+ margin-left: -7px;
31
+ background-color: #fff;
32
+ border-radius: 7px;
33
+ opacity: .1;
34
+ width: 16px;
35
+ height: 16px;
36
+ text-align: center;
37
+ cursor: pointer;
38
+
39
+ &:hover {
40
+ opacity: 1;
41
+ }
42
+
43
+ .glyphicon {
44
+ top: 0;
45
+ }
46
+
47
+ .neofiles-image-compact-empty & {
48
+ position: static;
49
+ display: block;
50
+ width: 100%;
51
+ height: 100px;
52
+ margin: 0;
53
+ text-align: center;
54
+ padding-top: 42px;
55
+ }
56
+ }
57
+ .neofiles-image-compact-remove, .neofiles-image-compact-options {
58
+ display: block;
59
+ position: absolute;
60
+ box-sizing: border-box;
61
+ width: 20px;
62
+ height: 20px;
63
+ padding-top: 2px;
64
+ right: -10px;
65
+ top: -10px;
66
+ border-radius: 10px;
67
+ text-align: center;
68
+ background-color: #ee5f5b;
69
+ box-shadow: 0px 2px 2px rgba(90, 50, 50, .5);
70
+ opacity: .2;
71
+
72
+ .glyphicon {
73
+ top: 1px;
74
+ font-size: 12px;
75
+ line-height: 12px;
76
+ color: #000;
77
+ text-shadow: 0 1px 1px rgba(255, 255, 255, .3);
78
+ }
79
+
80
+ &:hover {
81
+ opacity: 1;
82
+ }
83
+ }
84
+ .neofiles-image-compact-options {
85
+ top: 12px;
86
+ background-color: #fff;
87
+ }
88
+ INPUT.neofiles-image-compact-file {
89
+ display: none !important;
90
+ }
91
+ .neofiles-image-compact-loading {
92
+ opacity: .5;
93
+ background: image-path('neofiles/loading.gif') no-repeat center center;
94
+
95
+ .neofiles-image-compact-upload-icon,
96
+ .neofiles-image-compact-remove,
97
+ .neofiles-image-compact-options,
98
+ .neofiles-image-compact-file {
99
+ visibility: hidden;
100
+ }
101
+ }
102
+ .neofiles-image-compact-description {
103
+ position: absolute;
104
+ bottom: -25px;
105
+ width: 100%;
106
+ font-size: 12px;
107
+ margin-left: -4px;
108
+ line-height: 14px;
109
+ text-align: center;
110
+ }
111
+ .neofiles-image-compact-description-empty {
112
+ color: #ccc !important;
113
+ border-bottom-color: #ddd !important;
114
+ }
115
+ .neofiles-image-compact-description-handle {
116
+ color: #333;
117
+ border-bottom: 1px dotted #777;
118
+ text-decoration: none !important;
119
+
120
+ &:hover {
121
+ border-bottom: 0;
122
+ text-decoration: none !important;
123
+ }
124
+ }
125
+ .popover.neofiles-image-compact-description-popover {
126
+ width: 430px;
127
+ height: 400px;
128
+ max-width: none;
129
+
130
+ .popover-content {
131
+ width: 100%;
132
+ }
133
+ }
134
+ .neofiles-image-compact-description-input {
135
+ width: 400px;
136
+ height: 250px;
137
+ }
138
+
139
+ .neofiles-album-compact {
140
+ margin-bottom: 10px;
141
+ }
142
+ .neofiles-album-compact .neofiles-image-compact {
143
+ margin-right: 20px;
144
+ margin-bottom: 20px;
145
+
146
+ &.neofiles-image-compact-with-description {
147
+ margin-bottom: 50px;
148
+ }
149
+ }