EUtils.register_extra_engines! module EL module Finder def self.included base if EUtils.is_app?(base) base.class_exec do private # render a FileManager interface # # by default `el-finder` will use a textarea to edit files. # # to use Ace editor, install and load `el-ace` gem. # see https://github.com/espresso/el-ace for instructions # then simply set editor via `:editor` option: # # @example # finder 'path/to/files', editor: :ace, some: :opts # # to use CKEditor editor, install and load `el-ckeditor` gem. # see https://github.com/espresso/el-ckeditor for instructions # then simply set editor via `:editor` option: # # @example # finder 'path/to/files', editor: :ckeditor, some: :opts # def finder root, opts = {} (action = params[:action]) && (action = action.gsub(/\W/, '').to_sym) if action && self.class::ELFinderController[action] halt self.class::ELFinderController.new(action).call(env.merge(ROOT: root)) end if editor = opts[:editor] editors = [:ace, :ckeditor] editors.include?(editor) || raise(ArgumentError, 'Unknown editor "%s". Use one of :%s' % [escape_html(editor), editors*', :']) end fetch self.class::ELFinderController, :index, params do |env| env.update PARENT_ROUTE: route(action()), EDITOR_OPTS: opts, ROOT: root end end end base.mount_controller base.const_set(:ELFinderController, Class.new(E) { include EUtils include EL::FinderHelpers include EL::Ace if defined?(EL::Ace) include EL::CKE if defined?(EL::CKE) map '/el-finder-controller' engine :Slim view_prefix '/' view_fullpath File.expand_path('../templates', __FILE__).freeze layout false before do path = File.join(root, *params.values_at(:path, :name).compact) @entry = normalize_path(path).freeze end before /post_f/ do @current_entry = @entry @new_entry = File.join(@entry, normalize_path(params[:new_name])) File.directory?(@current_entry) || halt(400, '"%s" should be a directory' % escape_html(params[:name].to_s)) File.exists?(@new_entry) && halt(400, '"%s" already exists' % escape_html(params[:new_name].to_s)) end before /delete_f/ do if action_name == :file File.file?(@entry) || halt(400, '"%s" should be a file' % escape_html(params[:name].to_s)) elsif action_name == :folder File.directory?(@entry) || halt(400, '"%s" should be a directory' % escape_html(params[:name].to_s)) end end before :post_rename do @current_entry = @entry @new_entry = normalize_path(File.join(root, *params.values_at(:path, :new_name).compact)) File.exists?(@current_entry) || halt(400, '"%s" does not exists' % escape_html(params[:name].to_s)) File.exists?(@new_entry) && halt(400, '"%s" already exists' % escape_html(params[:new_name].to_s)) end def index render end def post_file FileUtils.touch(@new_entry) end def post_folder FileUtils.mkdir(@new_entry) end def post_rename FileUtils.mv(@current_entry, @new_entry) end def post_update File.open(@entry, 'w') {|f| f << params[:content]} end def delete_file File.unlink(@entry) end def delete_folder [root, @entry].map {|p| File.expand_path(normalize_path(p))}.uniq.size > 1 && FileUtils.rm_rf(@entry) end def get_image env['PATH_INFO'] = normalize_path(params[:image].to_s) send_file File.join(root, env['PATH_INFO']) end def get_download attachment @entry end def post_upload File.directory?(@entry) || halt(400, '"%s" should be a directory' % escape_html(@entry)) files = params[:files] || halt(400, 'No files given') puts p env files.each do |f| FileUtils.mv f[:tempfile], File.join(@entry, f[:filename]) end end def post_move source, target = params.values_at(:source, :target).map do |p| File.join(root, normalize_path(p)) end FileUtils.mv source, target end def get_assets(*) env['PATH_INFO'] = env['PATH_INFO'].to_s.sub(self.class::ASSETS_REGEXP, '') send_files self.class::ASSETS_PATH end }) else raise ArgumentError, '"%s" is not a Espresso controller' % base end end end module FinderHelpers ASSETS_PATH = File.expand_path('../../../assets', __FILE__).freeze ASSETS_EXT = '.el-finder'.freeze ASSETS_REGEXP = /#{Regexp.escape ASSETS_EXT}\Z/.freeze image_files = %w[.bmp .gif .jpg .jpeg .png .svg .ico .tif .tiff] IMAGE_FILES = image_files.concat(image_files.map(&:upcase)).freeze video_files = %w[.flv .swf .mpg .mpeg .asf .wmv .mov .mp4 .m4v .avi] VIDEO_FILES = video_files.concat(video_files.map(&:upcase)).freeze # TODO: find a more cross-platform way of detecting editable files def editable? file %x[file -b --mime "#{file}"].split(';').first.to_s =~ /text|empty/i end # Array#include is slow enough, so building a Hash on first request # then using hash lookup which is a lot faster def image? file (@@image_files ||= Hash[IMAGE_FILES.zip(IMAGE_FILES)])[File.extname(file)] end def video? file (@@video_files ||= Hash[VIDEO_FILES.zip(VIDEO_FILES)])[File.extname(file)] end def browseable? file image?(file) || video?(file) end def parent_route(action = nil, params = {}) action.is_a?(Hash) && (params = action) && (action = nil) params = params.merge(action: action.to_s) if action env[:PARENT_ROUTE] + (params.any? ? '?' << build_nested_query(params) : '') end def root env[:ROOT].to_s end def asset_url file route(:assets, file + self.class::ASSETS_EXT) end end end