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.
Files changed (240) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +174 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/builds/iron.css +2 -0
  6. data/app/assets/config/iron_manifest.js +1 -0
  7. data/app/assets/images/iron/icons/sprite.svg +9425 -0
  8. data/app/assets/tailwind/iron/actiontext.css +550 -0
  9. data/app/assets/tailwind/iron/application.css +66 -0
  10. data/app/assets/tailwind/iron/components/button.css +74 -0
  11. data/app/assets/tailwind/iron/components/dropdown.css +42 -0
  12. data/app/assets/tailwind/iron/components/fieldset.css +18 -0
  13. data/app/assets/tailwind/iron/components/form.css +14 -0
  14. data/app/assets/tailwind/iron/components/icon-picker.css +9 -0
  15. data/app/assets/tailwind/iron/components/input.css +44 -0
  16. data/app/assets/tailwind/iron/components/page.css +7 -0
  17. data/app/assets/tailwind/iron/components/select.css +36 -0
  18. data/app/assets/tailwind/iron/components/sidebar.css +21 -0
  19. data/app/assets/tailwind/iron/components/textarea.css +27 -0
  20. data/app/controllers/concerns/iron/authentication.rb +66 -0
  21. data/app/controllers/concerns/iron/authorization.rb +11 -0
  22. data/app/controllers/concerns/iron/locale_aware.rb +20 -0
  23. data/app/controllers/concerns/iron/web_page.rb +18 -0
  24. data/app/controllers/iron/application_controller.rb +7 -0
  25. data/app/controllers/iron/block_definitions/field_definitions_controller.rb +8 -0
  26. data/app/controllers/iron/block_definitions_controller.rb +52 -0
  27. data/app/controllers/iron/blocks_controller.rb +12 -0
  28. data/app/controllers/iron/content_types/field_definitions_controller.rb +8 -0
  29. data/app/controllers/iron/content_types_controller.rb +52 -0
  30. data/app/controllers/iron/entries_controller.rb +103 -0
  31. data/app/controllers/iron/field_definitions_controller.rb +55 -0
  32. data/app/controllers/iron/first_runs_controller.rb +29 -0
  33. data/app/controllers/iron/home_controller.rb +6 -0
  34. data/app/controllers/iron/icons_controller.rb +10 -0
  35. data/app/controllers/iron/locales_controller.rb +52 -0
  36. data/app/controllers/iron/passwords_controller.rb +36 -0
  37. data/app/controllers/iron/references_controller.rb +11 -0
  38. data/app/controllers/iron/schemas_controller.rb +32 -0
  39. data/app/controllers/iron/sessions_controller.rb +32 -0
  40. data/app/controllers/iron/settings_controller.rb +23 -0
  41. data/app/controllers/iron/users_controller.rb +66 -0
  42. data/app/helpers/iron/application_helper.rb +25 -0
  43. data/app/helpers/iron/avatar_helper.rb +35 -0
  44. data/app/helpers/iron/block_helper.rb +4 -0
  45. data/app/helpers/iron/buttons_helper.rb +14 -0
  46. data/app/helpers/iron/components/badge_helper.rb +70 -0
  47. data/app/helpers/iron/components/dropdown_helper.rb +161 -0
  48. data/app/helpers/iron/content_types_helper.rb +11 -0
  49. data/app/helpers/iron/entries_helper.rb +13 -0
  50. data/app/helpers/iron/field_definitions_helper.rb +5 -0
  51. data/app/helpers/iron/form_builder.rb +43 -0
  52. data/app/helpers/iron/icons_helper.rb +30 -0
  53. data/app/helpers/iron/image_helper.rb +50 -0
  54. data/app/javascript/iron/application.js +6 -0
  55. data/app/javascript/iron/controllers/application.js +9 -0
  56. data/app/javascript/iron/controllers/element_controller.js +11 -0
  57. data/app/javascript/iron/controllers/file_upload_controller.js +18 -0
  58. data/app/javascript/iron/controllers/form_controller.js +7 -0
  59. data/app/javascript/iron/controllers/handle_controller.js +27 -0
  60. data/app/javascript/iron/controllers/icon_picker_controller.js +48 -0
  61. data/app/javascript/iron/controllers/index.js +4 -0
  62. data/app/javascript/iron/controllers/sortable_list_controller.js +125 -0
  63. data/app/javascript/iron/controllers/toggle_controller.js +14 -0
  64. data/app/javascript/iron/controllers/trix_controller.js +19 -0
  65. data/app/javascript/iron/lib/lexorank.js +51 -0
  66. data/app/jobs/iron/application_job.rb +4 -0
  67. data/app/jobs/iron/generate_entry_routes_job.rb +15 -0
  68. data/app/mailers/iron/application_mailer.rb +6 -0
  69. data/app/mailers/passwords_mailer.rb +6 -0
  70. data/app/models/concerns/iron/csv_serializable.rb +28 -0
  71. data/app/models/iron/account/joinable.rb +16 -0
  72. data/app/models/iron/account.rb +13 -0
  73. data/app/models/iron/application_record.rb +5 -0
  74. data/app/models/iron/block_definition/portable.rb +20 -0
  75. data/app/models/iron/block_definition.rb +10 -0
  76. data/app/models/iron/content_type/field_queryable.rb +101 -0
  77. data/app/models/iron/content_type/portable.rb +30 -0
  78. data/app/models/iron/content_type/titlable.rb +13 -0
  79. data/app/models/iron/content_type/web_publishable.rb +47 -0
  80. data/app/models/iron/content_type.rb +39 -0
  81. data/app/models/iron/content_types/collection.rb +6 -0
  82. data/app/models/iron/content_types/single.rb +9 -0
  83. data/app/models/iron/current.rb +12 -0
  84. data/app/models/iron/entry/deep_validation.rb +32 -0
  85. data/app/models/iron/entry/presentable.rb +40 -0
  86. data/app/models/iron/entry/schemable.rb +16 -0
  87. data/app/models/iron/entry/titlable.rb +35 -0
  88. data/app/models/iron/entry/web_publishable.rb +85 -0
  89. data/app/models/iron/entry.rb +16 -0
  90. data/app/models/iron/field/belongs_to_entry.rb +29 -0
  91. data/app/models/iron/field.rb +20 -0
  92. data/app/models/iron/field_definition/portable.rb +36 -0
  93. data/app/models/iron/field_definition.rb +42 -0
  94. data/app/models/iron/field_definitions/block.rb +23 -0
  95. data/app/models/iron/field_definitions/block_list.rb +8 -0
  96. data/app/models/iron/field_definitions/boolean.rb +7 -0
  97. data/app/models/iron/field_definitions/date.rb +7 -0
  98. data/app/models/iron/field_definitions/file.rb +67 -0
  99. data/app/models/iron/field_definitions/number.rb +7 -0
  100. data/app/models/iron/field_definitions/reference.rb +8 -0
  101. data/app/models/iron/field_definitions/reference_list.rb +8 -0
  102. data/app/models/iron/field_definitions/rich_text_area.rb +4 -0
  103. data/app/models/iron/field_definitions/text_area.rb +7 -0
  104. data/app/models/iron/field_definitions/text_field.rb +25 -0
  105. data/app/models/iron/fields/block.rb +40 -0
  106. data/app/models/iron/fields/block_list.rb +16 -0
  107. data/app/models/iron/fields/boolean.rb +7 -0
  108. data/app/models/iron/fields/date.rb +7 -0
  109. data/app/models/iron/fields/file.rb +29 -0
  110. data/app/models/iron/fields/number.rb +7 -0
  111. data/app/models/iron/fields/reference.rb +14 -0
  112. data/app/models/iron/fields/reference_list.rb +17 -0
  113. data/app/models/iron/fields/rich_text_area.rb +9 -0
  114. data/app/models/iron/fields/text_area.rb +7 -0
  115. data/app/models/iron/fields/text_field.rb +19 -0
  116. data/app/models/iron/first_run.rb +13 -0
  117. data/app/models/iron/icon_catalog.rb +45 -0
  118. data/app/models/iron/locale.rb +30 -0
  119. data/app/models/iron/reference.rb +8 -0
  120. data/app/models/iron/schema_archive.rb +71 -0
  121. data/app/models/iron/schema_exporter.rb +15 -0
  122. data/app/models/iron/schema_importer.rb +278 -0
  123. data/app/models/iron/session.rb +5 -0
  124. data/app/models/iron/user/role.rb +21 -0
  125. data/app/models/iron/user.rb +37 -0
  126. data/app/views/active_storage/blobs/_blob.html.erb +14 -0
  127. data/app/views/iron/block_definitions/_block_definition.html.erb +21 -0
  128. data/app/views/iron/block_definitions/_form.html.erb +46 -0
  129. data/app/views/iron/block_definitions/edit.html.erb +8 -0
  130. data/app/views/iron/block_definitions/index.html.erb +18 -0
  131. data/app/views/iron/block_definitions/new.html.erb +8 -0
  132. data/app/views/iron/block_definitions/show.html.erb +34 -0
  133. data/app/views/iron/blocks/new.turbo_stream.erb +6 -0
  134. data/app/views/iron/content_types/_content_type.html.erb +21 -0
  135. data/app/views/iron/content_types/_form.html.erb +137 -0
  136. data/app/views/iron/content_types/edit.html.erb +8 -0
  137. data/app/views/iron/content_types/index.html.erb +28 -0
  138. data/app/views/iron/content_types/new.html.erb +8 -0
  139. data/app/views/iron/content_types/show.html.erb +49 -0
  140. data/app/views/iron/entries/_entry.html.erb +17 -0
  141. data/app/views/iron/entries/_entry_option.html.erb +19 -0
  142. data/app/views/iron/entries/_form.html.erb +63 -0
  143. data/app/views/iron/entries/edit.html.erb +52 -0
  144. data/app/views/iron/entries/entry.html.erb +21 -0
  145. data/app/views/iron/entries/fields/_block.html.erb +92 -0
  146. data/app/views/iron/entries/fields/_block_list.html.erb +45 -0
  147. data/app/views/iron/entries/fields/_boolean.html.erb +13 -0
  148. data/app/views/iron/entries/fields/_date.html.erb +11 -0
  149. data/app/views/iron/entries/fields/_file.html.erb +60 -0
  150. data/app/views/iron/entries/fields/_number.html.erb +11 -0
  151. data/app/views/iron/entries/fields/_reference.html.erb +12 -0
  152. data/app/views/iron/entries/fields/_reference_item.html.erb +28 -0
  153. data/app/views/iron/entries/fields/_reference_list.html.erb +44 -0
  154. data/app/views/iron/entries/fields/_rich_text_area.html.erb +13 -0
  155. data/app/views/iron/entries/fields/_text_area.html.erb +11 -0
  156. data/app/views/iron/entries/fields/_text_field.html.erb +28 -0
  157. data/app/views/iron/entries/index.html.erb +21 -0
  158. data/app/views/iron/entries/new.html.erb +8 -0
  159. data/app/views/iron/entries/search.html.erb +33 -0
  160. data/app/views/iron/field_definitions/_field_definition.html.erb +37 -0
  161. data/app/views/iron/field_definitions/block/_form.html.erb +16 -0
  162. data/app/views/iron/field_definitions/block_list/_form.html.erb +13 -0
  163. data/app/views/iron/field_definitions/boolean/_form.html.erb +2 -0
  164. data/app/views/iron/field_definitions/date/_form.html.erb +2 -0
  165. data/app/views/iron/field_definitions/edit.html.erb +16 -0
  166. data/app/views/iron/field_definitions/file/_form.html.erb +40 -0
  167. data/app/views/iron/field_definitions/index.html.erb +14 -0
  168. data/app/views/iron/field_definitions/layouts/_form.html.erb +47 -0
  169. data/app/views/iron/field_definitions/new.html.erb +22 -0
  170. data/app/views/iron/field_definitions/number/_form.html.erb +2 -0
  171. data/app/views/iron/field_definitions/reference/_form.html.erb +13 -0
  172. data/app/views/iron/field_definitions/reference_list/_form.html.erb +13 -0
  173. data/app/views/iron/field_definitions/rich_text_area/_form.html.erb +2 -0
  174. data/app/views/iron/field_definitions/text_area/_form.html.erb +2 -0
  175. data/app/views/iron/field_definitions/text_field/_form.html.erb +17 -0
  176. data/app/views/iron/first_runs/show.html.erb +44 -0
  177. data/app/views/iron/home/show.html.erb +2 -0
  178. data/app/views/iron/icons/index.html.erb +21 -0
  179. data/app/views/iron/locales/_form.html.erb +24 -0
  180. data/app/views/iron/locales/_locale.html.erb +15 -0
  181. data/app/views/iron/locales/edit.html.erb +8 -0
  182. data/app/views/iron/locales/index.html.erb +12 -0
  183. data/app/views/iron/locales/new.html.erb +8 -0
  184. data/app/views/iron/passwords/edit.html.erb +7 -0
  185. data/app/views/iron/passwords/new.html.erb +26 -0
  186. data/app/views/iron/passwords_mailer/reset.html.erb +4 -0
  187. data/app/views/iron/passwords_mailer/reset.text.erb +2 -0
  188. data/app/views/iron/published_pages/show.html.erb +10 -0
  189. data/app/views/iron/references/new.turbo_stream.erb +6 -0
  190. data/app/views/iron/schemas/new.html.erb +57 -0
  191. data/app/views/iron/sessions/new.html.erb +36 -0
  192. data/app/views/iron/settings/show.html.erb +38 -0
  193. data/app/views/iron/shared/_icon_picker.html.erb +60 -0
  194. data/app/views/iron/users/_form.html.erb +21 -0
  195. data/app/views/iron/users/_user.html.erb +27 -0
  196. data/app/views/iron/users/edit.html.erb +12 -0
  197. data/app/views/iron/users/index.html.erb +21 -0
  198. data/app/views/iron/users/new.html.erb +29 -0
  199. data/app/views/iron/users/show.html.erb +21 -0
  200. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  201. data/app/views/layouts/iron/_sidebar.html.erb +83 -0
  202. data/app/views/layouts/iron/_sidebar_item.html.erb +14 -0
  203. data/app/views/layouts/iron/_toast.html.erb +29 -0
  204. data/app/views/layouts/iron/application.html.erb +46 -0
  205. data/app/views/layouts/iron/authentication.html.erb +41 -0
  206. data/config/importmap.rb +9 -0
  207. data/config/routes.rb +40 -0
  208. data/db/migrate/20250422131656_initial_iron_schema.rb +105 -0
  209. data/db/migrate/20250427100754_create_iron_accounts.rb +10 -0
  210. data/db/migrate/20250427104020_add_role_to_iron_users.rb +5 -0
  211. data/db/migrate/20250428094923_add_default_iron_locale_to_iron_account.rb +5 -0
  212. data/db/migrate/20250504144731_create_join_table_content_types_field_definitions.rb +25 -0
  213. data/db/migrate/20250505090716_add_title_field_definition_to_iron_content_types.rb +5 -0
  214. data/db/migrate/20250512210352_create_iron_references.rb +11 -0
  215. data/db/migrate/20250524220355_add_referenced_entry_id_to_iron_fields.rb +5 -0
  216. data/db/migrate/20250529211703_add_metadata_to_iron_field_definitions.rb +5 -0
  217. data/db/migrate/20250529211704_add_value_boolean_to_iron_fields.rb +5 -0
  218. data/db/migrate/20250531172837_add_type_to_iron_content_types.rb +5 -0
  219. data/db/migrate/20250601080146_add_icon_to_iron_content_types.rb +5 -0
  220. data/db/migrate/20250609091605_add_web_publishing_to_iron_content_types.rb +7 -0
  221. data/db/migrate/20250609091813_add_route_to_iron_entries.rb +6 -0
  222. data/lib/generators/iron/pages/pages_generator.rb +34 -0
  223. data/lib/generators/iron/pages/templates/pages_controller.rb +14 -0
  224. data/lib/generators/iron/pages/templates/show.html.erb +9 -0
  225. data/lib/generators/iron/template/template_generator.rb +35 -0
  226. data/lib/generators/iron/template/templates/content_type.html.erb +5 -0
  227. data/lib/iron/cva.rb +69 -0
  228. data/lib/iron/engine.rb +42 -0
  229. data/lib/iron/image_analyzer.rb +26 -0
  230. data/lib/iron/lexorank/exceptions.rb +4 -0
  231. data/lib/iron/lexorank/rankable.rb +185 -0
  232. data/lib/iron/lexorank/rebalance_rank_job.rb +10 -0
  233. data/lib/iron/lexorank/utils.rb +77 -0
  234. data/lib/iron/lexorank.rb +7 -0
  235. data/lib/iron/routing.rb +66 -0
  236. data/lib/iron/sdk.rb +19 -0
  237. data/lib/iron/version.rb +3 -0
  238. data/lib/iron.rb +8 -0
  239. data/lib/puma/plugin/iron_tailwindcss.rb +78 -0
  240. metadata +458 -0
