tb_core 1.4.5 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +2 -2
- data/Rakefile +1 -1
- data/app/controllers/admin/password_resets_controller.rb +1 -0
- data/app/controllers/admin/roles_controller.rb +1 -3
- data/app/controllers/admin/settings_controller.rb +2 -2
- data/app/controllers/admin/setup_controller.rb +1 -1
- data/app/controllers/admin/users_controller.rb +4 -4
- data/app/controllers/concerns/tb_core/error_handling.rb +5 -4
- data/app/controllers/concerns/tb_core/redirection.rb +1 -0
- data/app/controllers/concerns/tb_core/user_authentication.rb +5 -0
- data/app/controllers/user_sessions_controller.rb +2 -2
- data/app/helpers/forgot_password_mailer_helper.rb +9 -0
- data/app/helpers/tb_core/application_helper.rb +17 -0
- data/app/mailers/tb_core_mailer.rb +2 -0
- data/app/models/concerns/tb_core/user_model.rb +55 -8
- data/app/models/spud_role.rb +2 -7
- data/app/views/layouts/admin/application.html.erb +1 -1
- data/app/views/tb_core_mailer/forgot_password_notification.html.erb +5 -1
- data/lib/generators/spud/controller_spec_generator.rb +1 -1
- data/lib/generators/spud/module_generator.rb +4 -4
- data/lib/generators/spud/setup_generator.rb +3 -5
- data/lib/tb_core/belongs_to_app.rb +2 -1
- data/lib/tb_core/engine.rb +1 -3
- data/lib/tb_core/form_builder.rb +1 -1
- data/lib/tb_core/test_files.rb +3 -3
- data/lib/tb_core/test_helper.rb +25 -25
- data/lib/tb_core/version.rb +1 -1
- data/spec/controllers/admin/application_controller_spec.rb +3 -3
- data/spec/controllers/admin/dashboard_controller_spec.rb +1 -1
- data/spec/controllers/admin/password_reset_controller_spec.rb +2 -2
- data/spec/controllers/admin/settings_controller_spec.rb +1 -1
- data/spec/controllers/admin/setup_controller_spec.rb +1 -1
- data/spec/controllers/admin/user_sessions_controller_spec.rb +1 -1
- data/spec/controllers/admin/users_controller_spec.rb +3 -3
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/config/application.rb +1 -46
- data/spec/dummy/config/initializers/secret_token.rb +0 -1
- data/spec/factories/spud_admin_permission_factories.rb +1 -1
- data/spec/factories/spud_user_factories.rb +2 -2
- data/spec/models/spud_user_spec.rb +2 -2
- data/spec/rails_helper.rb +1 -1
- data/vendor/assets/rails-validator/rails-validator.js +534 -0
- data/vendor/assets/rails-validator/rails-validator.js.map +1 -0
- data/vendor/assets/rails-validator/rails-validator.min.js +3 -0
- metadata +44 -11
@@ -5,7 +5,7 @@ class Spud::ModuleGenerator < ::Rails::Generators::Base
|
|
5
5
|
argument :module_name, type: :string
|
6
6
|
argument :attributes, type: :array, default: [], banner: 'field[:type][:index] field[:type][:index]'
|
7
7
|
|
8
|
-
source_root File.expand_path('
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
9
|
|
10
10
|
def create_module
|
11
11
|
install_templates
|
@@ -79,7 +79,7 @@ class Spud::ModuleGenerator < ::Rails::Generators::Base
|
|
79
79
|
resources :#{module_name_formatted}
|
80
80
|
end
|
81
81
|
resources :#{module_name_formatted}, :only => [:index, :show]
|
82
|
-
RUBY
|
82
|
+
RUBY
|
83
83
|
end
|
84
84
|
|
85
85
|
def create_specs
|
@@ -136,7 +136,7 @@ RUBY
|
|
136
136
|
inject_into_file "app/models/#{module_name.singularize.underscore}.rb", after: "ApplicationRecord\n" do <<-RUBY
|
137
137
|
scope :ordered, -> { order(#{sort_field}: :desc) }
|
138
138
|
scope :search, ->(term) { where('#{search_field} LIKE ?', "%\#{term}%") }
|
139
|
-
RUBY
|
139
|
+
RUBY
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
@@ -156,7 +156,7 @@ RUBY
|
|
156
156
|
|
157
157
|
validates #{string_attrs_as_symbols}, :length => {:maximum => 255}
|
158
158
|
|
159
|
-
|
159
|
+
RUBY
|
160
160
|
end
|
161
161
|
end
|
162
162
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Spud::SetupGenerator < ::Rails::Generators::Base
|
2
2
|
desc 'Base setup for a new TB application'
|
3
|
-
source_root File.expand_path('
|
3
|
+
source_root File.expand_path('templates', __dir__)
|
4
4
|
|
5
5
|
def setup
|
6
6
|
if ask(
|
@@ -16,9 +16,7 @@ class Spud::SetupGenerator < ::Rails::Generators::Base
|
|
16
16
|
environment(spud_core_configs())
|
17
17
|
rake('railties:install:migrations')
|
18
18
|
|
19
|
-
if ask('Migrate the database?', limited_to: ['y', 'n']).downcase == 'y'
|
20
|
-
rake('db:migrate')
|
21
|
-
end
|
19
|
+
rake('db:migrate') if ask('Migrate the database?', limited_to: ['y', 'n']).downcase == 'y'
|
22
20
|
end
|
23
21
|
|
24
22
|
private
|
@@ -49,7 +47,7 @@ private
|
|
49
47
|
config.site_name = "#{site_name}"
|
50
48
|
config.from_address = "no-reply@#{domain_name}.com"
|
51
49
|
end
|
52
|
-
RUBY
|
50
|
+
RUBY
|
53
51
|
end
|
54
52
|
|
55
53
|
def application_name
|
@@ -18,7 +18,7 @@ module TbCore
|
|
18
18
|
# only: An array of controller actions you want this to apply to (optional)
|
19
19
|
#
|
20
20
|
def belongs_to_app(name, page_title: nil, only: nil)
|
21
|
-
before_action ->
|
21
|
+
before_action ->{
|
22
22
|
act_as_app(name, page_title: page_title)
|
23
23
|
}, only: only
|
24
24
|
end
|
@@ -38,6 +38,7 @@ module TbCore
|
|
38
38
|
elsif !current_user.can_view_app?(@page_application)
|
39
39
|
raise AccessDeniedError.new(item: 'module', template: '/layouts/admin/error_page')
|
40
40
|
end
|
41
|
+
|
41
42
|
@page_thumbnail = @page_application[:thumbnail]
|
42
43
|
@page_name = determine_page_name(page_title || @page_application[:name], action_name)
|
43
44
|
end
|
data/lib/tb_core/engine.rb
CHANGED
@@ -49,9 +49,7 @@ module TbCore
|
|
49
49
|
|
50
50
|
initializer 'tb_core.catch_all_route' do |config|
|
51
51
|
# Handle 404 errors if Spud::Cms is not installed
|
52
|
-
unless defined?(Spud::Cms)
|
53
|
-
config.routes_reloader.paths << File.expand_path('../catch_all_route.rb', __FILE__)
|
54
|
-
end
|
52
|
+
config.routes_reloader.paths << File.expand_path('catch_all_route.rb', __dir__) unless defined?(Spud::Cms)
|
55
53
|
end
|
56
54
|
|
57
55
|
initializer 'tb_core.assets' do
|
data/lib/tb_core/form_builder.rb
CHANGED
@@ -219,7 +219,7 @@ class TbCore::FormBuilder < ActionView::Helpers::FormBuilder
|
|
219
219
|
# Builds a file field group
|
220
220
|
#
|
221
221
|
def tb_file_field(attribute, options={})
|
222
|
-
tb_input_field(attribute) do
|
222
|
+
tb_input_field(attribute, nil, options) do
|
223
223
|
file_field(attribute)
|
224
224
|
end
|
225
225
|
end
|
data/lib/tb_core/test_files.rb
CHANGED
@@ -9,15 +9,15 @@ module Spud
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def load_specs
|
12
|
-
Dir[File.join(File.expand_path('
|
12
|
+
Dir[File.join(File.expand_path('../..', __dir__), 'spec/**/*_spec.rb')].each {|f| require f}
|
13
13
|
end
|
14
14
|
|
15
15
|
def load_factories
|
16
|
-
Dir[File.join(File.expand_path('
|
16
|
+
Dir[File.join(File.expand_path('../..', __dir__), 'factories/*')].each {|f| require f}
|
17
17
|
end
|
18
18
|
|
19
19
|
def load_support
|
20
|
-
Dir[File.join(File.expand_path('
|
20
|
+
Dir[File.join(File.expand_path('../..', __dir__), 'spec/support/**/*.rb')].each {|f| require f}
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
data/lib/tb_core/test_helper.rb
CHANGED
@@ -1,29 +1,31 @@
|
|
1
1
|
require 'authlogic/test_case'
|
2
|
-
include Authlogic::TestCase
|
3
2
|
|
4
|
-
module TbCore
|
3
|
+
module TbCore
|
4
|
+
module TestHelper
|
5
|
+
include Authlogic::TestCase
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
# Use this helper in controller specs to establish a login session
|
8
|
+
# - admin: Set to true to create a super_admin
|
9
|
+
# - permissions: One or more permissions you want to assign to the user (a role will be auto generated)
|
10
|
+
#
|
11
|
+
def activate_session(admin: false, permissions: nil)
|
12
|
+
activate_authlogic()
|
13
|
+
if permissions
|
14
|
+
permissions = [permissions] unless permissions.is_a?(Array)
|
15
|
+
role = SpudRole.create(name: 'New Role', permission_tags: permissions)
|
16
|
+
else
|
17
|
+
role = nil
|
18
|
+
end
|
19
|
+
@user = FactoryBot.create(:spud_user, super_admin: admin, role: role)
|
20
|
+
SpudUserSession.create(@user)
|
21
|
+
return @user
|
17
22
|
end
|
18
|
-
@user = FactoryBot.create(:spud_user, super_admin: admin, role: role)
|
19
|
-
SpudUserSession.create(@user)
|
20
|
-
return @user
|
21
|
-
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# Returns the current user
|
25
|
+
#
|
26
|
+
def current_user
|
27
|
+
return @user
|
28
|
+
end
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -31,12 +33,10 @@ end
|
|
31
33
|
#
|
32
34
|
if defined?(RSpec)
|
33
35
|
RSpec.configure do |config|
|
34
|
-
config.include TbCore::
|
36
|
+
config.include TbCore::TestHelper
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
40
|
# Auto load the spud_user factory if FactoryBot is in use
|
39
41
|
#
|
40
|
-
if defined?(FactoryBot)
|
41
|
-
Dir[TbCore::Engine.root.join('spec/factories/spud_user_factories.rb')].each{ |f| require f }
|
42
|
-
end
|
42
|
+
Dir[TbCore::Engine.root.join('spec/factories/spud_user_factories.rb')].each{ |f| require f } if defined?(FactoryBot)
|
data/lib/tb_core/version.rb
CHANGED
@@ -17,9 +17,9 @@ RSpec.describe Admin::ApplicationController, type: :controller do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'should respond successfully if the current user is a super admin' do
|
20
|
-
@user.
|
20
|
+
@user.update(super_admin: true)
|
21
21
|
get :index
|
22
|
-
expect(response).to
|
22
|
+
expect(response).to be_successful
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'should respond successfully if the current user has admin permissions' do
|
@@ -28,7 +28,7 @@ RSpec.describe Admin::ApplicationController, type: :controller do
|
|
28
28
|
@user.role = @role
|
29
29
|
@user.save
|
30
30
|
get :index
|
31
|
-
expect(response).to
|
31
|
+
expect(response).to be_successful
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'should redirect to the login if the current user is not logged in' do
|
@@ -69,7 +69,7 @@ RSpec.describe Admin::DashboardController, type: :controller do
|
|
69
69
|
it 'should contain data array in reponse' do
|
70
70
|
get :badges
|
71
71
|
json = JSON.parse(response.body)
|
72
|
-
expect(response.
|
72
|
+
expect(response.media_type).to eq('application/json')
|
73
73
|
expect(json).to have_key('data')
|
74
74
|
end
|
75
75
|
|
@@ -8,7 +8,7 @@ describe Admin::PasswordResetsController, type: :controller do
|
|
8
8
|
describe 'index' do
|
9
9
|
it 'should return success' do
|
10
10
|
get :index
|
11
|
-
expect(response).to
|
11
|
+
expect(response).to be_successful
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -17,7 +17,7 @@ describe Admin::PasswordResetsController, type: :controller do
|
|
17
17
|
it 'should render the edit form' do
|
18
18
|
allow(SpudUser).to receive(:find_using_perishable_token).and_return(user)
|
19
19
|
get :show, params: { id: 1 }
|
20
|
-
expect(response).to
|
20
|
+
expect(response).to be_successful
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -39,7 +39,7 @@ describe Admin::UsersController, type: :controller do
|
|
39
39
|
SpudUserSession.create(u)
|
40
40
|
get :index
|
41
41
|
|
42
|
-
expect(response).to
|
42
|
+
expect(response).to be_successful
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'should not allow access to users without a role,
|
@@ -82,14 +82,14 @@ describe Admin::UsersController, type: :controller do
|
|
82
82
|
it 'should respond successfully' do
|
83
83
|
user = FactoryBot.create(:spud_user)
|
84
84
|
get :show, params: { id: user.id }
|
85
|
-
expect(response).to
|
85
|
+
expect(response).to be_successful
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
describe 'new' do
|
90
90
|
it 'should render the form' do
|
91
91
|
get :new, format: :html
|
92
|
-
expect(response).to
|
92
|
+
expect(response).to be_successful
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -12,51 +12,6 @@ module Dummy
|
|
12
12
|
config.from_address = 'no-reply@dummy.com'
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
# Application configuration should go into files in config/initializers
|
17
|
-
# -- all .rb files in that directory are automatically loaded.
|
18
|
-
|
19
|
-
# Custom directories with classes and modules you want to be autoloadable.
|
20
|
-
# config.autoload_paths += %W(#{config.root}/extras)
|
21
|
-
|
22
|
-
# Only load the plugins named here, in the order given (default is alphabetical).
|
23
|
-
# :all can be used as a placeholder for all plugins not explicitly named.
|
24
|
-
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
|
25
|
-
|
26
|
-
# Activate observers that should always be running.
|
27
|
-
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
|
28
|
-
|
29
|
-
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
30
|
-
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
31
|
-
# config.time_zone = 'Central Time (US & Canada)'
|
32
|
-
|
33
|
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
34
|
-
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
35
|
-
# config.i18n.default_locale = :de
|
36
|
-
config.i18n.enforce_available_locales = true
|
37
|
-
|
38
|
-
# Configure the default encoding used in templates for Ruby 1.9.
|
39
|
-
config.encoding = 'utf-8'
|
40
|
-
|
41
|
-
# Configure sensitive parameters which will be filtered from the log file.
|
42
|
-
config.filter_parameters += [:password]
|
43
|
-
|
44
|
-
# Use SQL instead of Active Record's schema dumper when creating the database.
|
45
|
-
# This is necessary if your schema can't be completely dumped by the schema dumper,
|
46
|
-
# like if you have constraints or database-specific column types
|
47
|
-
# config.active_record.schema_format = :sql
|
48
|
-
|
49
|
-
# Enforce whitelist mode for mass assignment.
|
50
|
-
# This will create an empty whitelist of attributes available for mass-assignment for all models
|
51
|
-
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
|
52
|
-
# parameters by using an attr_accessible or attr_protected declaration.
|
53
|
-
# config.active_record.whitelist_attributes = true
|
54
|
-
|
55
|
-
# Enable the asset pipeline
|
56
|
-
# config.assets.enabled = true
|
57
|
-
|
58
|
-
# Version of your assets, change this if you want to expire all your assets
|
59
|
-
# config.assets.version = '1.0'
|
60
|
-
|
15
|
+
config.load_defaults '6.0'
|
61
16
|
end
|
62
17
|
end
|
@@ -4,5 +4,4 @@
|
|
4
4
|
# If you change this key, all old signed cookies will become invalid!
|
5
5
|
# Make sure the secret is at least 30 characters and all random,
|
6
6
|
# no regular words or you'll be exposed to dictionary attacks.
|
7
|
-
Dummy::Application.config.secret_token = 'f89bbe176924c011473e15af21c4e8b72c3fd12f887f26014c24ea32e1e3d2bf0250a671a30232047d5c5431e67c7361aca07b10d847405b99fce4543589e117'
|
8
7
|
Dummy::Application.config.secret_key_base = 'b43711419c807b9c9efe9365dd79f87ebe31e9f72e1aa6744c1ecac68aace664c0a3a4f791d92bd2fa324fce1d6d09701a54b88453f131b7c460eeb9c530bd72'
|
@@ -24,8 +24,8 @@ FactoryBot.define do
|
|
24
24
|
last_name { FactoryBot.generate(:last_name) }
|
25
25
|
login { FactoryBot.generate(:login) }
|
26
26
|
email { FactoryBot.generate(:email) }
|
27
|
-
password 'password'
|
28
|
-
password_confirmation 'password'
|
27
|
+
password { 'password' }
|
28
|
+
password_confirmation { 'password' }
|
29
29
|
single_access_token { FactoryBot.generate(:single_access_token) }
|
30
30
|
end
|
31
31
|
end
|
@@ -66,14 +66,14 @@ describe SpudUser, type: :model do
|
|
66
66
|
it 'should not unset the requires_password_change flag' do
|
67
67
|
u = FactoryBot.create(:spud_user, requires_password_change: true)
|
68
68
|
expect{
|
69
|
-
u.
|
69
|
+
u.update(first_name: 'updated')
|
70
70
|
}.to_not change(u, :requires_password_change)
|
71
71
|
end
|
72
72
|
|
73
73
|
it 'should unset the requires_password_change flag' do
|
74
74
|
u = FactoryBot.create(:spud_user, requires_password_change: true)
|
75
75
|
expect{
|
76
|
-
u.
|
76
|
+
u.update(password: 'new password', password_confirmation: 'new password')
|
77
77
|
u.reload
|
78
78
|
}.to change(u, :requires_password_change).from(true).to(false)
|
79
79
|
end
|
data/spec/rails_helper.rb
CHANGED
@@ -4,7 +4,7 @@ SimpleCov.start 'rails'
|
|
4
4
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
5
5
|
ENV['RAILS_ENV'] ||= 'test'
|
6
6
|
|
7
|
-
require File.expand_path('
|
7
|
+
require File.expand_path('dummy/config/environment.rb', __dir__)
|
8
8
|
require 'spec_helper'
|
9
9
|
require 'tb_core/test_helper'
|
10
10
|
require 'rails-controller-testing'
|
@@ -0,0 +1,534 @@
|
|
1
|
+
/******/ (function(modules) { // webpackBootstrap
|
2
|
+
/******/ // The module cache
|
3
|
+
/******/ var installedModules = {};
|
4
|
+
/******/
|
5
|
+
/******/ // The require function
|
6
|
+
/******/ function __webpack_require__(moduleId) {
|
7
|
+
/******/
|
8
|
+
/******/ // Check if module is in cache
|
9
|
+
/******/ if(installedModules[moduleId]) {
|
10
|
+
/******/ return installedModules[moduleId].exports;
|
11
|
+
/******/ }
|
12
|
+
/******/ // Create a new module (and put it into the cache)
|
13
|
+
/******/ var module = installedModules[moduleId] = {
|
14
|
+
/******/ i: moduleId,
|
15
|
+
/******/ l: false,
|
16
|
+
/******/ exports: {}
|
17
|
+
/******/ };
|
18
|
+
/******/
|
19
|
+
/******/ // Execute the module function
|
20
|
+
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
21
|
+
/******/
|
22
|
+
/******/ // Flag the module as loaded
|
23
|
+
/******/ module.l = true;
|
24
|
+
/******/
|
25
|
+
/******/ // Return the exports of the module
|
26
|
+
/******/ return module.exports;
|
27
|
+
/******/ }
|
28
|
+
/******/
|
29
|
+
/******/
|
30
|
+
/******/ // expose the modules object (__webpack_modules__)
|
31
|
+
/******/ __webpack_require__.m = modules;
|
32
|
+
/******/
|
33
|
+
/******/ // expose the module cache
|
34
|
+
/******/ __webpack_require__.c = installedModules;
|
35
|
+
/******/
|
36
|
+
/******/ // define getter function for harmony exports
|
37
|
+
/******/ __webpack_require__.d = function(exports, name, getter) {
|
38
|
+
/******/ if(!__webpack_require__.o(exports, name)) {
|
39
|
+
/******/ Object.defineProperty(exports, name, {
|
40
|
+
/******/ configurable: false,
|
41
|
+
/******/ enumerable: true,
|
42
|
+
/******/ get: getter
|
43
|
+
/******/ });
|
44
|
+
/******/ }
|
45
|
+
/******/ };
|
46
|
+
/******/
|
47
|
+
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
48
|
+
/******/ __webpack_require__.n = function(module) {
|
49
|
+
/******/ var getter = module && module.__esModule ?
|
50
|
+
/******/ function getDefault() { return module['default']; } :
|
51
|
+
/******/ function getModuleExports() { return module; };
|
52
|
+
/******/ __webpack_require__.d(getter, 'a', getter);
|
53
|
+
/******/ return getter;
|
54
|
+
/******/ };
|
55
|
+
/******/
|
56
|
+
/******/ // Object.prototype.hasOwnProperty.call
|
57
|
+
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
58
|
+
/******/
|
59
|
+
/******/ // __webpack_public_path__
|
60
|
+
/******/ __webpack_require__.p = "";
|
61
|
+
/******/
|
62
|
+
/******/ // Load entry module and return exports
|
63
|
+
/******/ return __webpack_require__(__webpack_require__.s = 1);
|
64
|
+
/******/ })
|
65
|
+
/************************************************************************/
|
66
|
+
/******/ ([
|
67
|
+
/* 0 */
|
68
|
+
/***/ (function(module, exports, __webpack_require__) {
|
69
|
+
|
70
|
+
"use strict";
|
71
|
+
|
72
|
+
|
73
|
+
Object.defineProperty(exports, "__esModule", {
|
74
|
+
value: true
|
75
|
+
});
|
76
|
+
/*
|
77
|
+
* Build a path for redirection.
|
78
|
+
*
|
79
|
+
* - Adds a timestamp GET param for cache busting
|
80
|
+
* - Inserts an ID attribute if necessary
|
81
|
+
*/
|
82
|
+
var buildSuccessPath = exports.buildSuccessPath = function buildSuccessPath(path, json) {
|
83
|
+
var timestamp = new Date().valueOf();
|
84
|
+
if (path.indexOf('?') > -1) {
|
85
|
+
path += '&t=' + timestamp;
|
86
|
+
} else {
|
87
|
+
path += '?t=' + timestamp;
|
88
|
+
}
|
89
|
+
if (json && json.id) {
|
90
|
+
path = path.replace(':id', json.id);
|
91
|
+
}
|
92
|
+
return path;
|
93
|
+
};
|
94
|
+
|
95
|
+
/*
|
96
|
+
* Remote element
|
97
|
+
*/
|
98
|
+
var removeElement = exports.removeElement = function removeElement(element) {
|
99
|
+
element.parentElement.removeChild(element);
|
100
|
+
};
|
101
|
+
|
102
|
+
/*
|
103
|
+
* Create an error element
|
104
|
+
*/
|
105
|
+
var createErrorParagraph = exports.createErrorParagraph = function createErrorParagraph(text, base) {
|
106
|
+
var p = document.createElement('p');
|
107
|
+
p.classList.add('form-error');
|
108
|
+
p.classList.add(base ? 'form-error-base' : 'form-error-inline');
|
109
|
+
p.innerText = text;
|
110
|
+
return p;
|
111
|
+
};
|
112
|
+
|
113
|
+
/*
|
114
|
+
* Convert a query into an array of elements
|
115
|
+
*/
|
116
|
+
var querySelectorArray = exports.querySelectorArray = function querySelectorArray(parent, selector) {
|
117
|
+
var query = parent.querySelectorAll(selector);
|
118
|
+
var array = Array.prototype.slice.call(query);
|
119
|
+
return array;
|
120
|
+
};
|
121
|
+
|
122
|
+
/*
|
123
|
+
* Take a single error message and format it as if it were
|
124
|
+
* a json error coming from the server
|
125
|
+
*/
|
126
|
+
var formatStringAsErrors = exports.formatStringAsErrors = function formatStringAsErrors(errorString) {
|
127
|
+
return { base: [errorString] };
|
128
|
+
};
|
129
|
+
|
130
|
+
/*
|
131
|
+
* Return true if the element is in view
|
132
|
+
*/
|
133
|
+
var elementIsInView = exports.elementIsInView = function elementIsInView(el) {
|
134
|
+
var rect = el.getBoundingClientRect();
|
135
|
+
var w = window,
|
136
|
+
d = document;
|
137
|
+
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (w.innerHeight || d.documentElement.clientHeight) && rect.right <= (w.innerWidth || d.documentElement.clientWidth);
|
138
|
+
};
|
139
|
+
|
140
|
+
/***/ }),
|
141
|
+
/* 1 */
|
142
|
+
/***/ (function(module, exports, __webpack_require__) {
|
143
|
+
|
144
|
+
"use strict";
|
145
|
+
|
146
|
+
|
147
|
+
var _handlers = __webpack_require__(2);
|
148
|
+
|
149
|
+
var handlers = _interopRequireWildcard(_handlers);
|
150
|
+
|
151
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
152
|
+
|
153
|
+
var MODULE_LOADED = '_rails_validator_loaded';
|
154
|
+
|
155
|
+
var init = function init() {
|
156
|
+
window[MODULE_LOADED] = true;
|
157
|
+
addListener('form', 'ajax:before', handlers.onRemoteFormBefore);
|
158
|
+
addListener('form[data-success], a[data-success]', 'ajax:success', handlers.onRemoteFormSuccess);
|
159
|
+
addListener('form[data-errors]', 'ajax:error', handlers.onRemoteFormErrors);
|
160
|
+
};
|
161
|
+
|
162
|
+
var addListener = function addListener(selector, event, fn) {
|
163
|
+
document.addEventListener(event, function (e) {
|
164
|
+
if (e.target.matches(selector)) {
|
165
|
+
e.stopPropagation();
|
166
|
+
fn.call(e.target, e);
|
167
|
+
}
|
168
|
+
});
|
169
|
+
};
|
170
|
+
|
171
|
+
if (!window[MODULE_LOADED]) {
|
172
|
+
init();
|
173
|
+
}
|
174
|
+
|
175
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
|
176
|
+
if (!Element.prototype.matches) {
|
177
|
+
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
|
178
|
+
}
|
179
|
+
|
180
|
+
/***/ }),
|
181
|
+
/* 2 */
|
182
|
+
/***/ (function(module, exports, __webpack_require__) {
|
183
|
+
|
184
|
+
"use strict";
|
185
|
+
|
186
|
+
|
187
|
+
Object.defineProperty(exports, "__esModule", {
|
188
|
+
value: true
|
189
|
+
});
|
190
|
+
exports.onRemoteFormErrors = exports.onRemoteFormSuccess = exports.onRemoteFormBefore = undefined;
|
191
|
+
|
192
|
+
var _errors = __webpack_require__(3);
|
193
|
+
|
194
|
+
var errors = _interopRequireWildcard(_errors);
|
195
|
+
|
196
|
+
var _helpers = __webpack_require__(0);
|
197
|
+
|
198
|
+
var helpers = _interopRequireWildcard(_helpers);
|
199
|
+
|
200
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
201
|
+
|
202
|
+
/*
|
203
|
+
* Tell the server we want a json response
|
204
|
+
*/
|
205
|
+
var onRemoteFormBefore = exports.onRemoteFormBefore = function onRemoteFormBefore(event) {
|
206
|
+
var form = event.target;
|
207
|
+
if (form.getAttribute('data-type') === null) {
|
208
|
+
form.setAttribute('data-type', 'json');
|
209
|
+
}
|
210
|
+
};
|
211
|
+
|
212
|
+
/*
|
213
|
+
* Called when a remote form is submitted with a data-success configured
|
214
|
+
* Redirects to the configured path, or reloads the page if "reload" is passed
|
215
|
+
*
|
216
|
+
* ie:
|
217
|
+
* <form action="..." data-remote="true" data-success="/path/for/success/:id">
|
218
|
+
*/
|
219
|
+
var onRemoteFormSuccess = exports.onRemoteFormSuccess = function onRemoteFormSuccess(event) {
|
220
|
+
var form = event.target;
|
221
|
+
var successPath = form.getAttribute('data-success');
|
222
|
+
if (successPath === 'reload') {
|
223
|
+
window.location.reload(true);
|
224
|
+
} else {
|
225
|
+
var json = event.detail[0];
|
226
|
+
window.location = helpers.buildSuccessPath(successPath, json);
|
227
|
+
}
|
228
|
+
};
|
229
|
+
|
230
|
+
/*
|
231
|
+
* Called when a remote form is submitted and the server has returned an
|
232
|
+
* error, only if the form has a data-errors attribute
|
233
|
+
*
|
234
|
+
* ie:
|
235
|
+
* <form action="..." data-remote="true" data-errors="inline">
|
236
|
+
* - OR -
|
237
|
+
* <form action="..." data-remote="true" data-errors="alert">
|
238
|
+
*/
|
239
|
+
var onRemoteFormErrors = exports.onRemoteFormErrors = function onRemoteFormErrors(event) {
|
240
|
+
var json = event.detail[0];
|
241
|
+
var xhr = event.detail[2];
|
242
|
+
var form = event.target;
|
243
|
+
var errorType = form.getAttribute('data-errors');
|
244
|
+
|
245
|
+
if (xhr.status === 422) {
|
246
|
+
if (errorType === 'inline') {
|
247
|
+
errors.displayErrorsInline(form, json);
|
248
|
+
} else {
|
249
|
+
errors.displayErrorsAlert(json);
|
250
|
+
}
|
251
|
+
} else if (xhr.status === 401 || xhr.status === 403) {
|
252
|
+
window.alert('Error: Access denied.');
|
253
|
+
} else {
|
254
|
+
var errorThrown = event.detail[1];
|
255
|
+
window.alert('An unexpected error occurred: ' + errorThrown);
|
256
|
+
}
|
257
|
+
};
|
258
|
+
|
259
|
+
/***/ }),
|
260
|
+
/* 3 */
|
261
|
+
/***/ (function(module, exports, __webpack_require__) {
|
262
|
+
|
263
|
+
"use strict";
|
264
|
+
|
265
|
+
|
266
|
+
Object.defineProperty(exports, "__esModule", {
|
267
|
+
value: true
|
268
|
+
});
|
269
|
+
exports.displayErrorsAlert = exports.displayErrorsInline = exports.clearErrors = undefined;
|
270
|
+
|
271
|
+
var _helpers = __webpack_require__(0);
|
272
|
+
|
273
|
+
var helpers = _interopRequireWildcard(_helpers);
|
274
|
+
|
275
|
+
var _case = __webpack_require__(4);
|
276
|
+
|
277
|
+
var _case2 = _interopRequireDefault(_case);
|
278
|
+
|
279
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
280
|
+
|
281
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
282
|
+
|
283
|
+
/*
|
284
|
+
* Remove inline error messages
|
285
|
+
*/
|
286
|
+
var clearErrors = exports.clearErrors = function clearErrors(form) {
|
287
|
+
helpers.querySelectorArray(form, '.form-error-inline, .form-error-base').forEach(helpers.removeElement);
|
288
|
+
};
|
289
|
+
|
290
|
+
/*
|
291
|
+
* Append error text to form input fields and scroll to the first error
|
292
|
+
*/
|
293
|
+
var displayErrorsInline = exports.displayErrorsInline = function displayErrorsInline(form, json) {
|
294
|
+
if (typeof json === 'string') {
|
295
|
+
json = helpers.formatStringAsErrors(json);
|
296
|
+
}
|
297
|
+
|
298
|
+
clearErrors(form);
|
299
|
+
var errors = json.errors;
|
300
|
+
|
301
|
+
for (var key in errors) {
|
302
|
+
var messages = errors[key];
|
303
|
+
if (key === 'base') {
|
304
|
+
for (var i = 0; i < messages.length; i++) {
|
305
|
+
var p = helpers.createErrorParagraph(messages[i], true);
|
306
|
+
form.insertBefore(p, form.firstChild);
|
307
|
+
}
|
308
|
+
} else {
|
309
|
+
var input = void 0,
|
310
|
+
keyArr = void 0;
|
311
|
+
keyArr = key.split('.');
|
312
|
+
|
313
|
+
if (keyArr.length === 2) {
|
314
|
+
keyArr[0] += '_attributes';
|
315
|
+
input = form.querySelector('[name$=\'[' + keyArr.join('][') + ']\']');
|
316
|
+
} else {
|
317
|
+
var array = helpers.querySelectorArray(form, '[name$=\'[' + key + ']\']');
|
318
|
+
input = array.filter(function (input) {
|
319
|
+
// Filter out nested attribute inputs ie model[a][b]
|
320
|
+
return !input.name.match(/\]\[/);
|
321
|
+
})[0];
|
322
|
+
if (input === null && !key.match(/_id$/)) {
|
323
|
+
input = form.querySelector('[name$=\'[' + key + '_id]\']');
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
var message = messages[0];
|
328
|
+
if (input) {
|
329
|
+
input.parentNode.appendChild(helpers.createErrorParagraph(message));
|
330
|
+
} else {
|
331
|
+
// eslint-disable-next-line no-console
|
332
|
+
console.warn('Missing input field for key:', key);
|
333
|
+
form.insertBefore(helpers.createErrorParagraph(_case2.default.title(key) + ' ' + message), form.firstChild);
|
334
|
+
}
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
338
|
+
var firstError = form.querySelector('.form-error-inline, .form-error-base');
|
339
|
+
if (firstError && !helpers.elementIsInView(firstError) && typeof firstError.scrollIntoView === 'function') {
|
340
|
+
firstError.parentElement.scrollIntoView();
|
341
|
+
}
|
342
|
+
};
|
343
|
+
|
344
|
+
/*
|
345
|
+
* Display errors in a standard window.alert dialog
|
346
|
+
*/
|
347
|
+
var displayErrorsAlert = exports.displayErrorsAlert = function displayErrorsAlert(json) {
|
348
|
+
if (typeof json === 'string') {
|
349
|
+
json = helpers.formatStringAsErrors(json);
|
350
|
+
}
|
351
|
+
var text = 'Please correct the following errors:\n';
|
352
|
+
var message = void 0,
|
353
|
+
errors = json.errors;
|
354
|
+
for (var key in errors) {
|
355
|
+
message = errors[key][0];
|
356
|
+
if (key === 'base') {
|
357
|
+
text += ' - ' + message + '\n';
|
358
|
+
} else {
|
359
|
+
text += ' - ' + _case2.default.title(key) + ' ' + message + '\n';
|
360
|
+
}
|
361
|
+
}
|
362
|
+
window.alert(text);
|
363
|
+
};
|
364
|
+
|
365
|
+
/***/ }),
|
366
|
+
/* 4 */
|
367
|
+
/***/ (function(module, exports) {
|
368
|
+
|
369
|
+
/*! Case - v1.5.3 - 2017-07-11
|
370
|
+
* Copyright (c) 2017 Nathan Bubna; Licensed MIT, GPL */
|
371
|
+
(function() {
|
372
|
+
"use strict";
|
373
|
+
var unicodes = function(s, prefix) {
|
374
|
+
prefix = prefix || '';
|
375
|
+
return s.replace(/(^|-)/g, '$1\\u'+prefix).replace(/,/g, '\\u'+prefix);
|
376
|
+
},
|
377
|
+
basicSymbols = unicodes('20-26,28-2F,3A-40,5B-60,7B-7E,A0-BF,D7,F7', '00'),
|
378
|
+
baseLowerCase = 'a-z'+unicodes('DF-F6,F8-FF', '00'),
|
379
|
+
baseUpperCase = 'A-Z'+unicodes('C0-D6,D8-DE', '00'),
|
380
|
+
improperInTitle = 'A|An|And|As|At|But|By|En|For|If|In|Of|On|Or|The|To|Vs?\\.?|Via',
|
381
|
+
regexps = function(symbols, lowers, uppers, impropers) {
|
382
|
+
symbols = symbols || basicSymbols;
|
383
|
+
lowers = lowers || baseLowerCase;
|
384
|
+
uppers = uppers || baseUpperCase;
|
385
|
+
impropers = impropers || improperInTitle;
|
386
|
+
return {
|
387
|
+
capitalize: new RegExp('(^|['+symbols+'])(['+lowers+'])', 'g'),
|
388
|
+
pascal: new RegExp('(^|['+symbols+'])+(['+lowers+uppers+'])', 'g'),
|
389
|
+
fill: new RegExp('['+symbols+']+(.|$)','g'),
|
390
|
+
sentence: new RegExp('(^\\s*|[\\?\\!\\.]+"?\\s+"?|,\\s+")(['+lowers+'])', 'g'),
|
391
|
+
improper: new RegExp('\\b('+impropers+')\\b', 'g'),
|
392
|
+
relax: new RegExp('([^'+uppers+'])(['+uppers+']*)(['+uppers+'])(?=['+lowers+']|$)', 'g'),
|
393
|
+
upper: new RegExp('^[^'+lowers+']+$'),
|
394
|
+
hole: /[^\s]\s[^\s]/,
|
395
|
+
apostrophe: /'/g,
|
396
|
+
room: new RegExp('['+symbols+']')
|
397
|
+
};
|
398
|
+
},
|
399
|
+
re = regexps(),
|
400
|
+
_ = {
|
401
|
+
re: re,
|
402
|
+
unicodes: unicodes,
|
403
|
+
regexps: regexps,
|
404
|
+
types: [],
|
405
|
+
up: String.prototype.toUpperCase,
|
406
|
+
low: String.prototype.toLowerCase,
|
407
|
+
cap: function(s) {
|
408
|
+
return _.up.call(s.charAt(0))+s.slice(1);
|
409
|
+
},
|
410
|
+
decap: function(s) {
|
411
|
+
return _.low.call(s.charAt(0))+s.slice(1);
|
412
|
+
},
|
413
|
+
deapostrophe: function(s) {
|
414
|
+
return s.replace(re.apostrophe, '');
|
415
|
+
},
|
416
|
+
fill: function(s, fill, deapostrophe) {
|
417
|
+
if (fill != null) {
|
418
|
+
s = s.replace(re.fill, function(m, next) {
|
419
|
+
return next ? fill + next : '';
|
420
|
+
});
|
421
|
+
}
|
422
|
+
if (deapostrophe) {
|
423
|
+
s = _.deapostrophe(s);
|
424
|
+
}
|
425
|
+
return s;
|
426
|
+
},
|
427
|
+
prep: function(s, fill, pascal, upper) {
|
428
|
+
s = s == null ? '' : s + '';// force to string
|
429
|
+
if (!upper && re.upper.test(s)) {
|
430
|
+
s = _.low.call(s);
|
431
|
+
}
|
432
|
+
if (!fill && !re.hole.test(s)) {
|
433
|
+
var holey = _.fill(s, ' ');
|
434
|
+
if (re.hole.test(holey)) {
|
435
|
+
s = holey;
|
436
|
+
}
|
437
|
+
}
|
438
|
+
if (!pascal && !re.room.test(s)) {
|
439
|
+
s = s.replace(re.relax, _.relax);
|
440
|
+
}
|
441
|
+
return s;
|
442
|
+
},
|
443
|
+
relax: function(m, before, acronym, caps) {
|
444
|
+
return before + ' ' + (acronym ? acronym+' ' : '') + caps;
|
445
|
+
}
|
446
|
+
},
|
447
|
+
Case = {
|
448
|
+
_: _,
|
449
|
+
of: function(s) {
|
450
|
+
for (var i=0,m=_.types.length; i<m; i++) {
|
451
|
+
if (Case[_.types[i]].apply(Case, arguments) === s){ return _.types[i]; }
|
452
|
+
}
|
453
|
+
},
|
454
|
+
flip: function(s) {
|
455
|
+
return s.replace(/\w/g, function(l) {
|
456
|
+
return (l == _.up.call(l) ? _.low : _.up).call(l);
|
457
|
+
});
|
458
|
+
},
|
459
|
+
random: function(s) {
|
460
|
+
return s.replace(/\w/g, function(l) {
|
461
|
+
return (Math.round(Math.random()) ? _.up : _.low).call(l);
|
462
|
+
});
|
463
|
+
},
|
464
|
+
type: function(type, fn) {
|
465
|
+
Case[type] = fn;
|
466
|
+
_.types.push(type);
|
467
|
+
}
|
468
|
+
},
|
469
|
+
types = {
|
470
|
+
lower: function(s, fill, deapostrophe) {
|
471
|
+
return _.fill(_.low.call(_.prep(s, fill)), fill, deapostrophe);
|
472
|
+
},
|
473
|
+
snake: function(s) {
|
474
|
+
return Case.lower(s, '_', true);
|
475
|
+
},
|
476
|
+
constant: function(s) {
|
477
|
+
return Case.upper(s, '_', true);
|
478
|
+
},
|
479
|
+
camel: function(s) {
|
480
|
+
return _.decap(Case.pascal(s));
|
481
|
+
},
|
482
|
+
kebab: function(s) {
|
483
|
+
return Case.lower(s, '-', true);
|
484
|
+
},
|
485
|
+
header: function(s) {
|
486
|
+
return Case.capital(s, '-', true);
|
487
|
+
},
|
488
|
+
upper: function(s, fill, deapostrophe) {
|
489
|
+
return _.fill(_.up.call(_.prep(s, fill, false, true)), fill, deapostrophe);
|
490
|
+
},
|
491
|
+
capital: function(s, fill, deapostrophe) {
|
492
|
+
return _.fill(_.prep(s).replace(re.capitalize, function(m, border, letter) {
|
493
|
+
return border+_.up.call(letter);
|
494
|
+
}), fill, deapostrophe);
|
495
|
+
},
|
496
|
+
pascal: function(s) {
|
497
|
+
return _.fill(_.prep(s, false, true).replace(re.pascal, function(m, border, letter) {
|
498
|
+
return _.up.call(letter);
|
499
|
+
}), '', true);
|
500
|
+
},
|
501
|
+
title: function(s) {
|
502
|
+
return Case.capital(s).replace(re.improper, function(small, p, i, s) {
|
503
|
+
return i > 0 && i < s.lastIndexOf(' ') ? _.low.call(small) : small;
|
504
|
+
});
|
505
|
+
},
|
506
|
+
sentence: function(s, names) {
|
507
|
+
s = Case.lower(s).replace(re.sentence, function(m, prelude, letter) {
|
508
|
+
return prelude + _.up.call(letter);
|
509
|
+
});
|
510
|
+
if (names) {
|
511
|
+
names.forEach(function(name) {
|
512
|
+
s = s.replace(new RegExp('\\b'+Case.lower(name)+'\\b', "g"), _.cap);
|
513
|
+
});
|
514
|
+
}
|
515
|
+
return s;
|
516
|
+
}
|
517
|
+
};
|
518
|
+
|
519
|
+
// TODO: Remove "squish" in a future breaking release.
|
520
|
+
types.squish = types.pascal;
|
521
|
+
|
522
|
+
for (var type in types) {
|
523
|
+
Case.type(type, types[type]);
|
524
|
+
}
|
525
|
+
// export Case (AMD, commonjs, or global)
|
526
|
+
var define = typeof define === "function" ? define : function(){};
|
527
|
+
define(typeof module === "object" && module.exports ? module.exports = Case : this.Case = Case);
|
528
|
+
|
529
|
+
}).call(this);
|
530
|
+
|
531
|
+
|
532
|
+
/***/ })
|
533
|
+
/******/ ]);
|
534
|
+
//# sourceMappingURL=rails-validator.js.map
|