├── spec ├── dummy │ ├── log │ │ └── .gitkeep │ ├── app │ │ ├── mailers │ │ │ └── .gitkeep │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── comment.rb │ │ │ └── post.rb │ │ ├── helpers │ │ │ ├── posts_helper.rb │ │ │ ├── comments_helper.rb │ │ │ └── application_helper.rb │ │ ├── assets │ │ │ ├── stylesheets │ │ │ │ ├── application.scss │ │ │ │ └── main.scss │ │ │ └── javascripts │ │ │ │ ├── remote_posts.js │ │ │ │ ├── comments.js │ │ │ │ └── application.js │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ ├── simple_form_posts_controller.rb │ │ │ ├── comments_controller.rb │ │ │ └── posts_controller.rb │ │ └── views │ │ │ ├── posts │ │ │ ├── new.html.erb │ │ │ ├── show.html.erb │ │ │ ├── _post.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── index.html.erb │ │ │ ├── _form.html.erb │ │ │ └── new_with_comment.html.erb │ │ │ ├── simple_form_posts │ │ │ ├── new.html.erb │ │ │ └── _form.html.erb │ │ │ ├── comments │ │ │ ├── _index.html.erb │ │ │ ├── _new.html.erb │ │ │ └── _show.html.erb │ │ │ ├── remote_posts │ │ │ └── _form.html.erb │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── public │ │ ├── favicon.ico │ │ ├── test.fake │ │ ├── test.jpg │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── config.ru │ ├── config │ │ ├── initializers │ │ │ ├── cookies_serializer.rb │ │ │ ├── session_store.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── new_framework_defaults.rb │ │ │ ├── simple_form_bootstrap.rb │ │ │ └── simple_form.rb │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── routes.rb │ │ ├── secrets.yml │ │ ├── database.yml │ │ ├── locales │ │ │ └── simple_form.en.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ │ └── application.rb │ ├── db │ │ ├── migrate │ │ │ ├── 20120624163943_create_posts.rb │ │ │ ├── 20120710181942_create_comments.rb │ │ │ ├── 20120628174731_create_bootsy_images.bootsy.rb │ │ │ └── 20120628174732_create_bootsy_image_galleries.bootsy.rb │ │ └── schema.rb │ ├── Rakefile │ ├── script │ │ └── rails │ ├── lib │ │ └── templates │ │ │ └── erb │ │ │ └── scaffold │ │ │ └── _form.html.erb │ └── README.rdoc ├── lib │ ├── engine_spec.rb │ ├── core_ext_spec.rb │ ├── form_builder_spec.rb │ ├── bootsy_spec.rb │ ├── contaier_spec.rb │ ├── simple_form │ │ └── bootsy_input_spec.rb │ └── form_helper_spec.rb ├── factories │ ├── image_factory.rb │ └── image_gallery_factory.rb ├── models │ └── bootsy │ │ ├── image_spec.rb │ │ └── image_gallery_spec.rb ├── features │ ├── simple_form_spec.rb │ ├── unsaved_changes_prompt.rb │ ├── youtube_support_spec.rb │ ├── nested_attributes_spec.rb │ ├── remote_form_spec.rb │ ├── image_gallery_modal_spec.rb │ ├── hidden_editor_spec.rb │ ├── image_deletion_spec.rb │ ├── foreign_gallery_spec.rb │ ├── image_insertion_spec.rb │ ├── editor_customization_spec.rb │ └── image_upload_spec.rb ├── helpers │ └── application_helper_spec.rb ├── uploaders │ └── image_uploader_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── .rspec ├── db ├── .rubocop.yml └── migrate │ ├── 20120624171333_create_bootsy_images.rb │ └── 20120628124845_create_bootsy_image_galleries.rb ├── lib ├── .rubocop.yml ├── bootsy │ ├── version.rb │ ├── core_ext.rb │ ├── activerecord │ │ ├── image.rb │ │ └── image_gallery.rb │ ├── form_builder.rb │ ├── simple_form │ │ └── bootsy_input.rb │ ├── engine.rb │ ├── container.rb │ └── form_helper.rb └── bootsy.rb ├── app ├── .rubocop.yml ├── controllers │ └── bootsy │ │ ├── application_controller.rb │ │ └── images_controller.rb ├── assets │ ├── javascripts │ │ ├── bootsy.js │ │ └── bootsy │ │ │ ├── image_template.js │ │ │ ├── init.js │ │ │ ├── vendor │ │ │ ├── polyfill.js │ │ │ ├── bootstrap.file-input.js │ │ │ └── bootstrap-wysihtml5.js │ │ │ ├── editor_options.js │ │ │ ├── locales │ │ │ ├── en.js │ │ │ └── pt-BR.js │ │ │ ├── area.js │ │ │ └── modal.js │ └── stylesheets │ │ └── bootsy.css ├── helpers │ └── bootsy │ │ └── application_helper.rb ├── views │ └── bootsy │ │ └── images │ │ ├── _new.html.erb │ │ ├── _modal.html.erb │ │ ├── _image.html.erb │ │ └── _loader.html.erb └── uploaders │ └── bootsy │ └── image_uploader.rb ├── .travis.yml ├── .gitignore ├── .rubocop.yml ├── Rakefile ├── config ├── routes.rb ├── locales │ ├── bootsy.en.yml │ └── bootsy.pt-BR.yml └── initializers │ └── bootsy.rb ├── Gemfile ├── bootsy.gemspec ├── MIT-LICENSE ├── README.md ├── CHANGELOG.md └── Gemfile.lock /spec/dummy/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /db/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - ../app/.rubocop.yml 3 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module PostsHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/public/test.fake: -------------------------------------------------------------------------------- 1 | This file is used for testing invalid image uploads. 2 | -------------------------------------------------------------------------------- /lib/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - ../.rubocop.yml 3 | Style/ClassVars: 4 | Enabled: false 5 | -------------------------------------------------------------------------------- /app/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - ../.rubocop.yml 3 | Style/Documentation: 4 | Enabled: false 5 | -------------------------------------------------------------------------------- /lib/bootsy/version.rb: -------------------------------------------------------------------------------- 1 | # Public: The gem version 2 | module Bootsy 3 | VERSION = '2.3.1'.freeze 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/public/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrevorHinesley/bootsy/master/spec/dummy/public/test.jpg -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import 'bootstrap-sprockets'; 2 | @import 'bootstrap'; 3 | @import 'main'; 4 | @import 'bootsy'; 5 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | end 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.1 4 | before_script: 5 | - 'cd spec/dummy' 6 | - 'RAILS_ENV=test bundle exec rails db:migrate' 7 | - 'cd ../..' 8 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :hybrid 4 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /lib/bootsy/core_ext.rb: -------------------------------------------------------------------------------- 1 | # Extend and include Bootsy in proper scopes. 2 | 3 | ActionView::Base.send(:include, Bootsy::FormHelper) 4 | ActionView::Helpers::FormBuilder.send(:include, Bootsy::FormBuilder) 5 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/new.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 | <%= render 'form' %> 6 | 7 | <%= link_to 'Cancel', posts_path, class: 'btn btn-default' %> 8 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | body { 2 | padding-bottom: 40px; 3 | } 4 | 5 | #main-container { 6 | margin-top: 20px; 7 | } 8 | 9 | .sidebar-nav { 10 | padding: 9px 0; 11 | } 12 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render @post %> 2 | 3 | <%= link_to 'Edit', edit_post_path(@post), class: 'btn btn-default' %> 4 | 5 |
6 | 7 | <%= render 'comments/index', { post: @post } %> 8 | -------------------------------------------------------------------------------- /spec/dummy/app/views/simple_form_posts/new.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 | <%= render 'form' %> 6 | 7 | <%= link_to 'Cancel', posts_path, class: 'btn' %> 8 | -------------------------------------------------------------------------------- /spec/dummy/app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | if Rails::VERSION::STRING.split(".").first.to_i >= 5 3 | belongs_to :post, optional: true 4 | else 5 | belongs_to :post 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/_post.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | <%= raw post.content %> 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | spec/dummy/db/*.sqlite3 5 | spec/dummy/log/*.log 6 | spec/dummy/tmp/ 7 | spec/dummy/.sass-cache 8 | spec/dummy/public/bootsy_uploads 9 | spec/dummy/public/uploads 10 | coverage/ 11 | -------------------------------------------------------------------------------- /spec/lib/engine_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy::Engine do 4 | it 'includes helpers in ApplicationController' do 5 | expect(ApplicationController._helpers.included_modules).to include(Bootsy::ApplicationHelper) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.3 3 | Exclude: 4 | - 'spec/**/*' 5 | - 'script/*' 6 | Metrics/MethodLength: 7 | Max: 15 8 | 9 | Rails: 10 | Enabled: true 11 | 12 | Style/FrozenStringLiteralComment: 13 | Enabled: false 14 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | 6 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120624163943_create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration 2 | def change 3 | create_table :posts do |t| 4 | t.string :title 5 | t.text :content 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/image_factory.rb: -------------------------------------------------------------------------------- 1 | include ActionDispatch::TestProcess 2 | 3 | FactoryGirl.define do 4 | factory :image, class: Bootsy::Image do 5 | image_file { fixture_file_upload(Rails.root.to_s + '/public/test.jpg', 'image/jpeg') } 6 | image_gallery 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20120624171333_create_bootsy_images.rb: -------------------------------------------------------------------------------- 1 | class CreateBootsyImages < ActiveRecord::Migration 2 | def change 3 | create_table :bootsy_images do |t| 4 | t.string :image_file 5 | t.references :image_gallery 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20120628124845_create_bootsy_image_galleries.rb: -------------------------------------------------------------------------------- 1 | class CreateBootsyImageGalleries < ActiveRecord::Migration 2 | def change 3 | create_table :bootsy_image_galleries do |t| 4 | t.references :bootsy_resource, polymorphic: true 5 | t.timestamps 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | require 'rubocop/rake_task' 4 | 5 | RuboCop::RakeTask.new(:rubocop) do |task| 6 | task.patterns = ['app/**/*.rb', 'lib/**/*.rb'] 7 | end 8 | RSpec::Core::RakeTask.new(:spec) 9 | 10 | task default: [:rubocop, :spec] 11 | -------------------------------------------------------------------------------- /app/controllers/bootsy/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | class ApplicationController < Bootsy.base_controller 3 | # Prevent CSRF attacks by raising an exception. 4 | # For APIs, you may want to use :null_session instead. 5 | protect_from_forgery with: :exception 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/views/simple_form_posts/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for(@post, url: simple_form_posts_path) do |f| %> 2 | <%= f.error_notification %> 3 | 4 | <%= f.input :title %> 5 | <%= f.input :content, as: :bootsy, input_html: { rows: 16 } %> 6 | 7 | <%= f.button :submit %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/edit.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 | <%= render 'form' %> 6 | 7 | <%= link_to 'Cancel', @post, class: 'btn btn-default' %> 8 | <%= link_to 'Destroy', @post, method: :delete, class: 'btn btn-default', data: { confirm: 'Are you sure?' } %> 9 | -------------------------------------------------------------------------------- /spec/dummy/app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ActiveRecord::Base 2 | include Bootsy::Container 3 | 4 | default_scope { order(:created_at).reverse_order } 5 | 6 | has_many :comments, dependent: :destroy 7 | 8 | validates_presence_of :title, :content 9 | 10 | accepts_nested_attributes_for :comments 11 | end 12 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Bootsy::Engine.routes.draw do 2 | resources :image_galleries, only: [] do 3 | resources :images, only: [:index, :create, :destroy] 4 | end 5 | 6 | file_routes = [:index, :create] 7 | 8 | file_routes << :destroy if Bootsy.allow_destroy 9 | 10 | resources :images, only: file_routes 11 | end 12 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | mount Bootsy::Engine => '/bootsy', as: 'bootsy' 3 | 4 | resources :posts do 5 | resources :comments, only: [:create, :destroy, :update] 6 | end 7 | 8 | resources :simple_form_posts, only: [:new, :create] 9 | 10 | root to: 'posts#index' 11 | end 12 | -------------------------------------------------------------------------------- /spec/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120710181942_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.string :author 5 | t.text :content 6 | t.references :post 7 | 8 | t.timestamps 9 | end 10 | add_index :comments, :post_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120628174731_create_bootsy_images.bootsy.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from bootsy (originally 20120624171333) 2 | class CreateBootsyImages < ActiveRecord::Migration 3 | def change 4 | create_table :bootsy_images do |t| 5 | t.string :image_file 6 | t.references :image_gallery 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20120628174732_create_bootsy_image_galleries.bootsy.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from bootsy (originally 20120628124845) 2 | class CreateBootsyImageGalleries < ActiveRecord::Migration 3 | def change 4 | create_table :bootsy_image_galleries do |t| 5 | t.references :bootsy_resource, polymorphic: true 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/dummy/app/views/comments/_index.html.erb: -------------------------------------------------------------------------------- 1 |

Comments

