├── test ├── controllers │ ├── .keep │ └── filepond │ │ └── rails │ │ └── ingress_controller_test.rb ├── dummy │ ├── log │ │ └── .keep │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── public │ │ ├── favicon.ico │ │ ├── apple-touch-icon.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ └── .keep │ │ │ ├── config │ │ │ │ └── manifest.js │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── models │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── some_model.rb │ │ │ └── application_record.rb │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── application_controller.rb │ │ │ └── home_controller.rb │ │ ├── views │ │ │ ├── layouts │ │ │ │ ├── mailer.text.erb │ │ │ │ ├── mailer.html.erb │ │ │ │ └── application.html.erb │ │ │ └── home │ │ │ │ └── index.html.erb │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── channels │ │ │ └── application_cable │ │ │ │ ├── channel.rb │ │ │ │ └── connection.rb │ │ ├── mailers │ │ │ └── application_mailer.rb │ │ ├── javascript │ │ │ └── application.js │ │ └── jobs │ │ │ └── application_job.rb │ ├── bin │ │ ├── rake │ │ ├── rails │ │ └── setup │ ├── config │ │ ├── environment.rb │ │ ├── cable.yml │ │ ├── routes.rb │ │ ├── boot.rb │ │ ├── importmap.rb │ │ ├── initializers │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── permissions_policy.rb │ │ │ ├── assets.rb │ │ │ ├── inflections.rb │ │ │ └── content_security_policy.rb │ │ ├── database.yml │ │ ├── application.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── storage.yml │ │ ├── puma.rb │ │ └── environments │ │ │ ├── test.rb │ │ │ ├── development.rb │ │ │ └── production.rb │ ├── db │ │ ├── migrate │ │ │ ├── 20221229032128_create_some_models.rb │ │ │ └── 20221229033425_create_active_storage_tables.active_storage.rb │ │ └── schema.rb │ ├── config.ru │ └── Rakefile ├── fixtures │ └── files │ │ ├── .keep │ │ └── toronto.png ├── filepond │ └── rails_test.rb ├── application_system_test_case.rb ├── test_helper.rb └── system │ └── filepond │ └── rails │ └── uploads_test.rb ├── app ├── assets │ ├── config │ │ └── filepond_rails_manifest.js │ ├── javascripts │ │ └── filepond-rails.js │ └── stylesheets │ │ ├── filepond.min.css │ │ └── filepond.css └── controllers │ └── filepond │ └── rails │ ├── application_controller.rb │ └── ingress_controller.rb ├── lib ├── filepond │ ├── rails │ │ ├── version.rb │ │ └── engine.rb │ └── rails.rb └── tasks │ └── filepond │ └── rails_tasks.rake ├── Rakefile ├── config ├── importmap.rb └── routes.rb ├── .gitignore ├── script └── docker-dev-start.sh ├── Gemfile ├── CHANGELOG.md ├── bin └── rails ├── docker-compose.yml ├── .github └── workflows │ └── ci.yml ├── MIT-LICENSE ├── Dockerfile ├── licenses └── LICENSE-filepond.md ├── filepond-rails.gemspec ├── README.md └── Gemfile.lock /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/assets/config/filepond_rails_manifest.js: -------------------------------------------------------------------------------- 1 | // = link_directory ../javascripts .js 2 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/fixtures/files/toronto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Code-With-Rails/filepond-rails/HEAD/test/fixtures/files/toronto.png -------------------------------------------------------------------------------- /lib/filepond/rails/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Filepond 4 | module Rails 5 | VERSION = '1.0.1' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/some_model.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SomeModel < ApplicationRecord 4 | has_one_attached :picture 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | primary_abstract_class 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require_relative '../config/boot' 5 | require 'rake' 6 | Rake.application.run 7 | -------------------------------------------------------------------------------- /lib/tasks/filepond/rails_tasks.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # desc "Explaining what the task does" 3 | # task :filepond_rails do 4 | # # Task goes here 5 | # end 6 | -------------------------------------------------------------------------------- /app/controllers/filepond/rails/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Filepond 2 | module Rails 3 | class ApplicationController < ActionController::Base 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link filepond_rails_manifest.js 5 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | default from: 'from@example.com' 5 | layout 'mailer' 6 | end 7 | -------------------------------------------------------------------------------- /lib/filepond/rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'filepond/rails/version' 4 | require 'filepond/rails/engine' 5 | 6 | module Filepond 7 | module Rails 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | APP_PATH = File.expand_path('../config/application', __dir__) 5 | require_relative '../config/boot' 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 4 | load "rails/tasks/engine.rake" 5 | 6 | load "rails/tasks/statistics.rake" 7 | 8 | require "bundler/gem_tasks" 9 | -------------------------------------------------------------------------------- /config/importmap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | pin 'filepond', to: 'https://ga.jspm.io/npm:filepond@4.30.4/dist/filepond.js', preload: true 4 | pin_all_from File.expand_path("../app/assets/javascripts", __dir__) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /doc/ 3 | /log/*.log 4 | /pkg/ 5 | /tmp/ 6 | /test/dummy/db/*.sqlite3 7 | /test/dummy/db/*.sqlite3-* 8 | /test/dummy/log/*.log 9 | /test/dummy/storage/ 10 | /test/dummy/tmp/ 11 | 12 | .DS_Store 13 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20221229032128_create_some_models.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateSomeModels < ActiveRecord::Migration[7.0] 4 | def change 5 | create_table :some_models, &:timestamps 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | Rails.application.load_server 9 | -------------------------------------------------------------------------------- /test/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | mount Filepond::Rails::Engine => '/filepond' 5 | 6 | post '/', to: 'home#update' 7 | delete '/', to: 'home#destroy' 8 | root to: 'home#index' 9 | end 10 | -------------------------------------------------------------------------------- /test/filepond/rails_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | module Filepond 6 | class RailsTest < ActiveSupport::TestCase 7 | test 'it has a version number' do 8 | assert Filepond::Rails::VERSION 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | import { FilePondRails, FilePond } from 'filepond-rails' 2 | 3 | window.FilePond = FilePond 4 | window.FilePondRails = FilePondRails 5 | 6 | const input = document.querySelector('.filepond') 7 | window.filePondInstance = FilePondRails.create(input) 8 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) 5 | 6 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 7 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) 8 | -------------------------------------------------------------------------------- /script/docker-dev-start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -xeuo pipefail 4 | 5 | bundle 6 | 7 | if [[ -f ./test/dummy/tmp/pids/server.pid ]]; then 8 | rm ./test/dummy/tmp/pids/server.pid 9 | fi 10 | 11 | cd test/dummy 12 | 13 | bin/rails db:setup 14 | 15 | bin/rails server --port 3000 --binding 0.0.0.0 16 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 6 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: { 7 | browser: :remote, 8 | url: ENV['SELENIUM_REMOTE_URL'] 9 | } 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | # Automatically retry jobs that encountered a deadlock 5 | # retry_on ActiveRecord::Deadlocked 6 | 7 | # Most jobs are safe to ignore if the underlying records are no longer available 8 | # discard_on ActiveJob::DeserializationError 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Pin npm packages by running ./bin/importmap 4 | 5 | pin 'application', preload: true 6 | pin 'filepond', to: 'https://ga.jspm.io/npm:filepond@4.30.4/dist/filepond.js', preload: true 7 | pin '@rails/activestorage', to: 'https://ga.jspm.io/npm:@rails/activestorage@7.0.4/app/assets/javascripts/activestorage.esm.js' 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | # Specify your gem's dependencies in filepond-rails.gemspec. 7 | gemspec 8 | 9 | gem 'image_processing' 10 | gem 'importmap-rails' 11 | gem 'sprockets-rails' 12 | gem 'sqlite3' 13 | 14 | # Start debugger with binding.b [https://github.com/ruby/debug] 15 | # gem "debug", ">= 1.0.0" 16 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Filepond::Rails::Engine.routes.draw do 4 | # For details regarding FilePond endpoints, please refer to https://pqina.nl/filepond/docs/api/server 5 | 6 | # https://pqina.nl/filepond/docs/api/server/#fetch 7 | post 'active_storage/fetch', to: 'ingress#fetch' 8 | 9 | # https://pqina.nl/filepond/docs/api/server/#remove 10 | delete 'active_storage/remove', to: 'ingress#remove' 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | filepond-rails Dummy App 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | <%= stylesheet_link_tag 'application', 'data-turbo-track': 'reload' %> 9 | <%= javascript_importmap_tags %> 10 | 11 | 12 | 13 | <%= yield %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 6 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 7 | # notations and behaviors. 8 | Rails.application.config.filter_parameters += %i[ 9 | passw secret token _key crypt salt certificate otp ssn 10 | ] 11 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Define an application-wide HTTP permissions policy. For further 3 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 4 | # 5 | # Rails.application.config.permissions_policy do |f| 6 | # f.camera :none 7 | # f.gyroscope :none 8 | # f.microphone :none 9 | # f.usb :none 10 | # f.fullscreen :self 11 | # f.payment :self, "https://secure.example.com" 12 | # end 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.1 (January 10, 2023) 4 | * Update license information 5 | * Update README with instructions on running system tests 6 | 7 | ## 1.0.0 (December 29, 2022) 8 | * Add Docker to allow consistent development environment 9 | * Remove unneeded files which were added during scaffolding 10 | * Declare importmap-rails as required 11 | * Add tests for IngressController, system tests and fixes discovered through those tests 12 | * Add GitHub action CI 13 | 14 | ## 0.1.0 (December 25, 2022) 15 | * Initial release 16 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails gems 3 | # installed from the root of your application. 4 | 5 | ENGINE_ROOT = File.expand_path("..", __dir__) 6 | ENGINE_PATH = File.expand_path("../lib/filepond/rails/engine", __dir__) 7 | APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) 8 | 9 | # Set up gems listed in the Gemfile. 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 12 | 13 | require "rails/all" 14 | require "rails/engine/commands" 15 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = '1.0' 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /lib/filepond/rails/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Filepond 4 | module Rails 5 | class Engine < ::Rails::Engine 6 | isolate_namespace Filepond::Rails 7 | 8 | initializer 'filepond-rails.importmap', before: 'importmap' do |app| 9 | app.config.importmap.paths << Engine.root.join('config/importmap.rb') 10 | app.config.importmap.cache_sweepers << Engine.root.join('app/assets/javascripts') 11 | end 12 | 13 | initializer 'filepond-rails.assets.precompile' do |app| 14 | app.config.assets.precompile += %w( filepond-rails.js ) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | networks: 3 | backend: 4 | selenium: 5 | services: 6 | chrome_server: 7 | image: seleniarm/standalone-chromium 8 | volumes: 9 | - /dev/shm:/dev/shm 10 | networks: 11 | - selenium 12 | app: 13 | build: . 14 | tty: true 15 | volumes: 16 | - .:/filepond-rails 17 | working_dir: /filepond-rails 18 | environment: 19 | SELENIUM_REMOTE_URL: http://chrome_server:4444/wd/hub 20 | command: script/docker-dev-start.sh 21 | networks: 22 | - backend 23 | - selenium 24 | ports: 25 | - "3000:3000" 26 | depends_on: 27 | - chrome_server 28 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HomeController < ApplicationController 4 | before_action :setup_model 5 | 6 | def index; end 7 | 8 | def update 9 | @some_model.update(model_params) 10 | redirect_to root_path 11 | end 12 | 13 | def destroy 14 | @some_model.picture.purge 15 | redirect_to root_path 16 | end 17 | 18 | private 19 | 20 | def setup_model 21 | @some_model ||= SomeModel.first_or_create 22 | end 23 | 24 | def model_params 25 | params.require(:some_model).permit(:picture) 26 | rescue ActionController::ParameterMissing 27 | {} 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new inflection rules using the following format. Inflections 5 | # are locale specific, and you may define rules for as many different 6 | # locales as you wish. All of these examples are active by default: 7 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 8 | # inflect.plural /^(ox)$/i, "\\1en" 9 | # inflect.singular /^(ox)en/i, "\\1" 10 | # inflect.irregular "person", "people" 11 | # inflect.uncountable %w( fish sheep ) 12 | # end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym "RESTful" 17 | # end 18 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | *= require 'filepond' 16 | */ 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are 2 | # provided by a third-party and are governed by separate terms of service, 3 | # privacy policy, and support documentation. 4 | # 5 | # This workflow will install a prebuilt Ruby version, install dependencies, and 6 | # run tests and linters. 7 | name: 'CI' 8 | on: 9 | push: 10 | branches: [ 'main' ] 11 | pull_request: 12 | branches: [ 'main' ] 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | - name: Build container 20 | run: docker compose build 21 | - name: Boot containers 22 | run: docker-compose up -d app chrome_server 23 | - name: Run tests 24 | run: docker compose exec -T app bin/rails test 25 | - name: Run system tests 26 | run: docker compose exec -T app bin/rails app:test:system 27 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails/all' 6 | 7 | # Require the gems listed in Gemfile, including any gems 8 | # you've limited to :test, :development, or :production. 9 | Bundler.require(*Rails.groups) 10 | require 'filepond/rails' 11 | 12 | module Dummy 13 | class Application < Rails::Application 14 | config.load_defaults Rails::VERSION::STRING.to_f 15 | 16 | # For compatibility with applications that use this config 17 | config.action_controller.include_all_helpers = false 18 | 19 | # Configuration for the application, engines, and railties goes here. 20 | # 21 | # These settings can be overridden in specific environments using the files 22 | # in config/environments, which are processed later. 23 | # 24 | # config.time_zone = "Central Time (US & Canada)" 25 | # config.eager_load_paths << Rails.root.join("extras") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Configure Rails Environment 4 | ENV['RAILS_ENV'] = 'test' 5 | 6 | require_relative '../test/dummy/config/environment' 7 | ActiveRecord::Migrator.migrations_paths = [File.expand_path('../test/dummy/db/migrate', __dir__)] 8 | ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__) 9 | require 'rails/test_help' 10 | require 'capybara' 11 | 12 | # Load fixtures from the engine 13 | if ActiveSupport::TestCase.respond_to?(:fixture_path=) 14 | ActiveSupport::TestCase.fixture_path = File.expand_path('fixtures', __dir__) 15 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path 16 | ActiveSupport::TestCase.file_fixture_path = "#{ActiveSupport::TestCase.fixture_path}/files" 17 | ActiveSupport::TestCase.fixtures :all 18 | end 19 | 20 | Capybara.server_host = '0.0.0.0' 21 | Capybara.app_host = "http://#{ENV.fetch('HOSTNAME')}:#{Capybara.server_port}" 22 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Simon Chiu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.2-bullseye 2 | 3 | SHELL ["/bin/bash", "--login", "-c"] 4 | 5 | # Install apt based dependencies required to run Rails as 6 | # well as RubyGems. As the Ruby image itself is based on a 7 | # Debian image, we use apt-get to install those. 8 | RUN apt-get update && apt-get install -y build-essential nano libvips 9 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 10 | RUN nvm install 18.12.1 11 | 12 | # Configure the main working directory. This is the base 13 | # directory used in any further RUN, COPY, and ENTRYPOINT 14 | # commands. 15 | RUN mkdir -p /filepond-rails 16 | WORKDIR /filepond-rails 17 | 18 | # Copy the Gemfile as well as the Gemfile.lock and install 19 | # the RubyGems. This is a separate step so the dependencies 20 | # will be cached unless changes to one of those two files 21 | # are made. 22 | COPY lib/filepond/rails/version.rb ./lib/filepond/rails/ 23 | COPY filepond-rails.gemspec ./ 24 | COPY Gemfile* ./ 25 | RUN gem install bundler -v 2.3.3 && bundle install --jobs 20 --retry 5 26 | 27 | COPY . /filepond-rails 28 | RUN rm -rf tmp/* 29 | 30 | ADD . /filepond-rails 31 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'fileutils' 5 | 6 | # path to your application root. 7 | APP_ROOT = File.expand_path('..', __dir__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | FileUtils.chdir APP_ROOT do 14 | # This script is a way to set up or update your development environment automatically. 15 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 16 | # Add necessary setup steps to this file. 17 | 18 | puts '== Installing dependencies ==' 19 | system! 'gem install bundler --conservative' 20 | system('bundle check') || system!('bundle install') 21 | 22 | # puts "\n== Copying sample files ==" 23 | # unless File.exist?("config/database.yml") 24 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 25 | # end 26 | 27 | puts "\n== Preparing database ==" 28 | system! 'bin/rails db:prepare' 29 | 30 | puts "\n== Removing old logs and tempfiles ==" 31 | system! 'bin/rails log:clear tmp:clear' 32 | 33 | puts "\n== Restarting application server ==" 34 | system! 'bin/rails restart' 35 | end 36 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Define an application-wide content security policy. 5 | # See the Securing Rails Applications Guide for more information: 6 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 7 | 8 | # Rails.application.configure do 9 | # config.content_security_policy do |policy| 10 | # policy.default_src :self, :https 11 | # policy.font_src :self, :https, :data 12 | # policy.img_src :self, :https, :data 13 | # policy.object_src :none 14 | # policy.script_src :self, :https 15 | # policy.style_src :self, :https 16 | # # Specify URI for violation reports 17 | # # policy.report_uri "/csp-violation-report-endpoint" 18 | # end 19 | # 20 | # # Generate session nonces for permitted importmap and inline scripts 21 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 22 | # config.content_security_policy_nonce_directives = %w(script-src) 23 | # 24 | # # Report violations without enforcing the policy. 25 | # # config.content_security_policy_report_only = true 26 | # end 27 | -------------------------------------------------------------------------------- /test/dummy/app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 |

