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,278 @@
|
|
1
|
+
require "csv"
|
2
|
+
|
3
|
+
module Iron
|
4
|
+
class SchemaImporter
|
5
|
+
IMPORT_MODES = %w[merge replace safe].freeze
|
6
|
+
|
7
|
+
attr_reader :file, :mode, :errors
|
8
|
+
|
9
|
+
def self.import(file, mode: "merge")
|
10
|
+
new(file, mode:).import
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(file, mode: "merge")
|
14
|
+
@file = file
|
15
|
+
@mode = mode.to_s
|
16
|
+
@errors = []
|
17
|
+
|
18
|
+
validate_mode!
|
19
|
+
end
|
20
|
+
|
21
|
+
def import
|
22
|
+
return ImportResult.new(false, errors) unless valid?
|
23
|
+
|
24
|
+
ActiveRecord::Base.transaction do
|
25
|
+
Account.clear_schema! if mode == "replace"
|
26
|
+
|
27
|
+
archive = SchemaArchive.from_file(file)
|
28
|
+
|
29
|
+
unless archive.valid?
|
30
|
+
errors.concat(archive.errors)
|
31
|
+
return ImportResult.new(false, errors)
|
32
|
+
end
|
33
|
+
|
34
|
+
import_block_definitions(archive["block_definitions.csv"])
|
35
|
+
import_content_types(archive["content_types.csv"])
|
36
|
+
import_field_definitions(archive["field_definitions.csv"])
|
37
|
+
|
38
|
+
update_content_type_references(archive["content_types.csv"])
|
39
|
+
|
40
|
+
ImportResult.new(true, [])
|
41
|
+
end
|
42
|
+
rescue StandardError => e
|
43
|
+
errors << "Import failed: #{e.message}"
|
44
|
+
ImportResult.new(false, errors)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def valid?
|
50
|
+
validate_file_presence
|
51
|
+
errors.empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_mode!
|
55
|
+
unless IMPORT_MODES.include?(mode)
|
56
|
+
raise ArgumentError, "Invalid import mode: #{mode}. Must be one of: #{IMPORT_MODES.join(', ')}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_file_presence
|
61
|
+
errors << "No file provided" unless file
|
62
|
+
end
|
63
|
+
|
64
|
+
def import_block_definitions(csv_content)
|
65
|
+
return unless csv_content
|
66
|
+
|
67
|
+
CSV.parse(csv_content, headers: true) do |row|
|
68
|
+
handle = row["handle"]
|
69
|
+
next unless handle.present?
|
70
|
+
|
71
|
+
case mode
|
72
|
+
when "merge"
|
73
|
+
block_def = BlockDefinition.find_or_initialize_by(handle: handle)
|
74
|
+
block_def.assign_attributes(
|
75
|
+
name: row["name"],
|
76
|
+
description: row["description"]
|
77
|
+
)
|
78
|
+
block_def.save!
|
79
|
+
when "replace"
|
80
|
+
BlockDefinition.create!(
|
81
|
+
handle: handle,
|
82
|
+
name: row["name"],
|
83
|
+
description: row["description"]
|
84
|
+
)
|
85
|
+
when "safe"
|
86
|
+
next if BlockDefinition.exists?(handle: handle)
|
87
|
+
|
88
|
+
BlockDefinition.create!(
|
89
|
+
handle: handle,
|
90
|
+
name: row["name"],
|
91
|
+
description: row["description"]
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def import_content_types(csv_content)
|
98
|
+
return unless csv_content
|
99
|
+
|
100
|
+
CSV.parse(csv_content, headers: true) do |row|
|
101
|
+
handle = row["handle"]
|
102
|
+
next unless handle.present?
|
103
|
+
|
104
|
+
case mode
|
105
|
+
when "merge"
|
106
|
+
content_type = ContentType.find_or_initialize_by(handle: handle)
|
107
|
+
update_content_type(content_type, row)
|
108
|
+
when "replace"
|
109
|
+
content_type = build_content_type(row)
|
110
|
+
content_type.save!
|
111
|
+
when "safe"
|
112
|
+
next if ContentType.exists?(handle: handle)
|
113
|
+
|
114
|
+
content_type = build_content_type(row)
|
115
|
+
content_type.save!
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def import_field_definitions(csv_content)
|
121
|
+
return unless csv_content
|
122
|
+
|
123
|
+
CSV.parse(csv_content, headers: true) do |row|
|
124
|
+
parent_type = row["parent_type"]
|
125
|
+
parent_handle = row["parent_handle"]
|
126
|
+
handle = row["handle"]
|
127
|
+
|
128
|
+
next unless parent_type.present? && parent_handle.present? && handle.present?
|
129
|
+
|
130
|
+
parent = find_parent(parent_type, parent_handle)
|
131
|
+
next unless parent
|
132
|
+
|
133
|
+
case mode
|
134
|
+
when "merge"
|
135
|
+
field_def = parent.field_definitions.find_or_initialize_by(handle: handle)
|
136
|
+
update_field_definition(field_def, row)
|
137
|
+
when "replace"
|
138
|
+
field_def = build_field_definition(parent, row)
|
139
|
+
field_def.save!
|
140
|
+
when "safe"
|
141
|
+
next if parent.field_definitions.exists?(handle: handle)
|
142
|
+
|
143
|
+
field_def = build_field_definition(parent, row)
|
144
|
+
field_def.save!
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def find_parent(parent_type, parent_handle)
|
150
|
+
case parent_type
|
151
|
+
when "content_type"
|
152
|
+
ContentType.find_by(handle: parent_handle)
|
153
|
+
when "block"
|
154
|
+
BlockDefinition.find_by(handle: parent_handle)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def build_content_type(row)
|
159
|
+
klass = case row["type"]
|
160
|
+
when "single" then ContentTypes::Single
|
161
|
+
when "collection" then ContentTypes::Collection
|
162
|
+
else ContentType
|
163
|
+
end
|
164
|
+
|
165
|
+
klass.new(
|
166
|
+
handle: row["handle"],
|
167
|
+
name: row["name"],
|
168
|
+
description: row["description"],
|
169
|
+
icon: row["icon"],
|
170
|
+
web_publishing_enabled: row["web_publishing_enabled"] == "true",
|
171
|
+
base_path: row["base_path"]
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
def update_content_type(content_type, row)
|
176
|
+
content_type.assign_attributes(
|
177
|
+
name: row["name"],
|
178
|
+
description: row["description"],
|
179
|
+
icon: row["icon"],
|
180
|
+
web_publishing_enabled: row["web_publishing_enabled"] == "true",
|
181
|
+
base_path: row["base_path"]
|
182
|
+
)
|
183
|
+
|
184
|
+
# Update type if changed
|
185
|
+
if row["type"] && content_type.type != "Iron::ContentTypes::#{row['type'].camelize}"
|
186
|
+
content_type.type = "Iron::ContentTypes::#{row['type'].camelize}"
|
187
|
+
end
|
188
|
+
|
189
|
+
content_type.save!
|
190
|
+
end
|
191
|
+
|
192
|
+
def build_field_definition(parent, row)
|
193
|
+
klass = "Iron::FieldDefinitions::#{row['type'].camelize}".constantize
|
194
|
+
|
195
|
+
field_def = klass.new(
|
196
|
+
schemable: parent,
|
197
|
+
handle: row["handle"],
|
198
|
+
name: row["name"],
|
199
|
+
rank: row["rank"],
|
200
|
+
metadata: parse_metadata(row["metadata"])
|
201
|
+
)
|
202
|
+
|
203
|
+
# Set supported block definitions before saving
|
204
|
+
set_supported_block_definitions(field_def, row["supported_block_definitions"])
|
205
|
+
|
206
|
+
field_def
|
207
|
+
end
|
208
|
+
|
209
|
+
def update_field_definition(field_def, row)
|
210
|
+
field_def.assign_attributes(
|
211
|
+
name: row["name"],
|
212
|
+
rank: row["rank"],
|
213
|
+
metadata: parse_metadata(row["metadata"])
|
214
|
+
)
|
215
|
+
|
216
|
+
# Update type if changed
|
217
|
+
if row["type"] && field_def.type != "Iron::FieldDefinitions::#{row['type'].camelize}"
|
218
|
+
field_def.type = "Iron::FieldDefinitions::#{row['type'].camelize}"
|
219
|
+
end
|
220
|
+
|
221
|
+
# Set supported block definitions before saving
|
222
|
+
set_supported_block_definitions(field_def, row["supported_block_definitions"])
|
223
|
+
|
224
|
+
field_def.save!
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse_metadata(metadata_string)
|
228
|
+
return {} if metadata_string.blank?
|
229
|
+
|
230
|
+
JSON.parse(metadata_string)
|
231
|
+
rescue JSON::ParserError
|
232
|
+
{}
|
233
|
+
end
|
234
|
+
|
235
|
+
def update_content_type_references(content_types_csv)
|
236
|
+
return unless content_types_csv
|
237
|
+
|
238
|
+
CSV.parse(content_types_csv, headers: true) do |row|
|
239
|
+
handle = row["handle"]
|
240
|
+
content_type = ContentType.find_by(handle: handle)
|
241
|
+
next unless content_type
|
242
|
+
|
243
|
+
if row["title_field_handle"].present?
|
244
|
+
title_field = content_type.field_definitions.find_by(handle: row["title_field_handle"])
|
245
|
+
content_type.update!(title_field_definition: title_field) if title_field
|
246
|
+
end
|
247
|
+
|
248
|
+
if row["web_page_title_field_handle"].present?
|
249
|
+
page_title_field = content_type.field_definitions.find_by(handle: row["web_page_title_field_handle"])
|
250
|
+
content_type.update!(web_page_title_field_definition: page_title_field) if page_title_field
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def set_supported_block_definitions(field_def, supported_handles_string)
|
256
|
+
return unless supported_handles_string.present?
|
257
|
+
return unless field_def.respond_to?(:supported_block_definitions)
|
258
|
+
|
259
|
+
handles = supported_handles_string.split("|")
|
260
|
+
block_definitions = BlockDefinition.where(handle: handles)
|
261
|
+
|
262
|
+
field_def.supported_block_definitions = block_definitions
|
263
|
+
end
|
264
|
+
|
265
|
+
class ImportResult
|
266
|
+
attr_reader :errors
|
267
|
+
|
268
|
+
def initialize(success, errors)
|
269
|
+
@success = success
|
270
|
+
@errors = errors
|
271
|
+
end
|
272
|
+
|
273
|
+
def success?
|
274
|
+
@success
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Iron::User::Role
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
enum :role, %w[member administrator].index_with(&:itself), default: :member
|
6
|
+
validate :cannot_change_own_role, if: -> { role_changed? && persisted? }
|
7
|
+
end
|
8
|
+
|
9
|
+
def can_administer?
|
10
|
+
administrator?
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def cannot_change_own_role
|
16
|
+
if current?
|
17
|
+
errors.add(:role, "cannot be changed for your own account")
|
18
|
+
self.role = role_was
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Iron
|
2
|
+
class User < ApplicationRecord
|
3
|
+
include Role
|
4
|
+
|
5
|
+
before_destroy :ensure_not_current_user
|
6
|
+
|
7
|
+
has_secure_password
|
8
|
+
has_many :sessions, dependent: :destroy
|
9
|
+
|
10
|
+
normalizes :email_address, with: ->(e) { e.strip.downcase }
|
11
|
+
|
12
|
+
def current?
|
13
|
+
self == Current.user
|
14
|
+
end
|
15
|
+
|
16
|
+
def initials
|
17
|
+
email_local_part[0..1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
email_local_part
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def email_local_part
|
27
|
+
email_address.split("@").first
|
28
|
+
end
|
29
|
+
|
30
|
+
def ensure_not_current_user
|
31
|
+
if Current.user == self
|
32
|
+
errors.add(:base, "You cannot delete your own account while logged in")
|
33
|
+
throw :abort
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
2
|
+
<% if blob.representable? %>
|
3
|
+
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<figcaption class="attachment__caption">
|
7
|
+
<% if caption = blob.try(:caption) %>
|
8
|
+
<%= caption %>
|
9
|
+
<% else %>
|
10
|
+
<span class="attachment__name"><%= blob.filename %></span>
|
11
|
+
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
|
12
|
+
<% end %>
|
13
|
+
</figcaption>
|
14
|
+
</figure>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<div id="<%= dom_id block_definition %>" class="relative">
|
2
|
+
<%= link_to block_definition_path(block_definition), class: "block px-6 py-4 hover:bg-stone-50 dark:hover:bg-stone-800/50 transition-colors" do %>
|
3
|
+
<div class="flex items-start justify-between gap-4 pr-6">
|
4
|
+
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-8 min-w-0 flex-1">
|
5
|
+
<div class="flex items-center gap-3 shrink-0">
|
6
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white truncate"><%= block_definition.name %></h3>
|
7
|
+
<%= badge block_definition.handle, color: :blue, class: "hidden sm:inline-flex" %>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<% if block_definition.description.present? %>
|
11
|
+
<p class="mt-1 sm:mt-0 text-sm text-stone-500 dark:text-stone-400 line-clamp-1 min-w-0 flex-1"><%= block_definition.description %></p>
|
12
|
+
<% end %>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div class="absolute right-6 top-1/2 -translate-y-1/2 flex items-center gap-4">
|
17
|
+
<%= badge block_definition.handle, color: :blue, class: "sm:hidden" %>
|
18
|
+
<%= icon "chevron-right", variant: :mini, class: "text-stone-400 dark:text-stone-500" %>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<%= form_with(model: block_definition, class: "max-w-96") do |form| %>
|
2
|
+
<div class="field-group">
|
3
|
+
<div
|
4
|
+
class="flex gap-4"
|
5
|
+
data-controller="handle"
|
6
|
+
data-handle-automatic-value="<%= block_definition.new_record? %>"
|
7
|
+
>
|
8
|
+
<div class="field flex-1">
|
9
|
+
<%= form.label :name %>
|
10
|
+
<div>
|
11
|
+
<%= form.text_field :name,
|
12
|
+
data: {
|
13
|
+
handle_target: "source",
|
14
|
+
action: "handle#handleize",
|
15
|
+
"1p_ignore": "",
|
16
|
+
} %>
|
17
|
+
<%= form.error_for :name %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="field flex-1">
|
22
|
+
<%= form.label :handle %>
|
23
|
+
<div>
|
24
|
+
<%= form.text_field :handle,
|
25
|
+
data: {
|
26
|
+
handle_target: "destination",
|
27
|
+
action: "handle#edit",
|
28
|
+
} %>
|
29
|
+
<%= form.error_for :handle %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<div class="field">
|
35
|
+
<%= form.label :description %>
|
36
|
+
<div>
|
37
|
+
<%= form.textarea :description, rows: 4 %>
|
38
|
+
<%= form.error_for :description %>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div class="mt-4">
|
44
|
+
<%= form.submit class: "btn" %>
|
45
|
+
</div>
|
46
|
+
<% end %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% content_for :title, "Block definitions" %>
|
2
|
+
|
3
|
+
<div class="space-y-8">
|
4
|
+
<div class="flex justify-between items-center">
|
5
|
+
<h1 class="page-title">Block definitions</h1>
|
6
|
+
<%= link_to "New block definition", new_block_definition_path, class: "btn" %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div
|
10
|
+
id="block-definitions"
|
11
|
+
class="
|
12
|
+
bg-white dark:bg-stone-800/50 rounded-lg shadow-sm divide-y divide-stone-200
|
13
|
+
dark:divide-stone-700/20 overflow-hidden
|
14
|
+
"
|
15
|
+
>
|
16
|
+
<%= render @block_definitions %>
|
17
|
+
</div>
|
18
|
+
</div>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<% content_for :title, "#{@block_definition.name} block definition" %>
|
2
|
+
|
3
|
+
<div id="<%= dom_id @block_definition %>">
|
4
|
+
<%= back_button_to "Block definitions", block_definitions_path %>
|
5
|
+
<div class="flex justify-between">
|
6
|
+
<h1 class="page-title"><%= @block_definition.name %></h1>
|
7
|
+
<div>
|
8
|
+
<%= link_to "Edit", edit_block_definition_path(@block_definition), class: "btn", data: { variant: "outline" } %>
|
9
|
+
<div class="mt-4 inline-block ml-2">
|
10
|
+
<%= button_to "Destroy", @block_definition, method: :delete, class: "btn", data: { color: "destructive" } %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<p class="dark:text-stone-400"><%= @block_definition.description %></p>
|
15
|
+
|
16
|
+
<ul
|
17
|
+
id="fields"
|
18
|
+
class="
|
19
|
+
bg-white dark:bg-stone-800/50 rounded-lg shadow-sm divide-y divide-stone-200
|
20
|
+
dark:divide-stone-700/20 overflow-hidden mt-8
|
21
|
+
"
|
22
|
+
data-controller="sortable-list"
|
23
|
+
>
|
24
|
+
<% @block_definition.field_definitions.each do |field_definition| %>
|
25
|
+
<%= render "iron/field_definitions/field_definition", field_definition: field_definition %>
|
26
|
+
<% end %>
|
27
|
+
</ul>
|
28
|
+
|
29
|
+
<div class="mt-4">
|
30
|
+
<%= link_to "+ Add new field",
|
31
|
+
new_block_definition_field_definition_path(@block_definition),
|
32
|
+
class: "btn" %>
|
33
|
+
</div>
|
34
|
+
</div>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<%= turbo_stream.append dom_id(@block_list.definition) do %>
|
2
|
+
<%= fields_for "#{@form_field_name}[#{unique_id}]", @block do |builder| %>
|
3
|
+
<%= render "iron/entries/fields/block", builder: builder, field: @block %>
|
4
|
+
<%= builder.hidden_field :id %>
|
5
|
+
<% end %>
|
6
|
+
<% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<div id="<%= dom_id content_type %>" class="relative">
|
2
|
+
<%= link_to content_type_path(content_type), class: "block px-6 py-4 hover:bg-stone-50 dark:hover:bg-stone-800/50 transition-colors" do %>
|
3
|
+
<div class="flex items-center justify-between gap-4 pr-6">
|
4
|
+
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-8 min-w-0 flex-1">
|
5
|
+
<div class="flex items-center gap-3 shrink-0">
|
6
|
+
<h3 class="text-base font-semibold text-stone-900 dark:text-white truncate"><%= content_type.name %></h3>
|
7
|
+
<%= badge content_type.handle, color: :blue, class: "hidden sm:inline-flex" %>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<% if content_type.description.present? %>
|
11
|
+
<p class="mt-1 sm:mt-0 text-sm text-stone-500 dark:text-stone-400 line-clamp-1 min-w-0 flex-1"><%= content_type.description %></p>
|
12
|
+
<% end %>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div class="absolute right-6 top-1/2 -translate-y-1/2 flex items-center gap-4">
|
17
|
+
<%= badge content_type.handle, color: :blue, class: "sm:hidden" %>
|
18
|
+
<%= icon "chevron-right", class: "text-stone-400 dark:text-stone-500" %>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
@@ -0,0 +1,137 @@
|
|
1
|
+
<%= form_with(model: content_type, class: "max-w-96") do |form| %>
|
2
|
+
<div class="field-group">
|
3
|
+
<div
|
4
|
+
class="flex gap-4"
|
5
|
+
data-controller="handle"
|
6
|
+
data-handle-automatic-value="<%= content_type.new_record? %>"
|
7
|
+
>
|
8
|
+
<div class="field flex-1">
|
9
|
+
<%= form.label :name %>
|
10
|
+
<div>
|
11
|
+
<%= form.text_field :name,
|
12
|
+
data: {
|
13
|
+
handle_target: "source",
|
14
|
+
action: "handle#handleize",
|
15
|
+
"1p_ignore": "",
|
16
|
+
} %>
|
17
|
+
<%= form.error_for :name %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="field flex-1">
|
22
|
+
<%= form.label :handle %>
|
23
|
+
<div>
|
24
|
+
<%= form.text_field :handle,
|
25
|
+
data: {
|
26
|
+
handle_target: "destination",
|
27
|
+
action: "handle#edit",
|
28
|
+
} %>
|
29
|
+
<%= form.error_for :handle %>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<div class="field">
|
35
|
+
<%= form.label :description %>
|
36
|
+
<div>
|
37
|
+
<%= form.textarea :description, rows: 4 %>
|
38
|
+
<%= form.error_for :description %>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<div class="field">
|
43
|
+
<%= form.label :icon %>
|
44
|
+
<div>
|
45
|
+
<%= render "iron/shared/icon_picker", form: form, field: :icon %>
|
46
|
+
<p class="text-sm text-stone-500 mt-1">Icon to display in the sidebar</p>
|
47
|
+
<%= form.error_for :icon %>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<% if content_type.new_record? %>
|
52
|
+
<div class="field">
|
53
|
+
<%= form.label :type, "Type" %>
|
54
|
+
<div>
|
55
|
+
<%= form.select :type,
|
56
|
+
options_for_select(Iron::ContentType::TYPES.map { |t| [t, Iron::ContentType.classify_type(t)] }, content_type.type),
|
57
|
+
{},
|
58
|
+
{ class: "select" } %>
|
59
|
+
<p class="text-sm text-stone-500 mt-1">Single entry types have only one entry, collections can have multiple entries</p>
|
60
|
+
<%= form.error_for :type %>
|
61
|
+
</div>
|
62
|
+
</div>
|
63
|
+
<% end %>
|
64
|
+
|
65
|
+
<% if !content_type.new_record? && content_type.field_definitions.any? %>
|
66
|
+
<div class="field">
|
67
|
+
<%= form.label :title_field_definition_id, "Title Field" %>
|
68
|
+
<div>
|
69
|
+
<%= form.collection_select :title_field_definition_id,
|
70
|
+
content_type.titlable_definitions,
|
71
|
+
:id,
|
72
|
+
:name,
|
73
|
+
{ include_blank: "Select a field" },
|
74
|
+
{ class: "select" } %>
|
75
|
+
<p class="text-sm text-stone-500 mt-1">Field used for entry titles in the admin interface</p>
|
76
|
+
<%= form.error_for :title_field_definition_id %>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
<% end %>
|
80
|
+
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<% if !content_type.new_record? && content_type.collection? %>
|
84
|
+
<fieldset class="mt-6">
|
85
|
+
<legend>Web Publishing</legend>
|
86
|
+
|
87
|
+
<div class="field">
|
88
|
+
<div class="flex items-center gap-2">
|
89
|
+
<%= form.check_box :web_publishing_enabled,
|
90
|
+
data: {
|
91
|
+
controller: "toggle",
|
92
|
+
toggle_selector_value: "#web-publishing-fields",
|
93
|
+
action: "toggle#toggle"
|
94
|
+
} %>
|
95
|
+
<%= form.label :web_publishing_enabled, "Enable web publishing", class: "!font-normal" %>
|
96
|
+
</div>
|
97
|
+
<p class="text-sm text-stone-500 mt-1">Publish entries as web pages with unique URLs</p>
|
98
|
+
</div>
|
99
|
+
|
100
|
+
<div id="web-publishing-fields" data-ui="<%= content_type.web_publishing_enabled? ? 'visible' : 'hidden' %>" class="data-[ui~=hidden]:hidden">
|
101
|
+
<div class="field mt-4">
|
102
|
+
<%= form.label :web_page_title_field_definition_id, "Web Page Title Field" %>
|
103
|
+
<div>
|
104
|
+
<% if content_type.titlable_definitions.any? %>
|
105
|
+
<%= form.collection_select :web_page_title_field_definition_id,
|
106
|
+
content_type.titlable_definitions,
|
107
|
+
:id,
|
108
|
+
:name,
|
109
|
+
{ include_blank: "Select a field" },
|
110
|
+
{ class: "select" } %>
|
111
|
+
<% else %>
|
112
|
+
<%= form.select :web_page_title_field_definition_id,
|
113
|
+
options_for_select([["No compatible fields", ""]]),
|
114
|
+
{},
|
115
|
+
{ class: "select", disabled: true } %>
|
116
|
+
<% end %>
|
117
|
+
<p class="text-sm text-stone-500 mt-1">Field used to generate the URL handle for each entry</p>
|
118
|
+
<%= form.error_for :web_page_title_field_definition_id %>
|
119
|
+
</div>
|
120
|
+
</div>
|
121
|
+
|
122
|
+
<div class="field mt-4">
|
123
|
+
<%= form.label :base_path, "Base Path" %>
|
124
|
+
<div>
|
125
|
+
<%= form.text_field :base_path, placeholder: "e.g. news, blog, products" %>
|
126
|
+
<p class="text-sm text-stone-500 mt-1">Optional URL prefix for all entries (e.g. /news/my-article)</p>
|
127
|
+
<%= form.error_for :base_path %>
|
128
|
+
</div>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
</fieldset>
|
132
|
+
<% end %>
|
133
|
+
|
134
|
+
<div class="mt-4">
|
135
|
+
<%= form.submit %>
|
136
|
+
</div>
|
137
|
+
<% end %>
|