iron-cms 0.17.3 → 0.18.1
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 +4 -4
- data/README.md +88 -4
- data/app/assets/builds/iron.css +236 -106
- data/app/assets/tailwind/iron/lexxy.css +111 -87
- data/app/controllers/concerns/iron/schema_editing.rb +19 -0
- data/app/controllers/concerns/iron/web_page.rb +3 -1
- data/app/controllers/iron/api/schema/base_controller.rb +25 -0
- data/app/controllers/iron/api/schema/block_definitions_controller.rb +49 -0
- data/app/controllers/iron/api/schema/content_types_controller.rb +64 -0
- data/app/controllers/iron/api/schema/field_definitions_controller.rb +88 -0
- data/app/controllers/iron/api/schema/locales_controller.rb +52 -0
- data/app/controllers/iron/application_controller.rb +1 -1
- data/app/controllers/iron/block_definitions_controller.rb +1 -0
- data/app/controllers/iron/content_types_controller.rb +1 -0
- data/app/controllers/iron/field_definitions_controller.rb +2 -1
- data/app/controllers/iron/first_runs_controller.rb +1 -1
- data/app/controllers/iron/locales_controller.rb +1 -0
- data/app/controllers/iron/sessions_controller.rb +1 -1
- data/app/helpers/iron/avatar_helper.rb +24 -2
- data/app/models/iron/account.rb +1 -1
- data/app/models/iron/api/openapi_spec.rb +37 -15
- data/app/models/iron/block_definition/exportable.rb +1 -1
- data/app/models/iron/block_definition.rb +3 -1
- data/app/models/iron/content.rb +176 -0
- data/app/models/iron/content_type/exportable.rb +3 -1
- data/app/models/iron/content_type/importable.rb +1 -1
- data/app/models/iron/content_type.rb +6 -1
- data/app/models/iron/entry/content_assignable.rb +10 -2
- data/app/models/iron/entry/presentable.rb +1 -1
- data/app/models/iron/entry/web_publishable.rb +17 -3
- data/app/models/iron/exporter.rb +1 -26
- data/app/models/iron/field/length_constrained.rb +17 -0
- data/app/models/iron/field/validatable.rb +16 -0
- data/app/models/iron/field.rb +1 -1
- data/app/models/iron/field_definition/exportable.rb +3 -4
- data/app/models/iron/field_definition/importable.rb +16 -9
- data/app/models/iron/field_definition/ranked.rb +1 -1
- data/app/models/iron/field_definition/searchable.rb +2 -0
- data/app/models/iron/field_definition/validatable.rb +40 -0
- data/app/models/iron/field_definition/validations.rb +175 -0
- data/app/models/iron/field_definition.rb +1 -1
- data/app/models/iron/field_definitions/date.rb +2 -0
- data/app/models/iron/field_definitions/file.rb +3 -11
- data/app/models/iron/field_definitions/number.rb +2 -0
- data/app/models/iron/field_definitions/reference.rb +2 -0
- data/app/models/iron/field_definitions/rich_text_area.rb +1 -0
- data/app/models/iron/field_definitions/text_area.rb +2 -0
- data/app/models/iron/field_definitions/text_field.rb +1 -9
- data/app/models/iron/fields/block.rb +5 -1
- data/app/models/iron/fields/block_list.rb +5 -1
- data/app/models/iron/fields/date.rb +19 -2
- data/app/models/iron/fields/file.rb +5 -3
- data/app/models/iron/fields/number.rb +28 -1
- data/app/models/iron/fields/reference.rb +2 -0
- data/app/models/iron/fields/rich_text_area.rb +2 -0
- data/app/models/iron/fields/text_area.rb +4 -0
- data/app/models/iron/fields/text_field.rb +9 -5
- data/app/models/iron/importer.rb +1 -54
- data/app/models/iron/locale.rb +2 -0
- data/app/models/iron/schema/auto_dumpable.rb +26 -0
- data/app/models/iron/schema/diff.rb +194 -0
- data/app/models/iron/schema/validation.rb +214 -0
- data/app/models/iron/schema.rb +282 -0
- data/app/models/iron/seed.rb +5 -3
- data/app/models/iron/system.rb +7 -0
- data/app/models/iron/user/deactivatable.rb +5 -0
- data/app/models/iron/user.rb +18 -1
- data/app/views/iron/api/fields/_rich_text_area.json.jbuilder +2 -2
- data/app/views/iron/api/schema/block_definitions/_block_definition.json.jbuilder +4 -0
- data/app/views/iron/api/schema/block_definitions/index.json.jbuilder +1 -0
- data/app/views/iron/api/schema/block_definitions/show.json.jbuilder +1 -0
- data/app/views/iron/api/schema/content_types/_content_type.json.jbuilder +5 -0
- data/app/views/iron/api/schema/content_types/index.json.jbuilder +1 -0
- data/app/views/iron/api/schema/content_types/show.json.jbuilder +1 -0
- data/app/views/iron/api/schema/field_definitions/_field_definition.json.jbuilder +7 -0
- data/app/views/iron/api/schema/field_definitions/show.json.jbuilder +1 -0
- data/app/views/iron/api/schema/locales/_locale.json.jbuilder +4 -0
- data/app/views/iron/api/schema/locales/index.json.jbuilder +1 -0
- data/app/views/iron/api/schema/locales/show.json.jbuilder +1 -0
- data/app/views/iron/block_definitions/_empty_state.html.erb +5 -3
- data/app/views/iron/block_definitions/_form.html.erb +3 -1
- data/app/views/iron/block_definitions/edit.html.erb +19 -17
- data/app/views/iron/block_definitions/index.html.erb +7 -3
- data/app/views/iron/block_definitions/show.html.erb +14 -8
- data/app/views/iron/content_types/_content_type.html.erb +1 -1
- data/app/views/iron/content_types/_empty_state.html.erb +5 -3
- data/app/views/iron/content_types/_form.html.erb +12 -8
- data/app/views/iron/content_types/edit.html.erb +19 -17
- data/app/views/iron/content_types/index.html.erb +7 -3
- data/app/views/iron/content_types/show.html.erb +14 -8
- data/app/views/iron/entries/_empty_state.html.erb +1 -1
- data/app/views/iron/entries/edit.html.erb +1 -1
- data/app/views/iron/entries/entry.html.erb +1 -1
- data/app/views/iron/entries/fields/_block.html.erb +4 -0
- data/app/views/iron/entries/fields/_block_list.html.erb +2 -0
- data/app/views/iron/entries/fields/_boolean.html.erb +1 -0
- data/app/views/iron/entries/fields/_date.html.erb +3 -2
- data/app/views/iron/entries/fields/_field_errors.html.erb +7 -0
- data/app/views/iron/entries/fields/_field_label.html.erb +8 -0
- data/app/views/iron/entries/fields/_file.html.erb +4 -19
- data/app/views/iron/entries/fields/_number.html.erb +6 -2
- data/app/views/iron/entries/fields/_reference.html.erb +2 -1
- data/app/views/iron/entries/fields/_reference_list.html.erb +2 -0
- data/app/views/iron/entries/fields/_rich_text_area.html.erb +2 -1
- data/app/views/iron/entries/fields/_text_area.html.erb +6 -2
- data/app/views/iron/entries/fields/_text_field.html.erb +5 -12
- data/app/views/iron/field_definitions/_field_definition.html.erb +51 -29
- data/app/views/iron/field_definitions/date/_form.html.erb +5 -1
- data/app/views/iron/field_definitions/edit.html.erb +10 -8
- data/app/views/iron/field_definitions/file/_form.html.erb +6 -0
- data/app/views/iron/field_definitions/new.html.erb +5 -3
- data/app/views/iron/field_definitions/number/_form.html.erb +23 -1
- data/app/views/iron/field_definitions/reference/_form.html.erb +5 -0
- data/app/views/iron/field_definitions/rich_text_area/_form.html.erb +5 -0
- data/app/views/iron/field_definitions/text_area/_form.html.erb +23 -0
- data/app/views/iron/field_definitions/text_field/_form.html.erb +20 -1
- data/app/views/iron/home/_content_types.html.erb +1 -1
- data/app/views/iron/locales/_form.html.erb +5 -3
- data/app/views/iron/locales/edit.html.erb +1 -1
- data/app/views/iron/locales/index.html.erb +12 -6
- data/app/views/iron/shared/_schema_lock_badge.html.erb +19 -0
- data/config/locales/en.yml +13 -1
- data/config/locales/it.yml +18 -1
- data/config/routes.rb +11 -0
- data/db/migrate/20260612131538_create_iron_systems.rb +9 -0
- data/exe/iron +5 -0
- data/lib/generators/iron/agents/agents_generator.rb +52 -0
- data/lib/generators/iron/agents/templates/AGENTS.md +24 -0
- data/lib/generators/iron/agents/templates/SKILL.md +423 -0
- data/lib/generators/iron/install/install_generator.rb +118 -0
- data/lib/generators/iron/install/templates/iron_release.rake +5 -0
- data/lib/generators/iron/install/templates/schema.json +12 -0
- data/lib/generators/iron/install/templates/seeds.rb +13 -0
- data/lib/generators/iron/pages/pages_generator.rb +5 -0
- data/lib/generators/iron/pages/templates/pages_controller.rb +2 -2
- data/lib/generators/iron/pages/templates/show.html.erb +1 -1
- data/lib/install/template.rb +9 -0
- data/lib/iron/cli.rb +43 -0
- data/lib/iron/published_page_constraint.rb +33 -0
- data/lib/iron/routing.rb +2 -26
- data/lib/iron/version.rb +1 -1
- data/lib/tasks/iron_content.rake +82 -0
- data/lib/tasks/iron_schema.rake +45 -0
- metadata +62 -3
|
@@ -37,25 +37,26 @@
|
|
|
37
37
|
@apply focus-visible:outline-2 focus-visible:outline-sky-600 dark:focus-visible:outline-sky-500;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/* Toolbar
|
|
40
|
+
/* Toolbar — a single, non-wrapping row. Lexxy's controller measures how far
|
|
41
|
+
the buttons overflow the row (scrollWidth vs clientWidth) and tucks the
|
|
42
|
+
ones that don't fit into a trailing "more" menu, so the row must never
|
|
43
|
+
wrap. `relative` lets the dropdown panels anchor to the toolbar. */
|
|
41
44
|
lexxy-toolbar {
|
|
42
|
-
@apply flex flex-
|
|
45
|
+
@apply relative flex flex-nowrap items-center gap-1 p-2;
|
|
46
|
+
@apply max-w-full;
|
|
43
47
|
@apply border-b border-stone-950/10 dark:border-white/10;
|
|
44
48
|
@apply bg-stone-50 dark:bg-white/5;
|
|
45
49
|
@apply rounded-t-xl;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
/*
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
/* Items keep their natural size; shrinking them would let everything squeeze
|
|
53
|
+
in and rob the overflow measurement of the signal it needs. */
|
|
54
|
+
lexxy-toolbar > * {
|
|
55
|
+
@apply flex-none;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@apply mx-2 h-5 w-px bg-stone-300 dark:bg-white/20;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* Toolbar buttons */
|
|
58
|
+
/* Toolbar buttons — square, icon-only. Buttons that live inside dropdown
|
|
59
|
+
panels are reshaped by the panel rules below. */
|
|
59
60
|
lexxy-toolbar button,
|
|
60
61
|
lexxy-toolbar summary {
|
|
61
62
|
@apply inline-flex items-center justify-center;
|
|
@@ -64,12 +65,12 @@
|
|
|
64
65
|
@apply hover:bg-stone-200 dark:hover:bg-white/10;
|
|
65
66
|
@apply focus-visible:outline-2 focus-visible:outline-stone-600;
|
|
66
67
|
@apply disabled:opacity-40 disabled:pointer-events-none;
|
|
68
|
+
@apply aria-disabled:opacity-40 aria-disabled:pointer-events-none;
|
|
67
69
|
@apply transition-colors;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
lexxy-toolbar button[aria-pressed="true"],
|
|
71
|
-
lexxy-toolbar button.active
|
|
72
|
-
lexxy-toolbar details[open] > summary {
|
|
73
|
+
lexxy-toolbar button.active {
|
|
73
74
|
@apply bg-stone-200 text-stone-900;
|
|
74
75
|
@apply dark:bg-white/15 dark:text-white;
|
|
75
76
|
}
|
|
@@ -80,6 +81,41 @@
|
|
|
80
81
|
fill: currentColor;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/* Dropdown triggers wear a chevron, so let them outgrow the square button. */
|
|
85
|
+
lexxy-toolbar .lexxy-editor__toolbar-button--chevron {
|
|
86
|
+
@apply w-auto gap-1 px-1.5;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
lexxy-toolbar .lexxy-editor__toolbar-button--chevron::after {
|
|
90
|
+
content: "";
|
|
91
|
+
@apply size-1.5 rotate-45;
|
|
92
|
+
@apply border-b-2 border-e-2 border-current;
|
|
93
|
+
@apply opacity-50;
|
|
94
|
+
margin-block-start: -0.15rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Group separators — lexxy flags the last button of each cluster with
|
|
98
|
+
`…group-end`; we hang a hairline rule in the trailing gap. Scope to the
|
|
99
|
+
toolbar's own rows so the same class, reused inside the format menu and the
|
|
100
|
+
overflow menu, doesn't sprout stray dividers. */
|
|
101
|
+
lexxy-toolbar > button.lexxy-editor__toolbar-group-end,
|
|
102
|
+
lexxy-toolbar > lexxy-link-dropdown > button.lexxy-editor__toolbar-group-end {
|
|
103
|
+
@apply relative me-2.5;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
lexxy-toolbar > button.lexxy-editor__toolbar-group-end::after,
|
|
107
|
+
lexxy-toolbar > lexxy-link-dropdown > button.lexxy-editor__toolbar-group-end::after {
|
|
108
|
+
content: "";
|
|
109
|
+
@apply absolute inset-y-1 w-px;
|
|
110
|
+
inset-inline-end: -0.3125rem;
|
|
111
|
+
@apply bg-stone-300 dark:bg-white/20 pointer-events-none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* History controls and the overflow menu sit at the trailing edge. */
|
|
115
|
+
lexxy-toolbar .lexxy-editor__toolbar-button--push-right {
|
|
116
|
+
margin-inline-start: auto;
|
|
117
|
+
}
|
|
118
|
+
|
|
83
119
|
/* Editor content area — only the main editable surface, not decorator children */
|
|
84
120
|
lexxy-editor [contenteditable="true"] {
|
|
85
121
|
@apply block w-full min-h-48 p-4;
|
|
@@ -94,72 +130,38 @@
|
|
|
94
130
|
@apply pointer-events-none;
|
|
95
131
|
}
|
|
96
132
|
|
|
97
|
-
/*
|
|
133
|
+
/* ---- Dropdowns -------------------------------------------------------- */
|
|
134
|
+
|
|
135
|
+
/* Each dropdown anchors its panel beneath its own trigger. Compact mode (the
|
|
136
|
+
[overflowing] rules at the end) relaxes this so panels span the toolbar. */
|
|
98
137
|
.lexxy-editor__toolbar-dropdown {
|
|
99
138
|
@apply relative select-none;
|
|
100
139
|
}
|
|
101
140
|
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.lexxy-editor__toolbar-dropdown > summary::-webkit-details-marker {
|
|
108
|
-
display: none;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.lexxy-editor__toolbar-dropdown > summary::marker {
|
|
112
|
-
display: none;
|
|
113
|
-
content: "";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.lexxy-editor__toolbar-dropdown-content {
|
|
117
|
-
--dropdown-padding: --spacing(3);
|
|
118
|
-
--dropdown-gap: --spacing(1.5);
|
|
119
|
-
|
|
120
|
-
@apply absolute z-10;
|
|
121
|
-
@apply flex gap-(--dropdown-gap) p-(--dropdown-padding);
|
|
141
|
+
/* Shared panel chrome. Panels reveal/hide through the `hidden` attribute, so
|
|
142
|
+
each panel below sets its own display + padding once it is shown. */
|
|
143
|
+
lexxy-toolbar [data-dropdown-panel] {
|
|
144
|
+
@apply absolute z-20 top-full start-0 mt-1;
|
|
122
145
|
@apply bg-white dark:bg-stone-800;
|
|
123
146
|
@apply border-2 border-sky-200 dark:border-sky-500/30;
|
|
124
|
-
@apply rounded-md;
|
|
147
|
+
@apply rounded-md rounded-ss-none shadow-lg;
|
|
125
148
|
@apply text-stone-900 dark:text-white;
|
|
126
|
-
@apply top-8 start-0;
|
|
127
|
-
@apply max-w-[40ch];
|
|
128
|
-
@apply rounded-ss-none;
|
|
129
149
|
}
|
|
130
150
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
content buttons like highlight swatches that share the same class. */
|
|
134
|
-
.lexxy-editor__toolbar-dropdown:is([open]) > summary.lexxy-editor__toolbar-button {
|
|
135
|
-
@apply bg-sky-100 dark:bg-sky-500/20;
|
|
136
|
-
@apply rounded-ee-none rounded-es-none;
|
|
151
|
+
lexxy-toolbar [data-dropdown-panel][hidden] {
|
|
152
|
+
@apply hidden;
|
|
137
153
|
}
|
|
138
154
|
|
|
139
|
-
|
|
155
|
+
/* Open trigger — tint the button and square off the edge meeting the panel. */
|
|
156
|
+
.lexxy-editor__toolbar-dropdown > [data-dropdown-trigger][aria-expanded="true"],
|
|
157
|
+
.lexxy-editor__toolbar-dropdown > [data-dropdown-trigger][aria-expanded="true"]:hover {
|
|
140
158
|
@apply bg-sky-100 dark:bg-sky-500/20;
|
|
159
|
+
@apply rounded-ee-none rounded-es-none;
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
/*
|
|
144
|
-
[
|
|
145
|
-
@apply
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
[overflowing] .lexxy-editor__toolbar-dropdown-content {
|
|
149
|
-
--dropdown-padding: --spacing(2);
|
|
150
|
-
@apply end-(--dropdown-padding) start-(--dropdown-padding);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/* Vertical dropdown menu (used by the text-formatting dropdown) — items show
|
|
154
|
-
an icon followed by a text label; needs to override toolbar's h-8 w-8 rule. */
|
|
155
|
-
.lexxy-editor__toolbar-dropdown-list {
|
|
156
|
-
@apply absolute z-10;
|
|
157
|
-
@apply flex flex-col p-1;
|
|
158
|
-
@apply bg-white dark:bg-stone-800;
|
|
159
|
-
@apply border-2 border-sky-200 dark:border-sky-500/30;
|
|
160
|
-
@apply rounded-md rounded-ss-none;
|
|
161
|
-
@apply text-stone-900 dark:text-white;
|
|
162
|
-
@apply top-8 start-0 min-w-[12rem];
|
|
162
|
+
/* Text-formatting menu — a vertical list of icon + label rows. */
|
|
163
|
+
[data-dropdown-panel].lexxy-editor__toolbar-dropdown-list {
|
|
164
|
+
@apply flex flex-col gap-0.5 p-1 min-w-[12rem];
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
.lexxy-editor__toolbar-dropdown-list button {
|
|
@@ -180,25 +182,24 @@
|
|
|
180
182
|
fill: currentColor;
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
/* Lexxy emits explicit <div class="lexxy-editor__toolbar-separator"> elements
|
|
184
|
-
between groups inside the dropdown — relying on those means no extra
|
|
185
|
-
border-b for `group-end` (which would double up the divider). */
|
|
186
185
|
.lexxy-editor__toolbar-dropdown-list .lexxy-editor__toolbar-separator {
|
|
187
186
|
@apply my-1 h-px w-full;
|
|
188
187
|
@apply bg-stone-200 dark:bg-white/10;
|
|
189
188
|
}
|
|
190
189
|
|
|
191
|
-
/*
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
/* Overflow ("more") menu — a compact grid of the buttons that didn't fit,
|
|
191
|
+
anchored to the trailing edge under its trigger. */
|
|
192
|
+
.lexxy-editor__toolbar-overflow [data-dropdown-panel] {
|
|
193
|
+
@apply start-auto end-0 rounded-ss-md rounded-se-none;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
[
|
|
197
|
-
@apply
|
|
196
|
+
[data-dropdown-panel].lexxy-editor__toolbar-overflow-menu {
|
|
197
|
+
@apply grid grid-cols-5 gap-1 p-2 justify-items-center;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
/* Link dropdown — URL input with Link / Unlink action buttons. */
|
|
201
|
+
lexxy-link-dropdown [data-dropdown-panel] {
|
|
202
|
+
@apply flex items-center gap-2 p-2 min-w-[22rem];
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
lexxy-link-dropdown input[type="url"] {
|
|
@@ -236,15 +237,15 @@
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
/* Highlight/Color dropdown
|
|
239
|
-
Lexxy
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
lexxy-highlight-dropdown {
|
|
243
|
-
|
|
244
|
-
@apply flex flex-col gap-2;
|
|
240
|
+
Lexxy emits N text-color buttons followed by N background-color buttons.
|
|
241
|
+
A 2-row grid with `grid-auto-flow: column` fills column-by-column, so each
|
|
242
|
+
column pairs a color with its background (row 1 = color, row 2 = bg). */
|
|
243
|
+
lexxy-highlight-dropdown [data-dropdown-panel] {
|
|
244
|
+
@apply flex flex-col gap-2 p-2;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
lexxy-highlight-dropdown .lexxy-highlight-colors {
|
|
248
|
+
--button-size: --spacing(7);
|
|
248
249
|
display: grid;
|
|
249
250
|
grid-template-columns: repeat(var(--max-colors, 9), var(--button-size));
|
|
250
251
|
grid-template-rows: repeat(2, var(--button-size));
|
|
@@ -253,7 +254,9 @@
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
lexxy-highlight-dropdown .lexxy-highlight-button {
|
|
256
|
-
@apply relative
|
|
257
|
+
@apply relative rounded-md p-0;
|
|
258
|
+
width: var(--button-size);
|
|
259
|
+
height: var(--button-size);
|
|
257
260
|
@apply ring-1 ring-inset ring-stone-300 dark:ring-white/15;
|
|
258
261
|
@apply hover:opacity-80;
|
|
259
262
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
@@ -262,7 +265,7 @@
|
|
|
262
265
|
lexxy-highlight-dropdown .lexxy-highlight-button::after {
|
|
263
266
|
content: "Aa";
|
|
264
267
|
@apply absolute inset-0 flex items-center justify-center;
|
|
265
|
-
@apply font-semibold text-
|
|
268
|
+
@apply font-semibold text-xs leading-none;
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
/* Background-color button: letter in default ink color over the swatch */
|
|
@@ -281,7 +284,7 @@
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
lexxy-highlight-dropdown .lexxy-editor__toolbar-dropdown-reset {
|
|
284
|
-
@apply mt-
|
|
287
|
+
@apply mt-1 w-full whitespace-nowrap;
|
|
285
288
|
@apply button-secondary button-sm;
|
|
286
289
|
}
|
|
287
290
|
|
|
@@ -289,9 +292,30 @@
|
|
|
289
292
|
@apply hidden;
|
|
290
293
|
}
|
|
291
294
|
|
|
292
|
-
/*
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
/* ---- Compact (overflowing) toolbar ------------------------------------ */
|
|
296
|
+
|
|
297
|
+
/* When the row can't hold every control, lexxy sets `overflowing` on the
|
|
298
|
+
toolbar and moves the spill into the "more" menu. Panels then detach from
|
|
299
|
+
their trigger and drop below the whole toolbar, spanning its width so
|
|
300
|
+
nothing runs off a narrow screen. */
|
|
301
|
+
lexxy-toolbar[overflowing] .lexxy-editor__toolbar-dropdown {
|
|
302
|
+
position: static;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
lexxy-toolbar[overflowing] [data-dropdown-panel] {
|
|
306
|
+
@apply start-2 end-2 w-auto rounded-md;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
lexxy-toolbar[overflowing] [data-dropdown-trigger][aria-expanded="true"] {
|
|
310
|
+
@apply rounded-md;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
lexxy-toolbar[overflowing] lexxy-link-dropdown [data-dropdown-panel] {
|
|
314
|
+
@apply min-w-0 flex-col items-stretch;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
lexxy-toolbar[overflowing] lexxy-link-dropdown .lexxy-editor__toolbar-dropdown-actions {
|
|
318
|
+
@apply mt-2;
|
|
295
319
|
}
|
|
296
320
|
|
|
297
321
|
/* Attachment upload progress */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module SchemaEditing
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def ensure_schema_editable
|
|
8
|
+
return if Schema.editable?
|
|
9
|
+
|
|
10
|
+
if request.format.json?
|
|
11
|
+
render json: { schema: [ I18n.t("iron.schema.locked.api_error") ] }, status: :forbidden
|
|
12
|
+
elsif request.format.turbo_stream?
|
|
13
|
+
render turbo_stream: turbo_stream.refresh(request_id: nil)
|
|
14
|
+
else
|
|
15
|
+
redirect_back fallback_location: root_path, alert: I18n.t("iron.schema.locked.alert"), status: :see_other
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -12,7 +12,9 @@ module Iron
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def set_entry
|
|
15
|
-
@entry = @content_type.entries.find_by
|
|
15
|
+
@entry = @content_type.entries.find_by(route: params[:route])
|
|
16
|
+
|
|
17
|
+
raise ActiveRecord::RecordNotFound unless @entry&.published_in?(Current.locale)
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module Schema
|
|
4
|
+
class BaseController < Api::BaseController
|
|
5
|
+
include Iron::SchemaEditing
|
|
6
|
+
|
|
7
|
+
before_action :ensure_can_administer, only: %i[ create update destroy ]
|
|
8
|
+
before_action :ensure_schema_editable, only: %i[ create update destroy ]
|
|
9
|
+
|
|
10
|
+
rescue_from ActiveRecord::RecordNotUnique, with: :render_duplicate_handle
|
|
11
|
+
rescue_from ActiveModel::UnknownAttributeError, with: :render_unknown_attribute
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def render_duplicate_handle
|
|
16
|
+
render json: { handle: [ "has already been taken" ] }, status: :unprocessable_entity
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render_unknown_attribute(exception)
|
|
20
|
+
render json: { exception.attribute => [ "is not supported by this field type" ] }, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module Schema
|
|
4
|
+
class BlockDefinitionsController < BaseController
|
|
5
|
+
before_action :set_block_definition, only: %i[ show update destroy ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@block_definitions = BlockDefinition.order(:name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
@block_definition = BlockDefinition.new(block_definition_params)
|
|
16
|
+
|
|
17
|
+
if @block_definition.save
|
|
18
|
+
render :show, status: :created, location: api_schema_block_definition_url(@block_definition)
|
|
19
|
+
else
|
|
20
|
+
render json: @block_definition.errors, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update
|
|
25
|
+
if @block_definition.update(block_definition_params)
|
|
26
|
+
render :show
|
|
27
|
+
else
|
|
28
|
+
render json: @block_definition.errors, status: :unprocessable_entity
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def destroy
|
|
33
|
+
@block_definition.destroy!
|
|
34
|
+
head :no_content
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def set_block_definition
|
|
40
|
+
@block_definition = BlockDefinition.find(params[:id])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def block_definition_params
|
|
44
|
+
params.permit(:name, :handle, :description)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module Schema
|
|
4
|
+
class ContentTypesController < BaseController
|
|
5
|
+
before_action :set_content_type, only: %i[ show update destroy ]
|
|
6
|
+
before_action :ensure_supported_type, only: :create
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@content_types = ContentType.order(:name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
@content_type = ContentType.new(content_type_params.merge(type: classified_type))
|
|
17
|
+
|
|
18
|
+
if @content_type.save
|
|
19
|
+
render :show, status: :created, location: api_schema_content_type_url(@content_type)
|
|
20
|
+
else
|
|
21
|
+
render json: @content_type.errors, status: :unprocessable_entity
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def update
|
|
26
|
+
if @content_type.update(content_type_params)
|
|
27
|
+
render :show
|
|
28
|
+
else
|
|
29
|
+
render json: @content_type.errors, status: :unprocessable_entity
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def destroy
|
|
34
|
+
@content_type.destroy!
|
|
35
|
+
head :no_content
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def set_content_type
|
|
41
|
+
@content_type = ContentType.find(params[:id])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def content_type_params
|
|
45
|
+
params.permit(:name, :handle, :description, :icon, :base_path, :web_publishing_enabled, :title_field_definition_id, :web_page_title_field_definition_id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ensure_supported_type
|
|
49
|
+
if params[:type].present? && requested_type.nil?
|
|
50
|
+
render json: { type: [ "must be one of: #{ContentType::TYPES.map(&:downcase).join(", ")}" ] }, status: :unprocessable_entity
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def requested_type
|
|
55
|
+
ContentType::TYPES.find { |type| type.casecmp?(params[:type].to_s) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def classified_type
|
|
59
|
+
ContentType.classify_type(requested_type || "Collection")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module Schema
|
|
4
|
+
class FieldDefinitionsController < BaseController
|
|
5
|
+
before_action :set_schemable, :ensure_supported_type, only: :create
|
|
6
|
+
before_action :set_field_definition, only: %i[ show update destroy ]
|
|
7
|
+
before_action :ensure_valid_position, only: %i[ create update ]
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
@field_definition = @schemable.field_definitions.build(type: classified_type)
|
|
14
|
+
@field_definition.assign_attributes(field_definition_params)
|
|
15
|
+
place_at_requested_position
|
|
16
|
+
|
|
17
|
+
if @field_definition.save
|
|
18
|
+
render :show, status: :created, location: api_schema_field_definition_url(@field_definition)
|
|
19
|
+
else
|
|
20
|
+
render json: @field_definition.errors, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update
|
|
25
|
+
@field_definition.assign_attributes(field_definition_params)
|
|
26
|
+
place_at_requested_position
|
|
27
|
+
|
|
28
|
+
if @field_definition.save
|
|
29
|
+
render :show
|
|
30
|
+
else
|
|
31
|
+
render json: @field_definition.errors, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
@field_definition.destroy!
|
|
37
|
+
head :no_content
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_schemable
|
|
43
|
+
@schemable = if params[:content_type_id]
|
|
44
|
+
ContentType.find(params[:content_type_id])
|
|
45
|
+
else
|
|
46
|
+
BlockDefinition.find(params[:block_definition_id])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_field_definition
|
|
51
|
+
@field_definition = FieldDefinition.find(params[:id])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def field_definition_params
|
|
55
|
+
params.permit(:name, :handle, :required, :searchable, :file_scope, :min_length, :max_length, :min, :max, allowed_values: [], selected_presets: [], supported_block_definition_ids: [], supported_content_type_ids: [])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def ensure_valid_position
|
|
59
|
+
Integer(params[:position].to_s) if params.key?(:position)
|
|
60
|
+
rescue ArgumentError, TypeError
|
|
61
|
+
render json: { position: [ "must be an integer" ] }, status: :unprocessable_entity
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def place_at_requested_position
|
|
65
|
+
@field_definition.move_to(requested_position) if params.key?(:position)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def requested_position
|
|
69
|
+
Integer(params[:position].to_s).clamp(0, highest_position)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def highest_position
|
|
73
|
+
@field_definition.schemable.field_definitions.where.not(id: @field_definition.id).count
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def ensure_supported_type
|
|
77
|
+
unless FieldDefinition::TYPES.include?(params[:type].to_s)
|
|
78
|
+
render json: { type: [ "must be one of: #{FieldDefinition::TYPES.join(", ")}" ] }, status: :unprocessable_entity
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def classified_type
|
|
83
|
+
FieldDefinition.classify_type(params[:type])
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module Schema
|
|
4
|
+
class LocalesController < BaseController
|
|
5
|
+
before_action :set_locale, only: %i[ show update destroy ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@locales = Locale.order(:code)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
@locale = Locale.new(locale_params)
|
|
16
|
+
|
|
17
|
+
if @locale.save
|
|
18
|
+
render :show, status: :created, location: api_schema_locale_url(@locale)
|
|
19
|
+
else
|
|
20
|
+
render json: @locale.errors, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update
|
|
25
|
+
if @locale.update(locale_params)
|
|
26
|
+
render :show
|
|
27
|
+
else
|
|
28
|
+
render json: @locale.errors, status: :unprocessable_entity
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def destroy
|
|
33
|
+
if @locale.destroy
|
|
34
|
+
head :no_content
|
|
35
|
+
else
|
|
36
|
+
render json: @locale.errors, status: :unprocessable_entity
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_locale
|
|
43
|
+
@locale = Locale.find(params[:id])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def locale_params
|
|
47
|
+
params.permit(:code, :name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Iron
|
|
2
2
|
class ApplicationController < ActionController::Base
|
|
3
3
|
include ActiveStorage::SetCurrent
|
|
4
|
-
include AdminLocale, Authentication, Authorization
|
|
4
|
+
include AdminLocale, Authentication, Authorization, SchemaEditing
|
|
5
5
|
default_form_builder FormBuilder
|
|
6
6
|
end
|
|
7
7
|
end
|
|
@@ -2,6 +2,7 @@ module Iron
|
|
|
2
2
|
class BlockDefinitionsController < ApplicationController
|
|
3
3
|
before_action :set_block_definition, only: %i[ show edit update destroy ]
|
|
4
4
|
before_action :ensure_can_administer
|
|
5
|
+
before_action :ensure_schema_editable, only: %i[ create update destroy ]
|
|
5
6
|
|
|
6
7
|
def index
|
|
7
8
|
@block_definitions = BlockDefinition.all
|
|
@@ -2,6 +2,7 @@ module Iron
|
|
|
2
2
|
class ContentTypesController < ApplicationController
|
|
3
3
|
before_action :set_content_type, only: %i[ show edit update destroy ]
|
|
4
4
|
before_action :ensure_can_administer
|
|
5
|
+
before_action :ensure_schema_editable, only: %i[ create update destroy ]
|
|
5
6
|
|
|
6
7
|
def index
|
|
7
8
|
@content_types = ContentType.order(:name)
|
|
@@ -5,6 +5,7 @@ module Iron
|
|
|
5
5
|
before_action :set_schemable, only: %i[ new create ]
|
|
6
6
|
before_action :set_field_definition, only: %i[ edit update destroy ]
|
|
7
7
|
before_action :ensure_can_administer
|
|
8
|
+
before_action :ensure_schema_editable, only: %i[ create update destroy ]
|
|
8
9
|
|
|
9
10
|
def new
|
|
10
11
|
@field_definition = @schemable.field_definitions.build(type: selected_field_definition_type)
|
|
@@ -42,7 +43,7 @@ module Iron
|
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def field_definition_params
|
|
45
|
-
params.expect(field_definition: [ :type, :name, :handle, :rank, :allowed_values_text, :required, :searchable, :file_scope, selected_presets: [], supported_block_definition_ids: [], supported_content_type_ids: [] ])
|
|
46
|
+
params.expect(field_definition: [ :type, :name, :handle, :rank, :allowed_values_text, :required, :min_length, :max_length, :min, :max, :searchable, :file_scope, selected_presets: [], supported_block_definition_ids: [], supported_content_type_ids: [] ])
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def selected_field_definition_type
|
|
@@ -2,6 +2,7 @@ module Iron
|
|
|
2
2
|
class LocalesController < ApplicationController
|
|
3
3
|
before_action :set_locale, only: %i[ edit update destroy ]
|
|
4
4
|
before_action :ensure_can_administer
|
|
5
|
+
before_action :ensure_schema_editable, only: %i[ create update destroy ]
|
|
5
6
|
|
|
6
7
|
def index
|
|
7
8
|
@locales = Locale.all
|