kern 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +423 -0
- data/README.md +178 -0
- data/Rakefile +31 -0
- data/app/assets/builds/tailwind.css +2 -0
- data/app/controllers/concerns/authentication.rb +55 -0
- data/app/controllers/concerns/authentication.rb.tt +55 -0
- data/app/controllers/kern/application_controller.rb +7 -0
- data/app/controllers/kern/pages_controller.rb +6 -0
- data/app/controllers/kern/passwords_controller.rb +40 -0
- data/app/controllers/kern/sessions_controller.rb +30 -0
- data/app/controllers/kern/settings/users_controller.rb +26 -0
- data/app/controllers/kern/settings_controller.rb +6 -0
- data/app/controllers/kern/signups_controller.rb +31 -0
- data/app/controllers/kern/signups_controller.rb.tt +31 -0
- data/app/helpers/kern/component_helper.rb +15 -0
- data/app/helpers/kern/turbo_stream_actions_helper.rb +10 -0
- data/app/mailers/kern/application_mailer.rb +6 -0
- data/app/mailers/kern/passwords_mailer.rb +9 -0
- data/app/models/actor.rb +4 -0
- data/app/models/application_form.rb +12 -0
- data/app/models/current.rb +8 -0
- data/app/models/member/acting.rb +28 -0
- data/app/models/member/setup.rb +37 -0
- data/app/models/member.rb +10 -0
- data/app/models/role.rb +5 -0
- data/app/models/session.rb +5 -0
- data/app/models/signup.rb +38 -0
- data/app/models/user/workspace_member.rb +12 -0
- data/app/models/user.rb +14 -0
- data/app/models/workspace/members.rb +10 -0
- data/app/models/workspace/setup.rb +21 -0
- data/app/models/workspace.rb +7 -0
- data/app/views/components/_container.html.erb +2 -0
- data/app/views/components/_flash.html.erb +7 -0
- data/app/views/components/_heading.html.erb +12 -0
- data/app/views/components/flash/_message.html.erb +4 -0
- data/app/views/kern/pages/welcome.html.erb +42 -0
- data/app/views/kern/passwords/edit.html.erb +12 -0
- data/app/views/kern/passwords/new.html.erb +10 -0
- data/app/views/kern/passwords_mailer/reset.html.erb +6 -0
- data/app/views/kern/passwords_mailer/reset.html.erb.tt +6 -0
- data/app/views/kern/passwords_mailer/reset.text.erb +4 -0
- data/app/views/kern/passwords_mailer/reset.text.erb.tt +4 -0
- data/app/views/kern/sessions/new.html.erb +16 -0
- data/app/views/kern/settings/_cards.html.erb +10 -0
- data/app/views/kern/settings/show.html.erb +5 -0
- data/app/views/kern/settings/users/show.html.erb +15 -0
- data/app/views/kern/signups/new.html.erb +16 -0
- data/app/views/layouts/kern/application/_navigation.html.erb +30 -0
- data/app/views/layouts/kern/application.html.erb +31 -0
- data/app/views/layouts/kern/application.html.erb.tt +31 -0
- data/app/views/layouts/kern/auth.html.erb +42 -0
- data/bin/dev +4 -0
- data/bin/rails +14 -0
- data/bin/release +35 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20250101000001_create_users.rb +13 -0
- data/db/migrate/20250101000002_create_sessions.rb +11 -0
- data/db/migrate/20250101000003_create_workspaces.rb +12 -0
- data/db/migrate/20250101000004_create_members.rb +15 -0
- data/db/migrate/20250101000005_create_roles.rb +11 -0
- data/db/migrate/20250101000006_create_actors.rb +10 -0
- data/kern.gemspec +22 -0
- data/lib/generators/kern/feature/USAGE +17 -0
- data/lib/generators/kern/feature/feature_generator.rb +77 -0
- data/lib/generators/kern/helpers.rb +28 -0
- data/lib/generators/kern/install/USAGE +13 -0
- data/lib/generators/kern/install/install_generator.rb +48 -0
- data/lib/generators/kern/install/templates/configurations/README.md +33 -0
- data/lib/generators/kern/install/templates/configurations/urls.yml +9 -0
- data/lib/generators/kern/views/USAGE +22 -0
- data/lib/generators/kern/views/views_generator.rb +42 -0
- data/lib/kern/config.rb +25 -0
- data/lib/kern/engine.rb +17 -0
- data/lib/kern/form_builder/input.rb +100 -0
- data/lib/kern/form_builder/styles.rb +53 -0
- data/lib/kern/form_builder.rb +101 -0
- data/lib/kern/version.rb +3 -0
- data/lib/kern.rb +7 -0
- data/lib/sluggable.rb +66 -0
- data/lib/tasks/kern_tasks.rake +4 -0
- metadata +139 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "generators/kern/helpers"
|
|
2
|
+
|
|
3
|
+
module Kern
|
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
|
5
|
+
include Helpers
|
|
6
|
+
|
|
7
|
+
source_root File.expand_path("templates", __dir__)
|
|
8
|
+
|
|
9
|
+
class_option :skip_migrations, type: :boolean, default: false
|
|
10
|
+
|
|
11
|
+
def copy_migrations
|
|
12
|
+
return if options[:skip_migrations]
|
|
13
|
+
|
|
14
|
+
rails_command "railties:install:migrations FROM=kern", inline: true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inject_authentication
|
|
18
|
+
inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
|
|
19
|
+
" include Authentication\n"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def inject_kern_layout
|
|
24
|
+
inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
|
|
25
|
+
" layout \"kern/application\"\n"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def copy_urls_configuration
|
|
30
|
+
template "configurations/urls.yml", "config/configurations/urls.yml"
|
|
31
|
+
template "configurations/README.md", "config/configurations/README.md"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def enable_bcrypt
|
|
35
|
+
if File.read(File.expand_path("Gemfile", destination_root)).include?('gem "bcrypt"')
|
|
36
|
+
uncomment_lines "Gemfile", /gem "bcrypt"/
|
|
37
|
+
|
|
38
|
+
bundle_command("install --quiet")
|
|
39
|
+
else
|
|
40
|
+
bundle_command("add bcrypt", {}, quiet: true)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def mount_engine
|
|
45
|
+
route 'mount Kern::Engine => "/"'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Load all YAML files from `config/configurations/` into `Config` constants.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
Each YAML file in this directory becomes a `Config` constant. For example:
|
|
9
|
+
- `config/configurations/urls.yml` becomes `Config::Urls`
|
|
10
|
+
- `config/configurations/features.yml` becomes `Config::Features`
|
|
11
|
+
|
|
12
|
+
Access values using dot notation:
|
|
13
|
+
```ruby
|
|
14
|
+
Config::Urls.app
|
|
15
|
+
Config::Urls.site
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Environment variables
|
|
20
|
+
|
|
21
|
+
You can override any configuration value using environment variables. For example, in `urls.yml`:
|
|
22
|
+
```yaml
|
|
23
|
+
shared:
|
|
24
|
+
app: <%= ENV.fetch("URLS_APP", "http://localhost:3000") %>
|
|
25
|
+
site: <%= ENV.fetch("URLS_SITE", "https://example.com") %>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `URLS_` prefix is a recommended convention but not required. Use any naming convention that works for your application.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
Under the hood, this uses `Rails.application.config_for` which automatically handles environment-specific configurations and ERB processing.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
shared:
|
|
2
|
+
app: <%= ENV.fetch("URLS_APP", "http://localhost:3000") %>
|
|
3
|
+
site: <%= ENV.fetch("URLS_SITE", "https://saas.railsdesigner.com") %>
|
|
4
|
+
docs: <%= ENV.fetch("URLS_DOCS", "https://example.com/docs/") %>
|
|
5
|
+
terms: <%= ENV.fetch("URLS_TERMS", "https://example.com/legal/terms/") %>
|
|
6
|
+
privacy: <%= ENV.fetch("URLS_PRIVACY", "https://example.com/legal/privacy/") %>
|
|
7
|
+
|
|
8
|
+
production:
|
|
9
|
+
app: <%= ENV.fetch("URLS_APP", "https://app.example.com") %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates Kern view templates in your application.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
rails generate kern:views
|
|
6
|
+
|
|
7
|
+
This will generate all available Kern views.
|
|
8
|
+
|
|
9
|
+
rails generate kern:views sessions
|
|
10
|
+
|
|
11
|
+
This will generate only the sessions views.
|
|
12
|
+
|
|
13
|
+
rails generate kern:views sessions passwords
|
|
14
|
+
|
|
15
|
+
This will generate sessions and passwords views.
|
|
16
|
+
|
|
17
|
+
Available views:
|
|
18
|
+
passwords Password reset views
|
|
19
|
+
sessions Sign in views
|
|
20
|
+
settings User settings views
|
|
21
|
+
signups Sign up views
|
|
22
|
+
layouts Kern layout files
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Kern
|
|
2
|
+
class ViewsGenerator < Rails::Generators::Base
|
|
3
|
+
source_root File.expand_path("../../../../../app/views", __FILE__)
|
|
4
|
+
|
|
5
|
+
AVAILABLE_VIEWS = %w[passwords sessions settings signups layouts]
|
|
6
|
+
|
|
7
|
+
argument :views, type: :array, default: [], banner: "view view", desc: "Select specific view directories to generate (#{AVAILABLE_VIEWS.join(", ")}). Leave empty for all."
|
|
8
|
+
|
|
9
|
+
def copy_views
|
|
10
|
+
views_to_generate.map(&:inquiry).each do |view|
|
|
11
|
+
verify_view_exists!(view)
|
|
12
|
+
|
|
13
|
+
if view.layouts?
|
|
14
|
+
generate_layouts
|
|
15
|
+
else
|
|
16
|
+
directory "kern/#{view}", "app/views/#{view}", recursive: true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def views_to_generate
|
|
24
|
+
views.any? ? views : AVAILABLE_VIEWS
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def verify_view_exists!(view)
|
|
28
|
+
return if AVAILABLE_VIEWS.include?(view)
|
|
29
|
+
|
|
30
|
+
say "View `#{view}` not found. Available views: #{AVAILABLE_VIEWS.join(", ")}", :red
|
|
31
|
+
|
|
32
|
+
exit(1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def generate_layouts
|
|
36
|
+
template "layouts/kern/application.html.erb.tt", "app/views/layouts/application.html.erb"
|
|
37
|
+
copy_file "layouts/kern/application/_navigation.html.erb", "app/views/layouts/application/_navigation.html.erb"
|
|
38
|
+
|
|
39
|
+
copy_file "layouts/kern/auth.html.erb", "app/views/layouts/auth.html.erb"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/kern/config.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kern
|
|
4
|
+
module Config
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def load!
|
|
8
|
+
configurations_path = Rails.root.join("config", "configurations")
|
|
9
|
+
|
|
10
|
+
return unless File.directory?(configurations_path)
|
|
11
|
+
|
|
12
|
+
Dir.glob(configurations_path.join("*.yml")).each do |path|
|
|
13
|
+
file_name = File.basename(path, ".yml")
|
|
14
|
+
|
|
15
|
+
::Config.const_set(
|
|
16
|
+
file_name.camelize,
|
|
17
|
+
Rails.application.config_for("configurations/#{file_name}")
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Config
|
|
25
|
+
end
|
data/lib/kern/engine.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Kern
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace Kern
|
|
4
|
+
|
|
5
|
+
initializer "kern.load_config", before: :load_config_initializers do
|
|
6
|
+
require "kern/config"
|
|
7
|
+
|
|
8
|
+
Kern::Config.load!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
initializer "kern.helpers" do
|
|
12
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
13
|
+
helper Kern::Engine.helpers
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Kern
|
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
3
|
+
module Input
|
|
4
|
+
if Gem.loaded_specs.key?("rails_icons")
|
|
5
|
+
require "rails_icons"
|
|
6
|
+
|
|
7
|
+
include RailsIcons::Helpers::IconHelper
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
FIELD_TYPES = {
|
|
11
|
+
string: :text_field,
|
|
12
|
+
email: :email_field,
|
|
13
|
+
password: :password_field,
|
|
14
|
+
url: :url_field,
|
|
15
|
+
text: :text_area,
|
|
16
|
+
integer: :number_field,
|
|
17
|
+
float: :number_field,
|
|
18
|
+
decimal: :number_field,
|
|
19
|
+
datetime: :datetime_local_field,
|
|
20
|
+
date: :date_field
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
def input(attribute, options = {})
|
|
24
|
+
label_suffix = options.delete(:label_suffix)
|
|
25
|
+
hint = options.delete(:hint)
|
|
26
|
+
|
|
27
|
+
@template.content_tag(:div, data: {slot: "field"}, class: options[:field_css] || field_css) do
|
|
28
|
+
label_html = label(attribute, options)
|
|
29
|
+
|
|
30
|
+
@template.safe_join([
|
|
31
|
+
(if label_suffix
|
|
32
|
+
@template.content_tag(:div, class: "flex items-center justify-between") do
|
|
33
|
+
@template.safe_join([label_html, @template.content_tag(:span, label_suffix.html_safe, class: "text-xs font-medium text-gray-500")].compact)
|
|
34
|
+
end
|
|
35
|
+
end),
|
|
36
|
+
|
|
37
|
+
(label_html unless label_suffix),
|
|
38
|
+
|
|
39
|
+
input_field(attribute, options),
|
|
40
|
+
|
|
41
|
+
(@template.content_tag(:p, hint.html_safe, class: "flex items-center gap-x-1 mt-0.5 px-0.5 text-xs font-light text-gray-500") if hint)
|
|
42
|
+
].compact)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def label(attribute, text = nil, options = {}, &block)
|
|
47
|
+
text, options = nil, text if text.is_a?(Hash)
|
|
48
|
+
label_options = options[:label] || {}
|
|
49
|
+
field_errors = @object&.errors&.where(attribute)
|
|
50
|
+
|
|
51
|
+
label_options[:class] = class_names("flex items-center gap-1 text-sm font-medium", {"text-gray-700": field_errors&.none?, "text-red-600": field_errors&.any?})
|
|
52
|
+
label_text = text || I18n.t("labels.#{attribute}", default: attribute.to_s.humanize)
|
|
53
|
+
|
|
54
|
+
super(attribute, label_options) do
|
|
55
|
+
safe_join([
|
|
56
|
+
begin
|
|
57
|
+
icon("warning-circle", class: "size-3.5 scale-100 transition ml-0 ease-in-out duration-300 starting:ml-3.5 starting:scale-0") if field_errors&.any?
|
|
58
|
+
rescue
|
|
59
|
+
nil
|
|
60
|
+
end,
|
|
61
|
+
label_text.html_safe? ? label_text : label_text.to_s
|
|
62
|
+
].compact)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def toggle_wrapper(&block)
|
|
67
|
+
content_tag(:div, yield, data: {slot: "toggle-field"}, class: "flex items-start gap-x-2 [&>input]:mt-[0.15em]")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def toggle_label(method, text)
|
|
71
|
+
label(method, text.html_safe, class: toggle_label_css)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def input_field(attribute, options)
|
|
77
|
+
return select(attribute, options[:collection], {}, options.except(:as, :label, :collection)) if options[:collection]
|
|
78
|
+
|
|
79
|
+
send(FIELD_TYPES.fetch(options[:as] || type_for(attribute), :text_field), attribute, options.except(:as, :label, :collection))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def type_for(attribute)
|
|
83
|
+
return :email if attribute.to_s.end_with?("email")
|
|
84
|
+
return :password if attribute.to_s.include?("password")
|
|
85
|
+
return :url if attribute.match?(/url|website|site/)
|
|
86
|
+
return :string unless valid?(attribute)
|
|
87
|
+
|
|
88
|
+
@object.type_for_attribute(attribute.to_s).type
|
|
89
|
+
rescue NoMethodError
|
|
90
|
+
:string
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def valid?(attribute)
|
|
94
|
+
@object.respond_to?(attribute) &&
|
|
95
|
+
@object.respond_to?(:has_attribute?) &&
|
|
96
|
+
@object.has_attribute?(attribute)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Kern
|
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
3
|
+
module Styles
|
|
4
|
+
def field_css
|
|
5
|
+
"w-full [&[data-slot='field']+[data-slot='field']]:mt-4"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def input_css
|
|
9
|
+
<<~CSS
|
|
10
|
+
w-full px-2.5 py-1.5
|
|
11
|
+
text-sm font-normal text-gray-900
|
|
12
|
+
bg-white
|
|
13
|
+
border-0 ring ring-gray-200/80 rounded-sm
|
|
14
|
+
hover:ring-gray-300
|
|
15
|
+
disabled:opacity-50 disabled:hover:ring-gray-200/80
|
|
16
|
+
outline-none
|
|
17
|
+
focus:bg-white focus:ring focus:ring-brand-300
|
|
18
|
+
CSS
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def file_field_css
|
|
22
|
+
<<~CSS
|
|
23
|
+
w-full py-1.5
|
|
24
|
+
text-sm font-normal text-gray-600
|
|
25
|
+
disabled:opacity-50
|
|
26
|
+
file:mr-2 file:py-1 file:px-4
|
|
27
|
+
file:text-xs file:font-medium
|
|
28
|
+
file:bg-gray-100 file:text-gray-700
|
|
29
|
+
file:rounded-full file:border-0
|
|
30
|
+
hover:file:bg-gray-200
|
|
31
|
+
focus:outline-hidden focus:ring-0
|
|
32
|
+
CSS
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def toggle_input_css
|
|
36
|
+
<<~CSS
|
|
37
|
+
peer
|
|
38
|
+
accent-brand-500
|
|
39
|
+
border border-gray-100
|
|
40
|
+
rounded-md
|
|
41
|
+
focus:outline-hidden focus:ring focus:ring-offset-1 focus:ring-gray-300
|
|
42
|
+
disabled:opacity-25 disabled:cursor-not-allowed
|
|
43
|
+
CSS
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def toggle_label_css
|
|
47
|
+
<<~CSS
|
|
48
|
+
block text-sm font-normal text-gray-800 peer-disabled:opacity-50 peer-disabled:cursor-not-allowed
|
|
49
|
+
CSS
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require "kern/form_builder/styles"
|
|
2
|
+
require "kern/form_builder/input"
|
|
3
|
+
|
|
4
|
+
module Kern
|
|
5
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
6
|
+
include ActionView::Helpers::TagHelper
|
|
7
|
+
|
|
8
|
+
include Input
|
|
9
|
+
include Styles
|
|
10
|
+
|
|
11
|
+
%w[
|
|
12
|
+
text_field email_field password_field
|
|
13
|
+
search_field telephone_field url_field number_field
|
|
14
|
+
date_field time_field datetime_field datetime_local_field month_field week_field
|
|
15
|
+
].each do |field_type|
|
|
16
|
+
define_method(field_type) do |method, options = {}|
|
|
17
|
+
field_classes(options, input_css)
|
|
18
|
+
|
|
19
|
+
super(method, options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
alias_method :phone_field, :telephone_field
|
|
23
|
+
|
|
24
|
+
def text_area(method, options = {})
|
|
25
|
+
field_classes(options, input_css)
|
|
26
|
+
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
alias_method :textarea, :text_area
|
|
30
|
+
|
|
31
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0", &block)
|
|
32
|
+
field_classes(options, toggle_input_css)
|
|
33
|
+
label_text = block ? @template.capture(&block) : method.to_s.humanize
|
|
34
|
+
|
|
35
|
+
toggle_wrapper do
|
|
36
|
+
super(method, options, checked_value, unchecked_value) + label(method, label_text)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
alias_method :checkbox, :check_box
|
|
40
|
+
|
|
41
|
+
def radio_button(method, tag_value, options = {}, &block)
|
|
42
|
+
field_classes(options, toggle_input_css)
|
|
43
|
+
label_text = block ? @template.capture(&block) : tag_value.to_s.humanize
|
|
44
|
+
|
|
45
|
+
toggle_wrapper do
|
|
46
|
+
super(method, tag_value, options) + toggle_label("#{method}_#{tag_value}", label_text)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def select(method, choices = nil, options = {}, html_options = {})
|
|
51
|
+
field_classes(html_options, input_css)
|
|
52
|
+
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def file_field(method, options = {})
|
|
57
|
+
field_classes(options, file_field_css)
|
|
58
|
+
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
|
63
|
+
field_classes(html_options, input_css)
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
|
|
68
|
+
field_classes(html_options, input_css)
|
|
69
|
+
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
|
|
74
|
+
field_classes(html_options, toggle_input_css, "rounded-sm")
|
|
75
|
+
|
|
76
|
+
toggle_wrapper do
|
|
77
|
+
@template.collection_check_boxes(object_name, method, collection, value_method, text_method, options, html_options) do |template|
|
|
78
|
+
template.check_box(class: html_options[:class]) + toggle_label("#{method}_#{template.value.to_s.underscore}", template.text)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
alias_method :collection_checkboxes, :collection_check_boxes
|
|
83
|
+
|
|
84
|
+
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
|
|
85
|
+
field_classes(html_options, toggle_input_css)
|
|
86
|
+
|
|
87
|
+
toggle_wrapper do
|
|
88
|
+
@template.collection_radio_buttons(object_name, method, collection, value_method, text_method, options, html_options) do |template|
|
|
89
|
+
template.radio_button(class: html_options[:class]) + toggle_label("#{method}_#{template.value.to_s.underscore}", template.text)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def field_classes(options, base_css, *additional_classes)
|
|
97
|
+
options[:class] = options[:class].presence || class_names(base_css, *additional_classes, options[:additional_class])
|
|
98
|
+
options.delete(:additional_class)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
data/lib/kern/version.rb
ADDED
data/lib/kern.rb
ADDED
data/lib/sluggable.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Usage
|
|
2
|
+
#
|
|
3
|
+
# Migration:
|
|
4
|
+
# add_column :<table_name>, :slug, null: false
|
|
5
|
+
# add_index :<table_name>, :slug, unique: true, # or:
|
|
6
|
+
# add_index :<table_name>, [:slug, :workspace_id], unique: true
|
|
7
|
+
#
|
|
8
|
+
# In app/models/<name>.rb
|
|
9
|
+
# include Sluggable
|
|
10
|
+
#
|
|
11
|
+
# In app/controller/model_name_plural_controller.rb
|
|
12
|
+
# `<model_name>.sluggble.find(params[:id])`
|
|
13
|
+
#
|
|
14
|
+
module Sluggable
|
|
15
|
+
extend ActiveSupport::Concern
|
|
16
|
+
|
|
17
|
+
class_methods do
|
|
18
|
+
def create_slug(from:)
|
|
19
|
+
@slug_source = from
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
included do
|
|
24
|
+
before_create :set_slug
|
|
25
|
+
|
|
26
|
+
scope :sluggable, -> { Sluggable::Finder.new(self) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_key = [slug]
|
|
30
|
+
|
|
31
|
+
def to_param = slug
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def set_slug
|
|
36
|
+
return if slug
|
|
37
|
+
|
|
38
|
+
slug = if slug_source && send(slug_source).present?
|
|
39
|
+
send(slug_source).parameterize
|
|
40
|
+
else
|
|
41
|
+
random_slug
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
self.slug = slug
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def random_slug
|
|
48
|
+
loop do
|
|
49
|
+
slug = SecureRandom.hex(4)
|
|
50
|
+
|
|
51
|
+
return slug unless self.class.name.constantize.where(slug: slug).exists?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def slug_source = self.class.instance_variable_get(:@slug_source)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Sluggable::Finder
|
|
59
|
+
def initialize(scope)
|
|
60
|
+
@scope = scope
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def find(slug) = @scope.find_by(slug: slug)
|
|
64
|
+
|
|
65
|
+
def find!(slug) = @scope.find_by!(slug: slug)
|
|
66
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kern
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Rails Designer
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 8.0.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 8.0.0
|
|
26
|
+
description: 'A Rails engine that handles the SaaS essentials: authentication, team
|
|
27
|
+
invitations, subscription/billing, and common partials. Skip the boilerplate and
|
|
28
|
+
start shipping the actual product. Launch faster.'
|
|
29
|
+
email: devs@railsdeigner.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- Gemfile
|
|
35
|
+
- Gemfile.lock
|
|
36
|
+
- README.md
|
|
37
|
+
- Rakefile
|
|
38
|
+
- app/assets/builds/tailwind.css
|
|
39
|
+
- app/controllers/concerns/authentication.rb
|
|
40
|
+
- app/controllers/concerns/authentication.rb.tt
|
|
41
|
+
- app/controllers/kern/application_controller.rb
|
|
42
|
+
- app/controllers/kern/pages_controller.rb
|
|
43
|
+
- app/controllers/kern/passwords_controller.rb
|
|
44
|
+
- app/controllers/kern/sessions_controller.rb
|
|
45
|
+
- app/controllers/kern/settings/users_controller.rb
|
|
46
|
+
- app/controllers/kern/settings_controller.rb
|
|
47
|
+
- app/controllers/kern/signups_controller.rb
|
|
48
|
+
- app/controllers/kern/signups_controller.rb.tt
|
|
49
|
+
- app/helpers/kern/component_helper.rb
|
|
50
|
+
- app/helpers/kern/turbo_stream_actions_helper.rb
|
|
51
|
+
- app/mailers/kern/application_mailer.rb
|
|
52
|
+
- app/mailers/kern/passwords_mailer.rb
|
|
53
|
+
- app/models/actor.rb
|
|
54
|
+
- app/models/application_form.rb
|
|
55
|
+
- app/models/current.rb
|
|
56
|
+
- app/models/member.rb
|
|
57
|
+
- app/models/member/acting.rb
|
|
58
|
+
- app/models/member/setup.rb
|
|
59
|
+
- app/models/role.rb
|
|
60
|
+
- app/models/session.rb
|
|
61
|
+
- app/models/signup.rb
|
|
62
|
+
- app/models/user.rb
|
|
63
|
+
- app/models/user/workspace_member.rb
|
|
64
|
+
- app/models/workspace.rb
|
|
65
|
+
- app/models/workspace/members.rb
|
|
66
|
+
- app/models/workspace/setup.rb
|
|
67
|
+
- app/views/components/_container.html.erb
|
|
68
|
+
- app/views/components/_flash.html.erb
|
|
69
|
+
- app/views/components/_heading.html.erb
|
|
70
|
+
- app/views/components/flash/_message.html.erb
|
|
71
|
+
- app/views/kern/pages/welcome.html.erb
|
|
72
|
+
- app/views/kern/passwords/edit.html.erb
|
|
73
|
+
- app/views/kern/passwords/new.html.erb
|
|
74
|
+
- app/views/kern/passwords_mailer/reset.html.erb
|
|
75
|
+
- app/views/kern/passwords_mailer/reset.html.erb.tt
|
|
76
|
+
- app/views/kern/passwords_mailer/reset.text.erb
|
|
77
|
+
- app/views/kern/passwords_mailer/reset.text.erb.tt
|
|
78
|
+
- app/views/kern/sessions/new.html.erb
|
|
79
|
+
- app/views/kern/settings/_cards.html.erb
|
|
80
|
+
- app/views/kern/settings/show.html.erb
|
|
81
|
+
- app/views/kern/settings/users/show.html.erb
|
|
82
|
+
- app/views/kern/signups/new.html.erb
|
|
83
|
+
- app/views/layouts/kern/application.html.erb
|
|
84
|
+
- app/views/layouts/kern/application.html.erb.tt
|
|
85
|
+
- app/views/layouts/kern/application/_navigation.html.erb
|
|
86
|
+
- app/views/layouts/kern/auth.html.erb
|
|
87
|
+
- bin/dev
|
|
88
|
+
- bin/rails
|
|
89
|
+
- bin/release
|
|
90
|
+
- config/routes.rb
|
|
91
|
+
- db/migrate/20250101000001_create_users.rb
|
|
92
|
+
- db/migrate/20250101000002_create_sessions.rb
|
|
93
|
+
- db/migrate/20250101000003_create_workspaces.rb
|
|
94
|
+
- db/migrate/20250101000004_create_members.rb
|
|
95
|
+
- db/migrate/20250101000005_create_roles.rb
|
|
96
|
+
- db/migrate/20250101000006_create_actors.rb
|
|
97
|
+
- kern.gemspec
|
|
98
|
+
- lib/generators/kern/feature/USAGE
|
|
99
|
+
- lib/generators/kern/feature/feature_generator.rb
|
|
100
|
+
- lib/generators/kern/helpers.rb
|
|
101
|
+
- lib/generators/kern/install/USAGE
|
|
102
|
+
- lib/generators/kern/install/install_generator.rb
|
|
103
|
+
- lib/generators/kern/install/templates/configurations/README.md
|
|
104
|
+
- lib/generators/kern/install/templates/configurations/urls.yml
|
|
105
|
+
- lib/generators/kern/views/USAGE
|
|
106
|
+
- lib/generators/kern/views/views_generator.rb
|
|
107
|
+
- lib/kern.rb
|
|
108
|
+
- lib/kern/config.rb
|
|
109
|
+
- lib/kern/engine.rb
|
|
110
|
+
- lib/kern/form_builder.rb
|
|
111
|
+
- lib/kern/form_builder/input.rb
|
|
112
|
+
- lib/kern/form_builder/styles.rb
|
|
113
|
+
- lib/kern/version.rb
|
|
114
|
+
- lib/sluggable.rb
|
|
115
|
+
- lib/tasks/kern_tasks.rake
|
|
116
|
+
homepage: https://saas.railsdesigner.com/
|
|
117
|
+
licenses:
|
|
118
|
+
- MIT
|
|
119
|
+
metadata:
|
|
120
|
+
homepage_uri: https://saas.railsdesigner.com/
|
|
121
|
+
source_code_uri: https://github.com/Rails-Designer/kern/
|
|
122
|
+
rdoc_options: []
|
|
123
|
+
require_paths:
|
|
124
|
+
- lib
|
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: 3.4.0
|
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
requirements: []
|
|
136
|
+
rubygems_version: 4.0.1
|
|
137
|
+
specification_version: 4
|
|
138
|
+
summary: Rails engine with auth, billing, and common components for SaaS apps
|
|
139
|
+
test_files: []
|