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,45 @@
1
+ # Special case of Neofiles::File for dealing with SWF movies.
2
+ #
3
+ # Alongside usual file things, stores width & height of Flash clip.
4
+ #
5
+ require_dependency 'image_spec'
6
+
7
+ class Neofiles::Swf < Neofiles::File
8
+
9
+ class SwfFormatException < Exception; end
10
+
11
+ field :width, type: Integer
12
+ field :height, type: Integer
13
+
14
+ before_save :compute_dimensions
15
+
16
+ # Return array with width & height decorated with singleton function to_s returning 'WxH' string.
17
+ def dimensions
18
+ dim = [width, height]
19
+ def dim.to_s
20
+ join 'x'
21
+ end
22
+ dim
23
+ end
24
+
25
+ # Overrides parent "admin views" with square 100x100 Flash thumbnail.
26
+ def admin_compact_view(view_context)
27
+ view_context.neofiles_link self, view_context.tag(:img, src: view_context.image_path('neofiles/swf-thumb-100x100.png')), target: '_blank'
28
+ end
29
+
30
+
31
+
32
+ private
33
+
34
+ # Store dimensions on #save.
35
+ def compute_dimensions
36
+ return unless @file
37
+
38
+ spec = ::ImageSpec.new(@file)
39
+ if spec.content_type != 'application/x-shockwave-flash'
40
+ raise SwfFormatException.new I18n.t('neofiles.swf_type_incorrect', content_type: spec.content_type)
41
+ end
42
+ self.width = spec.width
43
+ self.height = spec.height
44
+ end
45
+ end
@@ -0,0 +1,85 @@
1
+ - widget_id ||= input_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, '_').sub(/_$/, '')
2
+ - desc = file.try(:description).try(:to_s).try!(:strip).try!(:truncate, 15).presence
3
+ - empty_desc = t 'neofiles.views.no_description'
4
+
5
+ - classes = []
6
+ - classes << 'neofiles-image-compact-empty' unless file
7
+ - classes << 'neofiles-image-compact-with-description' if file && with_desc
8
+ .neofiles-image-compact(id=widget_id data-url=neofiles_file_save_path){class: classes}
9
+
10
+ - if error.present?
11
+ %p.neofiles-error
12
+ = error
13
+
14
+ - if file
15
+ .neofiles-image-compact-view= file.admin_compact_view self
16
+
17
+ %a(href=neofiles_file_remove_path class="neofiles-image-compact-remove")
18
+ %i.icon-remove.glyphicon.glyphicon-remove
19
+
20
+ - if Neofiles.is_admin? self
21
+ - options_form = capture do
22
+ - if file.description.present?
23
+ %p= file.description
24
+
25
+ %p= t 'neofiles.views.created_at', local_date: l(file.created_at, format: t('neofiles.views.local_date_format'))
26
+
27
+ - file_options = []
28
+ - if file.respond_to? :dimensions
29
+ - file_options << "#{file.dimensions.join('×').html_safe} px"
30
+ - file_options << "#{number_to_human_size file.length, precision: 0}"
31
+ %p= file_options.join(', ')
32
+
33
+ - if file.owner_type.present? || file.owner_id.present?
34
+ %p #{t 'neofiles.views.owner'}: #{file.owner_type}#{file.owner_id}
35
+
36
+ - if file.respond_to? :no_wm
37
+ %p.checkbox
38
+ - uniq_name = "#{widget_id}_no_wm"
39
+ %label(for=uniq_name)
40
+ = check_box_tag uniq_name, '1', file.no_wm, class: 'neofiles-image-compact-nowm', data: {update_url: neofiles_file_update_path}
41
+ = t 'neofiles.views.no_wm'
42
+
43
+ %a(href="#" tabindex="0" class="neofiles-image-compact-options" data-toggle="popover" data-trigger="click" data-placement="top" data-html="true" data-animation="false" data-title="#{file.filename}" data-content=options_form)
44
+ %i.icon-wrench.glyphicon.glyphicon-wrench
45
+
46
+ - if with_desc
47
+ - description_form = capture do
48
+ - area_name = "#{widget_id}_description"
49
+ = text_area_tag area_name, file.description, class: 'neofiles-image-compact-description-input', rows: 10, data: {update_url: neofiles_file_update_path}
50
+ .text-center
51
+ %button.btn.btn-primary.btn-small.neofiles-image-compact-description-save= t 'neofiles.views.save'
52
+
53
+ - popover_template = capture do
54
+ .popover.neofiles-image-compact-description-popover(role="tooltip")
55
+ .arrow
56
+ %h3.popover-title
57
+ .popover-content
58
+
59
+ .neofiles-image-compact-description
60
+ %a.neofiles-image-compact-description-handle(href="#" data-empty=empty_desc data-toggle="popover" data-trigger="click" data-placement="bottom" data-html="true" data-animation="false" data-title="#{t 'neofiles.views.description'}" data-content=description_form data-template=popover_template){class: ('neofiles-image-compact-description-empty' unless desc)}= desc || empty_desc
61
+
62
+ %span.neofiles-image-compact-upload-icon
63
+ %i.icon-upload.glyphicon.glyphicon-upload
64
+
65
+ - common_options = {id: nil, disabled: disabled}
66
+ = hidden_field_tag input_name, file.try(:id), common_options.merge(class: 'neofiles-image-transfer-input')
67
+ = hidden_field_tag 'neofiles[id]', file.try(:id), common_options
68
+ = hidden_field_tag 'neofiles[input_name]', input_name, common_options
69
+ = hidden_field_tag 'neofiles[widget_id]', widget_id, common_options
70
+
71
+ = hidden_field_tag 'neofiles[clean_remove]', clean_remove ? 1 : 0, common_options
72
+ = hidden_field_tag 'neofiles[append_create]', file ? 0 : (append_create ? 1 : 0), common_options
73
+ = hidden_field_tag 'neofiles[multiple]', multiple ? 1 : 0, common_options
74
+ = hidden_field_tag 'neofiles[with_desc]', with_desc ? 1 : 0, common_options
75
+
76
+ = file_field_tag 'neofiles[file][]', id: nil, class: 'neofiles-image-compact-file', disabled: disabled, multiple: multiple && !file
77
+
78
+ :javascript
79
+ $(function() {
80
+ $("##{widget_id}").image();
81
+ });
82
+
83
+ - if append_create and file
84
+ = render partial: 'file_compact', locals: {file: nil, input_name: input_name, widget_id: widget_id + '_ap', clean_remove: clean_remove, append_create: true, error: nil, disabled: disabled, multiple: multiple, with_desc: with_desc}
85
+
@@ -0,0 +1 @@
1
+ = render partial: 'file_compact', locals: {file: @file, widget_id: @widget_id, input_name: @input_name, error: @error, clean_remove: @clean_remove, append_create: @append_create, disabled: @disabled, multiple: @multiple, with_desc: @with_desc}
@@ -0,0 +1,7 @@
1
+ <div id="zzz_cont"></div>
2
+
3
+ <script type="text/javascript">
4
+ $(function() {
5
+ $("#zzz_cont").load("<%= neofiles_file_compact_path(id: @file_id, input_name: 'zzz') %>");
6
+ });
7
+ </script>
@@ -0,0 +1,21 @@
1
+ ru:
2
+ neofiles:
3
+ 403_access_denied: Ошибка 403: недостаточно прав для получения файла в таком формате
4
+ 404_not_found: Ошибка 404: файл не найден
5
+ file_not_found: Файл не найден
6
+ data_not_passed: Не переданы данные для сохранения
7
+ file_not_passed: Не передан файл для сохранения
8
+ id_not_passed: Не передан ID
9
+ unknown_file_type: Не могу создать файл неизвестного типа %{type}
10
+ swf_type_incorrect: Файл с расширением SWF определяется как %{content_type}
11
+ mini_magick_error: Не могу обработать переданное изображение
12
+ unsupported_image_type: Формат картинок %{type} не поддерживается
13
+
14
+ views:
15
+ no_description: Без описания
16
+ local_date_format: %d.%m.%Y <span class="muted text-muted">в %H:%M</span>
17
+ created_at: Создан %{local_date}
18
+ owner: Владелец
19
+ no_wm: не ставить водяной знак
20
+ save: Сохранить
21
+ description: Описание
@@ -0,0 +1 @@
1
+ Neofiles::Engine.routes.draw(&Neofiles.routes_proc)
@@ -0,0 +1,94 @@
1
+ require 'neofiles/engine'
2
+
3
+ module Neofiles
4
+ # Attach Neofiles specific routes in your routes.rb file:
5
+ #
6
+ # instance_eval &Neofiles.routes_proc
7
+ mattr_accessor :routes_proc
8
+ @@routes_proc = proc do
9
+ scope 'neofiles', module: :neofiles do
10
+ # admin routes
11
+ get '/admin/file_compact/', to: 'admin#file_compact', as: :neofiles_file_compact
12
+ post '/admin/file_save/', to: 'admin#file_save', as: :neofiles_file_save
13
+ post '/admin/file_remove/', to: 'admin#file_remove', as: :neofiles_file_remove
14
+ post '/admin/file_update/', to: 'admin#file_update', as: :neofiles_file_update
15
+
16
+ # admin routes for WYSIWYG editor Redactor.js
17
+ post '/admin/redactor-upload/', to: 'admin#redactor_upload', as: :neofiles_redactor_upload
18
+ get '/admin/redactor-list/:owner_type/:owner_id/:type', to: 'admin#redactor_list', as: :neofiles_redactor_list
19
+
20
+ # web frontend for serving images and other files
21
+ get '/serve/:id', to: 'files#show', as: :neofiles_file
22
+ get '/serve-image/:id(/:format(/c:crop)(/q:quality))', to: 'images#show', as: :neofiles_image, constraints: {format: /[1-9]\d*x[1-9]\d*/, crop: /[10]/, quality: /[1-9]\d*/}
23
+
24
+ # serve images w/o watermark - path has prefix nowm_ to let Nginx ot other web server not cache these queries,
25
+ # unlike usual /serve-image/:id
26
+ get '/nowm-serve-image/:id', to: 'images#show', as: :neofiles_image_nowm, defaults: {nowm: true}
27
+ end
28
+ end
29
+
30
+ # Calculate image dimensions after resize. Returns [w, h] or nil if some info is lacking (e.g. image passed as ID so
31
+ # no width & height available).
32
+ #
33
+ # image_file - Neofiles::Image, ID or Hash
34
+ # width, height - max width and height after resize
35
+ # resize_options - {crop: '1'/'0'}, @see Neofiles::ImagesController#show
36
+ #
37
+ # Can call ImageMagick.
38
+ #
39
+ def resized_image_dimensions(image_file, width, height, resize_options)
40
+ # dimensions are equal to requested ones if cropping
41
+ return width, height if crop_requested? resize_options
42
+
43
+ # otherwise ask ImageMagick - prepare input vars...
44
+ image_file = Neofiles::Image.find image_file if image_file.is_a?(String)
45
+ return nil if image_file.nil?
46
+
47
+ if image_file.is_a? Neofiles::Image
48
+ image_file_width = image_file.width
49
+ image_file_height = image_file.height
50
+ else
51
+ image_file_width = image_file[:width]
52
+ image_file_height = image_file[:height]
53
+ end
54
+
55
+ return if image_file_width.blank? || image_file_height.blank?
56
+
57
+ # ... construct request ...
58
+ command = MiniMagick::CommandBuilder.new(:convert) # convert input file...
59
+ command.size([image_file_width, image_file_height].join 'x') # with the given dimensions...
60
+ command.xc('white') # and filled with whites...
61
+ command.resize([width, height].join 'x') # to fit in the given rectangle...
62
+ command.push('info:-') # return info about the resulting file
63
+
64
+ # ... and send it to ImageMagick
65
+ # the result will be: xc:white XC 54x100 54x100+0+0 16-bit DirectClass 0.070u 0:00.119
66
+ # extract dimensions and return them as array of integers
67
+ MiniMagick::Image.new(nil, nil).run(command).match(/ (\d+)x(\d+) /).values_at(1, 2).map(&:to_i)
68
+
69
+ rescue
70
+ nil
71
+ end
72
+
73
+ # Is request params hash contains crop request?
74
+ def crop_requested?(params)
75
+ params[:crop].present? and params[:crop] != '0'
76
+ end
77
+
78
+ # Is request params hash contains quality request?
79
+ def quality_requested?(params)
80
+ !!quality_requested(params)
81
+ end
82
+
83
+ # The quality value requested, from request params hash.
84
+ def quality_requested(params)
85
+ params[:quality].to_i if params[:quality].present? and params[:quality] != '0'
86
+ end
87
+
88
+ # Is current user considered "admin"? "Admin" means the user can fetch images w/o watermarks.
89
+ def is_admin?(context)
90
+ Rails.application.config.neofiles.try(:current_admin).try(:call, context)
91
+ end
92
+
93
+ module_function :resized_image_dimensions, :crop_requested?, :quality_requested?, :quality_requested, :is_admin?
94
+ end
@@ -0,0 +1,33 @@
1
+ module Neofiles
2
+ class Engine < ::Rails::Engine
3
+ config.autoload_paths << File.expand_path('../..', __FILE__)
4
+ config.neofiles = ActiveSupport::OrderedOptions.new
5
+
6
+ # mongo specific settings
7
+ config.neofiles.mongo_files_collection = 'files.files'
8
+ config.neofiles.mongo_chunks_collection = 'files.chunks'
9
+ config.neofiles.mongo_client = 'neofiles'
10
+ config.neofiles.mongo_default_chunk_size = 4.megabytes
11
+
12
+ # image related settings
13
+ config.neofiles.image_rotate_exif = true # rotate image, if exif contains orientation info
14
+ config.neofiles.image_clean_exif = true # clean all exif fields on save
15
+ config.neofiles.image_max_dimensions = nil # resize huge originals to meaningful size: [w, h], {width: w, height: h}, wh
16
+ config.neofiles.image_max_crop_width = 2000 # users can request resizing only up to this width
17
+ config.neofiles.image_max_crop_height = 2000 # users can request resizing only up to this height
18
+
19
+ # default watermarker — redefine to set special watermarking logic
20
+ # by default, watermark only images larger than 300x300 with watermark at the bottom center, taken from file
21
+ # /app/assets/images/neofiles/watermark.png
22
+ config.neofiles.watermarker = ->(image, no_watermark: false, watermark_width:, watermark_height:){
23
+ if watermark_width < 300 || watermark_height < 300 || no_watermark
24
+ return image.to_blob
25
+ end
26
+
27
+ image.composite(MiniMagick::Image.open(Rails.root.join('app', 'assets', 'images', 'neofiles', 'watermark.png'))) do |c|
28
+ c.gravity 'south'
29
+ c.geometry '200x+0+20'
30
+ end.to_blob
31
+ }
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Neofiles
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: neofiles
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Konanykhin Ilya
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: mongoid
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-imagespec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 0.4.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.4.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: mini_magick
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 3.7.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 3.7.0
69
+ description: 'Library for managing files: creating & storing, linking to file owners,
70
+ serving files from MongoDB'
71
+ email:
72
+ - rails@neolabs.kz
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - LICENSE
78
+ - README.md
79
+ - Rakefile
80
+ - app/assets/images/neofiles/loading.gif
81
+ - app/assets/images/neofiles/swf-thumb-100x100.png
82
+ - app/assets/images/neofiles/watermark.png
83
+ - app/assets/javascripts/neofiles/index.js.coffee
84
+ - app/assets/javascripts/neofiles/jquery.fileupload.js
85
+ - app/assets/javascripts/neofiles/jquery.iframe-transport.js
86
+ - app/assets/javascripts/neofiles/jquery.neofiles.js.coffee
87
+ - app/assets/stylesheets/neofiles/index.css.scss
88
+ - app/assets/stylesheets/neofiles/neofiles.css.scss
89
+ - app/controllers/concerns/neofiles/not_found.rb
90
+ - app/controllers/neofiles/admin_controller.rb
91
+ - app/controllers/neofiles/admin_test_controller.rb
92
+ - app/controllers/neofiles/files_controller.rb
93
+ - app/controllers/neofiles/images_controller.rb
94
+ - app/helpers/neofiles/neofiles_helper.rb
95
+ - app/models/neofiles/file.rb
96
+ - app/models/neofiles/file_chunk.rb
97
+ - app/models/neofiles/image.rb
98
+ - app/models/neofiles/swf.rb
99
+ - app/views/neofiles/admin/_file_compact.html.haml
100
+ - app/views/neofiles/admin/file_compact.html.haml
101
+ - app/views/neofiles/admin_test/file_compact.erb
102
+ - config/locales/ru.yml
103
+ - config/routes.rb
104
+ - lib/neofiles.rb
105
+ - lib/neofiles/engine.rb
106
+ - lib/neofiles/version.rb
107
+ homepage: http://neoweb.kz
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.5.1
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Serves and manages files & images.
131
+ test_files: []