iron-cms 0.1.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/MIT-LICENSE +20 -0
- data/README.md +174 -0
- data/Rakefile +8 -0
- data/app/assets/builds/iron.css +2 -0
- data/app/assets/config/iron_manifest.js +1 -0
- data/app/assets/images/iron/icons/sprite.svg +9425 -0
- data/app/assets/tailwind/iron/actiontext.css +550 -0
- data/app/assets/tailwind/iron/application.css +66 -0
- data/app/assets/tailwind/iron/components/button.css +74 -0
- data/app/assets/tailwind/iron/components/dropdown.css +42 -0
- data/app/assets/tailwind/iron/components/fieldset.css +18 -0
- data/app/assets/tailwind/iron/components/form.css +14 -0
- data/app/assets/tailwind/iron/components/icon-picker.css +9 -0
- data/app/assets/tailwind/iron/components/input.css +44 -0
- data/app/assets/tailwind/iron/components/page.css +7 -0
- data/app/assets/tailwind/iron/components/select.css +36 -0
- data/app/assets/tailwind/iron/components/sidebar.css +21 -0
- data/app/assets/tailwind/iron/components/textarea.css +27 -0
- data/app/controllers/concerns/iron/authentication.rb +66 -0
- data/app/controllers/concerns/iron/authorization.rb +11 -0
- data/app/controllers/concerns/iron/locale_aware.rb +20 -0
- data/app/controllers/concerns/iron/web_page.rb +18 -0
- data/app/controllers/iron/application_controller.rb +7 -0
- data/app/controllers/iron/block_definitions/field_definitions_controller.rb +8 -0
- data/app/controllers/iron/block_definitions_controller.rb +52 -0
- data/app/controllers/iron/blocks_controller.rb +12 -0
- data/app/controllers/iron/content_types/field_definitions_controller.rb +8 -0
- data/app/controllers/iron/content_types_controller.rb +52 -0
- data/app/controllers/iron/entries_controller.rb +103 -0
- data/app/controllers/iron/field_definitions_controller.rb +55 -0
- data/app/controllers/iron/first_runs_controller.rb +29 -0
- data/app/controllers/iron/home_controller.rb +6 -0
- data/app/controllers/iron/icons_controller.rb +10 -0
- data/app/controllers/iron/locales_controller.rb +52 -0
- data/app/controllers/iron/passwords_controller.rb +36 -0
- data/app/controllers/iron/references_controller.rb +11 -0
- data/app/controllers/iron/schemas_controller.rb +32 -0
- data/app/controllers/iron/sessions_controller.rb +32 -0
- data/app/controllers/iron/settings_controller.rb +23 -0
- data/app/controllers/iron/users_controller.rb +66 -0
- data/app/helpers/iron/application_helper.rb +25 -0
- data/app/helpers/iron/avatar_helper.rb +35 -0
- data/app/helpers/iron/block_helper.rb +4 -0
- data/app/helpers/iron/buttons_helper.rb +14 -0
- data/app/helpers/iron/components/badge_helper.rb +70 -0
- data/app/helpers/iron/components/dropdown_helper.rb +161 -0
- data/app/helpers/iron/content_types_helper.rb +11 -0
- data/app/helpers/iron/entries_helper.rb +13 -0
- data/app/helpers/iron/field_definitions_helper.rb +5 -0
- data/app/helpers/iron/form_builder.rb +43 -0
- data/app/helpers/iron/icons_helper.rb +30 -0
- data/app/helpers/iron/image_helper.rb +50 -0
- data/app/javascript/iron/application.js +6 -0
- data/app/javascript/iron/controllers/application.js +9 -0
- data/app/javascript/iron/controllers/element_controller.js +11 -0
- data/app/javascript/iron/controllers/file_upload_controller.js +18 -0
- data/app/javascript/iron/controllers/form_controller.js +7 -0
- data/app/javascript/iron/controllers/handle_controller.js +27 -0
- data/app/javascript/iron/controllers/icon_picker_controller.js +48 -0
- data/app/javascript/iron/controllers/index.js +4 -0
- data/app/javascript/iron/controllers/sortable_list_controller.js +125 -0
- data/app/javascript/iron/controllers/toggle_controller.js +14 -0
- data/app/javascript/iron/controllers/trix_controller.js +19 -0
- data/app/javascript/iron/lib/lexorank.js +51 -0
- data/app/jobs/iron/application_job.rb +4 -0
- data/app/jobs/iron/generate_entry_routes_job.rb +15 -0
- data/app/mailers/iron/application_mailer.rb +6 -0
- data/app/mailers/passwords_mailer.rb +6 -0
- data/app/models/concerns/iron/csv_serializable.rb +28 -0
- data/app/models/iron/account/joinable.rb +16 -0
- data/app/models/iron/account.rb +13 -0
- data/app/models/iron/application_record.rb +5 -0
- data/app/models/iron/block_definition/portable.rb +20 -0
- data/app/models/iron/block_definition.rb +10 -0
- data/app/models/iron/content_type/field_queryable.rb +101 -0
- data/app/models/iron/content_type/portable.rb +30 -0
- data/app/models/iron/content_type/titlable.rb +13 -0
- data/app/models/iron/content_type/web_publishable.rb +47 -0
- data/app/models/iron/content_type.rb +39 -0
- data/app/models/iron/content_types/collection.rb +6 -0
- data/app/models/iron/content_types/single.rb +9 -0
- data/app/models/iron/current.rb +12 -0
- data/app/models/iron/entry/deep_validation.rb +32 -0
- data/app/models/iron/entry/presentable.rb +40 -0
- data/app/models/iron/entry/schemable.rb +16 -0
- data/app/models/iron/entry/titlable.rb +35 -0
- data/app/models/iron/entry/web_publishable.rb +85 -0
- data/app/models/iron/entry.rb +16 -0
- data/app/models/iron/field/belongs_to_entry.rb +29 -0
- data/app/models/iron/field.rb +20 -0
- data/app/models/iron/field_definition/portable.rb +36 -0
- data/app/models/iron/field_definition.rb +42 -0
- data/app/models/iron/field_definitions/block.rb +23 -0
- data/app/models/iron/field_definitions/block_list.rb +8 -0
- data/app/models/iron/field_definitions/boolean.rb +7 -0
- data/app/models/iron/field_definitions/date.rb +7 -0
- data/app/models/iron/field_definitions/file.rb +67 -0
- data/app/models/iron/field_definitions/number.rb +7 -0
- data/app/models/iron/field_definitions/reference.rb +8 -0
- data/app/models/iron/field_definitions/reference_list.rb +8 -0
- data/app/models/iron/field_definitions/rich_text_area.rb +4 -0
- data/app/models/iron/field_definitions/text_area.rb +7 -0
- data/app/models/iron/field_definitions/text_field.rb +25 -0
- data/app/models/iron/fields/block.rb +40 -0
- data/app/models/iron/fields/block_list.rb +16 -0
- data/app/models/iron/fields/boolean.rb +7 -0
- data/app/models/iron/fields/date.rb +7 -0
- data/app/models/iron/fields/file.rb +29 -0
- data/app/models/iron/fields/number.rb +7 -0
- data/app/models/iron/fields/reference.rb +14 -0
- data/app/models/iron/fields/reference_list.rb +17 -0
- data/app/models/iron/fields/rich_text_area.rb +9 -0
- data/app/models/iron/fields/text_area.rb +7 -0
- data/app/models/iron/fields/text_field.rb +19 -0
- data/app/models/iron/first_run.rb +13 -0
- data/app/models/iron/icon_catalog.rb +45 -0
- data/app/models/iron/locale.rb +30 -0
- data/app/models/iron/reference.rb +8 -0
- data/app/models/iron/schema_archive.rb +71 -0
- data/app/models/iron/schema_exporter.rb +15 -0
- data/app/models/iron/schema_importer.rb +278 -0
- data/app/models/iron/session.rb +5 -0
- data/app/models/iron/user/role.rb +21 -0
- data/app/models/iron/user.rb +37 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/app/views/iron/block_definitions/_block_definition.html.erb +21 -0
- data/app/views/iron/block_definitions/_form.html.erb +46 -0
- data/app/views/iron/block_definitions/edit.html.erb +8 -0
- data/app/views/iron/block_definitions/index.html.erb +18 -0
- data/app/views/iron/block_definitions/new.html.erb +8 -0
- data/app/views/iron/block_definitions/show.html.erb +34 -0
- data/app/views/iron/blocks/new.turbo_stream.erb +6 -0
- data/app/views/iron/content_types/_content_type.html.erb +21 -0
- data/app/views/iron/content_types/_form.html.erb +137 -0
- data/app/views/iron/content_types/edit.html.erb +8 -0
- data/app/views/iron/content_types/index.html.erb +28 -0
- data/app/views/iron/content_types/new.html.erb +8 -0
- data/app/views/iron/content_types/show.html.erb +49 -0
- data/app/views/iron/entries/_entry.html.erb +17 -0
- data/app/views/iron/entries/_entry_option.html.erb +19 -0
- data/app/views/iron/entries/_form.html.erb +63 -0
- data/app/views/iron/entries/edit.html.erb +52 -0
- data/app/views/iron/entries/entry.html.erb +21 -0
- data/app/views/iron/entries/fields/_block.html.erb +92 -0
- data/app/views/iron/entries/fields/_block_list.html.erb +45 -0
- data/app/views/iron/entries/fields/_boolean.html.erb +13 -0
- data/app/views/iron/entries/fields/_date.html.erb +11 -0
- data/app/views/iron/entries/fields/_file.html.erb +60 -0
- data/app/views/iron/entries/fields/_number.html.erb +11 -0
- data/app/views/iron/entries/fields/_reference.html.erb +12 -0
- data/app/views/iron/entries/fields/_reference_item.html.erb +28 -0
- data/app/views/iron/entries/fields/_reference_list.html.erb +44 -0
- data/app/views/iron/entries/fields/_rich_text_area.html.erb +13 -0
- data/app/views/iron/entries/fields/_text_area.html.erb +11 -0
- data/app/views/iron/entries/fields/_text_field.html.erb +28 -0
- data/app/views/iron/entries/index.html.erb +21 -0
- data/app/views/iron/entries/new.html.erb +8 -0
- data/app/views/iron/entries/search.html.erb +33 -0
- data/app/views/iron/field_definitions/_field_definition.html.erb +37 -0
- data/app/views/iron/field_definitions/block/_form.html.erb +16 -0
- data/app/views/iron/field_definitions/block_list/_form.html.erb +13 -0
- data/app/views/iron/field_definitions/boolean/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/date/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/edit.html.erb +16 -0
- data/app/views/iron/field_definitions/file/_form.html.erb +40 -0
- data/app/views/iron/field_definitions/index.html.erb +14 -0
- data/app/views/iron/field_definitions/layouts/_form.html.erb +47 -0
- data/app/views/iron/field_definitions/new.html.erb +22 -0
- data/app/views/iron/field_definitions/number/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/reference/_form.html.erb +13 -0
- data/app/views/iron/field_definitions/reference_list/_form.html.erb +13 -0
- data/app/views/iron/field_definitions/rich_text_area/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/text_area/_form.html.erb +2 -0
- data/app/views/iron/field_definitions/text_field/_form.html.erb +17 -0
- data/app/views/iron/first_runs/show.html.erb +44 -0
- data/app/views/iron/home/show.html.erb +2 -0
- data/app/views/iron/icons/index.html.erb +21 -0
- data/app/views/iron/locales/_form.html.erb +24 -0
- data/app/views/iron/locales/_locale.html.erb +15 -0
- data/app/views/iron/locales/edit.html.erb +8 -0
- data/app/views/iron/locales/index.html.erb +12 -0
- data/app/views/iron/locales/new.html.erb +8 -0
- data/app/views/iron/passwords/edit.html.erb +7 -0
- data/app/views/iron/passwords/new.html.erb +26 -0
- data/app/views/iron/passwords_mailer/reset.html.erb +4 -0
- data/app/views/iron/passwords_mailer/reset.text.erb +2 -0
- data/app/views/iron/published_pages/show.html.erb +10 -0
- data/app/views/iron/references/new.turbo_stream.erb +6 -0
- data/app/views/iron/schemas/new.html.erb +57 -0
- data/app/views/iron/sessions/new.html.erb +36 -0
- data/app/views/iron/settings/show.html.erb +38 -0
- data/app/views/iron/shared/_icon_picker.html.erb +60 -0
- data/app/views/iron/users/_form.html.erb +21 -0
- data/app/views/iron/users/_user.html.erb +27 -0
- data/app/views/iron/users/edit.html.erb +12 -0
- data/app/views/iron/users/index.html.erb +21 -0
- data/app/views/iron/users/new.html.erb +29 -0
- data/app/views/iron/users/show.html.erb +21 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/iron/_sidebar.html.erb +83 -0
- data/app/views/layouts/iron/_sidebar_item.html.erb +14 -0
- data/app/views/layouts/iron/_toast.html.erb +29 -0
- data/app/views/layouts/iron/application.html.erb +46 -0
- data/app/views/layouts/iron/authentication.html.erb +41 -0
- data/config/importmap.rb +9 -0
- data/config/routes.rb +40 -0
- data/db/migrate/20250422131656_initial_iron_schema.rb +105 -0
- data/db/migrate/20250427100754_create_iron_accounts.rb +10 -0
- data/db/migrate/20250427104020_add_role_to_iron_users.rb +5 -0
- data/db/migrate/20250428094923_add_default_iron_locale_to_iron_account.rb +5 -0
- data/db/migrate/20250504144731_create_join_table_content_types_field_definitions.rb +25 -0
- data/db/migrate/20250505090716_add_title_field_definition_to_iron_content_types.rb +5 -0
- data/db/migrate/20250512210352_create_iron_references.rb +11 -0
- data/db/migrate/20250524220355_add_referenced_entry_id_to_iron_fields.rb +5 -0
- data/db/migrate/20250529211703_add_metadata_to_iron_field_definitions.rb +5 -0
- data/db/migrate/20250529211704_add_value_boolean_to_iron_fields.rb +5 -0
- data/db/migrate/20250531172837_add_type_to_iron_content_types.rb +5 -0
- data/db/migrate/20250601080146_add_icon_to_iron_content_types.rb +5 -0
- data/db/migrate/20250609091605_add_web_publishing_to_iron_content_types.rb +7 -0
- data/db/migrate/20250609091813_add_route_to_iron_entries.rb +6 -0
- data/lib/generators/iron/pages/pages_generator.rb +34 -0
- data/lib/generators/iron/pages/templates/pages_controller.rb +14 -0
- data/lib/generators/iron/pages/templates/show.html.erb +9 -0
- data/lib/generators/iron/template/template_generator.rb +35 -0
- data/lib/generators/iron/template/templates/content_type.html.erb +5 -0
- data/lib/iron/cva.rb +69 -0
- data/lib/iron/engine.rb +42 -0
- data/lib/iron/image_analyzer.rb +26 -0
- data/lib/iron/lexorank/exceptions.rb +4 -0
- data/lib/iron/lexorank/rankable.rb +185 -0
- data/lib/iron/lexorank/rebalance_rank_job.rb +10 -0
- data/lib/iron/lexorank/utils.rb +77 -0
- data/lib/iron/lexorank.rb +7 -0
- data/lib/iron/routing.rb +66 -0
- data/lib/iron/sdk.rb +19 -0
- data/lib/iron/version.rb +3 -0
- data/lib/iron.rb +8 -0
- data/lib/puma/plugin/iron_tailwindcss.rb +78 -0
- metadata +458 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Iron
|
2
|
+
class FirstRunsController < ApplicationController
|
3
|
+
layout "iron/authentication"
|
4
|
+
|
5
|
+
allow_unauthenticated_access
|
6
|
+
|
7
|
+
before_action :prevent_running_after_setup
|
8
|
+
|
9
|
+
def show
|
10
|
+
@user = User.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
user = FirstRun.create!(user_params)
|
15
|
+
start_new_session_for user
|
16
|
+
|
17
|
+
redirect_to root_url
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def prevent_running_after_setup
|
22
|
+
redirect_to root_url if User.any?
|
23
|
+
end
|
24
|
+
|
25
|
+
def user_params
|
26
|
+
params.require(:user).permit(:email_address, :password)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Iron
|
2
|
+
class LocalesController < ApplicationController
|
3
|
+
before_action :set_locale, only: %i[ edit update destroy ]
|
4
|
+
before_action :ensure_can_administer
|
5
|
+
|
6
|
+
def index
|
7
|
+
@locales = Locale.all
|
8
|
+
end
|
9
|
+
|
10
|
+
def new
|
11
|
+
@locale = Locale.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def edit
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
@locale = Locale.new(locale_params)
|
19
|
+
|
20
|
+
if @locale.save
|
21
|
+
redirect_to locales_path, notice: "Locale was successfully created."
|
22
|
+
else
|
23
|
+
render :new, status: :unprocessable_entity
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def update
|
28
|
+
if @locale.update(locale_params)
|
29
|
+
redirect_to locales_path, notice: "Locale was successfully updated.", status: :see_other
|
30
|
+
else
|
31
|
+
render :edit, status: :unprocessable_entity
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def destroy
|
36
|
+
if @locale.destroy
|
37
|
+
redirect_to locales_path, notice: "Locale was successfully destroyed.", status: :see_other
|
38
|
+
else
|
39
|
+
redirect_back fallback_location: locales_path, alert: @locale.errors.full_messages.to_sentence, status: :see_other
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def set_locale
|
45
|
+
@locale = Locale.find(params.expect(:id))
|
46
|
+
end
|
47
|
+
|
48
|
+
def locale_params
|
49
|
+
params.expect(locale: [ :code, :name ])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Iron
|
2
|
+
class PasswordsController < ApplicationController
|
3
|
+
layout "iron/authentication"
|
4
|
+
allow_unauthenticated_access
|
5
|
+
before_action :set_user_by_token, only: %i[ edit update ]
|
6
|
+
|
7
|
+
def new
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
if user = User.find_by(email_address: params[:email_address])
|
12
|
+
PasswordsMailer.reset(user).deliver_later
|
13
|
+
end
|
14
|
+
|
15
|
+
redirect_to new_session_url, notice: "Password reset instructions sent (if user with that email address exists)."
|
16
|
+
end
|
17
|
+
|
18
|
+
def edit
|
19
|
+
end
|
20
|
+
|
21
|
+
def update
|
22
|
+
if @user.update(params.permit(:password, :password_confirmation))
|
23
|
+
redirect_to new_session_url, notice: "Password has been reset."
|
24
|
+
else
|
25
|
+
redirect_to edit_password_url(params[:token]), alert: "Passwords did not match."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def set_user_by_token
|
31
|
+
@user = User.find_by_password_reset_token!(params[:token])
|
32
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
33
|
+
redirect_to new_password_url, alert: "Password reset link is invalid or has expired."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Iron
|
2
|
+
class ReferencesController < ApplicationController
|
3
|
+
def new
|
4
|
+
@form_field_name = params[:form_field_name]
|
5
|
+
@reference_list_dom_id = params[:reference_list_dom_id]
|
6
|
+
|
7
|
+
@entry = Entry.find(params[:entry_id])
|
8
|
+
@reference = Reference.build(entry: @entry)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Iron
|
2
|
+
class SchemasController < ApplicationController
|
3
|
+
def export
|
4
|
+
send_data SchemaExporter.export,
|
5
|
+
filename: "iron-schema-#{Date.current}.tar",
|
6
|
+
type: "application/x-tar",
|
7
|
+
disposition: "attachment"
|
8
|
+
end
|
9
|
+
|
10
|
+
def new
|
11
|
+
@import_modes = SchemaImporter::IMPORT_MODES
|
12
|
+
end
|
13
|
+
|
14
|
+
def import
|
15
|
+
file = params[:schema_file]
|
16
|
+
mode = params[:import_mode]
|
17
|
+
|
18
|
+
unless file.present?
|
19
|
+
redirect_to schema_path, alert: "Please select a file to import"
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
result = SchemaImporter.import(file, mode:)
|
24
|
+
|
25
|
+
if result.success?
|
26
|
+
redirect_to content_types_path, notice: "Schema imported successfully"
|
27
|
+
else
|
28
|
+
redirect_to schema_path, alert: result.errors.join(". ")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Iron
|
2
|
+
class SessionsController < ApplicationController
|
3
|
+
layout "iron/authentication"
|
4
|
+
allow_unauthenticated_access only: %i[ new create ]
|
5
|
+
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to sign_in_url, alert: "Try again later." }
|
6
|
+
|
7
|
+
before_action :ensure_user_exists, only: :new
|
8
|
+
|
9
|
+
def new
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
if user = User.authenticate_by(params.permit(:email_address, :password))
|
14
|
+
start_new_session_for user
|
15
|
+
redirect_to after_authentication_url
|
16
|
+
else
|
17
|
+
redirect_to sign_in_url, alert: "Try another email address or password."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy
|
22
|
+
terminate_session
|
23
|
+
redirect_to sign_in_url
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def ensure_user_exists
|
29
|
+
redirect_to first_run_url if User.none?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Iron
|
2
|
+
class SettingsController < ApplicationController
|
3
|
+
before_action :ensure_can_administer
|
4
|
+
|
5
|
+
def show
|
6
|
+
@account = Current.account
|
7
|
+
@locales = Locale.all
|
8
|
+
end
|
9
|
+
|
10
|
+
def update
|
11
|
+
if Current.account.update(account_params)
|
12
|
+
redirect_back fallback_location: settings_path, notice: "Settings updated successfully."
|
13
|
+
else
|
14
|
+
render :edit, status: :unprocessable_entity
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def account_params
|
20
|
+
params.require(:account).permit(:name, :default_locale_id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Iron
|
2
|
+
class UsersController < ApplicationController
|
3
|
+
layout "iron/authentication", only: %i[ new create ]
|
4
|
+
require_unauthenticated_access only: %i[ new create ]
|
5
|
+
|
6
|
+
before_action :set_user, only: %i[ show update destroy ]
|
7
|
+
before_action :verify_join_code, only: %i[ new create ]
|
8
|
+
before_action :ensure_can_administer, except: %i[ new create ]
|
9
|
+
|
10
|
+
def index
|
11
|
+
@users = User.all
|
12
|
+
end
|
13
|
+
|
14
|
+
def new
|
15
|
+
@user = User.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def show
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
@user = User.new(join_params)
|
23
|
+
if @user.save
|
24
|
+
start_new_session_for @user
|
25
|
+
redirect_to root_url
|
26
|
+
else
|
27
|
+
render :new, status: :unprocessable_entity
|
28
|
+
end
|
29
|
+
rescue ActiveRecord::RecordNotUnique
|
30
|
+
redirect_to sign_in_url(email_address: join_params[:email_address])
|
31
|
+
end
|
32
|
+
|
33
|
+
def update
|
34
|
+
if @user.update(update_params)
|
35
|
+
redirect_back fallback_location: users_path, notice: "User was successfully updated.", status: :see_other
|
36
|
+
else
|
37
|
+
redirect_back fallback_location: users_path, alert: @user.errors.full_messages.to_sentence, status: :see_other
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy
|
42
|
+
if @user.destroy
|
43
|
+
redirect_back fallback_location: users_path, notice: "User was successfully removed.", status: :see_other
|
44
|
+
else
|
45
|
+
redirect_back fallback_location: users_path, alert: @user.errors.full_messages.to_sentence, status: :see_other
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def set_user
|
51
|
+
@user = User.find(params.expect(:id))
|
52
|
+
end
|
53
|
+
|
54
|
+
def join_params
|
55
|
+
params.expect(user: [ :email_address, :password, :password_confirmation ])
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_params
|
59
|
+
params.expect(user: [ :email_address, :role ])
|
60
|
+
end
|
61
|
+
|
62
|
+
def verify_join_code
|
63
|
+
head :not_found if Current.account.join_code != params[:join_code]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "tailwind_merge"
|
2
|
+
module Iron
|
3
|
+
module ApplicationHelper
|
4
|
+
include IconsHelper, AvatarHelper, ButtonsHelper, EntriesHelper, ContentTypesHelper
|
5
|
+
include Components::DropdownHelper, Components::BadgeHelper
|
6
|
+
|
7
|
+
def tw(*classes)
|
8
|
+
TailwindMerge::Merger.new.merge(classes.reject(&:blank?).compact.join(" "))
|
9
|
+
end
|
10
|
+
|
11
|
+
def cva(base_classes = "", options = {})
|
12
|
+
Cva::VariantBuilder.new(
|
13
|
+
base_classes: base_classes,
|
14
|
+
variants: options[:variants] || {},
|
15
|
+
defaults: options[:defaults] || {},
|
16
|
+
compound_variants: options[:compound_variants] || [],
|
17
|
+
required: options[:required] || []
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def unique_id
|
22
|
+
rand(10000000..100000000)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Iron
|
2
|
+
module AvatarHelper
|
3
|
+
def avatar(user, options = {})
|
4
|
+
variant = options.delete(:variant) || "circle"
|
5
|
+
alt = options.delete(:alt) || ""
|
6
|
+
css_class = tw(
|
7
|
+
options.delete(:class),
|
8
|
+
# Basic layout
|
9
|
+
"inline-grid shrink-0 align-middle [--avatar-radius:20%] [--ring-opacity:20%] *:col-start-1 *:row-start-1",
|
10
|
+
"outline outline-1 -outline-offset-1 outline-black/(--ring-opacity) dark:outline-white/(--ring-opacity)",
|
11
|
+
# Add the correct border radius
|
12
|
+
variant == "square" ? "rounded-(--avatar-radius) *:rounded-(--avatar-radius)" : "rounded-full *:rounded-full"
|
13
|
+
)
|
14
|
+
|
15
|
+
tag.span class: css_class, data: { slot: "avatar" } do
|
16
|
+
tag.svg class: "size-full select-none fill-current p-[5%] text-[48px] font-medium uppercase",
|
17
|
+
viewbox: "0 0 100 100",
|
18
|
+
"aria-hidden": alt.present? ? nil : true do
|
19
|
+
safe_join([
|
20
|
+
alt.present? ? tag.title(alt) : nil,
|
21
|
+
tag.text(
|
22
|
+
user.initials,
|
23
|
+
x: "50%",
|
24
|
+
y: "50%",
|
25
|
+
"alignment-baseline": "middle",
|
26
|
+
"dominant-baseline": "middle",
|
27
|
+
"text-anchor": "middle",
|
28
|
+
dy: ".125em",
|
29
|
+
)
|
30
|
+
])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Iron::ButtonsHelper
|
2
|
+
def back_button_to(title, url, options = {}, &)
|
3
|
+
link_to url,
|
4
|
+
class: tw(
|
5
|
+
"inline-flex items-center gap-2 text-sm/6 text-stone-500 dark:text-stone-400",
|
6
|
+
options.delete(:class)
|
7
|
+
) do
|
8
|
+
safe_join([
|
9
|
+
icon("chevron-left", class: "size-4"),
|
10
|
+
tag.span(title)
|
11
|
+
])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Iron
|
2
|
+
module Components::BadgeHelper
|
3
|
+
def badge_class(**options)
|
4
|
+
@badge_variants ||= cva(
|
5
|
+
# Base classes
|
6
|
+
"inline-flex items-center gap-x-1.5 rounded-md px-1.5 py-0.5 text-sm/5 font-medium sm:text-xs/5 forced-colors:outline",
|
7
|
+
variants: {
|
8
|
+
color: {
|
9
|
+
red: "bg-red-500/15 text-red-700 group-hover:bg-red-500/25 dark:bg-red-500/10 dark:text-red-400 dark:group-hover:bg-red-500/20",
|
10
|
+
orange: "bg-orange-500/15 text-orange-700 group-hover:bg-orange-500/25 dark:bg-orange-500/10 dark:text-orange-400 dark:group-hover:bg-orange-500/20",
|
11
|
+
amber: "bg-amber-400/20 text-amber-700 group-hover:bg-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400 dark:group-hover:bg-amber-400/15",
|
12
|
+
yellow: "bg-yellow-400/20 text-yellow-700 group-hover:bg-yellow-400/30 dark:bg-yellow-400/10 dark:text-yellow-300 dark:group-hover:bg-yellow-400/15",
|
13
|
+
lime: "bg-lime-400/20 text-lime-700 group-hover:bg-lime-400/30 dark:bg-lime-400/10 dark:text-lime-300 dark:group-hover:bg-lime-400/15",
|
14
|
+
green: "bg-green-500/15 text-green-700 group-hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:group-hover:bg-green-500/20",
|
15
|
+
emerald: "bg-emerald-500/15 text-emerald-700 group-hover:bg-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400 dark:group-hover:bg-emerald-500/20",
|
16
|
+
teal: "bg-teal-500/15 text-teal-700 group-hover:bg-teal-500/25 dark:bg-teal-500/10 dark:text-teal-300 dark:group-hover:bg-teal-500/20",
|
17
|
+
cyan: "bg-cyan-400/20 text-cyan-700 group-hover:bg-cyan-400/30 dark:bg-cyan-400/10 dark:text-cyan-300 dark:group-hover:bg-cyan-400/15",
|
18
|
+
sky: "bg-sky-500/15 text-sky-700 group-hover:bg-sky-500/25 dark:bg-sky-500/10 dark:text-sky-300 dark:group-hover:bg-sky-500/20",
|
19
|
+
blue: "bg-blue-500/15 text-blue-700 group-hover:bg-blue-500/25 dark:text-blue-400 dark:group-hover:bg-blue-500/25",
|
20
|
+
indigo: "bg-indigo-500/15 text-indigo-700 group-hover:bg-indigo-500/25 dark:text-indigo-400 dark:group-hover:bg-indigo-500/20",
|
21
|
+
violet: "bg-violet-500/15 text-violet-700 group-hover:bg-violet-500/25 dark:text-violet-400 dark:group-hover:bg-violet-500/20",
|
22
|
+
purple: "bg-purple-500/15 text-purple-700 group-hover:bg-purple-500/25 dark:text-purple-400 dark:group-hover:bg-purple-500/20",
|
23
|
+
fuchsia: "bg-fuchsia-400/15 text-fuchsia-700 group-hover:bg-fuchsia-400/25 dark:bg-fuchsia-400/10 dark:text-fuchsia-400 dark:group-hover:bg-fuchsia-400/20",
|
24
|
+
pink: "bg-pink-400/15 text-pink-700 group-hover:bg-pink-400/25 dark:bg-pink-400/10 dark:text-pink-400 dark:group-hover:bg-pink-400/20",
|
25
|
+
rose: "bg-rose-400/15 text-rose-700 group-hover:bg-rose-400/25 dark:bg-rose-400/10 dark:text-rose-400 dark:group-hover:bg-rose-400/20",
|
26
|
+
stone: "bg-stone-600/10 text-stone-700 group-hover:bg-stone-600/20 dark:bg-white/5 dark:text-stone-400 dark:group-hover:bg-white/10"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
defaults: {
|
30
|
+
color: :stone
|
31
|
+
}
|
32
|
+
)
|
33
|
+
@badge_variants.call(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def badge(content = nil, **options, &block)
|
37
|
+
css_class = options.delete(:class)
|
38
|
+
|
39
|
+
tag.span class: tw(
|
40
|
+
badge_class(**options),
|
41
|
+
css_class
|
42
|
+
), **options do
|
43
|
+
block_given? ? capture(&block) : content
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def badge_button_to(content = nil, url = nil, **options, &block)
|
48
|
+
css_class = options.delete(:class)
|
49
|
+
|
50
|
+
base_class = tw(
|
51
|
+
"group relative inline-flex rounded-md focus:outline-hidden focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500",
|
52
|
+
css_class
|
53
|
+
)
|
54
|
+
|
55
|
+
if url.nil?
|
56
|
+
button_tag class: base_class, **options do
|
57
|
+
tag.div class: "btn-touch-target" do
|
58
|
+
badge(content || capture(&block), **options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
else
|
62
|
+
link_to url, class: base_class, **options do
|
63
|
+
tag.div class: "btn-touch-target" do
|
64
|
+
badge(content || capture(&block), **options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# module Iron
|
2
|
+
# module Components::DropdownHelper
|
3
|
+
# def dropdown_divider
|
4
|
+
# tag.div role: "separator", class: "col-span-full mx-3.5 my-1 h-px border-0 bg-stone-950/5 sm:mx-3 dark:bg-white/10 forced-colors:bg-[CanvasText]"
|
5
|
+
# end
|
6
|
+
# end
|
7
|
+
# end
|
8
|
+
|
9
|
+
module Iron
|
10
|
+
module Components::DropdownHelper
|
11
|
+
# Main dropdown container
|
12
|
+
def dropdown_menu(options = {}, &block)
|
13
|
+
# Initialize storage for different parts of the dropdown
|
14
|
+
@dropdown_id = options[:id] || "dropdown-#{SecureRandom.hex(4)}"
|
15
|
+
@dropdown_trigger = nil
|
16
|
+
@dropdown_content = nil
|
17
|
+
|
18
|
+
# Capture the nested content
|
19
|
+
capture(&block)
|
20
|
+
|
21
|
+
# Build the complete dropdown
|
22
|
+
content_tag(:div, class: options[:class]) do
|
23
|
+
safe_join([
|
24
|
+
@dropdown_trigger,
|
25
|
+
@dropdown_content
|
26
|
+
].compact)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Button that toggles the dropdown
|
31
|
+
def dropdown_menu_trigger(options = {}, &block)
|
32
|
+
css_class = options.delete(:class)
|
33
|
+
trigger_content = capture(&block)
|
34
|
+
|
35
|
+
@dropdown_trigger = content_tag(:button,
|
36
|
+
trigger_content,
|
37
|
+
id: "#{@dropdown_id}-trigger",
|
38
|
+
popovertarget: @dropdown_id,
|
39
|
+
class: tw("inline-flex items-center justify-center gap-2", css_class),
|
40
|
+
style: "anchor-name: --#{@dropdown_id}-trigger;",
|
41
|
+
**options
|
42
|
+
)
|
43
|
+
|
44
|
+
nil # Return nil to avoid content being rendered twice
|
45
|
+
end
|
46
|
+
|
47
|
+
def dropdown_menu_content(options = {}, &block)
|
48
|
+
css_class = options.delete(:class)
|
49
|
+
width = options[:width] || "16rem"
|
50
|
+
align = options[:align] || "end" # Default to end alignment
|
51
|
+
side = options[:side]
|
52
|
+
|
53
|
+
positioning = "position-anchor: --#{@dropdown_id}-trigger;"
|
54
|
+
|
55
|
+
# Add alignment to positioning
|
56
|
+
positioning += " position-align: #{align};" if align.present?
|
57
|
+
|
58
|
+
# Add side to positioning if provided
|
59
|
+
positioning += " position-side: #{side};" if side.present?
|
60
|
+
|
61
|
+
content = capture(&block)
|
62
|
+
|
63
|
+
@dropdown_content = content_tag(:div,
|
64
|
+
content,
|
65
|
+
id: @dropdown_id,
|
66
|
+
popover: "",
|
67
|
+
class: tw("dropdown", css_class),
|
68
|
+
style: "#{positioning} --popover-width: #{width};",
|
69
|
+
**options
|
70
|
+
)
|
71
|
+
|
72
|
+
nil # Return nil to avoid content being rendered twice
|
73
|
+
end
|
74
|
+
|
75
|
+
# Individual dropdown menu item
|
76
|
+
def dropdown_menu_item(*args, &block)
|
77
|
+
options = args.extract_options!
|
78
|
+
|
79
|
+
# Determine what the remaining args are
|
80
|
+
text = args[0]
|
81
|
+
url = args[1]
|
82
|
+
|
83
|
+
# Extract common options
|
84
|
+
css_class = options.delete(:class)
|
85
|
+
icon_name = options.delete(:icon)
|
86
|
+
method = options.delete(:method)
|
87
|
+
|
88
|
+
item_content = if block_given?
|
89
|
+
capture(&block)
|
90
|
+
elsif icon_name.present?
|
91
|
+
safe_join([
|
92
|
+
icon(icon_name, variant: :micro, data: { slot: "icon" }),
|
93
|
+
content_tag(:div, text, data: { slot: "label" })
|
94
|
+
])
|
95
|
+
else
|
96
|
+
text
|
97
|
+
end
|
98
|
+
|
99
|
+
if url.nil? && method.present?
|
100
|
+
# Button form
|
101
|
+
button_tag(item_content,
|
102
|
+
type: "button",
|
103
|
+
class: tw("dropdown-item w-full text-left", css_class),
|
104
|
+
**options
|
105
|
+
)
|
106
|
+
elsif method.present?
|
107
|
+
# Button_to form
|
108
|
+
button_to(url.to_s,
|
109
|
+
method: method,
|
110
|
+
class: tw("dropdown-item w-full", css_class),
|
111
|
+
**options
|
112
|
+
) { item_content }
|
113
|
+
elsif url.present?
|
114
|
+
# Link form
|
115
|
+
link_to(url.to_s,
|
116
|
+
class: tw("dropdown-item", css_class),
|
117
|
+
**options
|
118
|
+
) { item_content }
|
119
|
+
else
|
120
|
+
# Plain item
|
121
|
+
content_tag(:div, item_content,
|
122
|
+
class: tw("dropdown-item", css_class),
|
123
|
+
**options
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Label for dropdown sections
|
129
|
+
def dropdown_menu_label(text = nil, options = {}, &block)
|
130
|
+
css_class = options.delete(:class)
|
131
|
+
|
132
|
+
content = block_given? ? capture(&block) : text
|
133
|
+
|
134
|
+
content_tag(:div, content,
|
135
|
+
class: tw("px-3.5 py-2 text-sm font-semibold text-stone-950 dark:text-white sm:px-3", css_class),
|
136
|
+
**options
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Divider between dropdown items
|
141
|
+
def dropdown_menu_separator(options = {})
|
142
|
+
css_class = options.delete(:class)
|
143
|
+
|
144
|
+
content_tag(:div, nil,
|
145
|
+
role: "separator",
|
146
|
+
class: tw("dropdown-divider", css_class),
|
147
|
+
**options
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Group of related dropdown items
|
152
|
+
def dropdown_menu_group(options = {}, &block)
|
153
|
+
css_class = options.delete(:class)
|
154
|
+
|
155
|
+
content_tag(:div, capture(&block),
|
156
|
+
class: tw("p-1", css_class),
|
157
|
+
**options
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Iron::EntriesHelper
|
2
|
+
def entry_fields_for(field, prefix, &)
|
3
|
+
fields_for "#{prefix}[fields_attributes][#{field.id || unique_id}]", field, &
|
4
|
+
end
|
5
|
+
|
6
|
+
def current_content_page?(content_type)
|
7
|
+
request.path.start_with?(content_type_entries_path(content_type)) || (
|
8
|
+
controller_name == "entries" &&
|
9
|
+
%w[show edit].include?(action_name) &&
|
10
|
+
Iron::Entry.find(params[:id]).content_type == content_type
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|