filepond-rails development

2 | 3 |
4 | 5 |

This is the "dummy" test app for the filepond-rails gem. Manually testing the dummy app here should be done before any new releases.

6 | 7 |

Steps to test:

8 | 9 |
    10 |
  1. Drag and drop a file and ensure it can be uploaded
  2. 11 |
  3. Drag and drop a URL of an image from a browser bar and ensure it can be uploaded
  4. 12 |
  5. Ensure that cancelling an upload removes the blob from the server
  6. 13 |
14 | 15 |

16 | Picture:
17 | <% if @some_model.picture.attached? %> 18 |
19 | <%= image_tag(@some_model.picture.representation(resize_to_limit: [100, 100])) %> 20 | <%= button_to 'Delete', root_path, method: :delete %> 21 | <% else %> 22 | No picture 23 | <% end %> 24 |

25 | 26 | <%= form_with model: @some_model, url: root_path, method: :post do |f| %> 27 | <%= field_set_tag 'Upload Test Form' do %> 28 | <%= f.file_field :picture, class: 'filepond', direct_upload: true %>

29 | <%= f.button 'Save' %> 30 | <% end %> 31 | <% end %> 32 | 33 |

34 | Versions:
35 | Rails <%= Rails::VERSION::STRING %>
36 | filepond-rails <%= Filepond::Rails::VERSION %> 37 |

