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,42 @@
1
+ @utility dropdown {
2
+ /* Anchor position */
3
+ /* position: absolute; */
4
+ position-area: top span-right;
5
+ position-anchor: var(--popover-anchor);
6
+ /* position-try-options: flip-block, flip-inline, flip-block flip-inline; */
7
+ @apply m-0 p-0 min-w-64;
8
+ /* Base styles */
9
+ @apply isolate w-max rounded-xl p-1;
10
+ /* Invisible border that is only visible in `forced-colors` mode for accessibility purposes */
11
+ @apply outline outline-1 outline-transparent focus:outline-hidden;
12
+ /* Handle scrolling when menu won't fit in viewport */
13
+ @apply overflow-y-auto;
14
+ /* Popover background */
15
+ @apply bg-white/75 backdrop-blur-xl dark:bg-stone-800/75;
16
+ /* Shadows */
17
+ @apply shadow-lg ring-1 ring-stone-950/10 dark:ring-inset dark:ring-white/10;
18
+ }
19
+
20
+ @utility dropdown-item {
21
+ /* Base styles */
22
+ @apply cursor-default rounded-lg px-3.5 py-2.5 focus:outline-hidden sm:px-3 sm:py-1.5;
23
+ /* Text styles */
24
+ @apply text-left text-base/6 text-stone-950 sm:text-sm/6 dark:text-white forced-colors:text-[CanvasText];
25
+ /* Focus */
26
+ @apply focus:bg-blue-500 hover:bg-blue-500 focus:text-white;
27
+ /* Disabled state */
28
+ @apply disabled:opacity-50;
29
+ /* Forced colors mode */
30
+ @apply forced-color-adjust-none forced-colors:focus:bg-[Highlight] forced-colors:focus:text-[HighlightText] forced-colors:focus:*:data-[slot=icon]:text-[HighlightText];
31
+ /* Children positioning */
32
+ @apply flex items-center;
33
+ /* Icons */
34
+ @apply *:data-[slot=icon]:col-start-1 *:data-[slot=icon]:row-start-1 *:data-[slot=icon]:-ml-0.5 *:data-[slot=icon]:mr-2.5 *:data-[slot=icon]:size-5 sm:*:data-[slot=icon]:mr-2 sm:*:data-[slot=icon]:size-4;
35
+ @apply *:data-[slot=icon]:text-stone-500 focus:*:data-[slot=icon]:text-white hover:*:data-[slot=icon]:text-white dark:*:data-[slot=icon]:text-stone-400 dark:focus:*:data-[slot=icon]:text-white;
36
+ /* Avatar */
37
+ @apply *:data-[slot=avatar]:-ml-1 *:data-[slot=avatar]:mr-2.5 *:data-[slot=avatar]:size-6 sm:*:data-[slot=avatar]:mr-2 sm:*:data-[slot=avatar]:size-5;
38
+ }
39
+
40
+ @utility dropdown-divider {
41
+ @apply 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];
42
+ }
@@ -0,0 +1,18 @@
1
+ @utility field-group {
2
+ @apply space-y-4;
3
+ }
4
+
5
+ @utility field {
6
+ @apply [&>label+[data-slot=control]]:mt-2;
7
+ @apply [&>label+[data-slot=description]]:mt-1;
8
+ @apply [&>[data-slot=description]+[data-slot=control]]:mt-2;
9
+ @apply [&>[data-slot=control]+[data-slot=description]]:mt-2;
10
+ @apply [&>[data-slot=control]+[data-slot=error]]:mt-2;
11
+ @apply [&>label]:font-medium;
12
+ & label {
13
+ @apply select-none text-base/6 text-stone-950 disabled:opacity-50 sm:text-sm/6 dark:text-white;
14
+ }
15
+ & [data-slot='description'] {
16
+ @apply text-base/6 text-stone-500 data-disabled:opacity-50 sm:text-sm/6 dark:text-stone-400;
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ @layer base {
2
+ form {
3
+ textarea {
4
+ @apply textarea;
5
+ }
6
+
7
+ input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]) {
8
+ @apply input;
9
+ }
10
+ input[type="submit"], input[type="button"], button {
11
+ @apply btn;
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ [data-controller="icon-picker"] {
2
+ [data-icon-picker-target="icon"][data-ui~="selected"] {
3
+ @apply bg-stone-600 ring-2 ring-stone-500;
4
+ }
5
+
6
+ [data-icon-picker-target="trigger"][data-ui~="active"] {
7
+ @apply ring-2 ring-stone-500;
8
+ }
9
+ }
@@ -0,0 +1,44 @@
1
+ @utility input {
2
+ @apply relative block w-full appearance-none rounded-lg;
3
+ @apply px-3 py-2 sm:px-2.5 sm:py-1.5;
4
+ @apply text-base/6 sm:text-sm/6;
5
+ @apply text-stone-100 placeholder:text-stone-500;
6
+
7
+ /* Border and background */
8
+ @apply border border-stone-950/10;
9
+ @apply dark:border-white/10;
10
+ @apply bg-transparent dark:bg-white/5;
11
+
12
+ /* Focus state */
13
+ @apply focus:outline focus:outline-2 focus:outline-stone-500;
14
+
15
+ /* States */
16
+ @apply disabled:opacity-50 disabled:bg-stone-700 disabled:border-stone-600;
17
+
18
+ /* Error state */
19
+ &.is-invalid {
20
+ @apply border-red-500 hover:border-red-500;
21
+ }
22
+
23
+ /* Date and time input specific styling */
24
+ &[type="date"],
25
+ &[type="datetime-local"],
26
+ &[type="month"],
27
+ &[type="time"],
28
+ &[type="week"] {
29
+ @apply [color-scheme:dark];
30
+ @apply [&::-webkit-calendar-picker-indicator]:invert-[.8];
31
+ @apply [&::-webkit-datetime-edit-fields-wrapper]:p-0;
32
+ @apply [&::-webkit-date-and-time-value]:min-h-[1.5em];
33
+ @apply [&::-webkit-datetime-edit]:inline-flex;
34
+ @apply [&::-webkit-datetime-edit]:p-0;
35
+ @apply [&::-webkit-datetime-edit-year-field]:p-0;
36
+ @apply [&::-webkit-datetime-edit-month-field]:p-0;
37
+ @apply [&::-webkit-datetime-edit-day-field]:p-0;
38
+ @apply [&::-webkit-datetime-edit-hour-field]:p-0;
39
+ @apply [&::-webkit-datetime-edit-minute-field]:p-0;
40
+ @apply [&::-webkit-datetime-edit-second-field]:p-0;
41
+ @apply [&::-webkit-datetime-edit-millisecond-field]:p-0;
42
+ @apply [&::-webkit-datetime-edit-meridiem-field]:p-0;
43
+ }
44
+ }
@@ -0,0 +1,7 @@
1
+ @utility page-title {
2
+ @apply text-2xl/8 font-semibold text-stone-950 sm:text-xl/8 dark:text-white;
3
+ }
4
+
5
+ @utility breadcrumb {
6
+ @apply text-stone-400 no-underline font-semibold hover:underline block mb-2;
7
+ }
@@ -0,0 +1,36 @@
1
+ @utility select {
2
+ @apply block w-full rounded-lg appearance-none;
3
+ @apply py-[calc(--spacing(2.5)-1px)] sm:py-[calc(--spacing(1.5)-1px)];
4
+ @apply pl-[calc(--spacing(3.5)-1px)] pr-[calc(--spacing(4.5)-1px)]
5
+ sm:pl-[calc(--spacing(3)-1px)] sm:pr-[calc(--spacing(4)-1px)];
6
+ @apply text-base/6 sm:text-sm/6 text-stone-950 placeholder:text-stone-500
7
+ dark:text-white;
8
+ @apply border border-stone-950/10 hover:border-stone-950/20
9
+ dark:border-white/10 dark:hover:border-white/20;
10
+ @apply bg-white dark:bg-stone-800;
11
+ @apply focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500;
12
+ @apply disabled:opacity-50 disabled:border-stone-950/20
13
+ dark:disabled:border-white/15 dark:disabled:bg-white/[2.5%];
14
+
15
+ /* Multi-select needs equal padding both sides */
16
+ &[multiple] { @apply px-[calc(--spacing(3.5)-1px)]
17
+ sm:px-[calc(--spacing(3)-1px)]; }
18
+
19
+ /* Validation error, toggled with attribute */
20
+ &[data-ui~="invalid"] {
21
+ @apply border-red-500 hover:border-red-500
22
+ dark:border-red-500 dark:hover:border-red-500;
23
+ }
24
+
25
+ @supports (appearance: base-select) {
26
+ appearance: base-select;
27
+
28
+ &::picker-icon {
29
+ @apply text-stone-500 dark:text-stone-400 transition duration-200;
30
+ }
31
+ &:open::picker-icon {
32
+ @apply rotate-180;
33
+ }
34
+ }
35
+ }
36
+
@@ -0,0 +1,21 @@
1
+ @utility sidebar-item {
2
+ /* Base */
3
+ @apply flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-base/6 font-medium text-stone-950 sm:py-2 sm:text-sm/5;
4
+ /* Leading icon/icon-only */
5
+ @apply *:data-[slot=icon]:size-6 *:data-[slot=icon]:shrink-0 sm:*:data-[slot=icon]:size-5;
6
+ /* Trailing icon (down chevron or similar) */
7
+ @apply *:last:data-[slot=icon]:ml-auto *:last:data-[slot=icon]:size-5 sm:*:last:data-[slot=icon]:size-4;
8
+ /* Avatar */
9
+ @apply *:data-[slot=avatar]:-m-0.5 *:data-[slot=avatar]:size-7 *:data-[slot=avatar]:[--ring-opacity:10%] sm:*:data-[slot=avatar]:size-6;
10
+ /* Hover */
11
+ @apply hover:bg-stone-950/5;
12
+ /* Active */
13
+ @apply active:bg-stone-950/5 active:*:data-[slot=icon]:fill-stone-950;
14
+ /* Current */
15
+ @apply data-current:*:data-[slot=icon]:bg-transparent;
16
+ /* Dark mode */
17
+ @apply dark:text-white dark:*:data-[slot=icon]:text-stone-400;
18
+ @apply dark:hover:bg-white/5 dark:hover:*:data-[slot=icon]:text-white;
19
+ @apply dark:active:bg-white/5 dark:active:*:data-[slot=icon]:text-white;
20
+ @apply dark:data-current:*:data-[slot=icon]:text-white;
21
+ }
@@ -0,0 +1,27 @@
1
+ @utility textarea {
2
+ /* Basic layout */
3
+ @apply relative block w-full appearance-none rounded-lg;
4
+ @apply px-[calc(--spacing(3.5)-1px)] py-[calc(--spacing(2.5)-1px)];
5
+ @apply sm:px-[calc(--spacing(3)-1px)] sm:py-[calc(--spacing(1.5)-1px)];
6
+
7
+ /* Typography */
8
+ @apply text-base/6 text-stone-950 placeholder:text-stone-500 sm:text-sm/6;
9
+ @apply dark:text-white;
10
+
11
+ /* Border and background */
12
+ @apply border border-stone-950/10;
13
+ @apply dark:border-white/10;
14
+ @apply bg-transparent dark:bg-white/5;
15
+
16
+ /* Focus state */
17
+ @apply focus:outline focus:outline-2 focus:outline-stone-500;
18
+
19
+ /* Invalid state - using data attribute selector */
20
+ &[data-ui~="invalid"] {
21
+ @apply border-red-500 hover:border-red-500 dark:border-red-500 dark:hover:border-red-500;
22
+ }
23
+
24
+ /* Disabled state */
25
+ @apply disabled:border-stone-950/20 disabled:opacity-50;
26
+ @apply dark:disabled:border-white/15 dark:disabled:hover:border-white/15 dark:disabled:bg-white/[2.5%];
27
+ }
@@ -0,0 +1,66 @@
1
+ module Iron
2
+ module Authentication
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :require_authentication
7
+ helper_method :authenticated?
8
+ end
9
+
10
+ class_methods do
11
+ def require_unauthenticated_access(**options)
12
+ allow_unauthenticated_access **options
13
+ before_action :redirect_signed_in_user_to_root, **options
14
+ end
15
+
16
+ def allow_unauthenticated_access(**options)
17
+ skip_before_action :require_authentication, **options
18
+ end
19
+ end
20
+
21
+ private
22
+ def authenticated?
23
+ resume_session
24
+ end
25
+
26
+ def redirect_signed_in_user_to_root
27
+ redirect_to root_url if authenticated?
28
+ end
29
+
30
+ def require_authentication
31
+ resume_session || request_authentication
32
+ end
33
+
34
+
35
+ def resume_session
36
+ Current.session ||= find_session_by_cookie
37
+ end
38
+
39
+ def find_session_by_cookie
40
+ Session.find_by(id: cookies.signed[:session_id])
41
+ end
42
+
43
+
44
+ def request_authentication
45
+ session[:return_to_after_authenticating] = request.url
46
+ redirect_to sign_in_url
47
+ end
48
+
49
+ def after_authentication_url
50
+ session.delete(:return_to_after_authenticating) || root_url
51
+ end
52
+
53
+
54
+ def start_new_session_for(user)
55
+ user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
56
+ Current.session = session
57
+ cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
58
+ end
59
+ end
60
+
61
+ def terminate_session
62
+ Current.session.destroy
63
+ cookies.delete(:session_id)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,11 @@
1
+ module Iron
2
+ module Authorization
3
+ extend ActiveSupport::Concern
4
+
5
+ private
6
+
7
+ def ensure_can_administer
8
+ head :forbidden unless Current.user.can_administer?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ module Iron
2
+ module LocaleAware
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :set_current_locale
7
+ helper_method :current_locale if respond_to?(:helper_method)
8
+ end
9
+
10
+ def current_locale
11
+ Iron::Current.locale
12
+ end
13
+
14
+ private
15
+
16
+ def set_current_locale
17
+ Iron::Current.locale = Iron::Locale.detect(request) || Iron::Locale.default
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Iron
2
+ module WebPage
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :set_content_type, only: %i[ show ]
7
+ before_action :set_entry, only: %i[ show ]
8
+ end
9
+
10
+ def set_content_type
11
+ @content_type = Iron::ContentType.web_published.find_by_handle!(params[:content_type])
12
+ end
13
+
14
+ def set_entry
15
+ @entry = @content_type.entries.find_by!(route: params[:route])
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class ApplicationController < ActionController::Base
3
+ include ActiveStorage::SetCurrent
4
+ include Authentication, Authorization
5
+ default_form_builder FormBuilder
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Iron
2
+ class BlockDefinitions::FieldDefinitionsController < FieldDefinitionsController
3
+ private
4
+ def set_schemable
5
+ @schemable = BlockDefinition.find(params.expect(:block_definition_id))
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,52 @@
1
+ module Iron
2
+ class BlockDefinitionsController < ApplicationController
3
+ before_action :set_block_definition, only: %i[ show edit update destroy ]
4
+ before_action :ensure_can_administer
5
+
6
+ def index
7
+ @block_definitions = BlockDefinition.all
8
+ end
9
+
10
+ def show
11
+ end
12
+
13
+ def new
14
+ @block_definition = BlockDefinition.new
15
+ end
16
+
17
+ def edit
18
+ end
19
+
20
+ def create
21
+ @block_definition = BlockDefinition.new(block_definition_params)
22
+
23
+ if @block_definition.save
24
+ redirect_to @block_definition, notice: "Block definition was successfully created."
25
+ else
26
+ render :new, status: :unprocessable_entity
27
+ end
28
+ end
29
+
30
+ def update
31
+ if @block_definition.update(block_definition_params)
32
+ redirect_to @block_definition, notice: "Block definition was successfully updated.", status: :see_other
33
+ else
34
+ render :edit, status: :unprocessable_entity
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @block_definition.destroy!
40
+ redirect_to block_definitions_path, notice: "Block definition was successfully destroyed.", status: :see_other
41
+ end
42
+
43
+ private
44
+ def set_block_definition
45
+ @block_definition = BlockDefinition.find(params.expect(:id))
46
+ end
47
+
48
+ def block_definition_params
49
+ params.expect(block_definition: [ :name, :handle, :description ])
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ module Iron
2
+ class BlocksController < ApplicationController
3
+ def new
4
+ @block_list = Fields::BlockList.build(
5
+ locale: Locale.find(params[:locale_id]),
6
+ definition: FieldDefinition.find(params[:block_list_field_definition_id]),
7
+ )
8
+ @block = @block_list.blocks.build_for(BlockDefinition.find(params[:block_definition_id]))
9
+ @form_field_name = params[:form_field_name]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module Iron
2
+ class ContentTypes::FieldDefinitionsController < FieldDefinitionsController
3
+ private
4
+ def set_schemable
5
+ @schemable = ContentType.find(params.expect(:content_type_id))
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,52 @@
1
+ module Iron
2
+ class ContentTypesController < ApplicationController
3
+ before_action :set_content_type, only: %i[ show edit update destroy ]
4
+ before_action :ensure_can_administer
5
+
6
+ def index
7
+ @content_types = ContentType.all
8
+ end
9
+
10
+ def show
11
+ end
12
+
13
+ def new
14
+ @content_type = ContentType.new
15
+ end
16
+
17
+ def edit
18
+ end
19
+
20
+ def create
21
+ @content_type = ContentType.new(content_type_params)
22
+
23
+ if @content_type.save
24
+ redirect_to @content_type, notice: "Content type was successfully created."
25
+ else
26
+ render :new, status: :unprocessable_entity
27
+ end
28
+ end
29
+
30
+ def update
31
+ if @content_type.update(content_type_params)
32
+ redirect_to @content_type, notice: "Content type was successfully updated.", status: :see_other
33
+ else
34
+ render :edit, status: :unprocessable_entity
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @content_type.destroy!
40
+ redirect_to content_types_path, notice: "Content type was successfully destroyed.", status: :see_other
41
+ end
42
+
43
+ private
44
+ def set_content_type
45
+ @content_type = ContentType.find(params.expect(:id))
46
+ end
47
+
48
+ def content_type_params
49
+ params.expect(content_type: [ :name, :handle, :description, :type, :title_field_definition_id, :icon, :web_publishing_enabled, :web_page_title_field_definition_id, :base_path ])
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,103 @@
1
+ module Iron
2
+ class EntriesController < ApplicationController
3
+ before_action :set_locale
4
+ before_action :set_entry, only: %i[ edit update destroy ]
5
+ before_action :set_content_type, only: %i[ index new create ]
6
+ before_action :set_content_type_from_params, only: %i[ entry ]
7
+
8
+ def index
9
+ if @content_type.single?
10
+ redirect_to entry_content_type_path(@content_type)
11
+ else
12
+ @entries = @content_type.entries
13
+ end
14
+ end
15
+
16
+ def entry
17
+ @entry = @content_type.entry
18
+ end
19
+
20
+ def search
21
+ @definition = FieldDefinitions::ReferenceList.find(params[:reference_list_field_definition_id])
22
+ @entries = Entry.with_content_type(@definition.supported_content_types.pluck(:id))
23
+ @entries = @entries.search_by_title(params[:q]) if params[:q].present?
24
+
25
+ @reference_list_field_definition_id = params[:reference_list_field_definition_id]
26
+ @form_field_name = params[:form_field_name]
27
+ @reference_list_dom_id = params[:reference_list_dom_id]
28
+ render layout: false
29
+ end
30
+
31
+ def new
32
+ @entry = @content_type.entries.build
33
+ end
34
+
35
+ def edit
36
+ end
37
+
38
+ def create
39
+ @entry = @content_type.entries.build(entry_params)
40
+
41
+ if @entry.save
42
+ if @entry.content_type.single?
43
+ redirect_to entry_content_type_url(@entry.content_type), notice: "#{@entry.content_type.name} was successfully created."
44
+ else
45
+ redirect_to edit_entry_url(@entry), notice: "#{@entry.content_type.name} was successfully created."
46
+ end
47
+ else
48
+ if @entry.errors.any?
49
+ flash.now[:alert] = "Could not save changes due to validation errors"
50
+ else
51
+ flash.now[:alert] = "#{@entry.content_type.name} could not be created"
52
+ end
53
+ render :new, status: :unprocessable_entity
54
+ end
55
+ end
56
+
57
+ def update
58
+ if @entry.update(entry_params)
59
+ if @entry.content_type.single?
60
+ redirect_to entry_content_type_url(@entry.content_type, locale: @locale.code), notice: "#{@entry.content_type.name} was successfully updated."
61
+ else
62
+ redirect_to edit_entry_url(@entry, locale: @locale.code), notice: "#{@entry.content_type.name} was successfully updated."
63
+ end
64
+ else
65
+ if @entry.errors.any?
66
+ flash.now[:alert] = "Could not save changes due to validation errors"
67
+ else
68
+ flash.now[:alert] = "#{@entry.content_type.name} could not be updated"
69
+ end
70
+ render :edit, status: :unprocessable_entity
71
+ end
72
+ end
73
+
74
+ def destroy
75
+ @entry.destroy!
76
+ redirect_to content_type_entries_path(@entry.content_type), notice: "Entry was successfully destroyed.", status: :see_other
77
+ end
78
+
79
+ private
80
+
81
+ def set_locale
82
+ @locale = Locale.find_by(code: params[:locale]) if params[:locale].present?
83
+ @locale ||= Locale.first
84
+ end
85
+
86
+ def set_entry
87
+ @entry = Entry.find(params.expect(:id))
88
+ end
89
+
90
+ def set_content_type
91
+ @content_type = ContentType.find(params[:content_type_id])
92
+ end
93
+
94
+ def set_content_type_from_params
95
+ @content_type = ContentType.find(params[:id])
96
+ end
97
+
98
+ def entry_params
99
+ params.require(:entry).permit!
100
+ # params.expect(entry: [ :content_type_id ])
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,55 @@
1
+ module Iron
2
+ class FieldDefinitionsController < ApplicationController
3
+ before_action :set_schemable, only: %i[ new create ]
4
+ before_action :set_field_definition, only: %i[ edit update destroy ]
5
+ before_action :ensure_can_administer
6
+
7
+ def index
8
+ @field_definitions = FieldDefinition.all
9
+ end
10
+
11
+ def new
12
+ @field_definition = @schemable.field_definitions.build(type: selected_field_definition_type)
13
+ end
14
+
15
+ def edit
16
+ end
17
+
18
+ def create
19
+ @field_definition = @schemable.field_definitions.build(field_definition_params)
20
+
21
+ if @field_definition.save
22
+ redirect_to @schemable, notice: "Field definition was successfully created."
23
+ else
24
+ render :new, status: :unprocessable_entity
25
+ end
26
+ end
27
+
28
+ def update
29
+ if @field_definition.update(field_definition_params)
30
+ redirect_to @field_definition.schemable, notice: "Field definition was successfully updated.", status: :see_other
31
+ else
32
+ render :edit, status: :unprocessable_entity
33
+ end
34
+ end
35
+
36
+ def destroy
37
+ @field_definition.destroy!
38
+ redirect_to @field_definition.schemable, notice: "Field definition was successfully destroyed.", status: :see_other
39
+ end
40
+
41
+ private
42
+ def set_field_definition
43
+ @field_definition = FieldDefinition.find(params.expect(:id))
44
+ end
45
+
46
+ def field_definition_params
47
+ params.expect(field_definition: [ :type, :name, :handle, :rank, :allowed_values_text, :required, :file_scope, selected_presets: [], supported_block_definition_ids: [], supported_content_type_ids: [] ])
48
+ end
49
+
50
+ def selected_field_definition_type
51
+ type = FieldDefinition::TYPES.include?(params[:type]) ? params[:type] : FieldDefinition::TYPES.first
52
+ FieldDefinition.classify_type(type)
53
+ end
54
+ end
55
+ end