@@ -0,0 +1,36 @@
1
+ <div>
2
+ <div class="mb-8 text-center">
3
+ <h1 class="text-2xl font-semibold tracking-tight">Welcome back</h1>
4
+ <p class="mt-2 text-sm text-stone-600 dark:text-stone-400">Sign in to your account to continue</p>
5
+ </div>
6
+
7
+ <%= form_with url: sign_in_path, class: "space-y-6" do |form| %>
8
+ <div>
9
+ <%= form.label :email_address, "Email address", class: "sr-only" %>
10
+ <%= form.email_field :email_address,
11
+ required: true,
12
+ autofocus: true,
13
+ autocomplete: "username",
14
+ placeholder: "Enter your email address",
15
+ value: params[:email_address] %>
16
+ </div>
17
+
18
+ <div>
19
+ <%= form.label :password, "Password", class: "sr-only" %>
20
+ <%= form.password_field :password,
21
+ required: true,
22
+ autocomplete: "current-password",
23
+ placeholder: "Enter your password",
24
+ maxlength: 72 %>
25
+ </div>
26
+
27
+ <%= form.submit "Sign in", class: "w-full" %>
28
+ <% end %>
29
+
30
+ <div class="mt-8 space-y-4 text-center text-sm">
31
+ <%= link_to "Forgot your password?",
32
+ new_password_path,
33
+ class:
34
+ "block text-stone-600 hover:text-stone-900 dark:text-stone-400 dark:hover:text-stone-300" %>
35
+ </div>
36
+ </div>
@@ -0,0 +1,38 @@
1
+ <% content_for :title, "Settings" %>
2
+
3
+ <div class="space-y-8">
4
+ <div class="flex justify-between items-center">
5
+ <h1 class="page-title">Settings</h1>
6
+ </div>
7
+
8
+ <div class="">
9
+ <%= form_with(model: @account, url: settings_path, class: "max-w-96") do |form| %>
10
+ <div class="field-group">
11
+ <div class="field">
12
+ <%= form.label :name %>
13
+ <div>
14
+ <%= form.text_field :name %>
15
+ <%= form.error_for :name %>
16
+ </div>
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= form.label :default_locale_id, "Default Locale" %>
21
+ <div>
22
+ <%= form.collection_select :default_locale_id,
23
+ @locales,
24
+ :id,
25
+ :name,
26
+ {},
27
+ { class: "select" } %>
28
+ <%= form.error_for :default_locale_id %>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="mt-4">
34
+ <%= form.submit "Save Settings" %>
35
+ </div>
36
+ <% end %>
37
+ </div>
38
+ </div>
@@ -0,0 +1,60 @@
1
+ <%# locals: (form:, field: :icon) -%>
2
+
3
+ <% picker_id = "icon_picker_#{SecureRandom.hex(8)}" %>
4
+ <% current_value = form.object.send(field) %>
5
+
6
+ <div data-controller="icon-picker">
7
+ <%= form.hidden_field field, data: { icon_picker_target: "input" } %>
8
+
9
+ <button
10
+ type="button"
11
+ popovertarget="<%= picker_id %>"
12
+ class="
13
+ flex items-center gap-2 px-3 py-2 bg-stone-800 border border-stone-700
14
+ rounded-md hover:bg-stone-700 focus:outline-none focus:ring-2
15
+ focus:ring-stone-500
16
+ "
17
+ >
18
+ <span data-icon-picker-target="preview">
19
+ <% if current_value.present? %>
20
+ <%= icon current_value %>
21
+ <% else %>
22
+ <%= icon "square-dashed" %>
23
+ <% end %>
24
+ </span>
25
+ <span data-icon-picker-target="label">
26
+ <%= current_value.presence || "Select an icon" %>
27
+ </span>
28
+ <%= icon "chevron-down", class: "size-4 ml-auto" %>
29
+ </button>
30
+
31
+ <div
32
+ id="<%= picker_id %>"
33
+ popover
34
+ class="
35
+ rounded-lg overflow-hidden mt-2 bg-stone-800/95 backdrop-blur-sm shadow-xl
36
+ border border-stone-700 w-[500px] h-[400px] [position-area:bottom_span-right]
37
+ "
38
+ data-icon-picker-target="popover"
39
+ >
40
+ <div class="p-4 border-b border-stone-700">
41
+ <input
42
+ type="text"
43
+ placeholder="Search icons..."
44
+ class="
45
+ w-full px-3 py-2 bg-stone-900 border border-stone-700 rounded-md text-sm
46
+ focus:outline-none focus:ring-2 focus:ring-stone-500
47
+ "
48
+ data-action="input->icon-picker#filter"
49
+ >
50
+ </div>
51
+
52
+ <div class="overflow-y-auto h-[calc(100%-73px)] p-4">
53
+ <%= turbo_frame_tag "icon_picker_grid_#{picker_id}", src: icons_path(selected: current_value, frame_id: picker_id) do %>
54
+ <div class="flex items-center justify-center h-full">
55
+ <div class="text-stone-500">Loading icons...</div>
56
+ </div>
57
+ <% end %>
58
+ </div>
59
+ </div>
60
+ </div>
@@ -0,0 +1,21 @@
1
+ <%= form_with(model: user, class: "mt-4 max-w-96") do |form| %>
2
+ <div class="flex flex-col gap-3">
3
+ <div class="field">
4
+ <%= form.label :email_address, "Email" %>
5
+ <div>
6
+ <%= form.text_field :email_address %>
7
+ <%= form.error_for :email_address %>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="field">
12
+ <%= form.label :role, "User role" %>
13
+ <div>
14
+ <%= form.collection_select :role, Iron::User.roles.keys, :to_s, :titleize, {}, { class: "select" } %>
15
+ <%= form.error_for :role %>
16
+ </div>
17
+ </div>
18
+ </div>
19
+
20
+ <%= form.submit class: "mt-4" %>
21
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <div id="<%= dom_id user %>" class="relative">
2
+ <div class="block px-6 py-3">
3
+ <div class="flex items-center justify-between gap-4">
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"><%= user.name %></h3>
7
+ <span class="text-sm text-stone-400"><%= user.email_address %></span>
8
+ </div>
9
+ </div>
10
+ <div>
11
+ <% if user.current? %>
12
+ <%= badge user.role, color: user.administrator? ? "blue" : "stone" %>
13
+ <% else %>
14
+ <%= form_with(model: user, class: "", data: { controller: "form", action: "change->form#submit" }) do |form| %>
15
+ <div class="field">
16
+ <%= form.label :role, "User role", class: "sr-only" %>
17
+ <div>
18
+ <%= form.collection_select :role, Iron::User.roles.keys, :to_s, :titleize, {}, { class: "select" } %>
19
+ <%= form.error_for :role %>
20
+ </div>
21
+ </div>
22
+ <% end %>
23
+ <% end %>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
@@ -0,0 +1,12 @@
1
+ <% content_for :title, "Editing user" %>
2
+
3
+ <h1>Editing user</h1>
4
+
5
+ <%= render "form", user: @user %>
6
+
7
+ <br>
8
+
9
+ <div>
10
+ <%= link_to "Show this user", @user %> |
11
+ <%= link_to "Back to users", users_path %>
12
+ </div>
@@ -0,0 +1,21 @@
1
+ <% content_for :title, "Users" %>
2
+
3
+ <% url = join_url(Iron::Current.account.join_code) %>
4
+
5
+ <div class="space-y-8">
6
+ <div class="flex justify-between items-center">
7
+ <h1 class="page-title">Users</h1>
8
+ </div>
9
+
10
+ <div>
11
+ <h2>Invite users</h2>
12
+ <div class="field">
13
+ <label for="join-url">Share to invite people to join</label>
14
+ <input type="text" id="join-url" class="input" value="<%= url %>" readonly>
15
+ </div>
16
+ </div>
17
+
18
+ <div id="users" class="bg-white dark:bg-stone-800/50 rounded-lg shadow-sm divide-y divide-stone-200 dark:divide-stone-700/20 overflow-hidden">
19
+ <%= render @users %>
20
+ </div>
21
+ </div>
@@ -0,0 +1,29 @@
1
+ <% content_for :title, "Join #{Iron::Current.account.name}" %>
2
+
3
+ <div>
4
+ <div class="mb-8 text-center">
5
+ <h1 class="text-2xl font-semibold tracking-tight">Join <%= Iron::Current.account.name %></h1>
6
+ </div>
7
+
8
+ <%= form_with(model: @user, url: join_path(params[:join_code]), class: "space-y-4") do |form| %>
9
+ <div>
10
+ <%= form.label :email_address %>
11
+ <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "email" %>
12
+ <%= form.error_for :email_address %>
13
+ </div>
14
+
15
+ <div>
16
+ <%= form.label :password %>
17
+ <%= form.password_field :password, required: true, autocomplete: "new-password" %>
18
+ <%= form.error_for :password %>
19
+ </div>
20
+
21
+ <div>
22
+ <%= form.label :password_confirmation %>
23
+ <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password" %>
24
+ <%= form.error_for :password_confirmation %>
25
+ </div>
26
+
27
+ <%= form.submit "Join", class: "mt-4 w-full" %>
28
+ <% end %>
29
+ </div>
@@ -0,0 +1,21 @@
1
+ <% content_for :title, "#{@user.name}" %>
2
+
3
+ <div id="<%= dom_id @user %>">
4
+ <%= back_button_to "Users", users_path %>
5
+ <div class="flex justify-between">
6
+ <h1 class="page-title"><%= @user.name %></h1>
7
+ <div>
8
+ <%= unless @user.current?
9
+ button_to "Destroy",
10
+ @user,
11
+ method: :delete,
12
+ class: "btn",
13
+ data: {
14
+ color: "destructive",
15
+ }
16
+ end %>
17
+ </div>
18
+ </div>
19
+
20
+ <%= render "form", user: @user %>
21
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="trix-content">
2
+ <%= yield -%>
3
+ </div>
@@ -0,0 +1,83 @@
1
+ <div class="fixed inset-y-0 left-0 w-64 max-lg:hidden">
2
+ <nav class="flex h-full min-h-0 flex-col">
3
+ <div class="border-b border-stone-950/5 p-4 dark:border-white/5">
4
+ <span class="font-semibold bg-stone-700/50 px-2 py-1 rounded-lg"><%= Iron::Current.account.name %></span>
5
+ </div>
6
+
7
+ <div class="flex flex-1 flex-col overflow-y-auto p-4 [&>section+section]:mt-8">
8
+ <% if Iron::Current.user.can_administer? %>
9
+ <section class="flex flex-col gap-0.5">
10
+ <h3 class="mb-1 px-2 text-xs/6 font-medium text-stone-500 dark:text-stone-400">Admin</h3>
11
+
12
+ <ul>
13
+ <%= render "layouts/iron/sidebar_item", text: "Content Types", url: content_types_path, icon: "boxes", current: controller_name == "content_types" %>
14
+ <ul class="ml-4">
15
+ <% Iron::ContentType.all.each do |content_type| %>
16
+ <%= render "layouts/iron/sidebar_item", text: content_type.name, url: content_type_path(content_type), icon: content_type.icon.present? ? content_type.icon : "box" %>
17
+ <% end %>
18
+ </ul>
19
+ <%= render "layouts/iron/sidebar_item", text: "Block definitions", url: block_definitions_path, icon: "blocks", current: controller_name == "block_definitions" %>
20
+ <ul class="ml-4">
21
+ <% Iron::BlockDefinition.all.each do |block_definition| %>
22
+ <%= render "layouts/iron/sidebar_item", text: block_definition.name, url: block_definition_path(block_definition), icon: "diamond" %>
23
+ <% end %>
24
+ </ul>
25
+ <%= render "layouts/iron/sidebar_item", text: "Locales", url: locales_path, icon: "languages", current: controller_name == "locales" %>
26
+ <%= render "layouts/iron/sidebar_item", text: "Users", url: users_path, icon: "users", current: controller_name == "users" %>
27
+ <%= render "layouts/iron/sidebar_item", text: "Settings", url: settings_path, icon: "settings", current: controller_name == "settings" %>
28
+ </ul>
29
+ </section>
30
+ <% end %>
31
+
32
+ <section class="flex flex-col gap-0.5">
33
+ <h3 class="mb-1 px-2 text-xs/6 font-medium text-stone-500 dark:text-stone-400">Content</h3>
34
+
35
+ <ul class="">
36
+ <% Iron::ContentType.all.each do |content_type| %>
37
+ <%= render "layouts/iron/sidebar_item", text: content_type.name, url: content_type_sidebar_url(content_type), icon: content_type.icon.present? ? content_type.icon : "folder-closed", current: current_content_page?(content_type) %>
38
+ <% end %>
39
+ </ul>
40
+ </section>
41
+ </div>
42
+
43
+ <div
44
+ class="
45
+ flex flex-col border-t border-stone-950/5 p-4 dark:border-white/5
46
+ [&>section+section]:mt-2.5
47
+ "
48
+ >
49
+ <%= dropdown_menu do %>
50
+ <%= dropdown_menu_trigger class: "sidebar-item" do %>
51
+ <span class="flex items-center gap-3 min-w-0">
52
+ <%= avatar Iron::Current.user, class: "size-10", variant: "square" %>
53
+ <span class="min-w-0">
54
+ <span
55
+ class="
56
+ block truncate text-sm/5 font-medium text-stone-950 dark:text-white
57
+ "
58
+ >
59
+ <%= Iron::Current.user.name %>
60
+ </span>
61
+ <span
62
+ class="
63
+ block truncate text-xs/5 font-normal text-stone-500 dark:text-stone-400
64
+ "
65
+ >
66
+ <%= Iron::Current.user.email_address %>
67
+ </span>
68
+ </span>
69
+ </span>
70
+ <%= icon "chevron-up", class: "size-4" %>
71
+ <% end %>
72
+
73
+ <%= dropdown_menu_content width: "16rem", align: "end" do %>
74
+ <%= dropdown_menu_item "My account", "#", icon: "circle-user" %>
75
+
76
+ <%= dropdown_menu_separator %>
77
+
78
+ <%= dropdown_menu_item "Sign out", session_path, method: :delete, icon: "log-out" %>
79
+ <% end %>
80
+ <% end %>
81
+ </div>
82
+ </nav>
83
+ </div>
@@ -0,0 +1,14 @@
1
+ <%# locals: (text:, url:, icon: nil, class: nil, current: false) -%>
2
+
3
+ <li class="<%= tw(local_assigns[:class], "relative") %>">
4
+ <% if current %>
5
+ <span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-stone-950 dark:bg-white"></span>
6
+ <% end %>
7
+
8
+ <%= link_to url, class: "sidebar-item", data: { current: current ? "true" : nil } do %>
9
+ <% if icon.present? %>
10
+ <%= icon(icon, data: { slot: "icon" }) %>
11
+ <% end %>
12
+ <span><%= text %></span>
13
+ <% end %>
14
+ </li>
@@ -0,0 +1,29 @@
1
+ <% if notice.present? %>
2
+ <div
3
+ data-controller="element"
4
+ data-action="animationend->element#remove"
5
+ class="fixed right-8 bottom-8 z-50"
6
+ >
7
+ <div class="animate-appear-then-fade flex items-center gap-3 rounded-lg bg-stone-50 px-3 py-2 shadow-lg ring-1 ring-stone-950/10 dark:bg-stone-800 dark:ring-white/10">
8
+ <p class="text-sm font-medium text-stone-900 dark:text-stone-100"><%= notice %></p>
9
+ <button class="rounded-md p-1 text-stone-500 hover:bg-stone-100 hover:text-stone-700 dark:text-stone-400 dark:hover:bg-stone-700 dark:hover:text-stone-200" data-action="element#remove">
10
+ <%= icon "x", class: "text-current size-4" %>
11
+ </button>
12
+ </div>
13
+ </div>
14
+ <% end %>
15
+ <% if alert.present? %>
16
+ <div
17
+ data-controller="element"
18
+ data-action="animationend->element#remove"
19
+ class="fixed right-8 bottom-8 z-50"
20
+ >
21
+ <div class="animate-appear-then-fade flex items-center gap-3 rounded-lg bg-stone-50 px-3 py-2 shadow-lg ring-1 ring-stone-950/10 dark:bg-stone-800 dark:ring-white/10">
22
+ <%= icon "triangle-alert", class: "text-red-500" %>
23
+ <p class="text-sm font-medium text-stone-900 dark:text-stone-100"><%= alert %></p>
24
+ <button class="rounded-md p-1 text-stone-500 hover:bg-stone-100 hover:text-stone-700 dark:text-stone-400 dark:hover:bg-stone-700 dark:hover:text-stone-200" data-action="element#remove">
25
+ <%= icon "x", class: "text-current" %>
26
+ </button>
27
+ </div>
28
+ </div>
29
+ <% end %>
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <html
3
+ class="
4
+ text-stone-950 antialiased lg:bg-stone-100 dark:bg-stone-900 dark:text-white
5
+ dark:lg:bg-stone-950
6
+ "
7
+ >
8
+ <head>
9
+ <title><%= content_for(:title).present? ? "#{content_for(:title)} | Iron CMS" : "Iron CMS" %></title>
10
+ <%= csrf_meta_tags %>
11
+ <%= csp_meta_tag %>
12
+
13
+ <meta name="viewport" content="width=device-width,initial-scale=1">
14
+
15
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css"/>
16
+ <%= stylesheet_link_tag "iron", "data-turbo-track": "reload" %>
17
+ <%= javascript_importmap_tags "application-iron" %>
18
+ <%= turbo_refreshes_with method: :morph, scroll: :preserve %>
19
+
20
+ <%= yield :head %>
21
+ </head>
22
+ <body>
23
+ <div
24
+ class="
25
+ relative isolate flex min-h-svh w-full bg-white max-lg:flex-col lg:bg-stone-100
26
+ dark:bg-stone-900 dark:lg:bg-stone-950
27
+ "
28
+ >
29
+ <%= render "layouts/iron/sidebar" %>
30
+ <main class="flex flex-1 flex-col pb-2 lg:min-w-0 lg:pl-64 lg:pr-2 lg:pt-2">
31
+ <div
32
+ class="
33
+ grow p-6 lg:rounded-lg lg:bg-white lg:p-10 lg:shadow-xs lg:ring-1
34
+ lg:ring-stone-950/5 dark:lg:bg-stone-900 dark:lg:ring-white/10
35
+ "
36
+ >
37
+ <div class="mx-auto max-w-6xl">
38
+ <%= yield %>
39
+ </div>
40
+ </div>
41
+ </main>
42
+ </div>
43
+
44
+ <%= render "layouts/iron/toast" %>
45
+ </body>
46
+ </html>
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html
3
+ class="
4
+ text-stone-950 antialiased bg-stone-100 dark:bg-stone-950 dark:text-white
5
+ "
6
+ >
7
+ <head>
8
+ <title><%= content_for(:title).present? ? "#{content_for(:title)} | Iron CMS" : "Iron CMS" %></title>
9
+ <%= csrf_meta_tags %>
10
+ <%= csp_meta_tag %>
11
+
12
+ <meta name="viewport" content="width=device-width,initial-scale=1">
13
+
14
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css"/>
15
+ <%= stylesheet_link_tag "iron", "data-turbo-track": "reload" %>
16
+ <%= javascript_importmap_tags "application-iron" %>
17
+ <%= turbo_refreshes_with method: :morph, scroll: :preserve %>
18
+
19
+ <%= yield :head %>
20
+ </head>
21
+ <body>
22
+ <div
23
+ class="
24
+ flex min-h-svh items-center justify-center bg-stone-100 p-6 dark:bg-stone-950
25
+ "
26
+ >
27
+ <div class="w-full max-w-md">
28
+ <div
29
+ class="
30
+ rounded-lg bg-white p-8 shadow-xs ring-1 ring-stone-950/5 dark:bg-stone-900
31
+ dark:ring-white/10
32
+ "
33
+ >
34
+ <%= yield %>
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <%= render "layouts/iron/toast" %>
40
+ </body>
41
+ </html>
@@ -0,0 +1,9 @@
1
+ pin "application-iron", to: "iron/application.js", preload: true
2
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
5
+ pin "trix" # @2.1.15
6
+ pin "@rails/actiontext", to: "actiontext.esm.js"
7
+
8
+ pin_all_from Iron::Engine.root.join("app/javascript/iron/lib"), under: "lib", to: "iron/lib"
9
+ pin_all_from Iron::Engine.root.join("app/javascript/iron/controllers"), under: "controllers", to: "iron/controllers"
data/config/routes.rb ADDED
@@ -0,0 +1,40 @@
1
+ Iron::Engine.routes.draw do
2
+ root to: "home#show"
3
+
4
+ resource :first_run, only: %i[ show create ]
5
+
6
+
7
+ resources :content_types do
8
+ member do
9
+ get :entry, to: "entries#entry"
10
+ end
11
+ resources :field_definitions, shallow: true, module: :content_types
12
+ resources :entries, shallow: true, except: %i[ show ]
13
+ end
14
+ resources :blocks, only: [ :new ]
15
+ resources :references, only: [ :new ]
16
+ get "entries/search", to: "entries#search"
17
+ resources :icons, only: [ :index ]
18
+
19
+ resource :schema, only: [] do
20
+ get :export, on: :member
21
+ get :new, on: :member
22
+ post :import, on: :member
23
+ end
24
+
25
+ resources :block_definitions do
26
+ resources :field_definitions, shallow: true, module: :block_definitions
27
+ end
28
+ resources :locales, except: %i[ show ]
29
+
30
+ resource :settings, only: %i[ show update ]
31
+ resources :users, only: %i[ index show update destroy ]
32
+
33
+ # Authentication
34
+ get "join/:join_code", to: "users#new", as: :join
35
+ post "join/:join_code", to: "users#create"
36
+ get "sign_in", to: "sessions#new"
37
+ post "sign_in", to: "sessions#create"
38
+ resource :session, only: %i[ destroy ]
39
+ resources :passwords, param: :token
40
+ end
@@ -0,0 +1,105 @@
1
+ class InitialIronSchema < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table "iron_block_definitions", force: :cascade do |t|
4
+ t.string "name"
5
+ t.string "handle"
6
+ t.text "description"
7
+ t.datetime "created_at", null: false
8
+ t.datetime "updated_at", null: false
9
+ t.index [ "handle" ], name: "index_iron_block_definitions_on_handle", unique: true
10
+ end
11
+
12
+ create_table "iron_block_definitions_field_definitions", id: false, force: :cascade do |t|
13
+ t.integer "block_definition_id", null: false
14
+ t.integer "field_definition_id", null: false
15
+ t.index [ "block_definition_id", "field_definition_id" ], name: "idx_on_block_definition_id_field_definition_id_b671d92478", unique: true
16
+ t.index [ "block_definition_id" ], name: "idx_on_block_definition_id_601f892a8c"
17
+ t.index [ "field_definition_id", "block_definition_id" ], name: "idx_on_field_definition_id_block_definition_id_be1c71c596", unique: true
18
+ t.index [ "field_definition_id" ], name: "idx_on_field_definition_id_d46765b01a"
19
+ end
20
+
21
+ create_table "iron_content_types", force: :cascade do |t|
22
+ t.string "name"
23
+ t.string "handle"
24
+ t.text "description"
25
+ t.datetime "created_at", null: false
26
+ t.datetime "updated_at", null: false
27
+ t.index [ "handle" ], name: "index_iron_content_types_on_handle"
28
+ end
29
+
30
+ create_table "iron_entries", force: :cascade do |t|
31
+ t.integer "content_type_id", null: false
32
+ t.datetime "created_at", null: false
33
+ t.datetime "updated_at", null: false
34
+ t.integer "creator_id"
35
+ t.index [ "content_type_id" ], name: "index_iron_entries_on_content_type_id"
36
+ t.index [ "creator_id" ], name: "index_iron_entries_on_creator_id"
37
+ end
38
+
39
+ create_table "iron_field_definitions", force: :cascade do |t|
40
+ t.string "type"
41
+ t.string "name"
42
+ t.string "handle"
43
+ t.string "schemable_type"
44
+ t.integer "schemable_id"
45
+ t.text "rank"
46
+ t.datetime "created_at", null: false
47
+ t.datetime "updated_at", null: false
48
+ t.index [ "handle", "schemable_type", "schemable_id" ], name: "idx_on_handle_schemable_type_schemable_id_29c7422ba4", unique: true
49
+ end
50
+
51
+ create_table "iron_fields", force: :cascade do |t|
52
+ t.string "type", null: false
53
+ t.integer "entry_id"
54
+ t.string "definition_type", null: false
55
+ t.integer "definition_id", null: false
56
+ t.integer "locale_id", null: false
57
+ t.string "parent_type"
58
+ t.integer "parent_id"
59
+ t.text "rank"
60
+ t.string "value_string"
61
+ t.text "value_text"
62
+ t.float "value_float"
63
+ t.datetime "value_datetime"
64
+ t.datetime "created_at", null: false
65
+ t.datetime "updated_at", null: false
66
+ t.index [ "definition_type", "definition_id" ], name: "index_iron_fields_on_definition"
67
+ t.index [ "entry_id" ], name: "index_iron_fields_on_entry_id"
68
+ t.index [ "locale_id" ], name: "index_iron_fields_on_locale_id"
69
+ t.index [ "parent_type", "parent_id", "rank" ], name: "index_iron_fields_on_parent_type_and_parent_id_and_rank", unique: true
70
+ t.index [ "parent_type", "parent_id" ], name: "index_iron_fields_on_parent"
71
+ end
72
+
73
+ create_table "iron_locales", force: :cascade do |t|
74
+ t.string "code"
75
+ t.string "name"
76
+ t.datetime "created_at", null: false
77
+ t.datetime "updated_at", null: false
78
+ end
79
+
80
+ create_table "iron_sessions", force: :cascade do |t|
81
+ t.integer "user_id", null: false
82
+ t.string "ip_address"
83
+ t.string "user_agent"
84
+ t.datetime "created_at", null: false
85
+ t.datetime "updated_at", null: false
86
+ t.index [ "user_id" ], name: "index_iron_sessions_on_user_id"
87
+ end
88
+
89
+ create_table "iron_users", force: :cascade do |t|
90
+ t.string "email_address", null: false
91
+ t.string "password_digest", null: false
92
+ t.datetime "created_at", null: false
93
+ t.datetime "updated_at", null: false
94
+ t.index [ "email_address" ], name: "index_iron_users_on_email_address", unique: true
95
+ end
96
+
97
+ add_foreign_key "iron_block_definitions_field_definitions", "iron_block_definitions", column: "block_definition_id"
98
+ add_foreign_key "iron_block_definitions_field_definitions", "iron_field_definitions", column: "field_definition_id"
99
+ add_foreign_key "iron_entries", "iron_content_types", column: "content_type_id"
100
+ add_foreign_key "iron_entries", "iron_users", column: "creator_id"
101
+ add_foreign_key "iron_fields", "iron_entries", column: "entry_id"
102
+ add_foreign_key "iron_fields", "iron_locales", column: "locale_id"
103
+ add_foreign_key "iron_sessions", "iron_users", column: "user_id"
104
+ end
105
+ end