38 | -------------------------------------------------------------------------------- /test/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /licenses/LICENSE-filepond.md: -------------------------------------------------------------------------------- 1 | The following files are copied from https://github.com/pqina/filepond/blob/4.30.4: 2 | * filepond.css 3 | * filepond.min.css 4 | 5 | --- 6 | 7 | Original: https://github.com/pqina/filepond/blob/4.30.4/LICENSE 8 | 9 | MIT License 10 | 11 | Copyright (c) 2019 PQINA | Rik Schennink 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | -------------------------------------------------------------------------------- /filepond-rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/filepond/rails/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'filepond-rails' 7 | spec.version = Filepond::Rails::VERSION 8 | spec.authors = ['Simon Chiu'] 9 | spec.email = ['simon@furvur.com'] 10 | spec.homepage = 'https://codewithrails.com/filepond-rails' 11 | spec.summary = 'FilePond integration for Rails' 12 | spec.description = 'Add FilePond server endpoints and form helpers for Rails' 13 | spec.license = 'MIT' 14 | 15 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' 16 | # to allow pushing to a single host or delete this section to allow pushing to any host. 17 | spec.metadata['allowed_push_host'] = 'https://rubygems.org' 18 | 19 | spec.metadata['homepage_uri'] = spec.homepage 20 | spec.metadata['source_code_uri'] = 'https://github.com/Code-With-Rails/filepond-rails' 21 | spec.metadata['changelog_uri'] = 'https://github.com/Code-With-Rails/filepond-rails/blob/main/CHANGELOG.md' 22 | 23 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 24 | Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md'] 25 | end 26 | 27 | spec.add_dependency 'rails', '>= 7.0', '< 8' 28 | spec.add_development_dependency 'puma', '~> 6.0' 29 | spec.add_development_dependency 'rubocop', '~> 1.1' 30 | spec.add_development_dependency 'webmock', '~> 3.18' 31 | spec.add_development_dependency 'capybara' 32 | spec.add_development_dependency 'dropybara' 33 | spec.add_development_dependency 'selenium-webdriver' 34 | end 35 | -------------------------------------------------------------------------------- /test/system/filepond/rails/uploads_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'application_system_test_case' 4 | require 'dropybara' 5 | 6 | class UploadsTest < ApplicationSystemTestCase 7 | setup do 8 | ActionController::Base.allow_forgery_protection = true 9 | visit(root_url) 10 | end 11 | 12 | teardown do 13 | ActionController::Base.allow_forgery_protection = false 14 | end 15 | 16 | test 'goes to homepage' do 17 | assert_selector 'h1', text: 'filepond-rails development' 18 | end 19 | 20 | test 'FilePond is loaded' do 21 | assert page.evaluate_script("typeof(FilePond) !== 'undefined'") 22 | end 23 | 24 | test 'FilePondRails is loaded' do 25 | assert page.evaluate_script("typeof(FilePondRails) !== 'undefined'") 26 | end 27 | 28 | test 'file upload via file upload' do 29 | assert_difference -> { ActiveStorage::Blob.count } do 30 | page.execute_script <<~JS 31 | window.filePondInstance.addFile("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==") 32 | window.filePondInstance.processFile() 33 | JS 34 | sleep(5) # There is a delay in FilePond due to the UI providing feedback 35 | end 36 | end 37 | 38 | test 'file upload via fetch' do 39 | assert_difference -> { ActiveStorage::Blob.count }, 2 do 40 | page.execute_script <<~JS 41 | window.filePondInstance.addFile( 42 | 'https://user-images.githubusercontent.com/16937/209497677-a0ace476-a04f-4efb-be8c-6d263ad5d0e0.png' 43 | ) 44 | window.filePondInstance.processFile() 45 | JS 46 | sleep(5) # There is a delay in FilePond due to the UI providing feedback 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) 10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } 11 | threads min_threads_count, max_threads_count 12 | 13 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 14 | # terminating a worker in development environments. 15 | # 16 | worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development' 17 | 18 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 19 | # 20 | port ENV.fetch('PORT', 3000) 21 | 22 | # Specifies the `environment` that Puma will run in. 23 | # 24 | environment ENV.fetch('RAILS_ENV', 'development') 25 | 26 | # Specifies the `pidfile` that Puma will use. 27 | pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid') 28 | 29 | # Specifies the number of `workers` to boot in clustered mode. 30 | # Workers are forked web server processes. If using threads and workers together 31 | # the concurrency of the application would be max `threads` * `workers`. 32 | # Workers do not work on JRuby or Windows (both of which do not support 33 | # processes). 34 | # 35 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 36 | 37 | # Use the `preload_app!` method when specifying a `workers` number. 38 | # This directive tells Puma to first boot the application and load code 39 | # before forking the application. This takes advantage of Copy On Write 40 | # process behavior so workers use less memory. 41 | # 42 | # preload_app! 43 | 44 | # Allow puma to be restarted by `bin/rails restart` command. 45 | plugin :tmp_restart 46 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/controllers/filepond/rails/ingress_controller_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | module Filepond 6 | module Rails 7 | class IngressControllerTest < ActionDispatch::IntegrationTest 8 | include Engine.routes.url_helpers 9 | 10 | # We're savages and will not stub this out 11 | def url 12 | 'https://user-images.githubusercontent.com/16937/209497677-a0ace476-a04f-4efb-be8c-6d263ad5d0e0.png' 13 | end 14 | 15 | def fetch_request(url:) 16 | post active_storage_fetch_url, env: { 'RAW_POST_DATA' => url } 17 | end 18 | 19 | def blob 20 | @blob ||= ActiveStorage::Blob.create_and_upload!( 21 | io: File.open(file_fixture('toronto.png')), 22 | filename: 'toronto.png' 23 | ) 24 | end 25 | 26 | def remove_request(signed_id:) 27 | delete active_storage_remove_url, env: { 'RAW_POST_DATA' => signed_id } 28 | end 29 | 30 | test '#fetch creates blob' do 31 | assert_difference -> { ActiveStorage::Blob.count } do 32 | fetch_request(url:) 33 | end 34 | end 35 | 36 | test '#fetch redirects to blob' do 37 | fetch_request(url:) 38 | blob = ActiveStorage::Blob.last 39 | assert_redirected_to "/rails/active_storage/blobs/redirect/#{blob.signed_id}/#{blob.filename}" 40 | end 41 | 42 | test '#fetch responds with 422 if something goes wrong' do 43 | fetch_request(url: 'http://nonexistent/404') 44 | assert_response :unprocessable_entity 45 | end 46 | 47 | test '#remove purges blob' do 48 | signed_id = blob.signed_id 49 | 50 | assert_difference -> { ActiveStorage::Blob.count }, -1 do 51 | remove_request(signed_id:) 52 | end 53 | end 54 | 55 | test '#remove returns 200 if blob purged' do 56 | remove_request(signed_id: blob.signed_id) 57 | assert_response :ok 58 | end 59 | 60 | test '#remove returns 404 if blob not found' do 61 | signed_id = 'abcdefg' 62 | 63 | remove_request(signed_id:) 64 | assert_response :not_found 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/assets/javascripts/filepond-rails.js: -------------------------------------------------------------------------------- 1 | import { DirectUpload } from '@rails/activestorage' 2 | import * as FilePond from 'filepond' 3 | 4 | let FilePondRails = { 5 | directUploadUrl: null, 6 | input: null, 7 | default_options: { 8 | server: { 9 | process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => { 10 | const uploader = new DirectUpload(file, FilePondRails.directUploadUrl, { 11 | directUploadWillStoreFileWithXHR: (request) => { 12 | request.upload.addEventListener( 13 | 'progress', 14 | event => progress(event.lengthComputable, event.loaded, event.total) 15 | ) 16 | } 17 | }) 18 | uploader.create((errorResponse, blob) => { 19 | if (errorResponse) { 20 | error(`Something went wrong: ${errorResponse}`) 21 | } else { 22 | const hiddenField = document.createElement('input') 23 | hiddenField.setAttribute('type', 'hidden') 24 | hiddenField.setAttribute('value', blob.signed_id) 25 | hiddenField.name = FilePondRails.input.name 26 | document.querySelector('form').appendChild(hiddenField) 27 | load(blob.signed_id) 28 | } 29 | }) 30 | 31 | return { 32 | abort: () => abort() 33 | } 34 | }, 35 | fetch: { 36 | url: './filepond/active_storage/fetch', 37 | method: 'POST', 38 | onload: (response) => { 39 | console.log(response) 40 | return response 41 | }, 42 | ondata: (response) => { 43 | console.log(response) 44 | return response 45 | } 46 | }, 47 | revert: { 48 | url: './filepond/active_storage/remove' 49 | }, 50 | headers: { 51 | 'X-CSRF-Token': document.head.querySelector("[name='csrf-token']").content 52 | } 53 | } 54 | }, 55 | 56 | // Convenience method to initialize FilePond based on the way this gem expects things to work 57 | create: function(input) { 58 | FilePondRails.directUploadUrl = input.dataset.directUploadUrl 59 | FilePondRails.input = input 60 | 61 | // Initialize FilePond on our element 62 | return FilePond.create(input, FilePondRails.default_options) 63 | } 64 | } 65 | 66 | export { 67 | FilePond as FilePond, 68 | FilePondRails as FilePondRails 69 | } 70 | -------------------------------------------------------------------------------- /app/controllers/filepond/rails/ingress_controller.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | 3 | module Filepond 4 | module Rails 5 | class IngressController < ApplicationController 6 | # FilePond calls this endpoint when a URL is dropped onto the 7 | # upload widget. The server acts as a "proxy" for the client in 8 | # order to load a file. We then redirect to the actual file 9 | # which is now served from our servers, and proceed through the 10 | # usual route. 11 | # 12 | # Note that the implementation below may not be the most efficient. 13 | # The alternative way would be to directly proxy the request to 14 | # the URL where the file is originally hosted. 15 | def fetch 16 | begin 17 | # We explicitly declare this for clarity of what is in the 18 | # raw_post value as sent by FilePond 19 | uri = URI.parse(raw_post) 20 | url = uri.to_s 21 | 22 | blob = ActiveStorage::Blob.create_and_upload!( 23 | io: URI.open(uri), 24 | filename: URI.parse(url).path.parameterize 25 | ) 26 | 27 | redirect_to ::Rails.application.routes.url_helpers.rails_service_blob_path( 28 | blob.signed_id, 29 | blob.filename 30 | ) 31 | # Why we casting such a wide net with StandardError (TL;DR: too many errors to catch): 32 | # See https://stackoverflow.com/a/46979718/20551849 33 | rescue StandardError 34 | head :unprocessable_entity 35 | end 36 | end 37 | 38 | # FilePond calls this endpoint when a user removes (ie. undos) 39 | # a file upload. This ensures that the blob is removed. 40 | def remove 41 | # We explicitly declare this for clarity of what is in the 42 | # raw_post value, as sent by FilePond 43 | signed_id = raw_post 44 | 45 | blob = ActiveStorage::Blob.find_signed(signed_id) 46 | if blob && blob.purge 47 | head :ok 48 | else 49 | # If we cannot find the blob, then we'll just return 404 50 | head :not_found 51 | end 52 | end 53 | 54 | private 55 | 56 | # FilePond sends the value (eg. file ID, URL, etc) and it comes 57 | # through as the POST body. We can retrieve that value with this 58 | # helper. 59 | def raw_post 60 | request.raw_post 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is auto-generated from the current state of the database. Instead 4 | # of editing this file, please use the migrations feature of Active Record to 5 | # incrementally modify your database, and then regenerate this schema definition. 6 | # 7 | # This file is the source Rails uses to define your schema when running `bin/rails 8 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 9 | # be faster and is potentially less error prone than running all of your 10 | # migrations from scratch. Old migrations may fail to apply correctly if those 11 | # migrations use external dependencies or application code. 12 | # 13 | # It's strongly recommended that you check this file into your version control system. 14 | 15 | ActiveRecord::Schema[7.0].define(version: 20_221_229_033_425) do 16 | create_table 'active_storage_attachments', force: :cascade do |t| 17 | t.string 'name', null: false 18 | t.string 'record_type', null: false 19 | t.bigint 'record_id', null: false 20 | t.bigint 'blob_id', null: false 21 | t.datetime 'created_at', null: false 22 | t.index ['blob_id'], name: 'index_active_storage_attachments_on_blob_id' 23 | t.index %w[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', 24 | unique: true 25 | end 26 | 27 | create_table 'active_storage_blobs', force: :cascade do |t| 28 | t.string 'key', null: false 29 | t.string 'filename', null: false 30 | t.string 'content_type' 31 | t.text 'metadata' 32 | t.string 'service_name', null: false 33 | t.bigint 'byte_size', null: false 34 | t.string 'checksum' 35 | t.datetime 'created_at', null: false 36 | t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true 37 | end 38 | 39 | create_table 'active_storage_variant_records', force: :cascade do |t| 40 | t.bigint 'blob_id', null: false 41 | t.string 'variation_digest', null: false 42 | t.index %w[blob_id variation_digest], name: 'index_active_storage_variant_records_uniqueness', unique: true 43 | end 44 | 45 | create_table 'some_models', force: :cascade do |t| 46 | t.datetime 'created_at', null: false 47 | t.datetime 'updated_at', null: false 48 | end 49 | 50 | add_foreign_key 'active_storage_attachments', 'active_storage_blobs', column: 'blob_id' 51 | add_foreign_key 'active_storage_variant_records', 'active_storage_blobs', column: 'blob_id' 52 | end 53 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20221229033425_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This migration comes from active_storage (originally 20170806125915) 4 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 5 | def change 6 | # Use Active Record's configured type for primary and foreign keys 7 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 8 | 9 | create_table :active_storage_blobs, id: primary_key_type do |t| 10 | t.string :key, null: false 11 | t.string :filename, null: false 12 | t.string :content_type 13 | t.text :metadata 14 | t.string :service_name, null: false 15 | t.bigint :byte_size, null: false 16 | t.string :checksum 17 | 18 | if connection.supports_datetime_with_precision? 19 | t.datetime :created_at, precision: 6, null: false 20 | else 21 | t.datetime :created_at, null: false 22 | end 23 | 24 | t.index [:key], unique: true 25 | end 26 | 27 | create_table :active_storage_attachments, id: primary_key_type do |t| 28 | t.string :name, null: false 29 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 30 | t.references :blob, null: false, type: foreign_key_type 31 | 32 | if connection.supports_datetime_with_precision? 33 | t.datetime :created_at, precision: 6, null: false 34 | else 35 | t.datetime :created_at, null: false 36 | end 37 | 38 | t.index %i[record_type record_id name blob_id], name: :index_active_storage_attachments_uniqueness, 39 | unique: true 40 | t.foreign_key :active_storage_blobs, column: :blob_id 41 | end 42 | 43 | create_table :active_storage_variant_records, id: primary_key_type do |t| 44 | t.belongs_to :blob, null: false, index: false, type: foreign_key_type 45 | t.string :variation_digest, null: false 46 | 47 | t.index %i[blob_id variation_digest], name: :index_active_storage_variant_records_uniqueness, unique: true 48 | t.foreign_key :active_storage_blobs, column: :blob_id 49 | end 50 | end 51 | 52 | private 53 | 54 | def primary_and_foreign_key_types 55 | config = Rails.configuration.generators 56 | setting = config.options[config.orm][:primary_key_type] 57 | primary_key_type = setting || :primary_key 58 | foreign_key_type = setting || :bigint 59 | [primary_key_type, foreign_key_type] 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | # The test environment is used exclusively to run your application's 6 | # test suite. You never need to work with it otherwise. Remember that 7 | # your test database is "scratch space" for the test suite and is wiped 8 | # and recreated between test runs. Don't rely on the data there! 9 | 10 | Rails.application.configure do 11 | # Settings specified here will take precedence over those in config/application.rb. 12 | 13 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 14 | config.cache_classes = true 15 | 16 | # Eager loading loads your whole application. When running a single test locally, 17 | # this probably isn't necessary. It's a good idea to do in a continuous integration 18 | # system, or in some way before deploying your code. 19 | config.eager_load = ENV['CI'].present? 20 | 21 | # Configure public file server for tests with Cache-Control for performance. 22 | config.public_file_server.enabled = true 23 | config.public_file_server.headers = { 24 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 25 | } 26 | 27 | # Show full error reports and disable caching. 28 | config.consider_all_requests_local = true 29 | config.action_controller.perform_caching = false 30 | config.cache_store = :null_store 31 | 32 | # Raise exceptions instead of rendering exception templates. 33 | config.action_dispatch.show_exceptions = false 34 | 35 | # Disable request forgery protection in test environment. 36 | config.action_controller.allow_forgery_protection = false 37 | 38 | # Store uploaded files on the local file system in a temporary directory. 39 | config.active_storage.service = :test 40 | 41 | config.action_mailer.perform_caching = false 42 | 43 | # Tell Action Mailer not to deliver emails to the real world. 44 | # The :test delivery method accumulates sent emails in the 45 | # ActionMailer::Base.deliveries array. 46 | config.action_mailer.delivery_method = :test 47 | 48 | # Print deprecation notices to the stderr. 49 | config.active_support.deprecation = :stderr 50 | 51 | # Raise exceptions for disallowed deprecations. 52 | config.active_support.disallowed_deprecation = :raise 53 | 54 | # Tell Active Support which deprecation messages to disallow. 55 | config.active_support.disallowed_deprecation_warnings = [] 56 | 57 | # Raises error for missing translations. 58 | # config.i18n.raise_on_missing_translations = true 59 | 60 | # Annotate rendered view with file names. 61 | # config.action_view.annotate_rendered_view_with_filenames = true 62 | end 63 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # In the development environment your application's code is reloaded any time 9 | # it changes. This slows down response time but is perfect for development 10 | # since you don't have to restart the web server when you make code changes. 11 | config.cache_classes = false 12 | 13 | # Do not eager load code on boot. 14 | config.eager_load = false 15 | 16 | # Show full error reports. 17 | config.consider_all_requests_local = true 18 | 19 | # Enable server timing 20 | config.server_timing = true 21 | 22 | # Enable/disable caching. By default caching is disabled. 23 | # Run rails dev:cache to toggle caching. 24 | if Rails.root.join('tmp/caching-dev.txt').exist? 25 | config.action_controller.perform_caching = true 26 | config.action_controller.enable_fragment_cache_logging = true 27 | 28 | config.cache_store = :memory_store 29 | config.public_file_server.headers = { 30 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 31 | } 32 | else 33 | config.action_controller.perform_caching = false 34 | 35 | config.cache_store = :null_store 36 | end 37 | 38 | # Store uploaded files on the local file system (see config/storage.yml for options). 39 | config.active_storage.service = :local 40 | 41 | # Don't care if the mailer can't send. 42 | config.action_mailer.raise_delivery_errors = false 43 | 44 | config.action_mailer.perform_caching = false 45 | 46 | # Print deprecation notices to the Rails logger. 47 | config.active_support.deprecation = :log 48 | 49 | # Raise exceptions for disallowed deprecations. 50 | config.active_support.disallowed_deprecation = :raise 51 | 52 | # Tell Active Support which deprecation messages to disallow. 53 | config.active_support.disallowed_deprecation_warnings = [] 54 | 55 | # Raise an error on page load if there are pending migrations. 56 | config.active_record.migration_error = :page_load 57 | 58 | # Highlight code that triggered database queries in logs. 59 | config.active_record.verbose_query_logs = true 60 | 61 | # Suppress logger output for asset requests. 62 | config.assets.quiet = true 63 | 64 | # Raises error for missing translations. 65 | # config.i18n.raise_on_missing_translations = true 66 | 67 | # Annotate rendered view with file names. 68 | # config.action_view.annotate_rendered_view_with_filenames = true 69 | 70 | # Uncomment if you wish to allow Action Cable access from any origin. 71 | # config.action_cable.disable_request_forgery_protection = true 72 | end 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filepond-rails 2 | 3 | [![Gem Version](https://badge.fury.io/rb/filepond-rails.svg)](https://badge.fury.io/rb/filepond-rails) 4 | ![Build Status](https://github.com/Code-With-Rails/filepond-rails/actions/workflows/ci.yml/badge.svg) 5 | 6 | This gem allows you to quickly integrate [FilePond](https://github.com/pqina/filepond) with your Ruby on Rails app. 7 | 8 | ## Requirements 9 | * Rails 7.0.x and above 10 | * Use of [importmap-rails](https://github.com/rails/importmap-rails) 11 | 12 | ## Installation 13 | Add this line to your application's Gemfile: 14 | 15 | ```ruby 16 | gem 'filepond-rails' 17 | ``` 18 | 19 | And then run: 20 | ```bash 21 | $ bundle 22 | ``` 23 | 24 | Add the following to your `application.css`: 25 | ```css 26 | *= require 'filepond' 27 | ``` 28 | 29 | Add `javascript_importmap_tags` in the `head` section of your layout: 30 | ```erb 31 | <%= javascript_importmap_tags %> 32 | ``` 33 | 34 | Mount `filepond-rails` routes: 35 | ```ruby 36 | Rails.application.routes.draw do 37 | mount Filepond::Rails::Engine, at: '/filepond' 38 | end 39 | ``` 40 | 41 | Note: You must mount the routes at `/filepond`. 42 | 43 | ## Usage 44 | 45 | This gem compromises of two parts: 46 | 1. An ingress controller for the FilePond `fetch` and `remove` server calls. The JavaScript code leverages Active Storage's existing direct upload endpoint. 47 | 2. JavaScript ESM module to automatically connect configured elements with the above endpoints. 48 | 49 | At the present moment, the above parts are designed (and assumed) to work together. 50 | 51 | The gem assumes that you set up your models and Active Storage configuration in a standard Rails way. Assuming you have a form that looks like this: 52 | 53 | ```erb 54 | <%= form_with model: @user, url: update_avatar_path, method: :post do |f| %> 55 | <%= f.label :avatar, 'Update your avatar' %> 56 | <%= f.file_field :avatar, class: 'filepond', direct_upload: true %> 57 | <%= f.button 'Update' %> 58 | <% end %> 59 | ``` 60 | 61 | You can instantiate the `file_field` component like so: 62 | 63 | ```js 64 | // app/javascript/application.js 65 | import { FilePondRails, FilePond } from 'filepond-rails' 66 | 67 | window.FilePond = FilePond 68 | window.FilePondRails = FilePondRails 69 | 70 | const input = document.querySelector('.filepond') 71 | FilePondRails.create(input) 72 | ``` 73 | 74 | The gem's JavaScript provide two available exports: 75 | 1. `FilePond` (which corresponds to the original FilePond library) 76 | 2. `FilePondRails` (which is a convenience helper that integrates and sets up FilePond to work with our `FilePond::Rails::IngressController` 77 | 78 | ## Development 79 | 80 | filepond-rails is configured with Docker to help developer-contributors be able to easily work on this gem locally. 81 | 82 | To get started: 83 | 84 | 1. Fork this repository 85 | 2. git clone it to your local machine and cd into it 86 | 3. Run `docker compose build` 87 | 88 | To run the gem through the "dummy" app, run: 89 | 90 | ```bash 91 | docker compose up 92 | ``` 93 | 94 | To enter the development environment where you can run `bin/rails g controller` and other commands: 95 | 96 | ```bash 97 | docker compose run app bash 98 | ``` 99 | 100 | You should now be able to run tests locally within this bash container: 101 | 102 | ```bash 103 | bin/rails test && bin/rails app:test:system 104 | ``` 105 | 106 | ## Reference 107 | * Read [How to create a Ruby on Rails gem from your existing code](https://codewithrails.com/create-rails-gem) for a background on this gem 108 | 109 | ## License 110 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 111 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Code is not reloaded between requests. 9 | config.cache_classes = true 10 | 11 | # Eager load code on boot. This eager loads most of Rails and 12 | # your application in memory, allowing both threaded web servers 13 | # and those relying on copy on write to perform better. 14 | # Rake tasks automatically ignore this option for performance. 15 | config.eager_load = true 16 | 17 | # Full error reports are disabled and caching is turned on. 18 | config.consider_all_requests_local = false 19 | config.action_controller.perform_caching = true 20 | 21 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 22 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 23 | # config.require_master_key = true 24 | 25 | # Disable serving static files from the `/public` folder by default since 26 | # Apache or NGINX already handles this. 27 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 28 | 29 | # Compress CSS using a preprocessor. 30 | # config.assets.css_compressor = :sass 31 | 32 | # Do not fallback to assets pipeline if a precompiled asset is missed. 33 | config.assets.compile = false 34 | 35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 36 | # config.asset_host = "http://assets.example.com" 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 40 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 41 | 42 | # Store uploaded files on the local file system (see config/storage.yml for options). 43 | config.active_storage.service = :local 44 | 45 | # Mount Action Cable outside main process or domain. 46 | # config.action_cable.mount_path = nil 47 | # config.action_cable.url = "wss://example.com/cable" 48 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 49 | 50 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 51 | # config.force_ssl = true 52 | 53 | # Include generic and useful information about system operation, but avoid logging too much 54 | # information to avoid inadvertent exposure of personally identifiable information (PII). 55 | config.log_level = :info 56 | 57 | # Prepend all log lines with the following tags. 58 | config.log_tags = [:request_id] 59 | 60 | # Use a different cache store in production. 61 | # config.cache_store = :mem_cache_store 62 | 63 | # Use a real queuing backend for Active Job (and separate queues per environment). 64 | # config.active_job.queue_adapter = :resque 65 | # config.active_job.queue_name_prefix = "dummy_production" 66 | 67 | config.action_mailer.perform_caching = false 68 | 69 | # Ignore bad email addresses and do not raise email delivery errors. 70 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 71 | # config.action_mailer.raise_delivery_errors = false 72 | 73 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 74 | # the I18n.default_locale when a translation cannot be found). 75 | config.i18n.fallbacks = true 76 | 77 | # Don't log any deprecations. 78 | config.active_support.report_deprecations = false 79 | 80 | # Use default logging formatter so that PID and timestamp are not suppressed. 81 | config.log_formatter = ::Logger::Formatter.new 82 | 83 | # Use a different logger for distributed setups. 84 | # require "syslog/logger" 85 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") 86 | 87 | if ENV['RAILS_LOG_TO_STDOUT'].present? 88 | logger = ActiveSupport::Logger.new($stdout) 89 | logger.formatter = config.log_formatter 90 | config.logger = ActiveSupport::TaggedLogging.new(logger) 91 | end 92 | 93 | # Do not dump schema after migrations. 94 | config.active_record.dump_schema_after_migration = false 95 | end 96 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | filepond-rails (1.0.1) 5 | rails (>= 7.0, < 8) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (7.0.4.1) 11 | actionpack (= 7.0.4.1) 12 | activesupport (= 7.0.4.1) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | actionmailbox (7.0.4.1) 16 | actionpack (= 7.0.4.1) 17 | activejob (= 7.0.4.1) 18 | activerecord (= 7.0.4.1) 19 | activestorage (= 7.0.4.1) 20 | activesupport (= 7.0.4.1) 21 | mail (>= 2.7.1) 22 | net-imap 23 | net-pop 24 | net-smtp 25 | actionmailer (7.0.4.1) 26 | actionpack (= 7.0.4.1) 27 | actionview (= 7.0.4.1) 28 | activejob (= 7.0.4.1) 29 | activesupport (= 7.0.4.1) 30 | mail (~> 2.5, >= 2.5.4) 31 | net-imap 32 | net-pop 33 | net-smtp 34 | rails-dom-testing (~> 2.0) 35 | actionpack (7.0.4.1) 36 | actionview (= 7.0.4.1) 37 | activesupport (= 7.0.4.1) 38 | rack (~> 2.0, >= 2.2.0) 39 | rack-test (>= 0.6.3) 40 | rails-dom-testing (~> 2.0) 41 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 42 | actiontext (7.0.4.1) 43 | actionpack (= 7.0.4.1) 44 | activerecord (= 7.0.4.1) 45 | activestorage (= 7.0.4.1) 46 | activesupport (= 7.0.4.1) 47 | globalid (>= 0.6.0) 48 | nokogiri (>= 1.8.5) 49 | actionview (7.0.4.1) 50 | activesupport (= 7.0.4.1) 51 | builder (~> 3.1) 52 | erubi (~> 1.4) 53 | rails-dom-testing (~> 2.0) 54 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 55 | activejob (7.0.4.1) 56 | activesupport (= 7.0.4.1) 57 | globalid (>= 0.3.6) 58 | activemodel (7.0.4.1) 59 | activesupport (= 7.0.4.1) 60 | activerecord (7.0.4.1) 61 | activemodel (= 7.0.4.1) 62 | activesupport (= 7.0.4.1) 63 | activestorage (7.0.4.1) 64 | actionpack (= 7.0.4.1) 65 | activejob (= 7.0.4.1) 66 | activerecord (= 7.0.4.1) 67 | activesupport (= 7.0.4.1) 68 | marcel (~> 1.0) 69 | mini_mime (>= 1.1.0) 70 | activesupport (7.0.4.1) 71 | concurrent-ruby (~> 1.0, >= 1.0.2) 72 | i18n (>= 1.6, < 2) 73 | minitest (>= 5.1) 74 | tzinfo (~> 2.0) 75 | addressable (2.8.1) 76 | public_suffix (>= 2.0.2, < 6.0) 77 | ast (2.4.2) 78 | builder (3.2.4) 79 | capybara (3.38.0) 80 | addressable 81 | matrix 82 | mini_mime (>= 0.1.3) 83 | nokogiri (~> 1.8) 84 | rack (>= 1.6.0) 85 | rack-test (>= 0.6.3) 86 | regexp_parser (>= 1.5, < 3.0) 87 | xpath (~> 3.2) 88 | concurrent-ruby (1.1.10) 89 | crack (0.4.5) 90 | rexml 91 | crass (1.0.6) 92 | date (3.3.3) 93 | dropybara (1.0.0) 94 | erubi (1.12.0) 95 | ffi (1.15.5) 96 | globalid (1.0.1) 97 | activesupport (>= 5.0) 98 | hashdiff (1.0.1) 99 | i18n (1.12.0) 100 | concurrent-ruby (~> 1.0) 101 | image_processing (1.12.2) 102 | mini_magick (>= 4.9.5, < 5) 103 | ruby-vips (>= 2.0.17, < 3) 104 | importmap-rails (1.1.5) 105 | actionpack (>= 6.0.0) 106 | railties (>= 6.0.0) 107 | json (2.6.3) 108 | loofah (2.19.1) 109 | crass (~> 1.0.2) 110 | nokogiri (>= 1.5.9) 111 | mail (2.8.0.1) 112 | mini_mime (>= 0.1.1) 113 | net-imap 114 | net-pop 115 | net-smtp 116 | marcel (1.0.2) 117 | matrix (0.4.2) 118 | method_source (1.0.0) 119 | mini_magick (4.12.0) 120 | mini_mime (1.1.2) 121 | mini_portile2 (2.8.1) 122 | minitest (5.17.0) 123 | net-imap (0.3.4) 124 | date 125 | net-protocol 126 | net-pop (0.1.2) 127 | net-protocol 128 | net-protocol (0.2.1) 129 | timeout 130 | net-smtp (0.3.3) 131 | net-protocol 132 | nio4r (2.5.8) 133 | nokogiri (1.14.0-aarch64-linux) 134 | racc (~> 1.4) 135 | nokogiri (1.14.0-arm64-darwin) 136 | racc (~> 1.4) 137 | nokogiri (1.14.0-x86_64-linux) 138 | racc (~> 1.4) 139 | parallel (1.22.1) 140 | parser (3.1.3.0) 141 | ast (~> 2.4.1) 142 | public_suffix (5.0.1) 143 | puma (6.0.1) 144 | nio4r (~> 2.0) 145 | racc (1.6.2) 146 | rack (2.2.6.2) 147 | rack-test (2.0.2) 148 | rack (>= 1.3) 149 | rails (7.0.4.1) 150 | actioncable (= 7.0.4.1) 151 | actionmailbox (= 7.0.4.1) 152 | actionmailer (= 7.0.4.1) 153 | actionpack (= 7.0.4.1) 154 | actiontext (= 7.0.4.1) 155 | actionview (= 7.0.4.1) 156 | activejob (= 7.0.4.1) 157 | activemodel (= 7.0.4.1) 158 | activerecord (= 7.0.4.1) 159 | activestorage (= 7.0.4.1) 160 | activesupport (= 7.0.4.1) 161 | bundler (>= 1.15.0) 162 | railties (= 7.0.4.1) 163 | rails-dom-testing (2.0.3) 164 | activesupport (>= 4.2.0) 165 | nokogiri (>= 1.6) 166 | rails-html-sanitizer (1.5.0) 167 | loofah (~> 2.19, >= 2.19.1) 168 | railties (7.0.4.1) 169 | actionpack (= 7.0.4.1) 170 | activesupport (= 7.0.4.1) 171 | method_source 172 | rake (>= 12.2) 173 | thor (~> 1.0) 174 | zeitwerk (~> 2.5) 175 | rainbow (3.1.1) 176 | rake (13.0.6) 177 | regexp_parser (2.6.1) 178 | rexml (3.2.5) 179 | rubocop (1.41.1) 180 | json (~> 2.3) 181 | parallel (~> 1.10) 182 | parser (>= 3.1.2.1) 183 | rainbow (>= 2.2.2, < 4.0) 184 | regexp_parser (>= 1.8, < 3.0) 185 | rexml (>= 3.2.5, < 4.0) 186 | rubocop-ast (>= 1.23.0, < 2.0) 187 | ruby-progressbar (~> 1.7) 188 | unicode-display_width (>= 1.4.0, < 3.0) 189 | rubocop-ast (1.24.0) 190 | parser (>= 3.1.1.0) 191 | ruby-progressbar (1.11.0) 192 | ruby-vips (2.1.4) 193 | ffi (~> 1.12) 194 | rubyzip (2.3.2) 195 | selenium-webdriver (4.7.1) 196 | rexml (~> 3.2, >= 3.2.5) 197 | rubyzip (>= 1.2.2, < 3.0) 198 | websocket (~> 1.0) 199 | sprockets (4.2.0) 200 | concurrent-ruby (~> 1.0) 201 | rack (>= 2.2.4, < 4) 202 | sprockets-rails (3.4.2) 203 | actionpack (>= 5.2) 204 | activesupport (>= 5.2) 205 | sprockets (>= 3.0.0) 206 | sqlite3 (1.5.4) 207 | mini_portile2 (~> 2.8.0) 208 | thor (1.2.1) 209 | timeout (0.3.1) 210 | tzinfo (2.0.5) 211 | concurrent-ruby (~> 1.0) 212 | unicode-display_width (2.3.0) 213 | webmock (3.18.1) 214 | addressable (>= 2.8.0) 215 | crack (>= 0.3.2) 216 | hashdiff (>= 0.4.0, < 2.0.0) 217 | websocket (1.2.9) 218 | websocket-driver (0.7.5) 219 | websocket-extensions (>= 0.1.0) 220 | websocket-extensions (0.1.5) 221 | xpath (3.2.0) 222 | nokogiri (~> 1.8) 223 | zeitwerk (2.6.6) 224 | 225 | PLATFORMS 226 | aarch64-linux 227 | arm64-darwin-22 228 | x86_64-linux 229 | 230 | DEPENDENCIES 231 | capybara 232 | dropybara 233 | filepond-rails! 234 | image_processing 235 | importmap-rails 236 | puma (~> 6.0) 237 | rubocop (~> 1.1) 238 | selenium-webdriver 239 | sprockets-rails 240 | sqlite3 241 | webmock (~> 3.18) 242 | 243 | BUNDLED WITH 244 | 2.4.1 245 | -------------------------------------------------------------------------------- /app/assets/stylesheets/filepond.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePond 4.30.4 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | .filepond--assistant{position:absolute;overflow:hidden;height:1px;width:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap}.filepond--browser.filepond--browser{position:absolute;margin:0;padding:0;left:1em;top:1.75em;width:calc(100% - 2em);opacity:0;font-size:0}.filepond--data{position:absolute;width:0;height:0;padding:0;margin:0;border:none;visibility:hidden;pointer-events:none;contain:strict}.filepond--drip{position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden;opacity:.1;pointer-events:none;border-radius:.5em;background:rgba(0,0,0,.01)}.filepond--drip-blob{-webkit-transform-origin:center center;transform-origin:center center;width:8em;height:8em;margin-left:-4em;margin-top:-4em;background:#292625;border-radius:50%}.filepond--drip-blob,.filepond--drop-label{position:absolute;top:0;left:0;will-change:transform,opacity}.filepond--drop-label{right:0;margin:0;color:#4f4f4f;display:flex;justify-content:center;align-items:center;height:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--drop-label.filepond--drop-label label{display:block;margin:0;padding:.5em}.filepond--drop-label label{cursor:default;font-size:.875em;font-weight:400;text-align:center;line-height:1.5}.filepond--label-action{text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto;-webkit-text-decoration-color:#a7a4a4;text-decoration-color:#a7a4a4;cursor:pointer}.filepond--root[data-disabled] .filepond--drop-label label{opacity:.5}.filepond--file-action-button.filepond--file-action-button{font-size:1em;width:1.625em;height:1.625em;font-family:inherit;line-height:inherit;margin:0;padding:0;border:none;outline:none;will-change:transform,opacity}.filepond--file-action-button.filepond--file-action-button span{position:absolute;overflow:hidden;height:1px;width:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap}.filepond--file-action-button.filepond--file-action-button svg{width:100%;height:100%}.filepond--file-action-button.filepond--file-action-button:after{position:absolute;left:-.75em;right:-.75em;top:-.75em;bottom:-.75em;content:""}.filepond--file-action-button{cursor:auto;color:#fff;border-radius:50%;background-color:rgba(0,0,0,.5);background-image:none;box-shadow:0 0 0 0 hsla(0,0%,100%,0);transition:box-shadow .25s ease-in}.filepond--file-action-button:focus,.filepond--file-action-button:hover{box-shadow:0 0 0 .125em hsla(0,0%,100%,.9)}.filepond--file-action-button[disabled]{color:hsla(0,0%,100%,.5);background-color:rgba(0,0,0,.25)}.filepond--file-action-button[hidden]{display:none}.filepond--action-edit-item.filepond--action-edit-item{width:2em;height:2em;padding:.1875em}.filepond--action-edit-item.filepond--action-edit-item[data-align*=center]{margin-left:-.1875em}.filepond--action-edit-item.filepond--action-edit-item[data-align*=bottom]{margin-bottom:-.1875em}.filepond--action-edit-item-alt{border:none;line-height:inherit;background:transparent;font-family:inherit;color:inherit;outline:none;padding:0;margin:0 0 0 .25em;pointer-events:all;position:absolute}.filepond--action-edit-item-alt svg{width:1.3125em;height:1.3125em}.filepond--action-edit-item-alt span{font-size:0;opacity:0}.filepond--file-info{position:static;display:flex;flex-direction:column;align-items:flex-start;flex:1;margin:0 .5em 0 0;min-width:0;will-change:transform,opacity;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--file-info *{margin:0}.filepond--file-info .filepond--file-info-main{font-size:.75em;line-height:1.2;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:100%}.filepond--file-info .filepond--file-info-sub{font-size:.625em;opacity:.5;transition:opacity .25s ease-in-out;white-space:nowrap}.filepond--file-info .filepond--file-info-sub:empty{display:none}.filepond--file-status{position:static;display:flex;flex-direction:column;align-items:flex-end;flex-grow:0;flex-shrink:0;margin:0;min-width:2.25em;text-align:right;will-change:transform,opacity;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--file-status *{margin:0;white-space:nowrap}.filepond--file-status .filepond--file-status-main{font-size:.75em;line-height:1.2}.filepond--file-status .filepond--file-status-sub{font-size:.625em;opacity:.5;transition:opacity .25s ease-in-out}.filepond--file-wrapper.filepond--file-wrapper{border:none;margin:0;padding:0;min-width:0;height:100%}.filepond--file-wrapper.filepond--file-wrapper>legend{position:absolute;overflow:hidden;height:1px;width:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);white-space:nowrap}.filepond--file{position:static;display:flex;height:100%;align-items:flex-start;padding:.5625em;color:#fff;border-radius:.5em}.filepond--file .filepond--file-status{margin-left:auto;margin-right:2.25em}.filepond--file .filepond--processing-complete-indicator{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:3}.filepond--file .filepond--file-action-button,.filepond--file .filepond--processing-complete-indicator,.filepond--file .filepond--progress-indicator{position:absolute}.filepond--file [data-align*=left]{left:.5625em}.filepond--file [data-align*=right]{right:.5625em}.filepond--file [data-align*=center]{left:calc(50% - .8125em)}.filepond--file [data-align*=bottom]{bottom:1.125em}.filepond--file [data-align=center]{top:calc(50% - .8125em)}.filepond--file .filepond--progress-indicator{margin-top:.1875em}.filepond--file .filepond--progress-indicator[data-align*=right]{margin-right:.1875em}.filepond--file .filepond--progress-indicator[data-align*=left]{margin-left:.1875em}[data-filepond-item-state*=error] .filepond--file-info,[data-filepond-item-state*=invalid] .filepond--file-info,[data-filepond-item-state=cancelled] .filepond--file-info{margin-right:2.25em}[data-filepond-item-state~=processing] .filepond--file-status-sub{opacity:0}[data-filepond-item-state~=processing] .filepond--action-abort-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}[data-filepond-item-state=processing-error] .filepond--file-status-sub{opacity:0}[data-filepond-item-state=processing-error] .filepond--action-retry-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing svg{-webkit-animation:fall .5s linear .125s both;animation:fall .5s linear .125s both}[data-filepond-item-state=processing-complete] .filepond--file-status-sub{opacity:.5}[data-filepond-item-state=processing-complete] .filepond--file-info-sub,[data-filepond-item-state=processing-complete] .filepond--processing-complete-indicator:not([style*=hidden])~.filepond--file-status .filepond--file-status-sub{opacity:0}[data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing~.filepond--file-info .filepond--file-info-sub{opacity:.5}[data-filepond-item-state*=error] .filepond--file-wrapper,[data-filepond-item-state*=error] .filepond--panel,[data-filepond-item-state*=invalid] .filepond--file-wrapper,[data-filepond-item-state*=invalid] .filepond--panel{-webkit-animation:shake .65s linear both;animation:shake .65s linear both}[data-filepond-item-state*=busy] .filepond--progress-indicator svg{-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes shake{10%,90%{-webkit-transform:translateX(-.0625em);transform:translateX(-.0625em)}20%,80%{-webkit-transform:translateX(.125em);transform:translateX(.125em)}30%,50%,70%{-webkit-transform:translateX(-.25em);transform:translateX(-.25em)}40%,60%{-webkit-transform:translateX(.25em);transform:translateX(.25em)}}@keyframes shake{10%,90%{-webkit-transform:translateX(-.0625em);transform:translateX(-.0625em)}20%,80%{-webkit-transform:translateX(.125em);transform:translateX(.125em)}30%,50%,70%{-webkit-transform:translateX(-.25em);transform:translateX(-.25em)}40%,60%{-webkit-transform:translateX(.25em);transform:translateX(.25em)}}@-webkit-keyframes fall{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}70%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}to{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes fall{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}70%{opacity:1;-webkit-transform:scale(1.1);transform:scale(1.1);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}to{-webkit-transform:scale(1);transform:scale(1);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.filepond--hopper[data-hopper-state=drag-over]>*{pointer-events:none}.filepond--hopper[data-hopper-state=drag-over]:after{content:"";position:absolute;left:0;top:0;right:0;bottom:0;z-index:100}.filepond--progress-indicator{z-index:103}.filepond--file-action-button{z-index:102}.filepond--file-status{z-index:101}.filepond--file-info{z-index:100}.filepond--item{position:absolute;top:0;left:0;right:0;z-index:1;padding:0;margin:.25em;will-change:transform,opacity}.filepond--item>.filepond--panel{z-index:-1}.filepond--item>.filepond--panel .filepond--panel-bottom{box-shadow:0 .0625em .125em -.0625em rgba(0,0,0,.25)}.filepond--item>.filepond--file-wrapper,.filepond--item>.filepond--panel{transition:opacity .15s ease-out}.filepond--item[data-drag-state]{cursor:-webkit-grab;cursor:grab}.filepond--item[data-drag-state]>.filepond--panel{transition:box-shadow .125s ease-in-out;box-shadow:0 0 0 transparent}.filepond--item[data-drag-state=drag]{cursor:-webkit-grabbing;cursor:grabbing}.filepond--item[data-drag-state=drag]>.filepond--panel{box-shadow:0 .125em .3125em rgba(0,0,0,.325)}.filepond--item[data-drag-state]:not([data-drag-state=idle]){z-index:2}.filepond--item-panel{background-color:#64605e}[data-filepond-item-state=processing-complete] .filepond--item-panel{background-color:#369763}[data-filepond-item-state*=error] .filepond--item-panel,[data-filepond-item-state*=invalid] .filepond--item-panel{background-color:#c44e47}.filepond--item-panel{border-radius:.5em;transition:background-color .25s}.filepond--list-scroller{position:absolute;top:0;left:0;right:0;margin:0;will-change:transform}.filepond--list-scroller[data-state=overflow] .filepond--list{bottom:0;right:0}.filepond--list-scroller[data-state=overflow]{overflow-y:scroll;overflow-x:hidden;-webkit-overflow-scrolling:touch;-webkit-mask:linear-gradient(180deg,#000 calc(100% - .5em),transparent);mask:linear-gradient(180deg,#000 calc(100% - .5em),transparent)}.filepond--list-scroller::-webkit-scrollbar{background:transparent}.filepond--list-scroller::-webkit-scrollbar:vertical{width:1em}.filepond--list-scroller::-webkit-scrollbar:horizontal{height:0}.filepond--list-scroller::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.3);border-radius:99999px;border:.3125em solid transparent;background-clip:content-box}.filepond--list.filepond--list{position:absolute;top:0;margin:0;padding:0;list-style-type:none;will-change:transform}.filepond--list{left:.75em;right:.75em}.filepond--root[data-style-panel-layout~=integrated]{width:100%;height:100%;max-width:none;margin:0}.filepond--root[data-style-panel-layout~=circle] .filepond--panel-root,.filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root{border-radius:0}.filepond--root[data-style-panel-layout~=circle] .filepond--panel-root>*,.filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root>*{display:none}.filepond--root[data-style-panel-layout~=circle] .filepond--drop-label,.filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{bottom:0;height:auto;display:flex;justify-content:center;align-items:center;z-index:7}.filepond--root[data-style-panel-layout~=circle] .filepond--item-panel,.filepond--root[data-style-panel-layout~=integrated] .filepond--item-panel{display:none}.filepond--root[data-style-panel-layout~=compact] .filepond--list-scroller,.filepond--root[data-style-panel-layout~=integrated] .filepond--list-scroller{overflow:hidden;height:100%;margin-top:0;margin-bottom:0}.filepond--root[data-style-panel-layout~=compact] .filepond--list,.filepond--root[data-style-panel-layout~=integrated] .filepond--list{left:0;right:0;height:100%}.filepond--root[data-style-panel-layout~=compact] .filepond--item,.filepond--root[data-style-panel-layout~=integrated] .filepond--item{margin:0}.filepond--root[data-style-panel-layout~=compact] .filepond--file-wrapper,.filepond--root[data-style-panel-layout~=integrated] .filepond--file-wrapper{height:100%}.filepond--root[data-style-panel-layout~=compact] .filepond--drop-label,.filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{z-index:7}.filepond--root[data-style-panel-layout~=circle]{border-radius:99999rem;overflow:hidden}.filepond--root[data-style-panel-layout~=circle]>.filepond--panel{border-radius:inherit}.filepond--root[data-style-panel-layout~=circle]>.filepond--panel>*{display:none}.filepond--root[data-style-panel-layout~=circle] .filepond--file-info,.filepond--root[data-style-panel-layout~=circle] .filepond--file-status{display:none}.filepond--root[data-style-panel-layout~=circle] .filepond--action-edit-item{opacity:1!important;visibility:visible!important}@media not all and (min-resolution:0.001dpcm){@supports (-webkit-appearance:none) and (stroke-color:transparent){.filepond--root[data-style-panel-layout~=circle]{will-change:transform}}}.filepond--panel-root{border-radius:.5em;background-color:#f1f0ef}.filepond--panel{position:absolute;left:0;top:0;right:0;margin:0;height:100%!important;pointer-events:none}.filepond-panel:not([data-scalable=false]){height:auto!important}.filepond--panel[data-scalable=false]>div{display:none}.filepond--panel[data-scalable=true]{-webkit-transform-style:preserve-3d;transform-style:preserve-3d;background-color:transparent!important;border:none!important}.filepond--panel-bottom,.filepond--panel-center,.filepond--panel-top{position:absolute;left:0;top:0;right:0;margin:0;padding:0}.filepond--panel-bottom,.filepond--panel-top{height:.5em}.filepond--panel-top{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important;border-bottom:none!important}.filepond--panel-top:after{content:"";position:absolute;height:2px;left:0;right:0;bottom:-1px;background-color:inherit}.filepond--panel-bottom,.filepond--panel-center{will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:left top;transform-origin:left top;-webkit-transform:translate3d(0,.5em,0);transform:translate3d(0,.5em,0)}.filepond--panel-bottom{border-top-left-radius:0!important;border-top-right-radius:0!important;border-top:none!important}.filepond--panel-bottom:before{content:"";position:absolute;height:2px;left:0;right:0;top:-1px;background-color:inherit}.filepond--panel-center{height:100px!important;border-top:none!important;border-bottom:none!important;border-radius:0!important}.filepond--panel-center:not([style]){visibility:hidden}.filepond--progress-indicator{position:static;width:1.25em;height:1.25em;color:#fff;margin:0;pointer-events:none;will-change:transform,opacity}.filepond--progress-indicator svg{width:100%;height:100%;vertical-align:top;transform-box:fill-box}.filepond--progress-indicator path{fill:none;stroke:currentColor}.filepond--list-scroller{z-index:6}.filepond--drop-label{z-index:5}.filepond--drip{z-index:3}.filepond--root>.filepond--panel{z-index:2}.filepond--browser{z-index:1}.filepond--root{box-sizing:border-box;position:relative;margin-bottom:1em;font-size:1rem;line-height:normal;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-weight:450;text-align:left;text-rendering:optimizeLegibility;direction:ltr;contain:layout style size}.filepond--root *{box-sizing:inherit;line-height:inherit}.filepond--root :not(text){font-size:inherit}.filepond--root[data-disabled]{pointer-events:none}.filepond--root[data-disabled] .filepond--list-scroller{pointer-events:all}.filepond--root[data-disabled] .filepond--list{pointer-events:none}.filepond--root .filepond--drop-label{min-height:4.75em}.filepond--root .filepond--list-scroller{margin-top:1em;margin-bottom:1em}.filepond--root .filepond--credits{position:absolute;right:0;opacity:.175;line-height:.85;font-size:11px;color:inherit;text-decoration:none;z-index:3;bottom:-14px}.filepond--root .filepond--credits[style]{top:0;bottom:auto;margin-top:14px} -------------------------------------------------------------------------------- /app/assets/stylesheets/filepond.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePond 4.30.4 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | .filepond--assistant { 9 | position: absolute; 10 | overflow: hidden; 11 | height: 1px; 12 | width: 1px; 13 | padding: 0; 14 | border: 0; 15 | clip: rect(1px, 1px, 1px, 1px); 16 | -webkit-clip-path: inset(50%); 17 | clip-path: inset(50%); 18 | white-space: nowrap; 19 | } 20 | /* Hard to override styles */ 21 | .filepond--browser.filepond--browser { 22 | /* is positioned absolute so it is focusable for form validation errors */ 23 | position: absolute; 24 | margin: 0; 25 | padding: 0; 26 | 27 | /* is positioned ~behind drop label */ 28 | left: 1em; 29 | top: 1.75em; 30 | width: calc(100% - 2em); 31 | 32 | /* hide visually */ 33 | opacity: 0; 34 | font-size: 0; /* removes text cursor in Internet Explorer 11 */ 35 | } 36 | .filepond--data { 37 | position: absolute; 38 | width: 0; 39 | height: 0; 40 | padding: 0; 41 | margin: 0; 42 | border: none; 43 | visibility: hidden; 44 | pointer-events: none; 45 | contain: strict; 46 | } 47 | .filepond--drip { 48 | position: absolute; 49 | top: 0; 50 | left: 0; 51 | right: 0; 52 | bottom: 0; 53 | overflow: hidden; 54 | opacity: 0.1; 55 | 56 | /* can't interact with this element */ 57 | pointer-events: none; 58 | 59 | /* inherit border radius from parent (needed for drip-blob cut of) */ 60 | border-radius: 0.5em; 61 | 62 | /* this seems to prevent Chrome from redrawing this layer constantly */ 63 | background: rgba(0, 0, 0, 0.01); 64 | } 65 | .filepond--drip-blob { 66 | position: absolute; 67 | -webkit-transform-origin: center center; 68 | transform-origin: center center; 69 | top: 0; 70 | left: 0; 71 | width: 8em; 72 | height: 8em; 73 | margin-left: -4em; 74 | margin-top: -4em; 75 | background: #292625; 76 | border-radius: 50%; 77 | 78 | /* will be animated */ 79 | will-change: transform, opacity; 80 | } 81 | .filepond--drop-label { 82 | position: absolute; 83 | left: 0; 84 | right: 0; 85 | top: 0; 86 | margin: 0; 87 | color: #4f4f4f; 88 | 89 | /* center contents */ 90 | display: flex; 91 | justify-content: center; 92 | align-items: center; 93 | 94 | /* fixes IE11 centering problems (is overruled by label min-height) */ 95 | height: 0px; 96 | 97 | /* dont allow selection */ 98 | -webkit-user-select: none; 99 | -moz-user-select: none; 100 | -ms-user-select: none; 101 | user-select: none; 102 | 103 | /* will be animated */ 104 | will-change: transform, opacity; 105 | } 106 | /* Hard to override styles on purpose */ 107 | .filepond--drop-label.filepond--drop-label label { 108 | display: block; 109 | margin: 0; 110 | padding: 0.5em; /* use padding instead of margin so click area is not impacted */ 111 | } 112 | .filepond--drop-label label { 113 | cursor: default; 114 | font-size: 0.875em; 115 | font-weight: normal; 116 | text-align: center; 117 | line-height: 1.5; 118 | } 119 | .filepond--label-action { 120 | text-decoration: underline; 121 | -webkit-text-decoration-skip: ink; 122 | text-decoration-skip-ink: auto; 123 | -webkit-text-decoration-color: #a7a4a4; 124 | text-decoration-color: #a7a4a4; 125 | cursor: pointer; 126 | } 127 | .filepond--root[data-disabled] .filepond--drop-label label { 128 | opacity: 0.5; 129 | } 130 | /* Hard to override styles */ 131 | .filepond--file-action-button.filepond--file-action-button { 132 | font-size: 1em; 133 | width: 1.625em; 134 | height: 1.625em; 135 | 136 | font-family: inherit; 137 | line-height: inherit; 138 | 139 | margin: 0; 140 | padding: 0; 141 | border: none; 142 | outline: none; 143 | 144 | will-change: transform, opacity; 145 | 146 | /* hidden label */ 147 | } 148 | .filepond--file-action-button.filepond--file-action-button span { 149 | position: absolute; 150 | overflow: hidden; 151 | height: 1px; 152 | width: 1px; 153 | padding: 0; 154 | border: 0; 155 | clip: rect(1px, 1px, 1px, 1px); 156 | -webkit-clip-path: inset(50%); 157 | clip-path: inset(50%); 158 | white-space: nowrap; 159 | } 160 | .filepond--file-action-button.filepond--file-action-button { 161 | /* scale SVG to fill button */ 162 | } 163 | .filepond--file-action-button.filepond--file-action-button svg { 164 | width: 100%; 165 | height: 100%; 166 | } 167 | .filepond--file-action-button.filepond--file-action-button { 168 | /* bigger touch area */ 169 | } 170 | .filepond--file-action-button.filepond--file-action-button::after { 171 | position: absolute; 172 | left: -0.75em; 173 | right: -0.75em; 174 | top: -0.75em; 175 | bottom: -0.75em; 176 | content: ''; 177 | } 178 | /* Soft styles */ 179 | .filepond--file-action-button { 180 | /* use default arrow cursor */ 181 | cursor: auto; 182 | 183 | /* reset default button styles */ 184 | color: #fff; 185 | 186 | /* set default look n feel */ 187 | border-radius: 50%; 188 | background-color: rgba(0, 0, 0, 0.5); 189 | background-image: none; 190 | 191 | /* we animate box shadow on focus */ 192 | /* it's only slightly slower than animating */ 193 | /* a pseudo-element with transforms and renders */ 194 | /* a lot better on chrome */ 195 | box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); 196 | transition: box-shadow 0.25s ease-in; 197 | } 198 | .filepond--file-action-button:hover, 199 | .filepond--file-action-button:focus { 200 | box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.9); 201 | } 202 | .filepond--file-action-button[disabled] { 203 | color: rgba(255, 255, 255, 0.5); 204 | background-color: rgba(0, 0, 0, 0.25); 205 | } 206 | .filepond--file-action-button[hidden] { 207 | display: none; 208 | } 209 | /* edit button */ 210 | .filepond--action-edit-item.filepond--action-edit-item { 211 | width: 2em; 212 | height: 2em; 213 | padding: 0.1875em; 214 | } 215 | .filepond--action-edit-item.filepond--action-edit-item[data-align*='center'] { 216 | margin-left: -0.1875em; 217 | } 218 | .filepond--action-edit-item.filepond--action-edit-item[data-align*='bottom'] { 219 | margin-bottom: -0.1875em; 220 | } 221 | .filepond--action-edit-item-alt { 222 | border: none; 223 | line-height: inherit; 224 | background: transparent; 225 | font-family: inherit; 226 | color: inherit; 227 | outline: none; 228 | padding: 0; 229 | margin: 0 0 0 0.25em; 230 | pointer-events: all; 231 | position: absolute; 232 | } 233 | .filepond--action-edit-item-alt svg { 234 | width: 1.3125em; 235 | height: 1.3125em; 236 | } 237 | .filepond--action-edit-item-alt span { 238 | font-size: 0; 239 | opacity: 0; 240 | } 241 | .filepond--file-info { 242 | position: static; 243 | display: flex; 244 | flex-direction: column; 245 | align-items: flex-start; 246 | flex: 1; 247 | margin: 0 0.5em 0 0; 248 | min-width: 0; 249 | 250 | /* will be animated */ 251 | will-change: transform, opacity; 252 | 253 | /* can't do anything with this info */ 254 | pointer-events: none; 255 | -webkit-user-select: none; 256 | -moz-user-select: none; 257 | -ms-user-select: none; 258 | user-select: none; 259 | 260 | /* no margins on children */ 261 | } 262 | .filepond--file-info * { 263 | margin: 0; 264 | } 265 | .filepond--file-info { 266 | /* we don't want to have these overrules so these selectors are a bit more specific */ 267 | } 268 | .filepond--file-info .filepond--file-info-main { 269 | font-size: 0.75em; 270 | line-height: 1.2; 271 | 272 | /* we want ellipsis if this bar gets too wide */ 273 | text-overflow: ellipsis; 274 | overflow: hidden; 275 | white-space: nowrap; 276 | width: 100%; 277 | } 278 | .filepond--file-info .filepond--file-info-sub { 279 | font-size: 0.625em; 280 | opacity: 0.5; 281 | transition: opacity 0.25s ease-in-out; 282 | white-space: nowrap; 283 | } 284 | .filepond--file-info .filepond--file-info-sub:empty { 285 | display: none; 286 | } 287 | .filepond--file-status { 288 | position: static; 289 | display: flex; 290 | flex-direction: column; 291 | align-items: flex-end; 292 | flex-grow: 0; 293 | flex-shrink: 0; 294 | 295 | margin: 0; 296 | min-width: 2.25em; 297 | text-align: right; 298 | 299 | /* will be animated */ 300 | will-change: transform, opacity; 301 | 302 | /* can't do anything with this info */ 303 | pointer-events: none; 304 | -webkit-user-select: none; 305 | -moz-user-select: none; 306 | -ms-user-select: none; 307 | user-select: none; 308 | 309 | /* no margins on children */ 310 | } 311 | .filepond--file-status * { 312 | margin: 0; 313 | white-space: nowrap; 314 | } 315 | .filepond--file-status { 316 | /* font sizes */ 317 | } 318 | .filepond--file-status .filepond--file-status-main { 319 | font-size: 0.75em; 320 | line-height: 1.2; 321 | } 322 | .filepond--file-status .filepond--file-status-sub { 323 | font-size: 0.625em; 324 | opacity: 0.5; 325 | transition: opacity 0.25s ease-in-out; 326 | } 327 | /* Hard to override styles */ 328 | .filepond--file-wrapper.filepond--file-wrapper { 329 | border: none; 330 | margin: 0; 331 | padding: 0; 332 | min-width: 0; 333 | height: 100%; 334 | 335 | /* hide legend for visual users */ 336 | } 337 | .filepond--file-wrapper.filepond--file-wrapper > legend { 338 | position: absolute; 339 | overflow: hidden; 340 | height: 1px; 341 | width: 1px; 342 | padding: 0; 343 | border: 0; 344 | clip: rect(1px, 1px, 1px, 1px); 345 | -webkit-clip-path: inset(50%); 346 | clip-path: inset(50%); 347 | white-space: nowrap; 348 | } 349 | .filepond--file { 350 | position: static; 351 | display: flex; 352 | height: 100%; 353 | align-items: flex-start; 354 | 355 | padding: 0.5625em 0.5625em; 356 | 357 | color: #fff; 358 | border-radius: 0.5em; 359 | 360 | /* control positions */ 361 | } 362 | .filepond--file .filepond--file-status { 363 | margin-left: auto; 364 | margin-right: 2.25em; 365 | } 366 | .filepond--file .filepond--processing-complete-indicator { 367 | pointer-events: none; 368 | -webkit-user-select: none; 369 | -moz-user-select: none; 370 | -ms-user-select: none; 371 | user-select: none; 372 | z-index: 3; 373 | } 374 | .filepond--file .filepond--processing-complete-indicator, 375 | .filepond--file .filepond--progress-indicator, 376 | .filepond--file .filepond--file-action-button { 377 | position: absolute; 378 | } 379 | .filepond--file { 380 | /* .filepond--file-action-button */ 381 | } 382 | .filepond--file [data-align*='left'] { 383 | left: 0.5625em; 384 | } 385 | .filepond--file [data-align*='right'] { 386 | right: 0.5625em; 387 | } 388 | .filepond--file [data-align*='center'] { 389 | left: calc(50% - 0.8125em); /* .8125 is half of button width */ 390 | } 391 | .filepond--file [data-align*='bottom'] { 392 | bottom: 1.125em; 393 | } 394 | .filepond--file [data-align='center'] { 395 | top: calc(50% - 0.8125em); 396 | } 397 | .filepond--file .filepond--progress-indicator { 398 | margin-top: 0.1875em; 399 | } 400 | .filepond--file .filepond--progress-indicator[data-align*='right'] { 401 | margin-right: 0.1875em; 402 | } 403 | .filepond--file .filepond--progress-indicator[data-align*='left'] { 404 | margin-left: 0.1875em; 405 | } 406 | /* make sure text does not overlap */ 407 | [data-filepond-item-state='cancelled'] .filepond--file-info, 408 | [data-filepond-item-state*='invalid'] .filepond--file-info, 409 | [data-filepond-item-state*='error'] .filepond--file-info { 410 | margin-right: 2.25em; 411 | } 412 | [data-filepond-item-state~='processing'] .filepond--file-status-sub { 413 | opacity: 0; 414 | } 415 | [data-filepond-item-state~='processing'] 416 | .filepond--action-abort-item-processing 417 | ~ .filepond--file-status 418 | .filepond--file-status-sub { 419 | opacity: 0.5; 420 | } 421 | [data-filepond-item-state='processing-error'] .filepond--file-status-sub { 422 | opacity: 0; 423 | } 424 | [data-filepond-item-state='processing-error'] 425 | .filepond--action-retry-item-processing 426 | ~ .filepond--file-status 427 | .filepond--file-status-sub { 428 | opacity: 0.5; 429 | } 430 | [data-filepond-item-state='processing-complete'] { 431 | /* busy state */ 432 | } 433 | [data-filepond-item-state='processing-complete'] .filepond--action-revert-item-processing svg { 434 | -webkit-animation: fall 0.5s 0.125s linear both; 435 | animation: fall 0.5s 0.125s linear both; 436 | } 437 | [data-filepond-item-state='processing-complete'] { 438 | /* hide details by default, only show when can revert */ 439 | } 440 | [data-filepond-item-state='processing-complete'] .filepond--file-status-sub { 441 | opacity: 0.5; 442 | } 443 | [data-filepond-item-state='processing-complete'] 444 | .filepond--processing-complete-indicator:not([style*='hidden']) 445 | ~ .filepond--file-status 446 | .filepond--file-status-sub { 447 | opacity: 0; 448 | } 449 | [data-filepond-item-state='processing-complete'] .filepond--file-info-sub { 450 | opacity: 0; 451 | } 452 | [data-filepond-item-state='processing-complete'] 453 | .filepond--action-revert-item-processing 454 | ~ .filepond--file-info 455 | .filepond--file-info-sub { 456 | opacity: 0.5; 457 | } 458 | /* file state can be invalid or error, both are visually similar but */ 459 | /* having them as separate states might be useful */ 460 | [data-filepond-item-state*='invalid'] .filepond--panel, 461 | [data-filepond-item-state*='invalid'] .filepond--file-wrapper, 462 | [data-filepond-item-state*='error'] .filepond--panel, 463 | [data-filepond-item-state*='error'] .filepond--file-wrapper { 464 | -webkit-animation: shake 0.65s linear both; 465 | animation: shake 0.65s linear both; 466 | } 467 | /* spins progress indicator when file is marked as busy */ 468 | [data-filepond-item-state*='busy'] .filepond--progress-indicator svg { 469 | -webkit-animation: spin 1s linear infinite; 470 | animation: spin 1s linear infinite; 471 | } 472 | /** 473 | * States 474 | */ 475 | @-webkit-keyframes spin { 476 | 0% { 477 | -webkit-transform: rotateZ(0deg); 478 | transform: rotateZ(0deg); 479 | } 480 | 481 | 100% { 482 | -webkit-transform: rotateZ(360deg); 483 | transform: rotateZ(360deg); 484 | } 485 | } 486 | @keyframes spin { 487 | 0% { 488 | -webkit-transform: rotateZ(0deg); 489 | transform: rotateZ(0deg); 490 | } 491 | 492 | 100% { 493 | -webkit-transform: rotateZ(360deg); 494 | transform: rotateZ(360deg); 495 | } 496 | } 497 | @-webkit-keyframes shake { 498 | 10%, 499 | 90% { 500 | -webkit-transform: translateX(-0.0625em); 501 | transform: translateX(-0.0625em); 502 | } 503 | 504 | 20%, 505 | 80% { 506 | -webkit-transform: translateX(0.125em); 507 | transform: translateX(0.125em); 508 | } 509 | 510 | 30%, 511 | 50%, 512 | 70% { 513 | -webkit-transform: translateX(-0.25em); 514 | transform: translateX(-0.25em); 515 | } 516 | 517 | 40%, 518 | 60% { 519 | -webkit-transform: translateX(0.25em); 520 | transform: translateX(0.25em); 521 | } 522 | } 523 | @keyframes shake { 524 | 10%, 525 | 90% { 526 | -webkit-transform: translateX(-0.0625em); 527 | transform: translateX(-0.0625em); 528 | } 529 | 530 | 20%, 531 | 80% { 532 | -webkit-transform: translateX(0.125em); 533 | transform: translateX(0.125em); 534 | } 535 | 536 | 30%, 537 | 50%, 538 | 70% { 539 | -webkit-transform: translateX(-0.25em); 540 | transform: translateX(-0.25em); 541 | } 542 | 543 | 40%, 544 | 60% { 545 | -webkit-transform: translateX(0.25em); 546 | transform: translateX(0.25em); 547 | } 548 | } 549 | @-webkit-keyframes fall { 550 | 0% { 551 | opacity: 0; 552 | -webkit-transform: scale(0.5); 553 | transform: scale(0.5); 554 | -webkit-animation-timing-function: ease-out; 555 | animation-timing-function: ease-out; 556 | } 557 | 558 | 70% { 559 | opacity: 1; 560 | -webkit-transform: scale(1.1); 561 | transform: scale(1.1); 562 | -webkit-animation-timing-function: ease-in-out; 563 | animation-timing-function: ease-in-out; 564 | } 565 | 566 | 100% { 567 | -webkit-transform: scale(1); 568 | transform: scale(1); 569 | -webkit-animation-timing-function: ease-out; 570 | animation-timing-function: ease-out; 571 | } 572 | } 573 | @keyframes fall { 574 | 0% { 575 | opacity: 0; 576 | -webkit-transform: scale(0.5); 577 | transform: scale(0.5); 578 | -webkit-animation-timing-function: ease-out; 579 | animation-timing-function: ease-out; 580 | } 581 | 582 | 70% { 583 | opacity: 1; 584 | -webkit-transform: scale(1.1); 585 | transform: scale(1.1); 586 | -webkit-animation-timing-function: ease-in-out; 587 | animation-timing-function: ease-in-out; 588 | } 589 | 590 | 100% { 591 | -webkit-transform: scale(1); 592 | transform: scale(1); 593 | -webkit-animation-timing-function: ease-out; 594 | animation-timing-function: ease-out; 595 | } 596 | } 597 | /* ignore all other interaction elements while dragging a file */ 598 | .filepond--hopper[data-hopper-state='drag-over'] > * { 599 | pointer-events: none; 600 | } 601 | /* capture all hit tests using a hidden layer, this speeds up the event flow */ 602 | .filepond--hopper[data-hopper-state='drag-over']::after { 603 | content: ''; 604 | position: absolute; 605 | left: 0; 606 | top: 0; 607 | right: 0; 608 | bottom: 0; 609 | z-index: 100; 610 | } 611 | .filepond--progress-indicator { 612 | z-index: 103; 613 | } 614 | .filepond--file-action-button { 615 | z-index: 102; 616 | } 617 | .filepond--file-status { 618 | z-index: 101; 619 | } 620 | .filepond--file-info { 621 | z-index: 100; 622 | } 623 | .filepond--item { 624 | position: absolute; 625 | top: 0; 626 | left: 0; 627 | right: 0; 628 | z-index: 1; 629 | 630 | padding: 0; 631 | margin: 0.25em; 632 | 633 | will-change: transform, opacity; 634 | 635 | /* item children order */ 636 | } 637 | .filepond--item > .filepond--panel { 638 | z-index: -1; 639 | } 640 | /* has a slight shadow */ 641 | .filepond--item > .filepond--panel .filepond--panel-bottom { 642 | box-shadow: 0 0.0625em 0.125em -0.0625em rgba(0, 0, 0, 0.25); 643 | } 644 | .filepond--item { 645 | /* drag related */ 646 | } 647 | .filepond--item > .filepond--file-wrapper, 648 | .filepond--item > .filepond--panel { 649 | transition: opacity 0.15s ease-out; 650 | } 651 | .filepond--item[data-drag-state] { 652 | cursor: -webkit-grab; 653 | cursor: grab; 654 | } 655 | .filepond--item[data-drag-state] > .filepond--panel { 656 | transition: box-shadow 0.125s ease-in-out; 657 | box-shadow: 0 0 0 rgba(0, 0, 0, 0); 658 | } 659 | .filepond--item[data-drag-state='drag'] { 660 | cursor: -webkit-grabbing; 661 | cursor: grabbing; 662 | } 663 | .filepond--item[data-drag-state='drag'] > .filepond--panel { 664 | box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.325); 665 | } 666 | .filepond--item[data-drag-state]:not([data-drag-state='idle']) { 667 | z-index: 2; 668 | } 669 | /* states */ 670 | .filepond--item-panel { 671 | background-color: #64605e; 672 | } 673 | [data-filepond-item-state='processing-complete'] .filepond--item-panel { 674 | background-color: #369763; 675 | } 676 | [data-filepond-item-state*='invalid'] .filepond--item-panel, 677 | [data-filepond-item-state*='error'] .filepond--item-panel { 678 | background-color: #c44e47; 679 | } 680 | /* style of item panel */ 681 | .filepond--item-panel { 682 | border-radius: 0.5em; 683 | transition: background-color 0.25s; 684 | } 685 | /* normal mode */ 686 | .filepond--list-scroller { 687 | position: absolute; 688 | top: 0; 689 | left: 0; 690 | right: 0; 691 | margin: 0; 692 | will-change: transform; 693 | } 694 | /* scroll mode */ 695 | .filepond--list-scroller[data-state='overflow'] .filepond--list { 696 | bottom: 0; 697 | right: 0; 698 | } 699 | .filepond--list-scroller[data-state='overflow'] { 700 | overflow-y: scroll; 701 | overflow-x: hidden; 702 | -webkit-overflow-scrolling: touch; 703 | -webkit-mask: linear-gradient(to bottom, #000 calc(100% - 0.5em), transparent 100%); 704 | mask: linear-gradient(to bottom, #000 calc(100% - 0.5em), transparent 100%); 705 | } 706 | /* style scrollbar */ 707 | .filepond--list-scroller::-webkit-scrollbar { 708 | background: transparent; 709 | } 710 | .filepond--list-scroller::-webkit-scrollbar:vertical { 711 | width: 1em; 712 | } 713 | .filepond--list-scroller::-webkit-scrollbar:horizontal { 714 | height: 0; 715 | } 716 | .filepond--list-scroller::-webkit-scrollbar-thumb { 717 | background-color: rgba(0, 0, 0, 0.3); 718 | border-radius: 99999px; 719 | border: 0.3125em solid transparent; 720 | background-clip: content-box; 721 | } 722 | /* hard to overide styles on purpose */ 723 | .filepond--list.filepond--list { 724 | position: absolute; 725 | top: 0; 726 | margin: 0; 727 | padding: 0; 728 | list-style-type: none; 729 | 730 | /* prevents endless paint calls on filepond--list-scroller */ 731 | will-change: transform; 732 | } 733 | /* used for padding so allowed to be restyled */ 734 | .filepond--list { 735 | left: 0.75em; 736 | right: 0.75em; 737 | } 738 | .filepond--root[data-style-panel-layout~='integrated'] { 739 | width: 100%; 740 | height: 100%; 741 | max-width: none; 742 | margin: 0; 743 | } 744 | .filepond--root[data-style-panel-layout~='circle'] .filepond--panel-root, 745 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--panel-root { 746 | border-radius: 0; 747 | } 748 | .filepond--root[data-style-panel-layout~='circle'] .filepond--panel-root > *, 749 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--panel-root > * { 750 | display: none; 751 | } 752 | .filepond--root[data-style-panel-layout~='circle'] .filepond--drop-label, 753 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--drop-label { 754 | bottom: 0; 755 | height: auto; 756 | display: flex; 757 | justify-content: center; 758 | align-items: center; 759 | z-index: 7; 760 | } 761 | .filepond--root[data-style-panel-layout~='circle'], 762 | .filepond--root[data-style-panel-layout~='integrated'] { 763 | /* we're only loading one item, this makes the intro animation a bit nicer */ 764 | } 765 | .filepond--root[data-style-panel-layout~='circle'] .filepond--item-panel, 766 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--item-panel { 767 | display: none; 768 | } 769 | .filepond--root[data-style-panel-layout~='compact'] .filepond--list-scroller, 770 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--list-scroller { 771 | overflow: hidden; 772 | height: 100%; 773 | margin-top: 0; 774 | margin-bottom: 0; 775 | } 776 | .filepond--root[data-style-panel-layout~='compact'] .filepond--list, 777 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--list { 778 | left: 0; 779 | right: 0; 780 | height: 100%; 781 | } 782 | .filepond--root[data-style-panel-layout~='compact'] .filepond--item, 783 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--item { 784 | margin: 0; 785 | } 786 | .filepond--root[data-style-panel-layout~='compact'] .filepond--file-wrapper, 787 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--file-wrapper { 788 | height: 100%; 789 | } 790 | .filepond--root[data-style-panel-layout~='compact'] .filepond--drop-label, 791 | .filepond--root[data-style-panel-layout~='integrated'] .filepond--drop-label { 792 | z-index: 7; 793 | } 794 | .filepond--root[data-style-panel-layout~='circle'] { 795 | border-radius: 99999rem; 796 | overflow: hidden; 797 | } 798 | .filepond--root[data-style-panel-layout~='circle'] > .filepond--panel { 799 | border-radius: inherit; 800 | } 801 | .filepond--root[data-style-panel-layout~='circle'] > .filepond--panel > * { 802 | display: none; 803 | } 804 | .filepond--root[data-style-panel-layout~='circle'] { 805 | /* circle cuts of this info, so best to hide it */ 806 | } 807 | .filepond--root[data-style-panel-layout~='circle'] .filepond--file-info { 808 | display: none; 809 | } 810 | .filepond--root[data-style-panel-layout~='circle'] .filepond--file-status { 811 | display: none; 812 | } 813 | .filepond--root[data-style-panel-layout~='circle'] .filepond--action-edit-item { 814 | opacity: 1 !important; 815 | visibility: visible !important; 816 | } 817 | /* dirfty way to fix circular overflow issue on safari 11+ */ 818 | @media not all and (min-resolution: 0.001dpcm) { 819 | @supports (-webkit-appearance: none) and (stroke-color: transparent) { 820 | .filepond--root[data-style-panel-layout~='circle'] { 821 | will-change: transform; 822 | } 823 | } 824 | } 825 | .filepond--panel-root { 826 | border-radius: 0.5em; 827 | background-color: #f1f0ef; 828 | } 829 | .filepond--panel { 830 | position: absolute; 831 | left: 0; 832 | top: 0; 833 | right: 0; 834 | margin: 0; 835 | 836 | /* defaults to 100% height (fixed height mode) this fixes problem with panel height in IE11 */ 837 | height: 100% !important; 838 | 839 | /* no interaction possible with panel */ 840 | pointer-events: none; 841 | } 842 | .filepond-panel:not([data-scalable='false']) { 843 | height: auto !important; 844 | } 845 | .filepond--panel[data-scalable='false'] > div { 846 | display: none; 847 | } 848 | .filepond--panel[data-scalable='true'] { 849 | /* this seems to fix Chrome performance issues */ 850 | /* - when box-shadow is enabled */ 851 | /* - when multiple ponds are active on the same page */ 852 | -webkit-transform-style: preserve-3d; 853 | transform-style: preserve-3d; 854 | 855 | /* prevent borders and backgrounds */ 856 | background-color: transparent !important; 857 | border: none !important; 858 | } 859 | .filepond--panel-top, 860 | .filepond--panel-bottom, 861 | .filepond--panel-center { 862 | position: absolute; 863 | left: 0; 864 | top: 0; 865 | right: 0; 866 | margin: 0; 867 | padding: 0; 868 | } 869 | .filepond--panel-top, 870 | .filepond--panel-bottom { 871 | height: 0.5em; 872 | } 873 | .filepond--panel-top { 874 | border-bottom-left-radius: 0 !important; 875 | border-bottom-right-radius: 0 !important; 876 | border-bottom: none !important; 877 | 878 | /* fixes tiny transparant line between top and center panel */ 879 | } 880 | .filepond--panel-top::after { 881 | content: ''; 882 | position: absolute; 883 | height: 2px; 884 | left: 0; 885 | right: 0; 886 | bottom: -1px; 887 | background-color: inherit; 888 | } 889 | .filepond--panel-center, 890 | .filepond--panel-bottom { 891 | will-change: transform; 892 | -webkit-backface-visibility: hidden; 893 | backface-visibility: hidden; 894 | -webkit-transform-origin: left top; 895 | transform-origin: left top; 896 | -webkit-transform: translate3d(0, 0.5em, 0); 897 | transform: translate3d(0, 0.5em, 0); 898 | } 899 | .filepond--panel-bottom { 900 | border-top-left-radius: 0 !important; 901 | border-top-right-radius: 0 !important; 902 | border-top: none !important; 903 | 904 | /* fixes tiny transparant line between bottom and center of panel */ 905 | } 906 | .filepond--panel-bottom::before { 907 | content: ''; 908 | position: absolute; 909 | height: 2px; 910 | left: 0; 911 | right: 0; 912 | top: -1px; 913 | background-color: inherit; 914 | } 915 | .filepond--panel-center { 916 | /* the center panel is scaled using scale3d to fit the correct height */ 917 | /* we use 100px instead of 1px as scaling 1px to a huge height is really laggy on chrome */ 918 | height: 100px !important; 919 | border-top: none !important; 920 | border-bottom: none !important; 921 | border-radius: 0 !important; 922 | 923 | /* hide if not transformed, prevents a little flash when the panel is at 100px height while attached for first time */ 924 | } 925 | .filepond--panel-center:not([style]) { 926 | visibility: hidden; 927 | } 928 | .filepond--progress-indicator { 929 | position: static; 930 | width: 1.25em; 931 | height: 1.25em; 932 | 933 | color: #fff; 934 | 935 | /* can't have margins */ 936 | margin: 0; 937 | 938 | /* no interaction possible with progress indicator */ 939 | pointer-events: none; 940 | 941 | /* will be animated */ 942 | will-change: transform, opacity; 943 | } 944 | .filepond--progress-indicator svg { 945 | width: 100%; 946 | height: 100%; 947 | vertical-align: top; 948 | transform-box: fill-box; /* should center the animation correctly when zoomed in */ 949 | } 950 | .filepond--progress-indicator path { 951 | fill: none; 952 | stroke: currentColor; 953 | } 954 | .filepond--list-scroller { 955 | z-index: 6; 956 | } 957 | .filepond--drop-label { 958 | z-index: 5; 959 | } 960 | .filepond--drip { 961 | z-index: 3; 962 | } 963 | .filepond--root > .filepond--panel { 964 | z-index: 2; 965 | } 966 | .filepond--browser { 967 | z-index: 1; 968 | } 969 | .filepond--root { 970 | /* layout*/ 971 | box-sizing: border-box; 972 | position: relative; 973 | margin-bottom: 1em; 974 | 975 | /* base font size for whole component */ 976 | font-size: 1rem; 977 | 978 | /* base line height */ 979 | line-height: normal; 980 | 981 | /* up uses default system font family */ 982 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 983 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 984 | 985 | /* will increase font weight a bit on Safari */ 986 | font-weight: 450; 987 | 988 | /* default text alignment */ 989 | text-align: left; 990 | 991 | /* better text rendering on Safari */ 992 | text-rendering: optimizeLegibility; 993 | 994 | /* text direction is ltr for now */ 995 | direction: ltr; 996 | 997 | /* optimize rendering */ 998 | /* https://developer.mozilla.org/en-US/docs/Web/CSS/contain */ 999 | contain: layout style size; 1000 | 1001 | /* correct box sizing, line-height and positioning on child elements */ 1002 | } 1003 | .filepond--root * { 1004 | box-sizing: inherit; 1005 | line-height: inherit; 1006 | } 1007 | .filepond--root *:not(text) { 1008 | font-size: inherit; 1009 | } 1010 | .filepond--root { 1011 | /* block everything */ 1012 | } 1013 | .filepond--root[data-disabled] { 1014 | pointer-events: none; 1015 | } 1016 | .filepond--root[data-disabled] .filepond--list-scroller { 1017 | pointer-events: all; 1018 | } 1019 | .filepond--root[data-disabled] .filepond--list { 1020 | pointer-events: none; 1021 | } 1022 | /** 1023 | * Root element children layout 1024 | */ 1025 | .filepond--root .filepond--drop-label { 1026 | min-height: 4.75em; 1027 | } 1028 | .filepond--root .filepond--list-scroller { 1029 | margin-top: 1em; 1030 | margin-bottom: 1em; 1031 | } 1032 | .filepond--root .filepond--credits { 1033 | position: absolute; 1034 | right: 0; 1035 | opacity: 0.175; 1036 | line-height: 0.85; 1037 | font-size: 11px; 1038 | color: inherit; 1039 | text-decoration: none; 1040 | z-index: 3; 1041 | bottom: -14px; 1042 | } 1043 | .filepond--root .filepond--credits[style] { 1044 | top: 0; 1045 | bottom: auto; 1046 | margin-top: 14px; 1047 | } 1048 | --------------------------------------------------------------------------------