2 | 3 |
4 |
5 | <% post.comments.each do |comment| %> 6 | <%= render 'comments/show', { comment: comment } %> 7 | <% end %> 8 | 9 |
10 | 11 | <%= render 'comments/new', { comment: (defined?(comment) ? comment : post.comments.new) } %> 12 |
13 |
14 | -------------------------------------------------------------------------------- /spec/models/bootsy/image_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy::Image do 4 | describe '#image_file' do 5 | it 'is a Bootsy uploader' do 6 | expect(subject.image_file).to be_a(Bootsy::ImageUploader) 7 | end 8 | 9 | it 'is required' do 10 | subject.valid? 11 | 12 | expect(subject.errors[:image_file].size).to eq 1 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy.js: -------------------------------------------------------------------------------- 1 | //= require bootsy/vendor/polyfill 2 | //= require bootsy/vendor/wysihtml5 3 | //= require bootsy/vendor/bootstrap-wysihtml5 4 | //= require bootsy/vendor/bootstrap.file-input 5 | //= require bootsy/area 6 | //= require bootsy/editor_options 7 | //= require bootsy/image_template 8 | //= require bootsy/init 9 | //= require bootsy/locales/en 10 | //= require bootsy/modal 11 | -------------------------------------------------------------------------------- /app/helpers/bootsy/application_helper.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | module ApplicationHelper 3 | def refresh_btn 4 | link_to t('bootsy.action.refresh'), 5 | '#refresh-gallery', 6 | class: 'btn btn-default btn-sm refresh-btn' 7 | end 8 | 9 | def resource_or_nil(resource) 10 | resource if resource.present? && resource.persisted? 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/dummy/config/secrets.yml: -------------------------------------------------------------------------------- 1 | development: 2 | secret_key_base: '78375cae3aa8e30cb8fce028f8faffb93d5d73214b16248b7f222f7fc37203ee0c7799193e7f79b61614b3a467eb41a213b0cc6a869a3d6acf8dfb753eb9ad2e' 3 | 4 | test: 5 | secret_key_base: '78375cae3aa8e30cb8fce028f8faffb93d5d73214b16248b7f222f7fc37203ee0c7799193e7f79b61614b3a467eb41a213b0cc6a869a3d6acf8dfb753eb9ad2e' 6 | 7 | production: 8 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 9 | -------------------------------------------------------------------------------- /spec/factories/image_gallery_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :image_gallery, class: Bootsy::ImageGallery do 3 | factory :image_gallery_with_images do 4 | transient do 5 | images_count 3 6 | end 7 | 8 | after :create do |image_gallery, evaluator| 9 | FactoryGirl.create_list :image, evaluator.images_count, image_gallery: image_gallery 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/bootsy/activerecord/image.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | # Public: Model to reference the actual image stored trough Bootsy. 3 | # It contains the CarrierWave uploader and belongs to a 4 | # particular image gallery. 5 | class Image < ActiveRecord::Base 6 | belongs_to :image_gallery, touch: true 7 | 8 | mount_uploader :image_file, ImageUploader 9 | 10 | validates_presence_of :image_file, :image_gallery_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/dummy/lib/templates/erb/scaffold/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%%= simple_form_for(@<%= singular_table_name %>) do |f| %> 2 | <%%= f.error_notification %> 3 | 4 |
5 | <%- attributes.each do |attribute| -%> 6 | <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> 7 | <%- end -%> 8 |
9 | 10 |
11 | <%%= f.button :submit %> 12 |
13 | <%% end %> 14 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /spec/lib/core_ext_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'Core Extensions' do 4 | describe ActionView::Helpers::FormBuilder do 5 | it 'includes Bootsy::FormBuilder' do 6 | expect(ActionView::Helpers::FormBuilder).to include(Bootsy::FormBuilder) 7 | end 8 | end 9 | 10 | describe ActionView::Base do 11 | it 'includes Bootsy::FormHelper' do 12 | expect(ActionView::Base).to include(Bootsy::FormHelper) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | # Gems used by the dummy application 4 | gem 'rails', '~> 5.0' 5 | gem 'jquery-rails' 6 | gem 'sass-rails' 7 | gem 'bootstrap-sass' 8 | gem 'sqlite3' 9 | gem 'simple_form' 10 | gem 'sprockets-rails' 11 | 12 | gemspec 13 | 14 | # Development dependencies 15 | gem 'rspec-rails' 16 | gem 'factory_girl_rails' 17 | gem 'database_cleaner' 18 | gem 'capybara' 19 | gem 'poltergeist' 20 | gem 'pry-rails' 21 | gem 'rubocop' 22 | gem 'sham_rack', require: false 23 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/image_template.js: -------------------------------------------------------------------------------- 1 | window.Bootsy = window.Bootsy || {}; 2 | 3 | window.Bootsy.imageTemplate = function(locale, options) { 4 | var size = (options && options.size) ? ' btn-' + options.size : ''; 5 | 6 | return '
  • ' + 7 | '' + 8 | '' + 9 | '' + 10 | '
  • '; 11 | }; 12 | -------------------------------------------------------------------------------- /spec/features/simple_form_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'simple form', type: :feature, js: true do 4 | it 'is compatible with Bootsy' do 5 | visit new_simple_form_post_path 6 | click_on 'Insert image' 7 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 8 | selector = "//div[contains(@class, 'bootsy-gallery')]//img[contains(@src, "\ 9 | "'/thumb_test.jpg')]" 10 | 11 | expect(page).to have_selector(:xpath, selector, visible: true) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= link_to 'New post', new_post_path, class: 'btn btn-default' %> 3 | <%= link_to 'New post with Simple Form', new_simple_form_post_path, class: 'btn btn-default' %> 4 | <%= link_to 'New remote post', '#new-remote-post', class: 'btn btn-default' %> 5 | <%= link_to 'New post with comment', new_post_path(with_comment: true), class: 'btn btn-default' %> 6 |
    7 | 8 | <%= render 'remote_posts/form' %> 9 | 10 |
    11 | <% @posts.each do |post| %> 12 | <%= render post %> 13 | <% end %> 14 |
    15 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/remote_posts.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var form = $('#new-remote-post'); 3 | 4 | if(form.length > 0) { 5 | form.hide(); 6 | 7 | $('a[href="#new-remote-post"]').on('click', function(e){ 8 | form.show(); 9 | 10 | e.preventDefault(); 11 | }); 12 | } 13 | 14 | $(document).on('ajax:success', '#new-remote-post', function(evt, data) { 15 | $('#new-remote-post').hide(); 16 | $(data.post).prependTo('#posts'); 17 | }); 18 | 19 | $(document).on('ajax:error', '#new-remote-post', function(evt, data, error) { 20 | alert(error); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/comments.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var editComment = function($comment) { 3 | cancelEditComment(); 4 | $comment.find('.content').hide(); 5 | $comment.find('.editor').removeClass('hide'); 6 | }; 7 | 8 | var cancelEditComment = function() { 9 | $('.comment .editor').addClass('hide'); 10 | $('.comment .content').show(); 11 | }; 12 | 13 | 14 | $('.comment a[href="#edit"]').click(function() { 15 | editComment($(this).parents('.comment')); 16 | }); 17 | 18 | $('.comment a[href="#cancel"]').click(function() { 19 | cancelEditComment(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /spec/dummy/app/views/remote_posts/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(Post.new, remote: true, html: { id: 'new-remote-post' }, data: { type: 'json' }) do |f| %> 2 | 5 | 6 |
    7 | <%= f.label :title %> 8 | <%= f.text_field :title, class: 'form-control' %> 9 |
    10 | 11 |
    12 | <%= f.label :content %> 13 | <%= f.bootsy_area :content, rows: 16, class: 'form-control', id: 'remote-post-area' %> 14 |
    15 | 16 |
    17 | <%= f.submit class: 'btn btn-primary' %> 18 |
    19 | <% end %> 20 | -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /spec/features/unsaved_changes_prompt.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'unsaved changes prompt', type: :feature, js: true do 4 | before do 5 | visit new_post_path 6 | page.execute_script "Bootsy.areas.post_content.editor.fire('change')" 7 | visit root_path 8 | end 9 | 10 | after do 11 | page.execute_script "if(Bootsy.areas.post_content) { Bootsy.areas.post_content.unsavedChanges = false; }" 12 | end 13 | 14 | it 'can be accepted' do 15 | page.driver.browser.switch_to.alert.accept 16 | expect(current_path).to eq root_path 17 | end 18 | 19 | it 'can be dismissed' do 20 | page.driver.browser.switch_to.alert.dismiss 21 | expect(current_path).to eq new_post_path 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/bootsy/form_builder.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | # Public: Convenience module to include Bootsy 3 | # in `ActionView::Helpers::FormBuilder`. 4 | module FormBuilder 5 | # Public: Return a textarea element with proper attributes to 6 | # be loaded as a WYSIWYG editor. 7 | # 8 | # method - The Symbol attribute name on the object assigned to the 9 | # form builder that will tailor the editor. 10 | # 11 | # options - The Hash of options used to enable/disable features of 12 | # the editor (default: {}). 13 | # Available options are: 14 | def bootsy_area(method, options = {}) 15 | @template.bootsy_area(@object_name, method, objectify_options(options)) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bootsy.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.push File.expand_path('../lib', __FILE__) 2 | 3 | require 'bootsy/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'bootsy' 7 | s.version = Bootsy::VERSION 8 | s.authors = ['Volmer Soares'] 9 | s.email = ['rubygems@radicaos.com'] 10 | s.homepage = 'http://github.com/volmer/bootsy' 11 | s.summary = 'A beautiful WYSIWYG editor with image uploads for Rails.' 12 | s.description = 'A beautiful WYSIWYG editor with image uploads for Rails.' 13 | s.license = 'MIT' 14 | 15 | s.files = 16 | Dir['{app,config,db,lib}/**/*'] + ['MIT-LICENSE', 'Rakefile', 'README.md'] 17 | 18 | s.add_dependency 'mini_magick', '~> 4.5' 19 | s.add_dependency 'carrierwave', '~> 0.11' 20 | end 21 | -------------------------------------------------------------------------------- /spec/features/youtube_support_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'YouTube support', type: :feature, js: true do 4 | it 'allows YouTube embed codes' do 5 | visit new_post_path 6 | 7 | embed = '' 9 | page.execute_script "Bootsy.areas.post_content.editor.setValue('#{embed}')" 10 | 11 | expected_embed = '' 13 | content = page.evaluate_script( 14 | 'Bootsy.areas.post_content.editor.getValue()') 15 | expect(content).to include(expected_embed) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    We're sorry, but something went wrong.

    23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/lib/form_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy::FormBuilder do 4 | let(:object) { Post.new } 5 | 6 | let(:template) { double('template') } 7 | 8 | subject do 9 | ActionView::Helpers::FormBuilder.new(:post, object, template, {}) 10 | end 11 | 12 | describe '#bootsy_area' do 13 | it "is a proxy to the template's bootsy_area method" do 14 | expect(template).to receive(:bootsy_area).with(:post, :content, { object: object }) 15 | 16 | subject.bootsy_area(:content) 17 | end 18 | 19 | it 'allows an optional hash of options' do 20 | expect(template).to receive(:bootsy_area).with(:post, :content, { op: 'op', object: object }) 21 | 22 | subject.bootsy_area(:content, op: 'op') 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/features/nested_attributes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'nested attributes', type: :feature, js: true do 4 | it 'is compatible with the editor' do 5 | visit root_path 6 | click_on 'New post with comment' 7 | fill_in 'Title', with: 'My post' 8 | page.execute_script "Bootsy.areas.post_content.editor.setValue('Content')" 9 | fill_in 'Author', with: 'Comment author' 10 | page.execute_script 'Bootsy.areas.post_comments_attributes_0_content.'\ 11 | "editor.setValue('My comment')" 12 | click_on 'Create Post' 13 | 14 | expect(page).to have_content('My post') 15 | expect(page).to have_content('Content') 16 | expect(page).to have_content('Comment author') 17 | expect(page).to have_content('My comment') 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@post) do |f| %> 2 | <% if @post.errors.any? %> 3 |
    4 |

    <%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:

    5 | 6 | 11 |
    12 | <% end %> 13 | 14 |
    15 | <%= f.label :title %> 16 | <%= f.text_field :title, class: 'form-control' %> 17 |
    18 | 19 |
    20 | <%= f.label :content %> 21 | <%= f.bootsy_area :content, rows: 16, class: 'form-control' %> 22 |
    23 | 24 |
    25 | <%= f.submit class: 'btn btn-primary' %> 26 |
    27 | <% end %> 28 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require bootstrap-sprockets 16 | //= require bootsy 17 | //= require bootsy/locales/pt-BR 18 | //= require_tree . 19 | -------------------------------------------------------------------------------- /spec/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The change you wanted was rejected.

    23 |

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

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /config/locales/bootsy.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | bootsy: 3 | action: 4 | refresh: Refresh 5 | destroy: Delete 6 | close: Close 7 | load: Load 8 | or: or 9 | upload: Upload New Image 10 | submit_upload: Go 11 | enter_image_url: Enter a URL from the web... 12 | image_gallery: Image Gallery 13 | image: 14 | s: Image 15 | p: Images 16 | size: Size 17 | large: Large 18 | medium: Medium 19 | small: Small 20 | original: Original 21 | new: New image 22 | confirm: 23 | destroy: Are you sure you want to delete this image? 24 | position: 25 | left: Left 26 | right: Right 27 | inline: Inline 28 | no_images_uploaded: There are currently no uploaded images. 29 | js: 30 | alert_unsaved: You have unsaved changes. 31 | -------------------------------------------------------------------------------- /app/views/bootsy/images/_new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for([bootsy, resource_or_nil(gallery), image], 2 | html: { multipart: true, class: 'bootsy-upload-form form-inline' }, format: 'json') do |f| %> 3 | <%= hidden_field_tag :authenticity_token, form_authenticity_token %> 4 | 5 | <%= render 'bootsy/images/loader', image_class: 'bootsy-upload-loader' %> 6 | 7 |
    8 | <%= f.url_field :remote_image_file_url, class: 'form-control', placeholder: t('bootsy.action.enter_image_url') %> 9 | 10 | <%= f.submit t('bootsy.action.submit_upload'), class: 'btn btn-default' %> 11 | 12 |
    13 | 14 | <%= t('bootsy.action.or') %> 15 | 16 | <%= f.file_field :image_file, title: t('bootsy.action.upload'), accept: 'image/*' %> 17 | <% end %> 18 | -------------------------------------------------------------------------------- /spec/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The page you were looking for doesn't exist.

    23 |

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

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Labels and hints examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | 27 | -------------------------------------------------------------------------------- /config/locales/bootsy.pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | bootsy: 3 | action: 4 | refresh: Atualizar 5 | destroy: Apagar 6 | close: Fechar 7 | load: Carregar 8 | or: ou 9 | upload: Nova imagem 10 | submit_upload: Ir 11 | enter_image_url: Digite uma URL da web... 12 | image_gallery: Galeria de Imagens 13 | image: 14 | s: Imagem 15 | p: Imagens 16 | size: Tamanho 17 | large: Grande 18 | medium: Médio 19 | small: Pequeno 20 | original: Original 21 | new: Nova imagem 22 | confirm: 23 | destroy: Tem certeza que deseja apagar esta imagem? 24 | position: 25 | left: Esquerda 26 | right: Direita 27 | inline: Mesma linha 28 | no_images_uploaded: Não existem imagens salvas. 29 | js: 30 | alert_unsaved: As suas modificações ainda não foram gravadas. 31 | -------------------------------------------------------------------------------- /lib/bootsy/simple_form/bootsy_input.rb: -------------------------------------------------------------------------------- 1 | require 'simple_form' 2 | 3 | # Public: A SimpleForm Input to wrap Bootsy areas 4 | class BootsyInput < SimpleForm::Inputs::Base 5 | enable :placeholder, :maxlength, :container, :editor_options, :uploader 6 | 7 | def input(wrapper_options = nil) 8 | bootsy_params = [:editor_options, :container, :uploader] 9 | input_html_options.merge!( 10 | input_options.select { |key, _| bootsy_params.include?(key) } 11 | ) 12 | 13 | # Check presence of `merge_wrapper_options` to keep 14 | # compatibility with both Simple Form 3.0 and 3.1. 15 | merged_input_options = 16 | if respond_to?(:merge_wrapper_options, true) 17 | merge_wrapper_options(input_html_options, wrapper_options) 18 | else 19 | input_html_options 20 | end 21 | 22 | @builder.bootsy_area(attribute_name, merged_input_options) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/dummy/app/views/comments/_new.html.erb: -------------------------------------------------------------------------------- 1 |

    Add comment

    2 | 3 | <%= form_for([comment.post,comment]) do |f| %> 4 | <% if comment.errors.any? %> 5 |
    6 |

    <%= pluralize(@comment.errors.count, 'error') %> prohibited this comment from being saved:

    7 | 8 | 13 |
    14 | <% end %> 15 | 16 |
    17 | <%= f.label :author %> 18 | <%= f.text_field :author, class: 'form-control' %> 19 |
    20 |
    21 | <%= f.label :content %> 22 | <%= f.bootsy_area :content, rows: 8, class: 'form-control', container: comment.post %> 23 |
    24 | 25 |
    26 | <%= f.submit class: 'btn btn-default' %> 27 |
    28 | <% end %> 29 | -------------------------------------------------------------------------------- /spec/dummy/app/views/comments/_show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= comment.author %> says: 3 |
    4 |

    5 | <%= raw comment.content %> 6 |

    7 | 8 | <%= link_to 'Edit comment', '#edit' %> 9 |
    10 | 11 |
    12 | <%= form_for([comment.post,comment]) do |f| %> 13 |
    14 | <%= f.label :author %> 15 | <%= f.text_field :author, class: 'form-control' %> 16 |
    17 |
    18 | <%= f.label :content %> 19 | <%= f.bootsy_area :content, rows: 8, class: 'form-control', container: comment.post %> 20 |
    21 | 22 |
    23 | <%= f.submit class: 'btn btn-default' %> 24 |
    25 | <% end %> 26 | 27 | <%= link_to 'Cancel', '#cancel' %> 28 |
    29 |
    30 | -------------------------------------------------------------------------------- /spec/features/remote_form_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'remote form', type: :feature, js: true do 4 | it 'works with Bootsy' do 5 | visit posts_path 6 | click_on 'New remote post' 7 | fill_in 'Title', with: 'Awesome post' 8 | click_on 'Insert image' 9 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 10 | find('.bootsy-gallery img[src$="/thumb_test.jpg"]').click 11 | script = "$('.dropdown-submenu .dropdown-menu').hide(); "\ 12 | "$('a:contains(Small):visible').parent()."\ 13 | "find('.dropdown-menu').show()" 14 | 15 | page.execute_script(script) 16 | 17 | find( 18 | 'li.dropdown-submenu ul.dropdown-menu li a', 19 | visible: true, 20 | text: /Left/ 21 | ).click 22 | 23 | sleep 1 24 | click_button 'Create Post' 25 | 26 | expect(page).to have_css('#posts img[src$="/small_test.jpg"]') 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/features/image_gallery_modal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'image gallery modal', type: :feature, js: true do 4 | it 'is accessible with a new resource' do 5 | visit new_post_path 6 | 7 | click_on 'Insert image' 8 | 9 | expect(page).to have_css('.bootsy-modal', visible: true) 10 | expect(page).not_to have_css('.bootsy-image', visible: true) 11 | expect(page).to have_content('There are currently no uploaded images.') 12 | end 13 | 14 | it 'is accessible with a persisted resource' do 15 | post = Post.new(title: 'Test', content: 'test') 16 | post.bootsy_image_gallery_id = FactoryGirl.create( 17 | :image_gallery_with_images 18 | ).id 19 | post.save! 20 | visit edit_post_path(post) 21 | 22 | click_on 'Insert image' 23 | 24 | expect(page).to have_css('.bootsy-modal', visible: true) 25 | expect(page).to have_css('.bootsy-image', visible: true) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/features/hidden_editor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | # To ensure this feature there is the ability to 4 | # edit comments in the dummy app. To edit comments the 5 | # user needs to click on the "edit" link, and then 6 | # a hidden editor becomes visible. 7 | 8 | # It is important to ensure this feature due to some 9 | # issues with Firefox and hidden elements. 10 | 11 | describe 'hidden editor', type: :feature, js: true do 12 | it 'works' do 13 | post = Post.create!(title: 'Test', content: 'test') 14 | Comment.create!(post: post, author: 'someone', content: 'Nice post!') 15 | visit post_path(post) 16 | 17 | click_on 'Edit comment' 18 | page.execute_script "Bootsy.areas['comment_content'].editor."\ 19 | "setValue('Edited content')" 20 | click_on 'Update Comment' 21 | 22 | expect(page).to have_content('Comment was successfully updated') 23 | expect(page).to have_content('Edited content') 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/bootsy/engine.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | # Public: Define and setup Bootsy as a Rails engine. 3 | class Engine < Rails::Engine 4 | isolate_namespace Bootsy 5 | 6 | config.generators do |g| 7 | g.test_framework :rspec 8 | g.integration_tool :rspec 9 | end 10 | 11 | config.to_prepare do 12 | ActionController::Base.helper(Bootsy::ApplicationHelper) 13 | # Included at ApplicationController to prevent 14 | # missing helpers when it is eager loaded. 15 | ApplicationController.helper(Bootsy::ApplicationHelper) 16 | end 17 | 18 | config.after_initialize do 19 | orm = defined?(BOOTSY_ORM) ? BOOTSY_ORM : :activerecord 20 | 21 | # Require Active Record models. Other ORMs must 22 | # include their own Bootsy models. 23 | if orm == :activerecord 24 | Dir[File.expand_path('../activerecord/*.rb', __FILE__)].each do |f| 25 | require f 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/views/bootsy/images/_modal.html.erb: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /app/uploaders/bootsy/image_uploader.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | class ImageUploader < CarrierWave::Uploader::Base 3 | include CarrierWave::MiniMagick 4 | 5 | storage Bootsy.storage 6 | 7 | def store_dir 8 | "#{Bootsy.store_dir}/#{model.class.to_s.underscore}/#{model.id}" 9 | end 10 | 11 | process resize_to_limit: [1160, 2000] 12 | 13 | version :large do 14 | process resize_to_fit: [ 15 | Bootsy.large_image[:width], Bootsy.large_image[:height] 16 | ] 17 | end 18 | 19 | version :medium do 20 | process resize_to_fit: [ 21 | Bootsy.medium_image[:width], Bootsy.medium_image[:height] 22 | ] 23 | end 24 | 25 | version :small do 26 | process resize_to_fit: [ 27 | Bootsy.small_image[:width], Bootsy.small_image[:height] 28 | ] 29 | end 30 | 31 | version :thumb do 32 | process resize_to_fill: [60, 60] 33 | end 34 | 35 | def extension_white_list 36 | %w(jpg jpeg gif png) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/features/image_deletion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'image deletion', type: :feature, js: true do 4 | let(:selector) do 5 | "//div[contains(@class, 'bootsy-gallery')]//img[contains(@src, "\ 6 | "'/thumb_test.jpg')]" 7 | end 8 | 9 | before do 10 | visit new_post_path 11 | click_on 'Insert image' 12 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 13 | end 14 | 15 | it 'can be performed' do 16 | find(:xpath, selector).click 17 | 18 | page.accept_confirm 'Are you sure you want to delete this image?' do 19 | click_link 'Delete' 20 | end 21 | 22 | expect(page).not_to have_selector(:xpath, selector, visible: true) 23 | expect(page).to have_content('There are currently no uploaded images.') 24 | end 25 | 26 | it 'can be interrupted' do 27 | find(:xpath, selector).click 28 | 29 | page.dismiss_confirm 'Are you sure you want to delete this image?' do 30 | click_link 'Delete' 31 | end 32 | 33 | expect(page).to have_selector(:xpath, selector, visible: true) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/simple_form_posts_controller.rb: -------------------------------------------------------------------------------- 1 | class SimpleFormPostsController < ApplicationController 2 | # GET /posts/new 3 | # GET /posts/new.json 4 | def new 5 | @post = Post.new 6 | 7 | respond_to do |format| 8 | format.html # new.html.erb 9 | format.json { render json: @post } 10 | end 11 | end 12 | 13 | # POST /posts 14 | # POST /posts.json 15 | def create 16 | @post = Post.new(post_params) 17 | 18 | respond_to do |format| 19 | if @post.save 20 | format.html { redirect_to @post, notice: 'Post was successfully created.' } 21 | format.json { render json: @post, status: :created, location: @post } 22 | else 23 | format.html { render action: 'new' } 24 | format.json { render json: @post.errors, status: :unprocessable_entity } 25 | end 26 | end 27 | end 28 | 29 | private 30 | # Never trust parameters from the scary internet, only allow the white list through. 31 | def post_params 32 | params.require(:post).permit(:title, :content, :bootsy_image_gallery_id) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/init.js: -------------------------------------------------------------------------------- 1 | /* global Bootsy */ 2 | window.Bootsy = window.Bootsy || {}; 3 | 4 | // Public: Intialize Bootsy editors in all visible `textarea` 5 | // elements that has the `bootsy_text_area` class. 6 | Bootsy.init = function() { 7 | if (!Bootsy.areas) { 8 | Bootsy.areas = {}; 9 | } 10 | 11 | $('textarea.bootsy_text_area').each(function(index) { 12 | if (!$(this).data('bootsy-initialized')) { 13 | var area = new Bootsy.Area($(this)); 14 | var areaIdx = $(this).attr('id') || index; 15 | 16 | /* There's always people who let elements share ids */ 17 | if(Bootsy.areas[areaIdx] !== undefined) { 18 | areaIdx = $(this).attr('id') + index; 19 | } 20 | 21 | area.init(); 22 | 23 | Bootsy.areas[areaIdx] = area; 24 | } 25 | }); 26 | }; 27 | 28 | /* Initialize Bootsy on document load */ 29 | $(function() { 30 | $(window).load(function() { 31 | Bootsy.init(); 32 | 33 | /* Reload Bootsy on page load when using Turbolinks. */ 34 | document.addEventListener('turbolinks:load', Bootsy.init); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/vendor/polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | Bootsy makes use of Function.prototype.bind, which is not supported by some older browsers. 3 | Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 4 | */ 5 | 6 | if (!Function.prototype.bind) { 7 | Function.prototype.bind = function(oThis) { 8 | if (typeof this !== 'function') { 9 | // closest thing possible to the ECMAScript 5 10 | // internal IsCallable function 11 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 12 | } 13 | 14 | var aArgs = Array.prototype.slice.call(arguments, 1), 15 | fToBind = this, 16 | fNOP = function() {}, 17 | fBound = function() { 18 | return fToBind.apply(this instanceof fNOP && oThis 19 | ? this 20 | : oThis, 21 | aArgs.concat(Array.prototype.slice.call(arguments))); 22 | }; 23 | 24 | fNOP.prototype = this.prototype; 25 | fBound.prototype = new fNOP(); 26 | 27 | return fBound; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /spec/features/foreign_gallery_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'foreign gallery', type: :feature, js: true do 4 | it 'is possible' do 5 | post = Post.new(title: 'Test', content: 'test') 6 | post.bootsy_image_gallery_id = FactoryGirl.create( 7 | :image_gallery_with_images 8 | ).id 9 | post.save! 10 | visit post_path(post) 11 | click_on 'Insert image' 12 | expect(page).to have_css('.bootsy-modal', visible: true) 13 | expect(page).to have_css('.bootsy-image', visible: true) 14 | end 15 | 16 | it 'allows uploads' do 17 | post = Post.new(title: 'Test', content: 'test') 18 | post.save! 19 | visit post_path(post) 20 | 21 | click_on 'Insert image' 22 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 23 | find('[data-dismiss=modal]').click 24 | click_on 'Create Comment' 25 | click_on 'Edit' 26 | click_on 'Insert image' 27 | selector = "//div[contains(@class, 'bootsy-gallery')]//img[contains(@src, "\ 28 | "'/thumb_test.jpg')]" 29 | expect(page).to have_selector(:xpath, selector, visible: true) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2015 Volmer Soares 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 | -------------------------------------------------------------------------------- /spec/helpers/application_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy::ApplicationHelper do 4 | let(:unsaved_gallery) { FactoryGirl.build(:image_gallery) } 5 | let(:saved_gallery) { FactoryGirl.create(:image_gallery) } 6 | 7 | describe '#refresh_btn' do 8 | subject { helper.refresh_btn } 9 | 10 | it { is_expected.to include('class="btn btn-default btn-sm refresh-btn"') } 11 | 12 | it { is_expected.to include('Refresh') } 13 | 14 | it { is_expected.to include('href="#refresh-gallery"') } 15 | end 16 | 17 | describe '#resource_or_nil' do 18 | context 'argument is nil' do 19 | subject { helper.resource_or_nil nil } 20 | 21 | it { is_expected.to be_nil } 22 | end 23 | 24 | context 'argument is not persisted' do 25 | subject { helper.resource_or_nil unsaved_gallery } 26 | 27 | it { is_expected.to be_nil } 28 | end 29 | 30 | context 'argument is persisted' do 31 | subject { helper.resource_or_nil saved_gallery } 32 | 33 | it 'returns the given argument' do 34 | expect(subject).to eq saved_gallery 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/bootsy/activerecord/image_gallery.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | # Public: A model that groups all images related to a 3 | # Bootsy container (also called a resource). 4 | # 5 | # It is important to note that the relation gallery - resource 6 | # is not mandatory due to the need of having galleries 7 | # related to unsaved containers. This may lead to creation 8 | # of orphan galleries. Because of that, this model includes 9 | # the `destroy_orphans` method, that removes all galleries 10 | # that do not point to resources older than the given time 11 | # limit. 12 | class ImageGallery < ActiveRecord::Base 13 | if Rails::VERSION::STRING.split(".").first.to_i >= 5 14 | belongs_to :bootsy_resource, polymorphic: true, autosave: false, 15 | optional: true 16 | else 17 | belongs_to :bootsy_resource, polymorphic: true, autosave: false 18 | end 19 | 20 | has_many :images, dependent: :destroy 21 | 22 | scope :destroy_orphans, lambda { |time_limit| 23 | where( 24 | 'created_at < ? AND bootsy_resource_id IS NULL', 25 | time_limit 26 | ).destroy_all 27 | } 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/app/views/posts/new_with_comment.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 | <%= form_for(@post) do |f| %> 6 | <% if @post.errors.any? %> 7 |
    8 |

    <%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:

    9 | 10 | 15 |
    16 | <% end %> 17 | 18 | <%= f.label :title %> 19 | <%= f.text_field :title, class: 'form-control' %> 20 | 21 | <%= f.label :content %> 22 | <%= f.bootsy_area :content, rows: 16, class: 'form-control' %> 23 | 24 |

    Comment

    25 | 26 | <%= f.fields_for(:comments) do |sf| %> 27 | <%= sf.label :author %> 28 | <%= sf.text_field :author, class: 'form-control' %> 29 | 30 | <%= sf.label :content, 'Comment' %> 31 | <%= sf.bootsy_area :content, rows: 8, class: 'form-control' %> 32 | <% end %> 33 | 34 |
    35 | <%= f.submit class: 'btn btn-primary' %> 36 | 37 | <%= link_to 'Cancel', posts_path, class: 'btn' %> 38 |
    39 | <% end %> 40 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Read the Rails 5.0 release notes for more info on each option. 6 | 7 | # Enable per-form CSRF tokens. Previous versions had false. 8 | Rails.application.config.action_controller.per_form_csrf_tokens = true 9 | 10 | # Enable origin-checking CSRF mitigation. Previous versions had false. 11 | Rails.application.config.action_controller.forgery_protection_origin_check = true 12 | 13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 14 | # Previous versions had false. 15 | ActiveSupport.to_time_preserves_timezone = true 16 | 17 | # Require `belongs_to` associations by default. Previous versions had false. 18 | Rails.application.config.active_record.belongs_to_required_by_default = true 19 | 20 | # Do not halt callback chains when a callback returns false. Previous versions had true. 21 | ActiveSupport.halt_callback_chains_on_return_false = false 22 | 23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } 25 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/editor_options.js: -------------------------------------------------------------------------------- 1 | window.Bootsy = window.Bootsy || {}; 2 | 3 | var pageStylesheets = []; 4 | $('link[rel="stylesheet"]').each(function () { 5 | pageStylesheets.push($(this).attr('href')); 6 | }); 7 | 8 | window.Bootsy.options = {}; 9 | 10 | $.extend(true, window.Bootsy.options, $.fn.wysihtml5.defaultOptions, { 11 | parserRules: { 12 | classes: { 13 | "wysiwyg-float-left": 1, 14 | "wysiwyg-float-right": 1, 15 | "wysiwyg-float-inline": 1 16 | }, 17 | tags: { 18 | "cite": { 19 | "check_attributes": { 20 | "title": "alt" 21 | } 22 | }, 23 | "img": { 24 | "check_attributes": { 25 | "src": "src" 26 | }, 27 | "add_class": { 28 | "align": "align_img" 29 | } 30 | }, 31 | // this allows youtube embed codes 32 | "iframe": { 33 | set_attributes: { 34 | "frameborder": "0", 35 | "allowfullscreen": "1" 36 | }, 37 | check_attributes: { 38 | "width": "numbers", 39 | "height": "numbers", 40 | "src": "href" 41 | } 42 | } 43 | } 44 | }, 45 | color: true, 46 | stylesheets: pageStylesheets 47 | }); 48 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | end 30 | -------------------------------------------------------------------------------- /spec/uploaders/image_uploader_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'carrierwave/test/matchers' 3 | 4 | describe Bootsy::ImageUploader do 5 | include CarrierWave::Test::Matchers 6 | 7 | before :all do 8 | path_to_file = Rails.root.to_s + '/public/test.jpg' 9 | @uploader = Bootsy::ImageUploader.new FactoryGirl.build(:image), :image_file 10 | @uploader.store! File.open(path_to_file) 11 | end 12 | 13 | after(:all) { @uploader.remove! } 14 | 15 | it 'respects dimensions for the original version' do 16 | expect(@uploader).to be_no_larger_than(1160, 2000) 17 | end 18 | 19 | it 'respects dimensions for the thumb version' do 20 | expect(@uploader.thumb).to have_dimensions(60, 60) 21 | end 22 | 23 | it 'respects dimensions for the small version' do 24 | expect(@uploader.small).to be_no_larger_than(Bootsy.small_image[:width], Bootsy.small_image[:height]) 25 | end 26 | 27 | it 'respects dimensions for the medium version' do 28 | expect(@uploader.medium).to be_no_larger_than(Bootsy.medium_image[:width], Bootsy.medium_image[:height]) 29 | end 30 | 31 | it 'respects dimensions for the large version' do 32 | expect(@uploader.large).to be_no_larger_than(Bootsy.large_image[:width], Bootsy.large_image[:height]) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "sprockets/railtie" 8 | # require "rails/test_unit/railtie" 9 | 10 | # Require the gems listed in Gemfile, including any gems 11 | # you've limited to :test, :development, or :production. 12 | Bundler.require(:default, Rails.env) 13 | 14 | require 'bootsy' 15 | 16 | module Dummy 17 | class Application < Rails::Application 18 | # Settings in config/environments/* take precedence over those specified here. 19 | # Application configuration should go into files in config/initializers 20 | # -- all .rb files in that directory are automatically loaded. 21 | 22 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 23 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 24 | # config.time_zone = 'Central Time (US & Canada)' 25 | 26 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 27 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 28 | # config.i18n.default_locale = 'pt-BR' 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag "application", :media => "all" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | 12 | 19 | 20 |
    21 | <% flash.each do |name, msg| %> 22 |
    23 | 24 | <%= content_tag :div, msg, :id => "flash_#{name}" if msg.is_a?(String) %> 25 |
    26 | <% end %> 27 | 28 | <%= yield %> 29 | 30 |
    31 | 32 | 37 |
    38 | 39 | 40 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/locales/en.js: -------------------------------------------------------------------------------- 1 | /** 2 | * English translations for bootstrap-wysihtml5 and Bootsy 3 | */ 4 | (function($){ 5 | $.fn.wysihtml5.locale['en'] = { 6 | font_styles: { 7 | normal: 'Normal text', 8 | h1: 'Heading 1', 9 | h2: 'Heading 2', 10 | h3: 'Heading 3' 11 | }, 12 | emphasis: { 13 | bold: 'Bold', 14 | italic: 'Italic', 15 | underline: 'Underline', 16 | small: 'Small' 17 | }, 18 | lists: { 19 | unordered: 'Unordered list', 20 | ordered: 'Ordered list', 21 | outdent: 'Outdent', 22 | indent: 'Indent' 23 | }, 24 | link: { 25 | insert: 'Insert link', 26 | cancel: 'Cancel', 27 | target: 'Open link in new window' 28 | }, 29 | image: { 30 | insert: 'Insert image', 31 | cancel: 'Cancel' 32 | }, 33 | html: { 34 | edit: 'Edit HTML' 35 | }, 36 | colours: { 37 | black: 'Black', 38 | silver: 'Silver', 39 | gray: 'Grey', 40 | maroon: 'Maroon', 41 | red: 'Red', 42 | purple: 'Purple', 43 | green: 'Green', 44 | olive: 'Olive', 45 | navy: 'Navy', 46 | blue: 'Blue', 47 | orange: 'Orange' 48 | }, 49 | bootsy: { 50 | alertUnsaved: 'You have unsaved changes.', 51 | error: 'Something went wrong. Please try again later.' 52 | } 53 | }; 54 | }(jQuery)); 55 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/locales/pt-BR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Brazilian portuguese translation for bootstrap-wysihtml5 and Bootsy 3 | */ 4 | (function($){ 5 | $.fn.wysihtml5.locale['pt-BR'] = { 6 | font_styles: { 7 | normal: 'Texto normal', 8 | h1: 'Título 1', 9 | h2: 'Título 2', 10 | h3: 'Título 3' 11 | }, 12 | emphasis: { 13 | bold: 'Negrito', 14 | italic: 'Itálico', 15 | underline: 'Sublinhado', 16 | small: 'Pequeno' 17 | }, 18 | lists: { 19 | unordered: 'Lista', 20 | ordered: 'Lista numerada', 21 | outdent: 'Remover indentação', 22 | indent: 'Indentar' 23 | }, 24 | link: { 25 | insert: 'Inserir link', 26 | cancel: 'Cancelar', 27 | target: 'Abrir link em um nova janela' 28 | }, 29 | image: { 30 | insert: 'Inserir imagem', 31 | cancel: 'Cancelar' 32 | }, 33 | html: { 34 | edit: 'Editar HTML' 35 | }, 36 | colours: { 37 | black: 'Preto', 38 | silver: 'Prata', 39 | gray: 'Cinza', 40 | maroon: 'Marrom', 41 | red: 'Vermelho', 42 | purple: 'Roxo', 43 | green: 'Verde', 44 | olive: 'Oliva', 45 | navy: 'Marinho', 46 | blue: 'Azul', 47 | orange: 'Laranja' 48 | }, 49 | bootsy: { 50 | alertUnsaved: 'As suas modificações ainda não foram gravadas.', 51 | error: 'Algo deu errado. Por favor, tente novamente.' 52 | } 53 | }; 54 | }(jQuery)); 55 | -------------------------------------------------------------------------------- /spec/features/image_insertion_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'image insertion', type: :feature, js: true do 4 | let(:size_positions) do 5 | [ 6 | %w(Small Left), 7 | %w(Small Right), 8 | %w(Small Inline), 9 | %w(Medium Left), 10 | %w(Medium Right), 11 | %w(Medium Inline), 12 | %w(Large Left), 13 | %w(Large Right), 14 | %w(Large Inline), 15 | %w(Original Left), 16 | %w(Original Right), 17 | %w(Original Inline) 18 | ] 19 | end 20 | 21 | it 'can be performed' do 22 | size_positions.each do |size_position| 23 | visit new_post_path 24 | click_on 'Insert image' 25 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 26 | 27 | find('.bootsy-gallery img').click 28 | size = size_position.first 29 | position = size_position.last 30 | script = "$('.dropdown-submenu .dropdown-menu').hide(); "\ 31 | "$('a:contains(#{size}):visible').parent()."\ 32 | "find('.dropdown-menu').show()" 33 | page.execute_script(script) 34 | find('li.dropdown-submenu ul.dropdown-menu li a', text: position).click 35 | 36 | content = page.evaluate_script( 37 | 'Bootsy.areas.post_content.editor.getValue()') 38 | img_src = "/#{size.downcase}_test.jpg" 39 | img_src = 'test.jpg' if size == 'Original' 40 | expect(content).to include(img_src) 41 | expect(content).to include("align=\"#{position.downcase}\"") 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/bootsy/container.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | # Public: Methods and attributes to turn any 3 | # model into a Bootsy Container. 4 | # 5 | # Examples 6 | # 7 | # class Post < ActiveRecord::Base 8 | # include Bootsy::Container 9 | # end 10 | module Container 11 | extend ActiveSupport::Concern 12 | 13 | included do 14 | has_one :bootsy_image_gallery, 15 | class_name: 'Bootsy::ImageGallery', 16 | as: :bootsy_resource, 17 | dependent: :destroy 18 | end 19 | 20 | # Public: Get the `id` attribute of the image gallery. 21 | # 22 | # Returns an Integer id or nil when there is 23 | # not an image gallery. 24 | def bootsy_image_gallery_id 25 | bootsy_image_gallery.try(:id) 26 | end 27 | 28 | # Public: Set the image gallery `id` and save 29 | # the association between models. 30 | # 31 | # Examples 32 | # 33 | # container.id 34 | # # => 34 35 | # gallery.id 36 | # # => 12 37 | # container.bootsy_image_gallery_id = gallery.id 38 | # container.image_gallery_id 39 | # # => 12 40 | # gallery.bootsy_resource.id 41 | # # => 34 42 | def bootsy_image_gallery_id=(value) 43 | if bootsy_image_gallery.nil? && value.present? 44 | self.bootsy_image_gallery = Bootsy::ImageGallery.find(value) 45 | bootsy_image_gallery.bootsy_resource = self 46 | bootsy_image_gallery.save 47 | else 48 | value 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/lib/bootsy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy do 4 | subject { Bootsy } 5 | 6 | describe '.setup' do 7 | it 'yields self' do 8 | subject.setup do |config| 9 | expect(config).to eq subject 10 | end 11 | end 12 | end 13 | 14 | describe 'default values' do 15 | describe '.editor_options' do 16 | subject { Bootsy.editor_options } 17 | 18 | it { is_expected.to include(font_styles: true) } 19 | it { is_expected.to include(lists: true) } 20 | it { is_expected.to include(emphasis: true) } 21 | it { is_expected.to include(html: false) } 22 | it { is_expected.to include(image: true) } 23 | it { is_expected.to include(color: true) } 24 | end 25 | 26 | it 'has defaults for image_versions_available' do 27 | expect(subject.image_versions_available).to eq [:small, :medium, :large, :original] 28 | end 29 | 30 | it 'has defaults for allow_destroy' do 31 | expect(subject.allow_destroy).to be true 32 | end 33 | 34 | it 'has defaults for small_image' do 35 | expect(subject.small_image).to eq({ width: 160, height: 160 }) 36 | end 37 | 38 | it 'has defaults for medium_image' do 39 | expect(subject.medium_image).to eq({ width: 360, height: 360 }) 40 | end 41 | 42 | it 'has defaults for large_image' do 43 | expect(subject.large_image).to eq({ width: 760, height: 760 }) 44 | end 45 | 46 | it 'has defaults for original_image' do 47 | expect(subject.original_image).to be_empty 48 | end 49 | 50 | it 'has defaults for store_dir' do 51 | expect(subject.store_dir).to eq 'uploads' 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/lib/contaier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy::Container do 4 | let(:post) { Post.new } 5 | 6 | let(:post_with_gallery) do 7 | post_with_gallery = Post.new(title: 'Test', content: 'Test') 8 | post_with_gallery.bootsy_image_gallery = FactoryGirl.create(:image_gallery) 9 | post_with_gallery.save! 10 | post_with_gallery 11 | end 12 | 13 | it 'adds an image gallery' do 14 | expect(post).to respond_to(:bootsy_image_gallery) 15 | end 16 | 17 | describe '#bootsy_image_gallery' do 18 | it 'returns the resource which it belongs' do 19 | expect(post_with_gallery.bootsy_image_gallery.bootsy_resource).to eq(post_with_gallery) 20 | end 21 | end 22 | 23 | describe '#bootsy_image_gallery_id' do 24 | it 'returns the gallery id if present' do 25 | expect(post_with_gallery.bootsy_image_gallery_id).to eq(post_with_gallery.bootsy_image_gallery.id) 26 | end 27 | 28 | it 'returns nil if not present' do 29 | expect(post.bootsy_image_gallery_id).to be_nil 30 | end 31 | end 32 | 33 | describe '#bootsy_image_gallery_id=' do 34 | let(:image_gallery) { FactoryGirl.create(:image_gallery) } 35 | 36 | it 'sets an image gallery if container does not have one yet' do 37 | post.bootsy_image_gallery_id = image_gallery.id 38 | 39 | expect(post.bootsy_image_gallery).to eq(image_gallery) 40 | expect(post.bootsy_image_gallery_id).to eq(image_gallery.id) 41 | end 42 | 43 | it 'does not set an image gallery if container already has a gallery' do 44 | post_with_gallery.bootsy_image_gallery_id = image_gallery.id 45 | 46 | expect(post_with_gallery.bootsy_image_gallery_id).not_to eq(image_gallery.id) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /spec/models/bootsy/image_gallery_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Bootsy::ImageGallery do 4 | describe '.destroy_orphans' do 5 | it 'destroys all orphan galleries created before the given date' do 6 | ig1 = FactoryGirl.create(:image_gallery, created_at: 2.day.ago) 7 | ig2 = FactoryGirl.create(:image_gallery, created_at: 3.days.ago) 8 | ig3 = FactoryGirl.create(:image_gallery, created_at: 4.days.ago) 9 | 10 | Bootsy::ImageGallery.destroy_orphans(1.day.ago) 11 | 12 | expect(Bootsy::ImageGallery.all).to_not include(ig1, ig2, ig3) 13 | end 14 | 15 | it 'does not destroy orphan galleries created afther the given date' do 16 | ig1 = FactoryGirl.create(:image_gallery, created_at: 1.day.ago) 17 | ig2 = FactoryGirl.create(:image_gallery, created_at: 3.days.ago) 18 | ig3 = FactoryGirl.create(:image_gallery, created_at: 4.days.ago) 19 | 20 | Bootsy::ImageGallery.destroy_orphans(2.days.ago) 21 | 22 | expect(Bootsy::ImageGallery.all).to include(ig1) 23 | end 24 | 25 | it 'does not destroy non orphan galleries' do 26 | ig1 = FactoryGirl.create(:image_gallery, created_at: 2.day.ago) 27 | ig2 = FactoryGirl.create(:image_gallery, created_at: 3.days.ago, bootsy_resource: Post.create(title: 'a', content: 'b')) 28 | ig3 = FactoryGirl.create(:image_gallery, created_at: 4.days.ago) 29 | 30 | Bootsy::ImageGallery.destroy_orphans(1.day.ago) 31 | 32 | expect(Bootsy::ImageGallery.all).to include(ig2) 33 | end 34 | end 35 | 36 | it 'does not autosave its bootsy resource' do 37 | post = Post.new 38 | gallery = described_class.new 39 | gallery.bootsy_resource = post 40 | gallery.save! 41 | 42 | expect(post.errors).to be_empty 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/views/bootsy/images/_image.html.erb: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | 3 | # POST /comments 4 | # POST /comments.json 5 | def create 6 | @post = Post.find params[:post_id] 7 | @comment = @post.comments.new(comment_params) 8 | 9 | respond_to do |format| 10 | if @comment.save 11 | format.html { redirect_to @post, notice: 'Comment was successfully created.' } 12 | format.json { render json: @comment, status: :created, location: @comment } 13 | else 14 | format.html { render action: "new" } 15 | format.json { render json: @comment.errors, status: :unprocessable_entity } 16 | end 17 | end 18 | end 19 | 20 | # DELETE /comments/1 21 | # DELETE /comments/1.json 22 | def destroy 23 | @post = Post.find params[:post_id] 24 | @comment = Comment.find(params[:id]) 25 | @comment.destroy 26 | 27 | respond_to do |format| 28 | format.html { redirect_to @post } 29 | format.json { head :no_content } 30 | end 31 | end 32 | 33 | def update 34 | @post = Post.find params[:post_id] 35 | @comment = @post.comments.find(params[:id]) 36 | 37 | respond_to do |format| 38 | if @comment.update_attributes(comment_params) 39 | format.html { redirect_to @post, notice: 'Comment was successfully updated.' } 40 | format.json { head :no_content } 41 | else 42 | format.html { redirect_to @post, alert: 'Unable to update comment.' } 43 | format.json { render json: @comment.errors, status: :unprocessable_entity } 44 | end 45 | end 46 | end 47 | 48 | private 49 | # Never trust parameters from the scary internet, only allow the white list through. 50 | def comment_params 51 | params.require(:comment).permit(:author, :content) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/features/editor_customization_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe 'editor customization', type: :feature, js: true do 4 | let(:options) do 5 | { 6 | color: ['Black'], 7 | emphasis: %w(Bold Italic Underline), 8 | font_styles: ['Normal text'], 9 | html: ['Edit HTML'], 10 | image: ['Insert image'], 11 | link: ['Insert link'], 12 | lists: ['Unordered list', 'Ordered list', 'Indent', 'Outdent'] 13 | } 14 | end 15 | 16 | it 'allows enabling editor features' do 17 | options.keys.each do |config| 18 | allow(Bootsy).to receive(:editor_options).and_return(config => true) 19 | 20 | visit new_post_path 21 | 22 | toolbar = find('ul.wysihtml5-toolbar') 23 | 24 | options[config].each do |label| 25 | expect( 26 | toolbar.has_css?('a', text: label) || 27 | toolbar.has_css?("a[title='#{label}']") 28 | ).to be(true), "expected toolbar to include #{label}" 29 | end 30 | end 31 | end 32 | 33 | it 'allows disabling editor features' do 34 | options.keys.each do |config| 35 | allow(Bootsy).to receive(:editor_options).and_return(config => false) 36 | 37 | visit new_post_path 38 | 39 | toolbar = find('ul.wysihtml5-toolbar') 40 | 41 | options[config].each do |label| 42 | expect(toolbar).not_to have_css('a', text: label) 43 | expect(toolbar).not_to have_css("a[title='#{label}']") 44 | end 45 | end 46 | end 47 | 48 | it 'allows disabling unsaved changes prompt' do 49 | allow(Bootsy).to receive( 50 | :editor_options).and_return('alert-unsaved' => false) 51 | visit new_post_path 52 | 53 | page.execute_script "Bootsy.areas.post_content.editor.fire('change')" 54 | visit root_path 55 | 56 | expect(current_path).to eq root_path 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/bootsy.rb: -------------------------------------------------------------------------------- 1 | require 'carrierwave' 2 | require 'bootsy/engine' 3 | require 'bootsy/container' 4 | require 'bootsy/form_helper' 5 | require 'bootsy/form_builder' 6 | require 'bootsy/core_ext' 7 | 8 | autoload :BootsyInput, 'bootsy/simple_form/bootsy_input' 9 | 10 | # Public: Top Bootsy module 11 | module Bootsy 12 | ## CONFIGURATION OPTIONS 13 | 14 | # Default editor options 15 | mattr_accessor :editor_options 16 | @@editor_options = { 17 | font_styles: true, 18 | emphasis: true, 19 | lists: true, 20 | html: false, 21 | link: true, 22 | image: true, 23 | color: true 24 | } 25 | 26 | # Image versions available 27 | mattr_accessor :image_versions_available 28 | @@image_versions_available = [:small, :medium, :large, :original] 29 | 30 | # Whether user can destroy uploaded files 31 | mattr_accessor :allow_destroy 32 | @@allow_destroy = true 33 | 34 | # Settings for small images 35 | mattr_accessor :small_image 36 | @@small_image = { width: 160, height: 160 } 37 | 38 | # Settings for medium images 39 | mattr_accessor :medium_image 40 | @@medium_image = { width: 360, height: 360 } 41 | 42 | # Settings for large images 43 | mattr_accessor :large_image 44 | @@large_image = { width: 760, height: 760 } 45 | 46 | # Settings for the original version of images 47 | mattr_accessor :original_image 48 | @@original_image = {} 49 | 50 | # Storage mode 51 | mattr_accessor :storage 52 | @@storage = :file 53 | 54 | # Store directory (inside 'public') 55 | mattr_accessor :store_dir 56 | @@store_dir = 'uploads' 57 | 58 | # Specify which controller to inherit from 59 | mattr_accessor :base_controller 60 | @@base_controller = ActionController::Base 61 | 62 | # Default way to setup Bootsy. Run rails generate bootsy:install 63 | # to create a fresh initializer with all configuration values. 64 | def self.setup 65 | yield self 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20120710181942) do 15 | 16 | create_table "bootsy_image_galleries", :force => true do |t| 17 | t.integer "bootsy_resource_id" 18 | t.string "bootsy_resource_type" 19 | t.datetime "created_at", :null => false 20 | t.datetime "updated_at", :null => false 21 | end 22 | 23 | create_table "bootsy_images", :force => true do |t| 24 | t.string "image_file" 25 | t.integer "image_gallery_id" 26 | t.datetime "created_at", :null => false 27 | t.datetime "updated_at", :null => false 28 | end 29 | 30 | create_table "comments", :force => true do |t| 31 | t.string "author" 32 | t.text "content" 33 | t.integer "post_id" 34 | t.datetime "created_at", :null => false 35 | t.datetime "updated_at", :null => false 36 | end 37 | 38 | add_index "comments", ["post_id"], :name => "index_comments_on_post_id" 39 | 40 | create_table "posts", :force => true do |t| 41 | t.string "title" 42 | t.text "content" 43 | t.datetime "created_at", :null => false 44 | t.datetime "updated_at", :null => false 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /spec/lib/simple_form/bootsy_input_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe BootsyInput do 4 | let(:builder){ double.as_null_object } 5 | 6 | subject { 7 | BootsyInput.new(builder, :my_text_attr, '', 'bootsy', 8 | placeholder: 'a placeholder', maxlength: 50, container: :a_container, 9 | editor_options: :some_options, uploader: :uploader_value) 10 | } 11 | 12 | it 'has the proper options enabled' do 13 | expect(subject.input_options).to have_key(:placeholder) 14 | expect(subject.input_options).to have_key(:container) 15 | expect(subject.input_options).to have_key(:editor_options) 16 | expect(subject.input_options).to have_key(:maxlength) 17 | expect(subject.input_options).to have_key(:uploader) 18 | end 19 | 20 | describe '#input' do 21 | it 'passes the attribute_name to the bootsy_area' do 22 | expect(builder).to receive(:bootsy_area).with(:my_text_attr, anything) 23 | subject.input 24 | end 25 | 26 | it 'passes the input_html_options to the bootsy_area' do 27 | expect(builder).to receive(:bootsy_area).with(anything, subject.input_html_options) 28 | subject.input 29 | end 30 | 31 | context 'when a :container is passed' do 32 | it 'adds the :container to input_html_options[:container]' do 33 | expect(builder).to receive(:bootsy_area).with(anything, hash_including(container: :a_container)) 34 | subject.input 35 | end 36 | end 37 | 38 | context 'when :editor_options are passed' do 39 | it 'adds the :editor_options to input_html_options[:editor_options]' do 40 | expect(builder).to receive(:bootsy_area).with(anything, hash_including(editor_options: :some_options)) 41 | subject.input 42 | end 43 | end 44 | 45 | context 'when :uploader is passed' do 46 | it 'adds :uploader to input_html_options[:uploader]' do 47 | expect(builder).to receive(:bootsy_area).with(anything, hash_including(uploader: :uploader_value)) 48 | subject.input 49 | end 50 | end 51 | 52 | it 'passes the wrapper options to bootsy_area' do 53 | expect(builder).to receive(:bootsy_area).with(anything, hash_including(maxlength: 10)) 54 | subject.input(maxlength: 10) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /config/initializers/bootsy.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in Bootsy. 2 | Bootsy.setup do |config| 3 | # Default editor options 4 | # You can also override them locally by passing an 5 | # editor_options hash to bootsy_area 6 | # config.editor_options = { 7 | # font_styles: true, 8 | # emphasis: true, 9 | # lists: true, 10 | # html: false, 11 | # link: true, 12 | # image: true, 13 | # color: true 14 | # } 15 | # 16 | # Image versions available 17 | # Possible values: :small, :medium, :large and/or :original 18 | config.image_versions_available = [:small, :medium, :large, :original] 19 | # 20 | # 21 | # SMALL IMAGES 22 | # 23 | # Width limit for small images 24 | # config.small_image[:width] = 160 25 | # 26 | # Height limit for small images 27 | # config.small_image[:height] = 160 28 | # 29 | # 30 | # MEDIUM IMAGES 31 | # 32 | # Width limit for medium images 33 | # config.medium_image[:width] = 360 34 | # 35 | # Height limit for medium images 36 | # config.medium_image[:height] = 360 37 | # 38 | # 39 | # LARGE IMAGES 40 | # 41 | # Width limit for large images 42 | # config.large_image[:width] = 760 43 | # 44 | # Height limit for large images 45 | # config.large_image[:height] = 760 46 | # 47 | # 48 | # Whether user can destroy uploaded files 49 | # config.allow_destroy = true 50 | # 51 | # 52 | # Storage mode 53 | # You can change the sorage mode below from :file to :fog if you want 54 | # to use Amazon S3 and other cloud services. If you do that, please add 55 | # 'fog' to your Gemfile and create and configure your credentials in an 56 | # initializer file, as described in Carrierwave's docs: 57 | # https://github.com/carrierwaveuploader/carrierwave#using-amazon-s3 58 | # config.storage = :file 59 | # 60 | # 61 | # Store directory (inside 'public') for storage = :file 62 | # BE CAREFUL! Changing this may break previously uploaded file paths! 63 | # config.store_dir = 'uploads' 64 | # 65 | # 66 | # Specify the controller to inherit from. Using ApplicationController 67 | # allows you to perform authentication from within your app. 68 | # config.base_controller = ActionController::Base 69 | end 70 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/posts_controller.rb: -------------------------------------------------------------------------------- 1 | class PostsController < ApplicationController 2 | # GET /posts 3 | # GET /posts.json 4 | def index 5 | @posts = Post.all 6 | 7 | respond_to do |format| 8 | format.html # index.html.erb 9 | format.json { render json: @posts } 10 | end 11 | end 12 | 13 | # GET /posts/1 14 | # GET /posts/1.json 15 | def show 16 | @post = Post.find(params[:id]) 17 | 18 | respond_to do |format| 19 | format.html # show.html.erb 20 | format.json { render json: @post } 21 | end 22 | end 23 | 24 | # GET /posts/new 25 | # GET /posts/new.json 26 | def new 27 | @post = Post.new 28 | 29 | if params[:with_comment] 30 | @post.comments << Comment.new 31 | 32 | render(action: 'new_with_comment') and return 33 | end 34 | end 35 | 36 | # GET /posts/1/edit 37 | def edit 38 | @post = Post.find(params[:id]) 39 | end 40 | 41 | # POST /posts 42 | # POST /posts.json 43 | def create 44 | @post = Post.new(post_params) 45 | 46 | respond_to do |format| 47 | if @post.save 48 | format.html { redirect_to @post, notice: 'Post was successfully created.' } 49 | format.json { render json: { post: render_to_string(file: 'posts/_post', formats: [:html], layout: false, locals: { post: @post }) } } 50 | else 51 | format.html { render action: "new" } 52 | format.json { render json: @post.errors, status: :unprocessable_entity } 53 | end 54 | end 55 | end 56 | 57 | # PUT /posts/1 58 | # PUT /posts/1.json 59 | def update 60 | @post = Post.find(params[:id]) 61 | 62 | respond_to do |format| 63 | if @post.update_attributes(post_params) 64 | format.html { redirect_to @post, notice: 'Post was successfully updated.' } 65 | format.json { head :no_content } 66 | else 67 | format.html { render action: "edit" } 68 | format.json { render json: @post.errors, status: :unprocessable_entity } 69 | end 70 | end 71 | end 72 | 73 | # DELETE /posts/1 74 | # DELETE /posts/1.json 75 | def destroy 76 | @post = Post.find(params[:id]) 77 | @post.destroy 78 | 79 | respond_to do |format| 80 | format.html { redirect_to posts_url } 81 | format.json { head :no_content } 82 | end 83 | end 84 | 85 | private 86 | # Never trust parameters from the scary internet, only allow the white list through. 87 | def post_params 88 | params.require(:post).permit(:title, :content, :bootsy_image_gallery_id, comments_attributes: [:content, :author]) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/features/image_upload_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'sham_rack' 3 | 4 | describe 'image upload', type: :feature, js: true do 5 | let(:thumb_selector) do 6 | "//div[contains(@class, 'bootsy-gallery')]//img[contains(@src, "\ 7 | "'/thumb_test.jpg')]" 8 | end 9 | 10 | before do 11 | visit new_post_path 12 | click_on 'Insert image' 13 | 14 | ShamRack.at('stubhost.com').rackup do 15 | run Rack::Directory.new(Rails.root.join('public').to_s) 16 | end 17 | end 18 | 19 | it 'works with local files' do 20 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 21 | 22 | expect(page).to have_selector(:xpath, thumb_selector, visible: true) 23 | end 24 | 25 | it 'works with remote files' do 26 | fill_in 'image[remote_image_file_url]', with: 'http://stubhost.com/test.jpg' 27 | click_on 'Go' 28 | 29 | expect(page).to have_selector(:xpath, thumb_selector, visible: true) 30 | end 31 | 32 | it 'handles invalid images' do 33 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.fake' 34 | 35 | expect(page).not_to have_selector( 36 | :xpath, "//div[contains(@class, 'bootsy-gallery')]//img", visible: true) 37 | expect(page).to have_content('You are not allowed to upload') 38 | click_on 'Refresh' 39 | expect(page).not_to have_content('You are not allowed to upload') 40 | end 41 | 42 | it 'handles invalid remote images' do 43 | fill_in 'image[remote_image_file_url]', 44 | with: 'http://stubhost.com/test.fake' 45 | click_on 'Go' 46 | 47 | expect(page).not_to have_selector( 48 | :xpath, "//div[contains(@class, 'bootsy-gallery')]//img", visible: true) 49 | expect(page).to have_content('You are not allowed to upload') 50 | end 51 | 52 | it 'associates the uploaded image with the resource' do 53 | attach_file 'image[image_file]', Rails.root.to_s + '/public/test.jpg' 54 | find(:xpath, "//div[contains(@class, 'bootsy-gallery')]//img[contains(@src"\ 55 | ", '/thumb_test.jpg')]").click 56 | script = "$('.dropdown-submenu .dropdown-menu').hide(); "\ 57 | "$('a:contains(Small):visible').parent()."\ 58 | "find('.dropdown-menu').show()" 59 | page.execute_script(script) 60 | find( 61 | 'li.dropdown-submenu ul.dropdown-menu li a', 62 | visible: true, 63 | text: /Left/ 64 | ).click 65 | fill_in 'Title', with: 'Awesome post' 66 | click_on 'Create Post' 67 | click_on 'Edit' 68 | click_on 'Insert image' 69 | 70 | expect(page).to have_selector(:xpath, thumb_selector, visible: true) 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require File.expand_path('../../spec/dummy/config/environment', __FILE__) 4 | require 'rspec/rails' 5 | require 'database_cleaner' 6 | require 'capybara/poltergeist' 7 | 8 | Capybara.javascript_driver = :poltergeist 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 18 | 19 | # Checks for pending migrations before tests are run. 20 | # If you are not using ActiveRecord, you can remove this line. 21 | ActiveRecord::Migrator.migrations_paths = ['spec/dummy/db/migrate'] 22 | ActiveRecord::Migration.maintain_test_schema! 23 | 24 | RSpec.configure do |config| 25 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 26 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 27 | 28 | config.use_transactional_fixtures = false 29 | 30 | config.before(:suite) do 31 | DatabaseCleaner.clean_with(:truncation) 32 | end 33 | 34 | config.before(:each) do |example| 35 | DatabaseCleaner.strategy = example.metadata[:js] ? :truncation : :transaction 36 | DatabaseCleaner.start 37 | end 38 | 39 | config.after(:each) do 40 | DatabaseCleaner.clean 41 | end 42 | 43 | # RSpec Rails can automatically mix in different behaviours to your tests 44 | # based on their file location, for example enabling you to call `get` and 45 | # `post` in specs under `spec/controllers`. 46 | # 47 | # You can disable this behaviour by removing the line below, and instead 48 | # explicitly tag your specs with their type, e.g.: 49 | # 50 | # RSpec.describe UsersController, :type => :controller do 51 | # # ... 52 | # end 53 | # 54 | # The different available types are documented in the features, such as in 55 | # https://relishapp.com/rspec/rspec-rails/docs 56 | config.infer_spec_type_from_file_location! 57 | 58 | config.include(FactoryGirl::Syntax::Methods) 59 | 60 | # Manually config FactoryGirl 61 | FactoryGirl.definition_file_paths = [ 62 | Rails.root.join('../factories') 63 | ] 64 | FactoryGirl.find_definitions 65 | end 66 | -------------------------------------------------------------------------------- /app/controllers/bootsy/images_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'bootsy/application_controller' 2 | 3 | module Bootsy 4 | class ImagesController < Bootsy::ApplicationController 5 | before_action :set_gallery, only: [:index, :create] 6 | 7 | def index 8 | @images = @gallery.images 9 | 10 | respond_to do |format| 11 | format.html # index.html.erb 12 | 13 | format.json do 14 | render json: { 15 | images: @images.map { |image| image_markup(image) }, 16 | form: new_image_markup(@gallery) 17 | } 18 | end 19 | end 20 | end 21 | 22 | def create 23 | @gallery.save! 24 | @image = @gallery.images.new(image_params) 25 | 26 | create_and_respond 27 | end 28 | 29 | def destroy 30 | @image = Image.find(params[:id]) 31 | @image.destroy 32 | 33 | respond_to do |format| 34 | format.json do 35 | render json: { id: params[:id] } 36 | end 37 | 38 | format.html { redirect_to images_url } 39 | end 40 | end 41 | 42 | private 43 | 44 | def set_gallery 45 | @gallery = ImageGallery.find(params[:image_gallery_id]) 46 | end 47 | 48 | # Private: Returns the String markup to render 49 | # an image in the gallery modal. 50 | # 51 | # image - The `Bootsy::Image` instance that will 52 | # be rendered. 53 | def image_markup(image) 54 | render_to_string( 55 | file: 'bootsy/images/_image', 56 | formats: [:html], 57 | locals: { image: image }, 58 | layout: false 59 | ) 60 | end 61 | 62 | # Private: Returns the String markup to render 63 | # a form to upload a new image in a given gallery. 64 | # 65 | # gallery - The `Bootsy::ImageGallery` instance which 66 | # the image will be uploaded to. 67 | def new_image_markup(gallery) 68 | render_to_string( 69 | file: 'bootsy/images/_new', 70 | formats: [:html], 71 | locals: { gallery: gallery, image: gallery.images.new }, 72 | layout: false 73 | ) 74 | end 75 | 76 | def image_params 77 | params.require(:image).permit(:image_file, :remote_image_file_url) 78 | end 79 | 80 | def create_and_respond 81 | respond_to do |format| 82 | if @image.save 83 | format.json do 84 | render json: { 85 | image: image_markup(@image), 86 | form: new_image_markup(@gallery), 87 | gallery_id: @gallery.id 88 | } 89 | end 90 | else 91 | format.json do 92 | render json: @image.errors, status: :unprocessable_entity 93 | end 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/area.js: -------------------------------------------------------------------------------- 1 | /* global Bootsy */ 2 | 3 | window.Bootsy = window.Bootsy || {}; 4 | 5 | Bootsy.Area = function($el) { 6 | var self = this; 7 | 8 | this.$el = $el; 9 | this.unsavedChanges = false; 10 | this.locale = $el.data('bootsy-locale') || $('html').attr('lang'); 11 | if (!$.fn.wysihtml5.locale.hasOwnProperty(this.locale)) this.locale = 'en'; 12 | 13 | this.options = { 14 | locale: this.locale, 15 | alertUnsavedChanges: $el.data('bootsy-alert-unsaved'), 16 | uploader: $el.data('bootsy-uploader'), 17 | color: $el.data('bootsy-color'), 18 | emphasis: $el.data('bootsy-emphasis'), 19 | 'font-styles': $el.data('bootsy-font-styles'), 20 | html: $el.data('bootsy-html'), 21 | image: $el.data('bootsy-image'), 22 | link: $el.data('bootsy-link'), 23 | lists: $el.data('bootsy-lists'), 24 | events: { 25 | change: function() { 26 | self.unsavedChanges = true; 27 | $el.trigger('change'); 28 | } 29 | } 30 | }; 31 | }; 32 | 33 | // Alert for unsaved changes 34 | Bootsy.Area.prototype.unsavedChangesAlert = function () { 35 | if (this.unsavedChanges) { 36 | return $.fn.wysihtml5.locale[this.locale].bootsy.alertUnsaved; 37 | } 38 | }; 39 | 40 | // Clear everything 41 | Bootsy.Area.prototype.clear = function () { 42 | this.editor.clear(); 43 | this.setImageGalleryId(''); 44 | this.modal.$el.data('gallery-loaded', false); 45 | }; 46 | 47 | Bootsy.Area.prototype.setImageGalleryId = function(id) { 48 | this.$el.data('gallery-id', id); 49 | this.$el.siblings('.bootsy_image_gallery_id').val(id); 50 | }; 51 | 52 | // Init components 53 | Bootsy.Area.prototype.init = function() { 54 | if (!this.$el.data('bootsy-initialized')) { 55 | if ((this.options.image === true) && (this.options.uploader === true)) { 56 | this.modal = new Bootsy.Modal(this); 57 | this.options.image = false; 58 | this.options.customCommand = true; 59 | this.options.customCommandCallback = this.modal.show.bind(this.modal); 60 | this.options.customTemplates = { customCommand: Bootsy.imageTemplate }; 61 | } 62 | 63 | this.editor = this.$el.wysihtml5($.extend(true, {}, Bootsy.options, this.options)).data('wysihtml5').editor; 64 | 65 | // Mechanism for unsaved changes alert 66 | if (this.options.alertUnsavedChanges !== false) { 67 | window.onbeforeunload = this.unsavedChangesAlert.bind(this); 68 | } 69 | 70 | this.$el.closest('form').submit(function() { 71 | this.unsavedChanges = false; 72 | 73 | return true; 74 | }.bind(this)); 75 | 76 | this.$el.data('bootsy-initialized', true); 77 | } 78 | }; 79 | 80 | // Insert image in the text 81 | Bootsy.Area.prototype.insertImage = function(image) { 82 | this.editor.currentView.element.focus(); 83 | 84 | if (this.caretBookmark) { 85 | this.editor.composer.selection.setBookmark(this.caretBookmark); 86 | this.caretBookmark = null; 87 | } 88 | 89 | this.editor.composer.commands.exec('insertImage', image); 90 | }; 91 | -------------------------------------------------------------------------------- /app/views/bootsy/images/_loader.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.public_file_server.enabled = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # Version of your assets, change this if you want to expire all your assets. 36 | config.assets.version = '1.0' 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 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | 64 | # Ignore bad email addresses and do not raise email delivery errors. 65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 66 | # config.action_mailer.raise_delivery_errors = false 67 | 68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 69 | # the I18n.default_locale when a translation can not be found). 70 | config.i18n.fallbacks = true 71 | 72 | # Send deprecation notices to registered listeners. 73 | config.active_support.deprecation = :notify 74 | 75 | # Disable automatic flushing of the log to improve performance. 76 | # config.autoflush_log = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | end 81 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause this 4 | # file to always be loaded, without a need to explicitly require it in any files. 5 | # 6 | # Given that it is always loaded, you are encouraged to keep this file as 7 | # light-weight as possible. Requiring heavyweight dependencies from this file 8 | # will add to the boot time of your test suite on EVERY test run, even for an 9 | # individual file that may not need all of that loaded. Instead, make a 10 | # separate helper file that requires this one and then use it only in the specs 11 | # that actually need it. 12 | # 13 | # The `.rspec` file also contains a few flags that are not defaults but that 14 | # users commonly want. 15 | # 16 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 17 | RSpec.configure do |config| 18 | # The settings below are suggested to provide a good initial experience 19 | # with RSpec, but feel free to customize to your heart's content. 20 | =begin 21 | # These two settings work together to allow you to limit a spec run 22 | # to individual examples or groups you care about by tagging them with 23 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 24 | # get run. 25 | config.filter_run :focus 26 | config.run_all_when_everything_filtered = true 27 | 28 | # Many RSpec users commonly either run the entire suite or an individual 29 | # file, and it's useful to allow more verbose output when running an 30 | # individual spec file. 31 | if config.files_to_run.one? 32 | # Use the documentation formatter for detailed output, 33 | # unless a formatter has already been configured 34 | # (e.g. via a command-line flag). 35 | config.default_formatter = 'doc' 36 | end 37 | 38 | # Print the 10 slowest examples and example groups at the 39 | # end of the spec run, to help surface which specs are running 40 | # particularly slow. 41 | config.profile_examples = 10 42 | 43 | # Run specs in random order to surface order dependencies. If you find an 44 | # order dependency and want to debug it, you can fix the order by providing 45 | # the seed, which is printed after each run. 46 | # --seed 1234 47 | config.order = :random 48 | 49 | # Seed global randomization in this process using the `--seed` CLI option. 50 | # Setting this allows you to use `--seed` to deterministically reproduce 51 | # test failures related to randomization by passing the same `--seed` value 52 | # as the one that triggered the failure. 53 | Kernel.srand config.seed 54 | 55 | # rspec-expectations config goes here. You can use an alternate 56 | # assertion/expectation library such as wrong or the stdlib/minitest 57 | # assertions if you prefer. 58 | config.expect_with :rspec do |expectations| 59 | # Enable only the newer, non-monkey-patching expect syntax. 60 | # For more details, see: 61 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 62 | expectations.syntax = :expect 63 | end 64 | 65 | # rspec-mocks config goes here. You can use an alternate test double 66 | # library (such as bogus or mocha) by changing the `mock_with` option here. 67 | config.mock_with :rspec do |mocks| 68 | # Enable only the newer, non-monkey-patching expect syntax. 69 | # For more details, see: 70 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 71 | mocks.syntax = :expect 72 | 73 | # Prevents you from mocking or stubbing a method that does not exist on 74 | # a real object. This is generally recommended. 75 | mocks.verify_partial_doubles = true 76 | end 77 | =end 78 | end 79 | -------------------------------------------------------------------------------- /lib/bootsy/form_helper.rb: -------------------------------------------------------------------------------- 1 | module Bootsy 2 | # Public: Module to include Bootsy in `ActionView::Base`. 3 | module FormHelper 4 | # Public: Return a textarea element with proper attributes to 5 | # be loaded as a WYSIWYG editor. 6 | # 7 | # object_name - The String or Symbol identifier of the object assigned 8 | # to the template. 9 | # 10 | # method - The Symbol attribute name on the object assigned to the 11 | # form builder that will tailor the editor. 12 | # 13 | # options - The Hash of options used to enable/disable features of 14 | # the editor (default: {}): 15 | # :container - The `Bootsy::Container` instance model 16 | # that will be referenced by the editor's 17 | # image gallery. Defaults to the object 18 | # assigned to the template, if it is a 19 | # `Container`. 20 | # :uploader - The Boolean value used to enable/disable 21 | # the image upload feature. Default: true, 22 | # if a`Container` is found, false otherwise. 23 | # :editor_options - The Hash of options with Boolean values 24 | # usedto enable/disable features of the 25 | # editor. Available options are described ib 26 | # the Bootsyinitializer file (which is the 27 | # default for this argument). 28 | def bootsy_area(object_name, method, options = {}) 29 | container = options[:container] || options[:object] 30 | 31 | set_gallery_id(container, options) 32 | 33 | text_area(object_name, method, text_area_options(options)) + 34 | modal(options, container) + 35 | gallery_id_param(object_name, container, options) 36 | end 37 | 38 | private 39 | 40 | def enable_uploader?(options) 41 | if options[:uploader] == false 42 | false 43 | elsif options[:container].is_a?(Container) 44 | true 45 | elsif options[:container].blank? && options[:object].is_a?(Container) 46 | true 47 | else 48 | false 49 | end 50 | end 51 | 52 | def tag_class(options) 53 | classes = 54 | if options[:class].blank? 55 | [] 56 | elsif options[:class].is_a?(Array) 57 | options[:class] 58 | else 59 | [options[:class]] 60 | end 61 | 62 | classes << 'bootsy_text_area' 63 | end 64 | 65 | def data_options(options) 66 | (options[:data] || {}).deep_merge( 67 | Hash[bootsy_options(options).map do |key, value| 68 | ["bootsy-#{key}", value] 69 | end] 70 | ) 71 | end 72 | 73 | def bootsy_options(options) 74 | Bootsy.editor_options.merge(options[:editor_options] || {}) 75 | .merge(uploader: enable_uploader?(options)) 76 | end 77 | 78 | def text_area_options(options) 79 | options.except( 80 | :container, 81 | :uploader, 82 | :editor_options 83 | ).merge( 84 | data: data_options(options), 85 | class: tag_class(options) 86 | ) 87 | end 88 | 89 | def set_gallery_id(container, options) 90 | return unless enable_uploader?(options) 91 | 92 | container.bootsy_image_gallery_id ||= Bootsy::ImageGallery.create!.id 93 | options.deep_merge!( 94 | data: { gallery_id: container.bootsy_image_gallery_id } 95 | ) 96 | end 97 | 98 | def gallery_id_param(object_name, container, options) 99 | return unless enable_uploader?(options) && container.new_record? 100 | 101 | hidden_field( 102 | object_name, 103 | :bootsy_image_gallery_id, 104 | class: 'bootsy_image_gallery_id' 105 | ) 106 | end 107 | 108 | def modal(options, container) 109 | return unless enable_uploader?(options) 110 | render('bootsy/images/modal', container: container) 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/simple_form_bootstrap.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | config.button_class = 'btn btn-default' 4 | config.boolean_label_class = nil 5 | config.error_notification_class = 'alert alert-danger' 6 | 7 | config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 8 | b.use :html5 9 | b.use :placeholder 10 | b.use :label, class: 'control-label' 11 | 12 | b.wrapper tag: 'div' do |ba| 13 | ba.use :input, class: 'form-control' 14 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 15 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 16 | end 17 | end 18 | 19 | config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 20 | b.use :html5 21 | b.use :placeholder 22 | b.use :label, class: 'control-label' 23 | 24 | b.wrapper tag: 'div' do |ba| 25 | ba.use :input 26 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 27 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 28 | end 29 | end 30 | 31 | config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 32 | b.use :html5 33 | b.use :placeholder 34 | 35 | b.wrapper tag: 'div', class: 'checkbox' do |ba| 36 | ba.use :label_input 37 | end 38 | 39 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 40 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 41 | end 42 | 43 | config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 44 | b.use :html5 45 | b.use :placeholder 46 | b.use :label_input 47 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 48 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 49 | end 50 | 51 | config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 52 | b.use :html5 53 | b.use :placeholder 54 | b.use :label, class: 'col-sm-3 control-label' 55 | 56 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba| 57 | ba.use :input, class: 'form-control' 58 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 59 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 60 | end 61 | end 62 | 63 | config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 64 | b.use :html5 65 | b.use :placeholder 66 | b.use :label, class: 'col-sm-3 control-label' 67 | 68 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba| 69 | ba.use :input 70 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 71 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 72 | end 73 | end 74 | 75 | config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 76 | b.use :html5 77 | b.use :placeholder 78 | 79 | b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr| 80 | wr.wrapper tag: 'div', class: 'checkbox' do |ba| 81 | ba.use :label_input, class: 'col-sm-9' 82 | end 83 | 84 | wr.use :error, wrap_with: { tag: 'span', class: 'help-block' } 85 | wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 86 | end 87 | end 88 | 89 | config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 90 | b.use :html5 91 | b.use :placeholder 92 | 93 | b.use :label, class: 'col-sm-3 control-label' 94 | 95 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba| 96 | ba.use :input 97 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 98 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 99 | end 100 | end 101 | 102 | # Wrappers for forms and inputs using the Bootstrap toolkit. 103 | # Check the Bootstrap docs (http://getbootstrap.com) 104 | # to learn about the different styles for forms and inputs, 105 | # buttons and other elements. 106 | config.default_wrapper = :vertical_form 107 | end 108 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootsy/vendor/bootstrap.file-input.js: -------------------------------------------------------------------------------- 1 | /* 2 | Bootstrap - File Input 3 | ====================== 4 | 5 | This is meant to convert all file input tags into a set of elements that displays consistently in all browsers. 6 | 7 | Converts all 8 | 9 | into Bootstrap buttons 10 | Browse 11 | 12 | */ 13 | $(function() { 14 | 15 | $.fn.bootstrapFileInput = function() { 16 | 17 | this.each(function(i,elem){ 18 | 19 | var $elem = $(elem); 20 | 21 | // Maybe some fields don't need to be standardized. 22 | if (typeof $elem.attr('data-bfi-disabled') != 'undefined') { 23 | return; 24 | } 25 | 26 | // Set the word to be displayed on the button 27 | var buttonWord = 'Browse'; 28 | 29 | if (typeof $elem.attr('title') != 'undefined') { 30 | buttonWord = $elem.attr('title'); 31 | } 32 | 33 | // Start by getting the HTML of the input element. 34 | // Thanks for the tip http://stackoverflow.com/a/1299069 35 | var input = $('
    ').append( $elem.eq(0).clone() ).html(); 36 | var className = ''; 37 | 38 | if (!!$elem.attr('class')) { 39 | className = ' ' + $elem.attr('class'); 40 | } 41 | 42 | // Now we're going to replace that input field with a Bootstrap button. 43 | // The input will actually still be there, it will just be float above and transparent (done with the CSS). 44 | $elem.replaceWith(''+buttonWord+input+''); 45 | }) 46 | 47 | // After we have found all of the file inputs let's apply a listener for tracking the mouse movement. 48 | // This is important because the in order to give the illusion that this is a button in FF we actually need to move the button from the file input under the cursor. Ugh. 49 | .promise().done( function(){ 50 | 51 | // As the cursor moves over our new Bootstrap button we need to adjust the position of the invisible file input Browse button to be under the cursor. 52 | // This gives us the pointer cursor that FF denies us 53 | $('.file-input-wrapper').mousemove(function(cursor) { 54 | 55 | var input, wrapper, 56 | wrapperX, wrapperY, 57 | inputWidth, inputHeight, 58 | cursorX, cursorY; 59 | 60 | // This wrapper element (the button surround this file input) 61 | wrapper = $(this); 62 | // The invisible file input element 63 | input = wrapper.find("input"); 64 | // The left-most position of the wrapper 65 | wrapperX = wrapper.offset().left; 66 | // The top-most position of the wrapper 67 | wrapperY = wrapper.offset().top; 68 | // The with of the browsers input field 69 | inputWidth= input.width(); 70 | // The height of the browsers input field 71 | inputHeight= input.height(); 72 | //The position of the cursor in the wrapper 73 | cursorX = cursor.pageX; 74 | cursorY = cursor.pageY; 75 | 76 | //The positions we are to move the invisible file input 77 | // The 20 at the end is an arbitrary number of pixels that we can shift the input such that cursor is not pointing at the end of the Browse button but somewhere nearer the middle 78 | moveInputX = cursorX - wrapperX - inputWidth + 20; 79 | // Slides the invisible input Browse button to be positioned middle under the cursor 80 | moveInputY = cursorY- wrapperY - (inputHeight/2); 81 | 82 | // Apply the positioning styles to actually move the invisible file input 83 | input.css({ 84 | left:moveInputX, 85 | top:moveInputY 86 | }); 87 | }); 88 | 89 | $('.file-input-wrapper input[type=file]').change(function(){ 90 | 91 | var fileName; 92 | fileName = $(this).val(); 93 | 94 | // Remove any previous file names 95 | $(this).parent().next('.file-input-name').remove(); 96 | if (!!$(this).prop('files') && $(this).prop('files').length > 1) { 97 | fileName = $(this)[0].files.length+' files'; 98 | //$(this).parent().after(''+$(this)[0].files.length+' files'); 99 | } 100 | else { 101 | // var fakepath = 'C:\\fakepath\\'; 102 | // fileName = $(this).val().replace('C:\\fakepath\\',''); 103 | fileName = fileName.substring(fileName.lastIndexOf('\\')+1,fileName.length); 104 | } 105 | 106 | $(this).parent().after(''+fileName+''); 107 | }); 108 | 109 | }); 110 | 111 | }; 112 | 113 | // Add the styles before the first stylesheet 114 | // This ensures they can be easily overridden with developer styles 115 | var cssHtml = ''; 120 | $('link[rel=stylesheet]').eq(0).before(cssHtml); 121 | 122 | }); 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bootsy 2 | 3 | [![Gem Version](https://badge.fury.io/rb/bootsy.png)](http://badge.fury.io/rb/bootsy) 4 | [![Build Status](https://secure.travis-ci.org/volmer/bootsy.png?branch=master)](http://travis-ci.org/volmer/bootsy) 5 | [![Dependency Status](https://gemnasium.com/volmer/bootsy.png)](https://gemnasium.com/volmer/bootsy) 6 | [![Code Climate](https://codeclimate.com/github/volmer/bootsy.png)](https://codeclimate.com/github/volmer/bootsy) 7 | 8 | *Bootsy* is a WYSIWYG editor for Rails based on 9 | [Bootstrap-wysihtml5](https://github.com/jhollingworth/bootstrap-wysihtml5) with image uploads using 10 | [CarrierWave](https://github.com/carrierwaveuploader/carrierwave). 11 | 12 | ### Live demo 13 | 14 | * [bootsy-demo.herokuapp.com](http://bootsy-demo.herokuapp.com/) 15 | [![image](https://f.cloud.github.com/assets/301187/1365250/e1b7ba80-3854-11e3-9bfe-8bd1e090aca8.png)](http://bootsy-demo.herokuapp.com/) 16 | 17 | 18 | ## Requirements 19 | 20 | * ImageMagick or GraphicsMagick (for MiniMagick); 21 | * [Bootstrap 3](http://getbootstrap.com/) fully installed in your app. 22 | 23 | 24 | ## Installation 25 | 26 | 1. Add Bootsy to your Gemfile and `bundle install` it: 27 | ```ruby 28 | gem 'bootsy' 29 | ``` 30 | 31 | ```console 32 | bundle install 33 | ``` 34 | 35 | 2. Mount Bootsy at the beginning of your `config/routes.rb`: 36 | ```ruby 37 | Rails.application.routes.draw do 38 | mount Bootsy::Engine => '/bootsy', as: 'bootsy' 39 | 40 | ... 41 | 42 | end 43 | ``` 44 | 45 | 3. Require Bootsy in the asset pipeline: 46 | 47 | In your `app/assets/javascripts/application.js`, put this **after** 48 | requiring jQuery and Bootstrap: 49 | 50 | ```javascript 51 | //= require bootsy 52 | ``` 53 | 54 | In your `app/assets/stylesheets/application.css`, put this line **after** 55 | requiring Bootstrap: 56 | 57 | ```css 58 | *= require bootsy 59 | ``` 60 | 61 | 4. Add and run migrations: 62 | ```console 63 | bundle exec rake bootsy:install:migrations 64 | bundle exec rake db:migrate 65 | ``` 66 | 67 | 68 | ## Usage 69 | 70 | Just call `bootsy_area` in your `FormBuilder` instances, the 71 | same way you'd call `textarea`. Example: 72 | ```erb 73 | <%= form_for(@post) do |f| %> 74 | <%= f.label :title %> 75 | <%= f.text_field :title %> 76 | 77 | <%= f.label :content %> 78 | <%= f.bootsy_area :content %> 79 | 80 | <%= f.submit %> 81 | <% end %> 82 | ``` 83 | 84 | Bootsy will group the uploaded images as galleries and associate them to one of 85 | your models. For instance, if you have a `Post` model and you want to use `bootsy_area` 86 | with it, you must include the `Bootsy::Container` module: 87 | ```ruby 88 | class Post < ActiveRecord::Base 89 | include Bootsy::Container 90 | end 91 | ``` 92 | 93 | Don't forget to ensure the association between your model objects with Bootsy 94 | image galleries. For `strong_parameters`, you must whitelist the `bootsy_image_gallery_id` parameter 95 | in your controller: 96 | ```ruby 97 | private 98 | 99 | def post_params 100 | params.require(:post).permit(:title, :content, :bootsy_image_gallery_id) 101 | end 102 | ``` 103 | 104 | 105 | ## Bootsy with [Simple Form](https://github.com/plataformatec/simple_form) builders 106 | 107 | You can use `bootsy` as an input type in `SimpleForm::FormBuilder` instances. Example: 108 | ```erb 109 | <%= simple_form_for @post do |f| %> 110 | <%= f.input :title %> 111 | 112 | <%= f.input :content, as: :bootsy %> 113 | 114 | <%= f.button :submit %> 115 | <% end %> 116 | ``` 117 | 118 | 119 | ## Editor options 120 | 121 | You can customize Bootsy through a hash of `editor_options`: 122 | 123 | 124 | ### Enable/disable features 125 | 126 | You can enable and disable features as you like. For instance, if you don't want link and color features: 127 | ```erb 128 | <%= f.bootsy_area :my_attribute, editor_options: { link: false, color: false } %> 129 | ``` 130 | Available options are: `:blockquote`, `:font_styles`, `:emphasis`, `:lists`, `:html`, `:link`, `:image` and `:color`. 131 | 132 | 133 | ### Alert of unsaved changes 134 | 135 | By default Bootsy alerts the user about unsaved changes if the page is closed or reloaded. You can disable 136 | this feature with: 137 | ```erb 138 | <%= f.bootsy_area :my_attribute, editor_options: { alert_unsaved: false } %> 139 | ``` 140 | 141 | ## Uploads 142 | 143 | If you don't want to have image uploads, just call `bootsy_area` in a form builder not 144 | associated to a `Bootsy::Container` model. This way users will still be able to insert 145 | images in the text area using an external image URL. 146 | 147 | 148 | ## Configuration 149 | 150 | You can set the default editor options, image sizes available (small, medium, 151 | large and/or its original), dimensions and more. Create a copy of [Bootsy's initalizer 152 | file](https://github.com/volmer/bootsy/tree/master/config/initializers/bootsy.rb) 153 | in your `config/initializers` and feel free to uncomment and change the options 154 | as you like. 155 | 156 | 157 | ## I18n 158 | 159 | You can translate Bootsy to your own language. Simply create a locale file for 160 | it in your `config/locales` directory similar to [Bootsy's master English file](https://github.com/volmer/bootsy/tree/master/config/locales/bootsy.en.yml). 161 | 162 | You also need to translate Bootsy on the JavaScript side. Just follow 163 | [this example](https://github.com/volmer/bootsy/blob/master/app/assets/javascripts/bootsy/locales/en.js). 164 | Bootsy will try to guess the locale based on the `lang` attribute of the page's `` tag. 165 | You can set the locale directly by setting a `data-bootsy-locale` attribute on your `