plum-cms 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/README.md +66 -3
- data/app/assets/builds/tailwind.css +1 -1
- data/app/assets/stylesheets/plum/control_panel.css +1 -1
- data/app/assets/tailwind/application.css +42 -0
- data/app/controllers/plum/api/v1/entries_controller.rb +48 -0
- data/app/controllers/plum/cp/assets_controller.rb +4 -1
- data/app/controllers/plum/cp/content_types_controller.rb +25 -2
- data/app/controllers/plum/cp/entries_controller.rb +119 -9
- data/app/controllers/plum/cp/entry_revisions_controller.rb +27 -0
- data/app/controllers/plum/cp/fieldsets_controller.rb +37 -0
- data/app/controllers/plum/cp/site_settings_controller.rb +20 -1
- data/app/controllers/plum/pages_controller.rb +26 -1
- data/app/javascript/controllers/plum/asset_collection_controller.js +18 -0
- data/app/javascript/controllers/plum/blueprint_controller.js +202 -25
- data/app/javascript/controllers/plum/conditional_fields_controller.js +43 -0
- data/app/javascript/controllers/plum/focal_point_controller.js +16 -0
- data/app/javascript/controllers/plum/structured_field_controller.js +133 -0
- data/app/models/plum/asset.rb +5 -1
- data/app/models/plum/content_type.rb +63 -1
- data/app/models/plum/entry.rb +225 -1
- data/app/models/plum/entry_revision.rb +14 -0
- data/app/models/plum/fieldset.rb +17 -0
- data/app/models/plum/site.rb +15 -0
- data/app/models/plum/user.rb +1 -0
- data/app/services/plum/entry_serializer.rb +36 -0
- data/app/services/plum/field_expander.rb +12 -1
- data/app/services/plum/field_options.rb +21 -0
- data/app/services/plum/field_type_registry.rb +88 -0
- data/app/services/plum/liquid_context.rb +7 -1
- data/app/views/layouts/plum/cp.html.erb +2 -0
- data/app/views/plum/cp/assets/_form.html.erb +13 -1
- data/app/views/plum/cp/content_types/_form.html.erb +81 -24
- data/app/views/plum/cp/custom_fields/_input.html.erb +5 -0
- data/app/views/plum/cp/entries/_form.html.erb +108 -31
- data/app/views/plum/cp/entries/edit.html.erb +16 -0
- data/app/views/plum/cp/entry_revisions/index.html.erb +25 -0
- data/app/views/plum/cp/fieldsets/index.html.erb +30 -0
- data/app/views/plum/cp/site_settings/edit.html.erb +12 -0
- data/config/plum_routes.rb +15 -0
- data/db/engine_migrate/20260807130000_create_plum_entry_revisions.rb +16 -0
- data/db/engine_migrate/20260807140000_create_plum_fieldsets.rb +13 -0
- data/db/engine_migrate/20260807150000_add_focal_point_to_plum_assets.rb +6 -0
- data/db/engine_migrate/20260807160000_add_localization_to_plum_entries.rb +9 -0
- data/docs/blueprint-fields.md +55 -0
- data/docs/extensions.md +32 -0
- data/docs/product-principles.md +73 -0
- data/docs/roadmap.md +98 -0
- data/docs/site/homepage.md +104 -0
- data/docs/site/information-architecture.md +176 -0
- data/docs/statamic-parity.md +28 -0
- data/docs/vision.md +82 -0
- data/lib/plum/version.rb +1 -1
- data/lib/plum.rb +4 -0
- data/lib/tasks/plum_styles.rake +12 -0
- metadata +31 -6
|
@@ -46,34 +46,28 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
|
-
<div class="bg-white shadow rounded-lg p-6" data-controller="plum--blueprint"
|
|
49
|
+
<div class="bg-white shadow rounded-lg p-6" data-controller="plum--blueprint"
|
|
50
|
+
data-plum--blueprint-types-value="<%= Plum::FieldTypeRegistry.as_json.to_json %>"
|
|
51
|
+
data-plum--blueprint-nested-types-value="<%= Plum::FieldTypeRegistry.as_json.select { |type| Plum::FieldTypeRegistry::NESTED_FIELD_TYPES.include?(type[:handle]) }.to_json %>"
|
|
52
|
+
<% if content_type.persisted? %>data-plum--blueprint-apply-fieldset-url-value="<%= apply_fieldset_cp_content_type_path(content_type) %>"<% end %>>
|
|
50
53
|
<h3 class="text-lg font-medium text-gray-900 mb-4">Blueprint</h3>
|
|
51
|
-
<p class="text-sm text-gray-500 mb-4">
|
|
54
|
+
<p class="text-sm text-gray-500 mb-4">Choose fields and configure how editors enter content.</p>
|
|
52
55
|
|
|
53
56
|
<%= f.hidden_field :blueprint, value: (content_type.blueprint || { "fields" => [] }).to_json, data: { "plum--blueprint-target": "input" } %>
|
|
54
57
|
|
|
55
58
|
<div class="space-y-4" data-plum--blueprint-target="fields">
|
|
56
59
|
<% (content_type.fields || []).each_with_index do |field, index| %>
|
|
57
|
-
<div class="
|
|
60
|
+
<div class="plum-blueprint-field" data-plum--blueprint-target="field">
|
|
61
|
+
<div class="plum-blueprint-field-header col-span-full">
|
|
62
|
+
<p class="plum-blueprint-field-title" data-field-summary><%= field["label"].presence || field["handle"].presence || "Untitled field" %><span class="plum-blueprint-field-type"><%= field["type"].presence || "text" %></span></p>
|
|
63
|
+
<button type="button" data-action="plum--blueprint#removeField" class="text-sm font-medium text-red-600 hover:text-red-700">Remove</button>
|
|
64
|
+
</div>
|
|
58
65
|
<input type="text" value="<%= field['handle'] %>" placeholder="handle" data-field="handle"
|
|
59
66
|
data-action="input->plum--blueprint#inputChanged"
|
|
60
67
|
class="px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500 font-mono text-sm">
|
|
61
68
|
<select data-field="type" data-action="change->plum--blueprint#inputChanged"
|
|
62
69
|
class="px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500 text-sm">
|
|
63
|
-
|
|
64
|
-
<option value="textarea" <%= 'selected' if field['type'] == 'textarea' %>>Textarea</option>
|
|
65
|
-
<option value="rich_text" <%= 'selected' if field['type'] == 'rich_text' %>>Rich Text</option>
|
|
66
|
-
<option value="number" <%= 'selected' if field['type'] == 'number' %>>Number</option>
|
|
67
|
-
<option value="boolean" <%= 'selected' if field['type'] == 'boolean' %>>Boolean</option>
|
|
68
|
-
<option value="date" <%= 'selected' if field['type'] == 'date' %>>Date</option>
|
|
69
|
-
<option value="select" <%= 'selected' if field['type'] == 'select' %>>Select</option>
|
|
70
|
-
<option value="checkboxes" <%= 'selected' if field['type'] == 'checkboxes' %>>Checkboxes</option>
|
|
71
|
-
<option value="color" <%= 'selected' if field['type'] == 'color' %>>Color</option>
|
|
72
|
-
<option value="url" <%= 'selected' if field['type'] == 'url' %>>URL</option>
|
|
73
|
-
<option value="taxonomy" <%= 'selected' if field['type'] == 'taxonomy' %>>Taxonomy</option>
|
|
74
|
-
<option value="image" <%= 'selected' if field['type'] == 'image' %>>Image</option>
|
|
75
|
-
<option value="relationship" <%= 'selected' if field['type'] == 'relationship' %>>Relationship</option>
|
|
76
|
-
<option value="blocks" <%= 'selected' if field['type'] == 'blocks' %>>Blocks</option>
|
|
70
|
+
<%= options_for_select(Plum::FieldTypeRegistry.options, field["type"]) %>
|
|
77
71
|
</select>
|
|
78
72
|
<input type="text" value="<%= field['label'] %>" placeholder="Label" data-field="label"
|
|
79
73
|
data-action="input->plum--blueprint#inputChanged"
|
|
@@ -81,17 +75,72 @@
|
|
|
81
75
|
<input type="text" value="<%= field['content_type'] %>" placeholder="Related type handle" data-field="content_type" data-field-config="relationship"
|
|
82
76
|
data-action="input->plum--blueprint#inputChanged"
|
|
83
77
|
class="<%= 'hidden' unless field['type'] == 'relationship' %> px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500 font-mono text-sm">
|
|
78
|
+
<label class="<%= 'hidden' unless field['type'] == 'relationship' %> flex items-center gap-2 text-sm text-gray-700" data-field-config="relationship"><input type="checkbox" data-field="multiple" data-action="change->plum--blueprint#inputChanged" <%= "checked" if ActiveModel::Type::Boolean.new.cast(field['multiple']) %>> Allow multiple entries</label>
|
|
84
79
|
<input type="text" value="<%= field['taxonomy'] %>" placeholder="Taxonomy handle" data-field="taxonomy" data-field-config="taxonomy"
|
|
85
80
|
data-action="input->plum--blueprint#inputChanged"
|
|
86
81
|
class="<%= 'hidden' unless field['type'] == 'taxonomy' %> px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500 font-mono text-sm">
|
|
87
|
-
<
|
|
82
|
+
<textarea rows="4" placeholder="Published | published Draft | draft" data-field="options" data-field-config="select radio button_group checkboxes"
|
|
88
83
|
data-action="input->plum--blueprint#inputChanged"
|
|
89
|
-
class="<%= 'hidden' unless %w[select checkboxes].include?(field['type']) %> px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500 text-sm">
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
84
|
+
class="<%= 'hidden' unless %w[select radio button_group checkboxes].include?(field['type']) %> col-span-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500 font-mono text-sm"><%= Plum::FieldOptions.editor_value(field['options']) %></textarea>
|
|
85
|
+
<div class="<%= 'hidden' unless %w[group repeater].include?(field['type']) %> col-span-full space-y-3 rounded-md border border-gray-200 bg-white p-3" data-field-config="group repeater" data-nested-editor>
|
|
86
|
+
<div class="flex items-center justify-between">
|
|
87
|
+
<p class="text-sm font-medium text-gray-700">Nested fields</p>
|
|
88
|
+
<button type="button" data-action="plum--blueprint#addNestedField" class="text-sm font-medium text-purple-600 hover:text-purple-700">+ Add nested field</button>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="space-y-3" data-nested-fields>
|
|
91
|
+
<% Array(field["fields"]).each do |nested| %>
|
|
92
|
+
<div class="grid gap-2 rounded-md border border-gray-200 bg-gray-50 p-3 sm:grid-cols-3" data-nested-field>
|
|
93
|
+
<input type="text" value="<%= nested['handle'] %>" placeholder="handle" data-nested="handle" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 font-mono text-sm">
|
|
94
|
+
<select data-nested="type" data-action="change->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm"><%= options_for_select(Plum::FieldTypeRegistry.options.select { |_, handle| Plum::FieldTypeRegistry::NESTED_FIELD_TYPES.include?(handle) }, nested["type"]) %></select>
|
|
95
|
+
<input type="text" value="<%= nested['label'] %>" placeholder="Label" data-nested="label" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
96
|
+
<input type="text" value="<%= nested['instructions'] %>" placeholder="Instructions" data-nested="instructions" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
97
|
+
<input type="text" value="<%= nested['placeholder'] %>" placeholder="Placeholder" data-nested="placeholder" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
98
|
+
<input type="text" value="<%= nested['default'] %>" placeholder="Default value" data-nested="default" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
99
|
+
<label class="flex items-center gap-2 text-sm text-gray-700"><input type="checkbox" data-nested="required" data-action="change->plum--blueprint#inputChanged" <%= "checked" if ActiveModel::Type::Boolean.new.cast(nested['required']) %>> Required</label>
|
|
100
|
+
<div class="flex gap-3">
|
|
101
|
+
<button type="button" data-direction="-1" data-action="plum--blueprint#moveNestedField" class="text-sm text-gray-600">↑ Up</button>
|
|
102
|
+
<button type="button" data-direction="1" data-action="plum--blueprint#moveNestedField" class="text-sm text-gray-600">↓ Down</button>
|
|
103
|
+
<button type="button" data-action="plum--blueprint#removeNestedField" class="text-sm font-medium text-red-600">Remove</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<% end %>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="<%= 'hidden' unless field['type'] == 'number' %> col-span-full grid gap-3 rounded-md border border-gray-200 bg-white p-3 sm:grid-cols-4" data-field-config="number">
|
|
110
|
+
<%= select_tag nil, options_for_select([["Decimal", "decimal"], ["Integer", "integer"]], field["number_kind"]), data: { field: "number_kind", action: "change->plum--blueprint#inputChanged" }, class: "rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
111
|
+
<input type="number" value="<%= field['min'] %>" placeholder="Minimum" data-field="min" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
112
|
+
<input type="number" value="<%= field['max'] %>" placeholder="Maximum" data-field="max" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
113
|
+
<input type="number" value="<%= field['step'] %>" placeholder="Step" data-field="step" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
114
|
+
<input type="text" value="<%= field['unit'] %>" placeholder="Unit (optional)" data-field="unit" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
115
|
+
</div>
|
|
116
|
+
<div class="<%= 'hidden' unless field['type'] == 'date' %> col-span-full grid gap-3 rounded-md border border-gray-200 bg-white p-3 sm:grid-cols-3" data-field-config="date">
|
|
117
|
+
<%= select_tag nil, options_for_select([["Date", "date"], ["Time", "time"], ["Date and time", "datetime"]], field["date_mode"]), data: { field: "date_mode", action: "change->plum--blueprint#inputChanged" }, class: "rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
118
|
+
<input type="text" value="<%= field['min'] %>" placeholder="Earliest value" data-field="min" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
119
|
+
<input type="text" value="<%= field['max'] %>" placeholder="Latest value" data-field="max" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
120
|
+
</div>
|
|
121
|
+
<div class="<%= 'hidden' unless %w[list repeater images].include?(field['type']) || (field['type'] == 'relationship' && field['multiple']) %> col-span-full grid gap-3 rounded-md border border-gray-200 bg-white p-3 sm:grid-cols-3" data-field-config="list repeater images relationship" data-multiple-only>
|
|
122
|
+
<input type="number" min="0" value="<%= field['min_items'] %>" placeholder="Minimum items" data-field="min_items" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
123
|
+
<input type="number" min="0" value="<%= field['max_items'] %>" placeholder="Maximum items" data-field="max_items" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
124
|
+
<label class="<%= 'hidden' unless field['type'] == 'list' %> flex items-center gap-2 text-sm text-gray-700" data-field-config="list"><input type="checkbox" data-field="unique" data-action="change->plum--blueprint#inputChanged" <%= "checked" if ActiveModel::Type::Boolean.new.cast(field['unique']) %>> Unique values</label>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="col-span-full grid gap-3 border-t border-gray-200 pt-3 sm:grid-cols-3">
|
|
127
|
+
<input type="text" value="<%= field['instructions'] %>" placeholder="Instructions" data-field="instructions" data-action="input->plum--blueprint#inputChanged"
|
|
128
|
+
class="px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm">
|
|
129
|
+
<input type="text" value="<%= field['placeholder'] %>" placeholder="Placeholder" data-field="placeholder" data-action="input->plum--blueprint#inputChanged"
|
|
130
|
+
class="px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm">
|
|
131
|
+
<input type="text" value="<%= field['default'] %>" placeholder="Default value" data-field="default" data-action="input->plum--blueprint#inputChanged"
|
|
132
|
+
class="px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm">
|
|
133
|
+
<label class="flex items-center gap-2 text-sm text-gray-700">
|
|
134
|
+
<input type="checkbox" data-field="required" data-action="change->plum--blueprint#inputChanged" <%= "checked" if ActiveModel::Type::Boolean.new.cast(field['required']) %>>
|
|
135
|
+
Required
|
|
136
|
+
</label>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="col-span-full grid gap-3 rounded-md border border-gray-200 bg-white p-3 sm:grid-cols-4">
|
|
139
|
+
<%= select_tag nil, options_for_select([["Full width", 12], ["Three quarters", 9], ["Two thirds", 8], ["Half width", 6], ["One third", 4], ["Quarter width", 3]], field["width"].presence || 12), data: { field: "width", action: "change->plum--blueprint#inputChanged" }, class: "rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
140
|
+
<input type="text" value="<%= field.dig('condition', 'field') %>" placeholder="Show when field…" data-field="condition_field" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 font-mono text-sm">
|
|
141
|
+
<%= select_tag nil, options_for_select([["Equals", "equals"], ["Does not equal", "not_equals"], ["Contains", "contains"], ["Is empty", "empty"], ["Is not empty", "not_empty"]], field.dig("condition", "operator")), include_blank: "No condition", data: { field: "condition_operator", action: "change->plum--blueprint#inputChanged" }, class: "rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
142
|
+
<input type="text" value="<%= field.dig('condition', 'value') %>" placeholder="Value" data-field="condition_value" data-action="input->plum--blueprint#inputChanged" class="rounded-md border border-gray-300 px-3 py-2 text-sm">
|
|
143
|
+
</div>
|
|
95
144
|
</div>
|
|
96
145
|
<% end %>
|
|
97
146
|
</div>
|
|
@@ -103,6 +152,14 @@
|
|
|
103
152
|
</svg>
|
|
104
153
|
Add Field
|
|
105
154
|
</button>
|
|
155
|
+
<% if content_type.persisted? && @fieldsets.any? %>
|
|
156
|
+
<div class="mt-4 flex flex-wrap items-center gap-3 border-t border-gray-200 pt-4">
|
|
157
|
+
<%= select_tag nil, options_from_collection_for_select(@fieldsets, :id, :name), prompt: "Choose saved fieldset", data: { fieldset_picker: true }, class: "rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
158
|
+
<button type="button" data-action="plum--blueprint#applyFieldset" class="rounded-md border border-purple-200 bg-purple-50 px-3 py-2 text-sm font-semibold text-purple-700 hover:bg-purple-100">Insert fieldset</button>
|
|
159
|
+
<span class="hidden text-sm" data-fieldset-status></span>
|
|
160
|
+
</div>
|
|
161
|
+
<% end %>
|
|
162
|
+
<p class="mt-3 text-xs text-gray-500"><%= link_to "Manage reusable fieldsets", cp_fieldsets_path, class: "text-purple-600 hover:text-purple-700" %></p>
|
|
106
163
|
</div>
|
|
107
164
|
|
|
108
165
|
<div class="flex justify-end space-x-3">
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<%= text_field_tag "entry[data][#{field_handle}]", field_value,
|
|
2
|
+
id: field_id,
|
|
3
|
+
placeholder: field["placeholder"],
|
|
4
|
+
required: field["required"],
|
|
5
|
+
class: "mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-purple-500 focus:ring-purple-500" %>
|
|
@@ -29,64 +29,90 @@
|
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
31
|
<!-- Dynamic fields based on blueprint -->
|
|
32
|
-
<div class="bg-white shadow rounded-lg p-6
|
|
32
|
+
<div class="bg-white shadow rounded-lg p-6">
|
|
33
33
|
<h3 class="text-lg font-medium text-gray-900">Content</h3>
|
|
34
|
+
<div class="mt-6 grid grid-cols-12 gap-6" data-controller="plum--conditional-fields">
|
|
34
35
|
<% @content_type.fields.each do |field| %>
|
|
35
36
|
<% field_handle = field["handle"].to_s %>
|
|
36
37
|
<% field_id = "entry_data_#{field_handle.parameterize}" %>
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
<% field_value = entry.data&.dig(field_handle) %>
|
|
39
|
+
<% field_value = field["default"] if entry.new_record? && field_value.nil? && field.key?("default") %>
|
|
40
|
+
<% condition = field["condition"].presence %>
|
|
41
|
+
<div class="<%= 'border-b border-gray-200 pb-2 pt-4' if field['type'] == 'section' %>" style="grid-column: span <%= field['type'] == 'section' ? 12 : (field['width'].presence || 12) %> / span <%= field['type'] == 'section' ? 12 : (field['width'].presence || 12) %>;"<% if condition %> data-field-condition="<%= condition.to_json %>"<% end %>>
|
|
42
|
+
<label for="<%= field_id %>" class="block <%= field['type'] == 'section' ? 'text-lg font-semibold text-gray-900' : 'text-sm font-medium text-gray-700' %>"><%= field["label"] || field_handle.titleize %></label>
|
|
43
|
+
<% if field["instructions"].present? %><p class="mt-1 text-sm text-gray-500"><%= field["instructions"] %></p><% end %>
|
|
39
44
|
<% case field["type"] %>
|
|
45
|
+
<% when "section" %>
|
|
40
46
|
<% when "text" %>
|
|
41
|
-
<%= text_field_tag "entry[data][#{field_handle}]",
|
|
47
|
+
<%= text_field_tag "entry[data][#{field_handle}]", field_value, id: field_id, placeholder: field["placeholder"], required: field["required"],
|
|
42
48
|
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
43
49
|
<% when "textarea" %>
|
|
44
|
-
<%= text_area_tag "entry[data][#{field_handle}]",
|
|
50
|
+
<%= text_area_tag "entry[data][#{field_handle}]", field_value, id: field_id, rows: 4, placeholder: field["placeholder"], required: field["required"],
|
|
45
51
|
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
46
52
|
<% when "rich_text" %>
|
|
47
53
|
<div class="mt-1">
|
|
48
|
-
<%= hidden_field_tag "entry[data][#{field_handle}]",
|
|
49
|
-
<lexxy-editor value="<%=
|
|
54
|
+
<%= hidden_field_tag "entry[data][#{field_handle}]", field_value, id: "#{field_id}_hidden" %>
|
|
55
|
+
<lexxy-editor value="<%= field_value %>"
|
|
50
56
|
data-direct-upload-url="<%= main_app.rails_direct_uploads_url %>"
|
|
51
57
|
data-blob-url-template="<%= main_app.rails_service_blob_url(":signed_id", ":filename") %>"
|
|
52
58
|
data-hidden-field="<%= field_id %>_hidden"
|
|
53
59
|
class="lexxy-content min-h-48 rounded-lg border border-gray-300 bg-white shadow-sm"
|
|
54
|
-
placeholder="Write something…"></lexxy-editor>
|
|
60
|
+
placeholder="<%= field['placeholder'].presence || 'Write something…' %>"></lexxy-editor>
|
|
55
61
|
</div>
|
|
56
62
|
<% when "number" %>
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
<div class="mt-1 flex items-center gap-2">
|
|
64
|
+
<%= number_field_tag "entry[data][#{field_handle}]", field_value, id: field_id, placeholder: field["placeholder"], required: field["required"],
|
|
65
|
+
min: field["min"], max: field["max"], step: field["number_kind"] == "integer" ? 1 : (field["step"].presence || "any"),
|
|
66
|
+
class: "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
67
|
+
<% if field["unit"].present? %><span class="text-sm font-medium text-gray-500"><%= field["unit"] %></span><% end %>
|
|
68
|
+
</div>
|
|
59
69
|
<% when "boolean" %>
|
|
60
70
|
<div class="mt-1">
|
|
61
71
|
<%= hidden_field_tag "entry[data][#{field_handle}]", "false" %>
|
|
62
|
-
<%= check_box_tag "entry[data][#{field_handle}]", "true", ActiveModel::Type::Boolean.new.cast(
|
|
72
|
+
<%= check_box_tag "entry[data][#{field_handle}]", "true", ActiveModel::Type::Boolean.new.cast(field_value), id: field_id %>
|
|
63
73
|
</div>
|
|
64
74
|
<% when "date" %>
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
<% date_options = { id: field_id, required: field["required"], min: field["min"], max: field["max"], class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" } %>
|
|
76
|
+
<% if field["date_mode"] == "time" %>
|
|
77
|
+
<%= time_field_tag "entry[data][#{field_handle}]", field_value, date_options %>
|
|
78
|
+
<% elsif field["date_mode"] == "datetime" %>
|
|
79
|
+
<%= datetime_local_field_tag "entry[data][#{field_handle}]", field_value, date_options %>
|
|
80
|
+
<% else %>
|
|
81
|
+
<%= date_field_tag "entry[data][#{field_handle}]", field_value, date_options %>
|
|
82
|
+
<% end %>
|
|
67
83
|
<% when "select" %>
|
|
68
|
-
<% options =
|
|
84
|
+
<% options = Plum::FieldOptions.pairs(field["options"]) %>
|
|
69
85
|
<%= select_tag "entry[data][#{field_handle}]",
|
|
70
|
-
options_for_select([["— Select —", ""]] + options
|
|
71
|
-
id: field_id,
|
|
86
|
+
options_for_select([["— Select —", ""]] + options, field_value),
|
|
87
|
+
id: field_id, required: field["required"],
|
|
72
88
|
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
89
|
+
<% when "radio", "button_group" %>
|
|
90
|
+
<% options = Plum::FieldOptions.pairs(field["options"]) %>
|
|
91
|
+
<div class="mt-2 flex flex-wrap gap-2">
|
|
92
|
+
<% options.each do |label, value| %>
|
|
93
|
+
<label class="relative cursor-pointer">
|
|
94
|
+
<%= radio_button_tag "entry[data][#{field_handle}]", value, field_value.to_s == value, required: field["required"], class: "peer sr-only" %>
|
|
95
|
+
<span class="block rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 peer-checked:border-purple-500 peer-checked:bg-purple-50 peer-checked:text-purple-700 peer-focus:ring-2 peer-focus:ring-purple-500"><%= label %></span>
|
|
96
|
+
</label>
|
|
97
|
+
<% end %>
|
|
98
|
+
</div>
|
|
73
99
|
<% when "checkboxes" %>
|
|
74
|
-
<% options =
|
|
75
|
-
<% selected = Array(
|
|
100
|
+
<% options = Plum::FieldOptions.pairs(field["options"]) %>
|
|
101
|
+
<% selected = Array(field_value) %>
|
|
76
102
|
<div class="mt-1 space-y-2">
|
|
77
|
-
<% options.each do |
|
|
103
|
+
<% options.each do |label, value| %>
|
|
78
104
|
<label class="flex items-center gap-2 text-sm text-gray-700">
|
|
79
|
-
<%= check_box_tag "entry[data][#{field_handle}][]",
|
|
105
|
+
<%= check_box_tag "entry[data][#{field_handle}][]", value, selected.include?(value),
|
|
80
106
|
class: "h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded" %>
|
|
81
|
-
<%=
|
|
107
|
+
<%= label %>
|
|
82
108
|
</label>
|
|
83
109
|
<% end %>
|
|
84
110
|
</div>
|
|
85
111
|
<% when "color" %>
|
|
86
112
|
<div class="mt-1 flex items-center gap-3">
|
|
87
|
-
<%= color_field_tag "entry[data][#{field_handle}]",
|
|
113
|
+
<%= color_field_tag "entry[data][#{field_handle}]", field_value || "#000000", id: field_id,
|
|
88
114
|
class: "h-10 w-14 p-0 border border-gray-300 rounded-md cursor-pointer" %>
|
|
89
|
-
<span class="text-sm text-gray-500 font-mono"><%=
|
|
115
|
+
<span class="text-sm text-gray-500 font-mono"><%= field_value %></span>
|
|
90
116
|
</div>
|
|
91
117
|
<% when "taxonomy" %>
|
|
92
118
|
<% taxonomy_handle = field["taxonomy"].to_s %>
|
|
@@ -105,23 +131,62 @@
|
|
|
105
131
|
<% end %>
|
|
106
132
|
</div>
|
|
107
133
|
<% when "url" %>
|
|
108
|
-
<%= url_field_tag "entry[data][#{field_handle}]",
|
|
109
|
-
placeholder: "https://",
|
|
134
|
+
<%= url_field_tag "entry[data][#{field_handle}]", field_value, id: field_id,
|
|
135
|
+
placeholder: field["placeholder"].presence || "https://", required: field["required"],
|
|
110
136
|
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
111
137
|
<% when "image" %>
|
|
112
138
|
<%= render "plum/cp/shared/image_picker",
|
|
113
139
|
input_name: "entry[data][#{field_handle}]",
|
|
114
|
-
value:
|
|
140
|
+
value: field_value.to_s,
|
|
115
141
|
field_id: field_id,
|
|
116
142
|
save_url: entry.persisted? ? "#{cp_content_type_entry_path(@content_type, entry)}/image_field" : nil,
|
|
117
143
|
field_handle: field_handle %>
|
|
144
|
+
<% when "images" %>
|
|
145
|
+
<% selected_asset_ids = Array(field_value).map(&:to_i) %>
|
|
146
|
+
<div class="mt-2 space-y-3" data-controller="plum--asset-collection" <% if field['max_items'].present? %>data-plum--asset-collection-max-value="<%= field['max_items'] %>"<% end %>>
|
|
147
|
+
<%= hidden_field_tag "entry[data][#{field_handle}][]", "" %>
|
|
148
|
+
<div class="flex items-center justify-between">
|
|
149
|
+
<p class="text-xs text-gray-500">Choose images from the asset library.</p>
|
|
150
|
+
<span class="text-xs font-medium text-gray-600" data-plum--asset-collection-target="count"></span>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
|
153
|
+
<% @assets.each do |asset| %>
|
|
154
|
+
<label class="group relative cursor-pointer overflow-hidden rounded-lg border border-gray-200 bg-gray-50 p-2 has-checked:border-purple-500 has-checked:ring-2 has-checked:ring-purple-500">
|
|
155
|
+
<%= check_box_tag "entry[data][#{field_handle}][]", asset.id, selected_asset_ids.include?(asset.id), class: "sr-only", data: { "plum--asset-collection-target": "checkbox", action: "change->plum--asset-collection#refresh" } %>
|
|
156
|
+
<%= image_tag asset.url, alt: asset.alt_text.to_s, class: "aspect-square w-full rounded-md object-cover" %>
|
|
157
|
+
<span class="mt-1 block truncate text-xs text-gray-600"><%= asset.filename %></span>
|
|
158
|
+
</label>
|
|
159
|
+
<% end %>
|
|
160
|
+
</div>
|
|
161
|
+
<% if @assets.empty? %><p class="text-sm text-gray-500">No images available. <%= link_to "Upload images", cp_assets_path, class: "text-purple-600" %>.</p><% end %>
|
|
162
|
+
</div>
|
|
118
163
|
<% when "relationship" %>
|
|
119
|
-
<% selected_entry_id = entry.data&.dig(field_handle).to_s %>
|
|
120
164
|
<% relationship_entries = (@relationship_entries_by_field || {}).fetch(field_handle, []) %>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
165
|
+
<% relationship_options = relationship_entries.map { |related_entry| ["#{related_entry.title} (#{related_entry.content_type.name})", related_entry.id] } %>
|
|
166
|
+
<% if field["multiple"] %>
|
|
167
|
+
<%= select_tag "entry[data][#{field_handle}][]", options_for_select(relationship_options, Array(field_value).map(&:to_s)), multiple: true,
|
|
168
|
+
id: field_id, required: field["required"], size: [relationship_options.length, 8].min,
|
|
169
|
+
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
170
|
+
<% else %>
|
|
171
|
+
<%= select_tag "entry[data][#{field_handle}]", options_for_select([["No entry", ""]] + relationship_options, field_value.to_s),
|
|
172
|
+
id: field_id, required: field["required"], class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
173
|
+
<% end %>
|
|
174
|
+
<% when "list", "group", "repeater" %>
|
|
175
|
+
<% structured_value = field_value || (field["type"] == "group" ? {} : []) %>
|
|
176
|
+
<div class="mt-2 space-y-3" data-controller="plum--structured-field"
|
|
177
|
+
data-plum--structured-field-type-value="<%= field['type'] %>"
|
|
178
|
+
data-plum--structured-field-fields-value="<%= Array(field['fields']).to_json %>"
|
|
179
|
+
<% if field['min_items'].present? %>data-plum--structured-field-min-items-value="<%= field['min_items'] %>"<% end %>
|
|
180
|
+
<% if field['max_items'].present? %>data-plum--structured-field-max-items-value="<%= field['max_items'] %>"<% end %>>
|
|
181
|
+
<%= hidden_field_tag "entry[data][#{field_handle}]", structured_value.to_json,
|
|
182
|
+
data: { "plum--structured-field-target": "input" } %>
|
|
183
|
+
<p class="hidden text-sm text-gray-500" data-plum--structured-field-target="empty">No items yet.</p>
|
|
184
|
+
<div class="space-y-3" data-plum--structured-field-target="rows"></div>
|
|
185
|
+
<% unless field["type"] == "group" %>
|
|
186
|
+
<button type="button" data-action="plum--structured-field#add" data-plum--structured-field-target="add"
|
|
187
|
+
class="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40">+ Add <%= field["type"] == "list" ? "item" : "row" %></button>
|
|
188
|
+
<% end %>
|
|
189
|
+
</div>
|
|
125
190
|
<% when "blocks" %>
|
|
126
191
|
<% editor_config = Plum::BlockEditorConfig.new(@theme, allowed: field["blocks"]) %>
|
|
127
192
|
<% current_blocks = entry.data&.dig(field_handle) %>
|
|
@@ -150,9 +215,15 @@
|
|
|
150
215
|
</div>
|
|
151
216
|
</div>
|
|
152
217
|
<% end %>
|
|
218
|
+
<% else %>
|
|
219
|
+
<% definition = Plum::FieldTypeRegistry.find(field["type"]) %>
|
|
220
|
+
<% if definition&.partial.present? %>
|
|
221
|
+
<%= render definition.partial, field: field, field_handle: field_handle, field_id: field_id, field_value: field_value, entry: entry %>
|
|
222
|
+
<% end %>
|
|
153
223
|
<% end %>
|
|
154
224
|
</div>
|
|
155
225
|
<% end %>
|
|
226
|
+
</div>
|
|
156
227
|
<% if @content_type.fields.empty? %>
|
|
157
228
|
<p class="text-sm text-gray-500">No fields defined. <%= link_to "Edit content type", edit_cp_content_type_path(@content_type), class: "text-purple-600 hover:text-purple-900" %> to add fields.</p>
|
|
158
229
|
<% end %>
|
|
@@ -164,6 +235,12 @@
|
|
|
164
235
|
<div class="bg-white shadow rounded-lg p-6 space-y-4">
|
|
165
236
|
<h3 class="text-lg font-medium text-gray-900">Publishing</h3>
|
|
166
237
|
|
|
238
|
+
<div>
|
|
239
|
+
<%= f.label :locale, class: "block text-sm font-medium text-gray-700" %>
|
|
240
|
+
<%= f.select :locale, current_site.locales.map { |locale| [locale, locale] }, {},
|
|
241
|
+
class: "mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-purple-500 focus:ring-purple-500" %>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
167
244
|
<div>
|
|
168
245
|
<%= f.label :status, class: "block text-sm font-medium text-gray-700" %>
|
|
169
246
|
<%= f.select :status, Plum::Entry.statuses.keys.map { |s| [s.titleize, s] }, {},
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
<div class="flex items-center justify-between mb-8">
|
|
2
2
|
<h1 class="text-2xl font-bold text-gray-900">Edit <%= @entry.title %></h1>
|
|
3
|
+
<div class="flex items-center gap-3">
|
|
4
|
+
<%= link_to "History (#{@entry.revisions.count})", cp_content_type_entry_revisions_path(@content_type, @entry),
|
|
5
|
+
class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
|
|
3
6
|
<% if @entry.published? %>
|
|
4
7
|
<%= link_to "View", "#{root_path.chomp('/')}/#{@entry.slug}", target: "_blank",
|
|
5
8
|
class: "inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
|
|
6
9
|
<% end %>
|
|
10
|
+
</div>
|
|
7
11
|
</div>
|
|
8
12
|
|
|
9
13
|
<%= render "form", entry: @entry %>
|
|
14
|
+
|
|
15
|
+
<div class="mt-6 rounded-lg bg-white p-6 shadow">
|
|
16
|
+
<h2 class="text-lg font-semibold text-gray-900">Translations</h2>
|
|
17
|
+
<div class="mt-3 flex flex-wrap gap-2">
|
|
18
|
+
<% @entry.translation_group.each do |translation| %>
|
|
19
|
+
<%= link_to translation.locale, edit_cp_content_type_entry_path(@content_type, translation), class: "rounded-md border border-gray-300 px-3 py-2 text-sm font-medium #{translation == @entry ? 'bg-purple-50 text-purple-700' : 'bg-white text-gray-700'}" %>
|
|
20
|
+
<% end %>
|
|
21
|
+
<% (current_site.locales - @entry.translation_group.map(&:locale)).each do |locale| %>
|
|
22
|
+
<%= button_to "+ #{locale}", translate_cp_content_type_entry_path(@content_type, @entry), params: { locale: locale }, class: "rounded-md border border-dashed border-purple-300 px-3 py-2 text-sm font-medium text-purple-700" %>
|
|
23
|
+
<% end %>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<div class="mb-8 flex items-center justify-between gap-4">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="text-2xl font-bold text-gray-900">Revision history</h1>
|
|
4
|
+
<p class="mt-1 text-sm text-gray-500"><%= @entry.title %></p>
|
|
5
|
+
</div>
|
|
6
|
+
<%= link_to "Back to entry", edit_cp_content_type_entry_path(@content_type, @entry), class: "rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50" %>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="overflow-hidden rounded-lg bg-white shadow">
|
|
10
|
+
<ul class="divide-y divide-gray-200">
|
|
11
|
+
<% @revisions.each_with_index do |revision, index| %>
|
|
12
|
+
<li class="flex flex-wrap items-center justify-between gap-4 px-6 py-4">
|
|
13
|
+
<div>
|
|
14
|
+
<p class="text-sm font-semibold text-gray-900"><%= index.zero? ? "Current saved version" : revision.created_at.to_fs(:long) %></p>
|
|
15
|
+
<p class="mt-1 text-xs text-gray-500"><%= revision.editor_label %> · <%= revision.snapshot["status"].to_s.titleize %></p>
|
|
16
|
+
</div>
|
|
17
|
+
<% unless index.zero? %>
|
|
18
|
+
<%= button_to "Restore", restore_cp_content_type_entry_revision_path(@content_type, @entry, revision), method: :post,
|
|
19
|
+
form: { data: { turbo_confirm: "Restore this version? The current version will remain in history." } },
|
|
20
|
+
class: "rounded-md border border-purple-200 bg-purple-50 px-3 py-2 text-sm font-semibold text-purple-700 hover:bg-purple-100" %>
|
|
21
|
+
<% end %>
|
|
22
|
+
</li>
|
|
23
|
+
<% end %>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900">Fieldsets</h1>
|
|
3
|
+
<p class="mt-1 text-sm text-gray-500">Save a content type's fields, then reuse them in other blueprints.</p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="grid gap-6 lg:grid-cols-3">
|
|
7
|
+
<section class="rounded-lg bg-white p-6 shadow lg:col-span-2">
|
|
8
|
+
<h2 class="text-lg font-semibold text-gray-900">Saved fieldsets</h2>
|
|
9
|
+
<div class="mt-4 divide-y divide-gray-200">
|
|
10
|
+
<% @fieldsets.each do |fieldset| %>
|
|
11
|
+
<div class="flex items-center justify-between gap-4 py-4">
|
|
12
|
+
<div><p class="text-sm font-semibold text-gray-900"><%= fieldset.name %></p><p class="text-xs text-gray-500"><%= fieldset.handle %> · <%= fieldset.fields.length %> fields</p></div>
|
|
13
|
+
<%= button_to "Delete", cp_fieldset_path(fieldset), method: :delete, form: { data: { turbo_confirm: "Delete this fieldset? Existing blueprints are unaffected." } }, class: "text-sm font-medium text-red-600" %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% if @fieldsets.empty? %><p class="py-6 text-sm text-gray-500">No saved fieldsets yet.</p><% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
|
|
20
|
+
<section class="rounded-lg bg-white p-6 shadow">
|
|
21
|
+
<h2 class="text-lg font-semibold text-gray-900">Create from content type</h2>
|
|
22
|
+
<%= form_with url: cp_fieldsets_path, scope: :fieldset, class: "mt-4 space-y-4" do |form| %>
|
|
23
|
+
<%= form.text_field :name, value: @fieldset&.name, required: true, placeholder: "SEO fields", class: "block w-full rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
24
|
+
<%= form.text_field :handle, value: @fieldset&.handle, placeholder: "Generated from name", class: "block w-full rounded-md border border-gray-300 px-3 py-2 font-mono text-sm" %>
|
|
25
|
+
<%= form.collection_select :content_type_id, @content_types, :id, :name, { prompt: "Choose source content type" }, required: true, class: "block w-full rounded-md border border-gray-300 px-3 py-2 text-sm" %>
|
|
26
|
+
<% if @fieldset&.errors&.any? %><p class="text-sm text-red-600"><%= @fieldset.errors.full_messages.to_sentence %></p><% end %>
|
|
27
|
+
<%= form.submit "Save fieldset", class: "rounded-md bg-purple-600 px-4 py-2 text-sm font-semibold text-white hover:bg-purple-700" %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</section>
|
|
30
|
+
</div>
|
|
@@ -60,6 +60,18 @@
|
|
|
60
60
|
<%= f.email_field :support_email,
|
|
61
61
|
class: "mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-purple-500 focus:border-purple-500" %>
|
|
62
62
|
</div>
|
|
63
|
+
|
|
64
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
65
|
+
<div>
|
|
66
|
+
<%= f.label :locales, "Locales", class: "block text-sm font-medium text-gray-700" %>
|
|
67
|
+
<%= text_field_tag "site_setting[locales]", current_site.locales.join(", "), class: "mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm", placeholder: "en, es, fr-CA" %>
|
|
68
|
+
<p class="mt-1 text-xs text-gray-500">Comma-separated language or language-region codes.</p>
|
|
69
|
+
</div>
|
|
70
|
+
<div>
|
|
71
|
+
<%= f.label :default_locale, "Default locale", class: "block text-sm font-medium text-gray-700" %>
|
|
72
|
+
<%= text_field_tag "site_setting[default_locale]", current_site.default_locale, class: "mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm" %>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
63
75
|
</div>
|
|
64
76
|
|
|
65
77
|
<div class="bg-white shadow rounded-lg p-6 space-y-6">
|
data/config/plum_routes.rb
CHANGED
|
@@ -6,10 +6,16 @@ Plum::Engine.routes.draw do
|
|
|
6
6
|
namespace :cp do
|
|
7
7
|
root "dashboard#show"
|
|
8
8
|
resources :content_types do
|
|
9
|
+
post :apply_fieldset, on: :member
|
|
9
10
|
resources :entries do
|
|
10
11
|
patch :image_field, on: :member
|
|
12
|
+
post :translate, on: :member
|
|
13
|
+
resources :revisions, controller: "entry_revisions", only: [ :index ] do
|
|
14
|
+
post :restore, on: :member
|
|
15
|
+
end
|
|
11
16
|
end
|
|
12
17
|
end
|
|
18
|
+
resources :fieldsets, only: [ :index, :create, :destroy ]
|
|
13
19
|
resources :assets, except: [ :show ]
|
|
14
20
|
resources :globals
|
|
15
21
|
resources :nav_menus do
|
|
@@ -28,10 +34,19 @@ Plum::Engine.routes.draw do
|
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
post "forms/:handle", to: "form_submissions#create", as: :form
|
|
37
|
+
namespace :api do
|
|
38
|
+
namespace :v1 do
|
|
39
|
+
get "collections/:collection_handle/entries", to: "entries#index", as: :collection_entries
|
|
40
|
+
get "collections/:collection_handle/entries/:slug", to: "entries#show", as: :collection_entry
|
|
41
|
+
end
|
|
42
|
+
end
|
|
31
43
|
get "theme_assets/:theme_handle/*path", to: "theme_assets#show", as: :theme_asset, format: false
|
|
32
44
|
|
|
33
45
|
get "search", to: "pages#search", as: :search
|
|
34
46
|
|
|
47
|
+
get ":locale", to: "pages#localized_home", constraints: { locale: /[a-z]{2}(?:-[A-Z]{2})?/ }, as: :localized_root
|
|
48
|
+
get ":locale/*slug", to: "pages#show", constraints: { locale: /[a-z]{2}(?:-[A-Z]{2})?/ }, as: :localized_page
|
|
49
|
+
|
|
35
50
|
root "pages#home"
|
|
36
51
|
get "*slug", to: "pages#show", constraints: ->(req) { !req.path.start_with?("/rails/") }
|
|
37
52
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class CreatePlumEntryRevisions < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :plum_entry_revisions do |t|
|
|
4
|
+
t.references :site, null: false, foreign_key: { to_table: :plum_sites }
|
|
5
|
+
t.references :entry, null: false, foreign_key: { to_table: :plum_entries }
|
|
6
|
+
t.references :editor, null: true, foreign_key: { to_table: :plum_users }
|
|
7
|
+
t.string :editor_name
|
|
8
|
+
t.string :editor_email
|
|
9
|
+
t.string :editor_gid
|
|
10
|
+
t.json :snapshot, null: false, default: {}
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_index :plum_entry_revisions, [ :entry_id, :created_at ]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreatePlumFieldsets < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :plum_fieldsets do |t|
|
|
4
|
+
t.references :site, null: false, foreign_key: { to_table: :plum_sites }
|
|
5
|
+
t.string :name, null: false
|
|
6
|
+
t.string :handle, null: false
|
|
7
|
+
t.json :fields, null: false, default: []
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :plum_fieldsets, [ :site_id, :handle ], unique: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class AddLocalizationToPlumEntries < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :plum_entries, :locale, :string, null: false, default: "en"
|
|
4
|
+
add_reference :plum_entries, :origin, null: true, foreign_key: { to_table: :plum_entries }
|
|
5
|
+
remove_index :plum_entries, [ :site_id, :slug ], if_exists: true
|
|
6
|
+
add_index :plum_entries, [ :site_id, :locale, :slug ], unique: true
|
|
7
|
+
add_index :plum_entries, [ :origin_id, :locale ], unique: true
|
|
8
|
+
end
|
|
9
|
+
end
|