plum-cms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +252 -0
- data/app/assets/builds/tailwind.css +2 -0
- data/app/assets/images/plum-mark.svg +7 -0
- data/app/assets/images/table-needs-logo.svg +83 -0
- data/app/assets/javascripts/plum/application.js +15 -0
- data/app/assets/stylesheets/application.css +10 -0
- data/app/assets/stylesheets/plum/control_panel.css +2 -0
- data/app/assets/tailwind/application.css +47 -0
- data/app/controllers/plum/application_controller.rb +34 -0
- data/app/controllers/plum/cp/assets_controller.rb +84 -0
- data/app/controllers/plum/cp/base_controller.rb +45 -0
- data/app/controllers/plum/cp/content_types_controller.rb +59 -0
- data/app/controllers/plum/cp/dashboard_controller.rb +10 -0
- data/app/controllers/plum/cp/entries_controller.rb +240 -0
- data/app/controllers/plum/cp/form_definitions_controller.rb +105 -0
- data/app/controllers/plum/cp/form_submissions_controller.rb +26 -0
- data/app/controllers/plum/cp/globals_controller.rb +84 -0
- data/app/controllers/plum/cp/nav_items_controller.rb +68 -0
- data/app/controllers/plum/cp/nav_menus_controller.rb +56 -0
- data/app/controllers/plum/cp/site_settings_controller.rb +87 -0
- data/app/controllers/plum/cp/taxonomies_controller.rb +56 -0
- data/app/controllers/plum/cp/terms_controller.rb +55 -0
- data/app/controllers/plum/cp/theme_previews_controller.rb +27 -0
- data/app/controllers/plum/cp/themes_controller.rb +98 -0
- data/app/controllers/plum/form_submissions_controller.rb +44 -0
- data/app/controllers/plum/pages_controller.rb +161 -0
- data/app/controllers/plum/sessions_controller.rb +25 -0
- data/app/controllers/plum/theme_assets_controller.rb +14 -0
- data/app/helpers/application_helper.rb +2 -0
- data/app/helpers/markdown_helper.rb +21 -0
- data/app/helpers/plum/cp/assets_helper.rb +21 -0
- data/app/helpers/plum/cp/theme_settings_helper.rb +26 -0
- data/app/javascript/application.js +18 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/index.js +4 -0
- data/app/javascript/controllers/plum/blocks_editor_controller.js +275 -0
- data/app/javascript/controllers/plum/blueprint_controller.js +120 -0
- data/app/javascript/controllers/plum/form_fields_controller.js +84 -0
- data/app/javascript/controllers/plum/image_picker_controller.js +143 -0
- data/app/javascript/controllers/plum/theme_settings_controller.js +17 -0
- data/app/models/plum/application_record.rb +5 -0
- data/app/models/plum/asset.rb +69 -0
- data/app/models/plum/content_type.rb +35 -0
- data/app/models/plum/entry.rb +67 -0
- data/app/models/plum/entry_term.rb +8 -0
- data/app/models/plum/form_definition.rb +68 -0
- data/app/models/plum/form_submission.rb +76 -0
- data/app/models/plum/global.rb +24 -0
- data/app/models/plum/nav_item.rb +58 -0
- data/app/models/plum/nav_menu.rb +24 -0
- data/app/models/plum/site.rb +112 -0
- data/app/models/plum/site_scoped.rb +17 -0
- data/app/models/plum/site_setting.rb +35 -0
- data/app/models/plum/taxonomy.rb +24 -0
- data/app/models/plum/term.rb +23 -0
- data/app/models/plum/user.rb +16 -0
- data/app/services/plum/base_blocks.rb +161 -0
- data/app/services/plum/block_editor_config.rb +65 -0
- data/app/services/plum/block_library.rb +31 -0
- data/app/services/plum/builder_renderer.rb +63 -0
- data/app/services/plum/field_expander.rb +78 -0
- data/app/services/plum/form_renderer.rb +110 -0
- data/app/services/plum/liquid_context.rb +335 -0
- data/app/services/plum/liquid_filters.rb +58 -0
- data/app/services/plum/liquid_renderer.rb +49 -0
- data/app/services/plum/theme.rb +160 -0
- data/app/services/plum/theme_asset_path.rb +31 -0
- data/app/services/plum/theme_package_installer.rb +287 -0
- data/app/services/plum/theme_registry.rb +63 -0
- data/app/services/plum/theme_resolver.rb +55 -0
- data/app/services/plum/theme_settings_params.rb +54 -0
- data/app/themes/bagel-boy/assets/screenshot.svg +92 -0
- data/app/themes/bagel-boy/assets/theme.css +184 -0
- data/app/themes/bagel-boy/blocks/hours.liquid +4 -0
- data/app/themes/bagel-boy/blocks/menu_item.liquid +7 -0
- data/app/themes/bagel-boy/layouts/base.liquid +64 -0
- data/app/themes/bagel-boy/templates/collections/posts.liquid +32 -0
- data/app/themes/bagel-boy/templates/entries/_default.liquid +13 -0
- data/app/themes/bagel-boy/templates/entries/landing.liquid +4 -0
- data/app/themes/bagel-boy/templates/entries/pages.liquid +18 -0
- data/app/themes/bagel-boy/templates/entries/posts.liquid +8 -0
- data/app/themes/bagel-boy/templates/index.liquid +10 -0
- data/app/themes/bagel-boy/templates/search.liquid +28 -0
- data/app/themes/bagel-boy/theme.yml +47 -0
- data/app/themes/bagel-shop/assets/screenshot.svg +28 -0
- data/app/themes/bagel-shop/assets/theme.css +7 -0
- data/app/themes/bagel-shop/layouts/base.liquid +507 -0
- data/app/themes/bagel-shop/templates/entries/_default.liquid +15 -0
- data/app/themes/bagel-shop/templates/entries/pages.liquid +12 -0
- data/app/themes/bagel-shop/templates/entries/posts.liquid +19 -0
- data/app/themes/bagel-shop/templates/index.liquid +55 -0
- data/app/themes/bagel-shop/theme.yml +30 -0
- data/app/themes/default/assets/screenshot.svg +24 -0
- data/app/themes/default/assets/theme.css +89 -0
- data/app/themes/default/layouts/base.liquid +117 -0
- data/app/themes/default/templates/collections/_default.liquid +16 -0
- data/app/themes/default/templates/collections/posts.liquid +27 -0
- data/app/themes/default/templates/entries/_default.liquid +26 -0
- data/app/themes/default/templates/entries/landing.liquid +5 -0
- data/app/themes/default/templates/entries/pages.liquid +17 -0
- data/app/themes/default/templates/entries/posts.liquid +20 -0
- data/app/themes/default/templates/index.liquid +22 -0
- data/app/themes/default/templates/search.liquid +28 -0
- data/app/themes/default/templates/taxonomies/_default.liquid +25 -0
- data/app/themes/default/templates/taxonomies/_default_index.liquid +14 -0
- data/app/themes/default/theme.yml +23 -0
- data/app/views/layouts/plum/cp.html.erb +138 -0
- data/app/views/layouts/plum/session.html.erb +43 -0
- data/app/views/plum/cp/assets/_form.html.erb +44 -0
- data/app/views/plum/cp/assets/edit.html.erb +14 -0
- data/app/views/plum/cp/assets/index.html.erb +52 -0
- data/app/views/plum/cp/assets/new.html.erb +9 -0
- data/app/views/plum/cp/content_types/_form.html.erb +113 -0
- data/app/views/plum/cp/content_types/edit.html.erb +8 -0
- data/app/views/plum/cp/content_types/index.html.erb +48 -0
- data/app/views/plum/cp/content_types/new.html.erb +5 -0
- data/app/views/plum/cp/content_types/show.html.erb +59 -0
- data/app/views/plum/cp/dashboard/show.html.erb +50 -0
- data/app/views/plum/cp/entries/_form.html.erb +196 -0
- data/app/views/plum/cp/entries/edit.html.erb +9 -0
- data/app/views/plum/cp/entries/index.html.erb +51 -0
- data/app/views/plum/cp/entries/new.html.erb +5 -0
- data/app/views/plum/cp/form_definitions/_form.html.erb +90 -0
- data/app/views/plum/cp/form_definitions/edit.html.erb +11 -0
- data/app/views/plum/cp/form_definitions/index.html.erb +43 -0
- data/app/views/plum/cp/form_definitions/new.html.erb +5 -0
- data/app/views/plum/cp/form_definitions/show.html.erb +70 -0
- data/app/views/plum/cp/form_submissions/show.html.erb +20 -0
- data/app/views/plum/cp/globals/_form.html.erb +44 -0
- data/app/views/plum/cp/globals/edit.html.erb +11 -0
- data/app/views/plum/cp/globals/index.html.erb +41 -0
- data/app/views/plum/cp/globals/new.html.erb +5 -0
- data/app/views/plum/cp/globals/show.html.erb +14 -0
- data/app/views/plum/cp/nav_items/_form.html.erb +56 -0
- data/app/views/plum/cp/nav_items/edit.html.erb +11 -0
- data/app/views/plum/cp/nav_items/new.html.erb +6 -0
- data/app/views/plum/cp/nav_menus/_form.html.erb +33 -0
- data/app/views/plum/cp/nav_menus/_nav_item.html.erb +24 -0
- data/app/views/plum/cp/nav_menus/edit.html.erb +11 -0
- data/app/views/plum/cp/nav_menus/index.html.erb +41 -0
- data/app/views/plum/cp/nav_menus/new.html.erb +5 -0
- data/app/views/plum/cp/nav_menus/show.html.erb +24 -0
- data/app/views/plum/cp/shared/_icon.html.erb +39 -0
- data/app/views/plum/cp/shared/_image_picker.html.erb +38 -0
- data/app/views/plum/cp/site_settings/edit.html.erb +145 -0
- data/app/views/plum/cp/site_settings/show.html.erb +62 -0
- data/app/views/plum/cp/taxonomies/_form.html.erb +38 -0
- data/app/views/plum/cp/taxonomies/edit.html.erb +2 -0
- data/app/views/plum/cp/taxonomies/index.html.erb +38 -0
- data/app/views/plum/cp/taxonomies/new.html.erb +2 -0
- data/app/views/plum/cp/taxonomies/show.html.erb +46 -0
- data/app/views/plum/cp/terms/_form.html.erb +30 -0
- data/app/views/plum/cp/terms/edit.html.erb +2 -0
- data/app/views/plum/cp/terms/new.html.erb +2 -0
- data/app/views/plum/cp/themes/index.html.erb +160 -0
- data/app/views/plum/form_mailer/submission_notification.html.erb +13 -0
- data/app/views/plum/form_mailer/submission_notification.text.erb +7 -0
- data/app/views/plum/sessions/new.html.erb +20 -0
- data/compat/plum-cms.rb +1 -0
- data/config/locales/en.yml +31 -0
- data/config/plum_importmap.rb +4 -0
- data/config/plum_routes.rb +34 -0
- data/db/engine_migrate/20260101000001_create_plum_tables.rb +154 -0
- data/lib/generators/plum/install/install_generator.rb +54 -0
- data/lib/generators/plum/install/templates/plum_initializer.rb +37 -0
- data/lib/plum/configuration.rb +88 -0
- data/lib/plum/content_source.rb +45 -0
- data/lib/plum/content_source_context.rb +30 -0
- data/lib/plum/content_source_registry.rb +120 -0
- data/lib/plum/engine.rb +30 -0
- data/lib/plum/liquid_tags/form_tag.rb +17 -0
- data/lib/plum/version.rb +7 -0
- data/lib/plum.rb +11 -0
- data/vendor/javascript/lexxy.js +11859 -0
- metadata +405 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<%= form_with model: [:cp, asset], multipart: true, class: "space-y-4" do |f| %>
|
|
2
|
+
<% if asset.errors.any? %>
|
|
3
|
+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
|
4
|
+
<ul class="list-disc list-inside">
|
|
5
|
+
<% asset.errors.full_messages.each do |message| %>
|
|
6
|
+
<li><%= message %></li>
|
|
7
|
+
<% end %>
|
|
8
|
+
</ul>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<% if asset.persisted? && plum_asset_image_url(asset).present? %>
|
|
13
|
+
<div class="overflow-hidden rounded-lg border border-gray-200 bg-gray-100">
|
|
14
|
+
<%= image_tag plum_asset_image_url(asset), alt: asset.alt_text.to_s, class: "h-44 w-full object-cover" %>
|
|
15
|
+
</div>
|
|
16
|
+
<% end %>
|
|
17
|
+
|
|
18
|
+
<div>
|
|
19
|
+
<%= f.label :file, asset.persisted? ? "Replace image" : "Image", class: "block text-sm font-medium text-gray-700" %>
|
|
20
|
+
<%= f.file_field :file, accept: "image/*", class: "mt-1 block w-full text-sm text-gray-700" %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<%= f.label :alt_text, "Alt Text", class: "block text-sm font-medium text-gray-700" %>
|
|
25
|
+
<%= f.text_field :alt_text,
|
|
26
|
+
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" %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div>
|
|
30
|
+
<%= f.label :caption, class: "block text-sm font-medium text-gray-700" %>
|
|
31
|
+
<%= f.text_area :caption, rows: 3,
|
|
32
|
+
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" %>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div>
|
|
36
|
+
<%= f.label :folder, class: "block text-sm font-medium text-gray-700" %>
|
|
37
|
+
<%= f.text_field :folder,
|
|
38
|
+
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",
|
|
39
|
+
placeholder: "uploads, products, staff" %>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<%= f.submit asset.persisted? ? "Save Asset" : "Upload Asset",
|
|
43
|
+
class: "w-full px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 cursor-pointer" %>
|
|
44
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="flex items-center justify-between gap-6 mb-8">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="text-2xl font-bold text-gray-900">Edit Asset</h1>
|
|
4
|
+
<p class="mt-1 text-sm text-gray-500"><%= @asset.filename %></p>
|
|
5
|
+
</div>
|
|
6
|
+
<%= link_to "Back", cp_assets_path,
|
|
7
|
+
class: "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" %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="max-w-md">
|
|
11
|
+
<div class="bg-white shadow rounded-lg p-6">
|
|
12
|
+
<%= render "form", asset: @asset %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<div class="flex items-center justify-between gap-6 mb-8">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="text-2xl font-bold text-gray-900">Assets</h1>
|
|
4
|
+
<p class="mt-1 text-sm text-gray-500"><%= pluralize(@assets.size, "image") %></p>
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
|
9
|
+
<section class="lg:col-span-2">
|
|
10
|
+
<% if @assets.any? %>
|
|
11
|
+
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
|
12
|
+
<% @assets.each do |asset| %>
|
|
13
|
+
<article class="overflow-hidden bg-white shadow rounded-lg">
|
|
14
|
+
<div class="border-b border-gray-200 bg-gray-100">
|
|
15
|
+
<% if plum_asset_image_url(asset).present? %>
|
|
16
|
+
<%= image_tag plum_asset_image_url(asset), alt: asset.alt_text.to_s, class: "h-44 w-full object-cover" %>
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="p-4 space-y-3">
|
|
21
|
+
<div>
|
|
22
|
+
<h2 class="text-sm font-semibold text-gray-900 truncate"><%= asset.filename %></h2>
|
|
23
|
+
<p class="mt-1 text-xs text-gray-500"><%= [asset.folder.presence, plum_asset_file_size(asset)].compact.join(" - ") %></p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<% if asset.alt_text.present? %>
|
|
27
|
+
<p class="text-sm text-gray-600"><%= asset.alt_text %></p>
|
|
28
|
+
<% end %>
|
|
29
|
+
|
|
30
|
+
<div class="flex items-center gap-2">
|
|
31
|
+
<%= link_to "Edit", edit_cp_asset_path(asset),
|
|
32
|
+
class: "px-3 py-1.5 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" %>
|
|
33
|
+
<%= button_to "Delete", cp_asset_path(asset), method: :delete,
|
|
34
|
+
data: { turbo_confirm: "Delete this asset?" },
|
|
35
|
+
class: "px-3 py-1.5 border border-red-300 rounded-md text-sm font-medium text-red-700 bg-white hover:bg-red-50" %>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</article>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
<% else %>
|
|
42
|
+
<div class="bg-white shadow rounded-lg p-6">
|
|
43
|
+
<p class="text-sm text-gray-500">No assets uploaded yet.</p>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
<aside class="bg-white shadow rounded-lg p-6">
|
|
49
|
+
<h2 class="text-lg font-semibold text-gray-900 mb-4">Upload Image</h2>
|
|
50
|
+
<%= render "form", asset: @asset %>
|
|
51
|
+
</aside>
|
|
52
|
+
</div>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<%= form_with model: [:cp, content_type], class: "space-y-6" do |f| %>
|
|
2
|
+
<% if content_type.errors.any? %>
|
|
3
|
+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
|
4
|
+
<ul class="list-disc list-inside">
|
|
5
|
+
<% content_type.errors.full_messages.each do |msg| %>
|
|
6
|
+
<li><%= msg %></li>
|
|
7
|
+
<% end %>
|
|
8
|
+
</ul>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<div class="bg-white shadow rounded-lg p-6 space-y-6">
|
|
13
|
+
<div>
|
|
14
|
+
<%= f.label :name, class: "block text-sm font-medium text-gray-700" %>
|
|
15
|
+
<%= f.text_field :name, 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" %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div>
|
|
19
|
+
<%= f.label :handle, class: "block text-sm font-medium text-gray-700" %>
|
|
20
|
+
<%= f.text_field :handle, 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 font-mono",
|
|
21
|
+
placeholder: "auto-generated from name" %>
|
|
22
|
+
<p class="mt-1 text-sm text-gray-500">Used in URLs and templates. Lowercase letters, numbers, and underscores only.</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div>
|
|
26
|
+
<%= f.label :icon, class: "block text-sm font-medium text-gray-700" %>
|
|
27
|
+
<%= f.hidden_field :icon, id: "content_type_icon" %>
|
|
28
|
+
<div class="mt-1 flex flex-wrap gap-2">
|
|
29
|
+
<% %w[document newspaper photo calendar star folder tag bookmark shopping-bag briefcase megaphone chat globe heart music film code].each do |icon_name| %>
|
|
30
|
+
<% selected = content_type.icon == icon_name %>
|
|
31
|
+
<button type="button"
|
|
32
|
+
data-icon="<%= icon_name %>"
|
|
33
|
+
onclick="document.getElementById('content_type_icon').value=this.dataset.icon;this.parentElement.querySelectorAll('button').forEach(function(b){b.style.outline='';b.style.backgroundColor=''});this.style.outline='2px solid var(--plum-accent)';this.style.backgroundColor='#f3f0ff'"
|
|
34
|
+
class="p-2 rounded-md border border-gray-200 hover:bg-gray-50"
|
|
35
|
+
style="<%= 'outline: 2px solid var(--plum-accent); background-color: #f3f0ff;' if selected %>"
|
|
36
|
+
title="<%= icon_name.titleize %>">
|
|
37
|
+
<%= render "plum/cp/shared/icon", name: icon_name %>
|
|
38
|
+
</button>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="flex items-center">
|
|
44
|
+
<%= f.check_box :singleton, class: "h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded" %>
|
|
45
|
+
<%= f.label :singleton, "Singleton (only one entry allowed)", class: "ml-2 block text-sm text-gray-700" %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="bg-white shadow rounded-lg p-6" data-controller="plum--blueprint">
|
|
50
|
+
<h3 class="text-lg font-medium text-gray-900 mb-4">Blueprint</h3>
|
|
51
|
+
<p class="text-sm text-gray-500 mb-4">Define the fields for this content type using JSON format.</p>
|
|
52
|
+
|
|
53
|
+
<%= f.hidden_field :blueprint, value: (content_type.blueprint || { "fields" => [] }).to_json, data: { "plum--blueprint-target": "input" } %>
|
|
54
|
+
|
|
55
|
+
<div class="space-y-4" data-plum--blueprint-target="fields">
|
|
56
|
+
<% (content_type.fields || []).each_with_index do |field, index| %>
|
|
57
|
+
<div class="grid grid-cols-1 gap-4 p-4 bg-gray-50 rounded-lg sm:grid-cols-2 lg:grid-cols-5" data-plum--blueprint-target="field">
|
|
58
|
+
<input type="text" value="<%= field['handle'] %>" placeholder="handle" data-field="handle"
|
|
59
|
+
data-action="input->plum--blueprint#inputChanged"
|
|
60
|
+
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
|
+
<select data-field="type" data-action="change->plum--blueprint#inputChanged"
|
|
62
|
+
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
|
+
<option value="text" <%= 'selected' if field['type'] == 'text' %>>Text</option>
|
|
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>
|
|
77
|
+
</select>
|
|
78
|
+
<input type="text" value="<%= field['label'] %>" placeholder="Label" data-field="label"
|
|
79
|
+
data-action="input->plum--blueprint#inputChanged"
|
|
80
|
+
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">
|
|
81
|
+
<input type="text" value="<%= field['content_type'] %>" placeholder="Related type handle" data-field="content_type" data-field-config="relationship"
|
|
82
|
+
data-action="input->plum--blueprint#inputChanged"
|
|
83
|
+
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">
|
|
84
|
+
<input type="text" value="<%= field['taxonomy'] %>" placeholder="Taxonomy handle" data-field="taxonomy" data-field-config="taxonomy"
|
|
85
|
+
data-action="input->plum--blueprint#inputChanged"
|
|
86
|
+
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
|
+
<input type="text" value="<%= Array(field['options']).join(', ') %>" placeholder="Option 1, Option 2, Option 3" data-field="options" data-field-config="select checkboxes"
|
|
88
|
+
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
|
+
<button type="button" data-action="plum--blueprint#removeField" class="justify-self-start text-red-500 hover:text-red-700">
|
|
91
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
92
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
93
|
+
</svg>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
<% end %>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<button type="button" data-action="plum--blueprint#addField"
|
|
100
|
+
class="mt-4 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">
|
|
101
|
+
<svg class="h-5 w-5 mr-2 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
102
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
103
|
+
</svg>
|
|
104
|
+
Add Field
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="flex justify-end space-x-3">
|
|
109
|
+
<%= link_to "Cancel", cp_content_types_path,
|
|
110
|
+
class: "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" %>
|
|
111
|
+
<%= f.submit class: "px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 cursor-pointer" %>
|
|
112
|
+
</div>
|
|
113
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div class="flex items-center justify-between mb-8">
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900">Edit <%= @content_type.name %></h1>
|
|
3
|
+
<%= button_to "Delete", cp_content_type_path(@content_type), method: :delete,
|
|
4
|
+
data: { turbo_confirm: "Are you sure you want to delete this content type and all its entries?" },
|
|
5
|
+
class: "px-4 py-2 border border-red-300 rounded-md shadow-sm text-sm font-medium text-red-700 bg-white hover:bg-red-50" %>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<%= render "form", content_type: @content_type %>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<div class="flex items-center justify-between mb-8">
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900">Content Types</h1>
|
|
3
|
+
<%= link_to "New Content Type", new_cp_content_type_path,
|
|
4
|
+
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700" %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<% if @content_types.any? %>
|
|
8
|
+
<div class="bg-white shadow rounded-lg overflow-hidden">
|
|
9
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
10
|
+
<thead class="bg-gray-50">
|
|
11
|
+
<tr>
|
|
12
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
|
13
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Handle</th>
|
|
14
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Entries</th>
|
|
15
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fields</th>
|
|
16
|
+
<th class="relative px-6 py-3"><span class="sr-only">Actions</span></th>
|
|
17
|
+
</tr>
|
|
18
|
+
</thead>
|
|
19
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
20
|
+
<% @content_types.each do |ct| %>
|
|
21
|
+
<tr>
|
|
22
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
23
|
+
<%= link_to ct.name, cp_content_type_path(ct), class: "text-sm font-medium text-purple-600 hover:text-purple-900" %>
|
|
24
|
+
</td>
|
|
25
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= ct.handle %></td>
|
|
26
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= ct.entries.count %></td>
|
|
27
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= ct.fields.count %></td>
|
|
28
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
29
|
+
<%= link_to "Edit", edit_cp_content_type_path(ct), class: "text-purple-600 hover:text-purple-900" %>
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
<% end %>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
</div>
|
|
36
|
+
<% else %>
|
|
37
|
+
<div class="text-center py-12 bg-white rounded-lg shadow">
|
|
38
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
39
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
|
40
|
+
</svg>
|
|
41
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">No content types</h3>
|
|
42
|
+
<p class="mt-1 text-sm text-gray-500">Get started by creating a new content type.</p>
|
|
43
|
+
<div class="mt-6">
|
|
44
|
+
<%= link_to "New Content Type", new_cp_content_type_path,
|
|
45
|
+
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700" %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<div class="flex items-center justify-between mb-8">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="text-2xl font-bold text-gray-900"><%= @content_type.name %></h1>
|
|
4
|
+
<p class="mt-1 text-sm text-gray-500">Handle: <code class="bg-gray-100 px-2 py-1 rounded"><%= @content_type.handle %></code></p>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="flex space-x-3">
|
|
7
|
+
<%= link_to "Edit Type", edit_cp_content_type_path(@content_type),
|
|
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" %>
|
|
9
|
+
<%= link_to "New Entry", new_cp_content_type_entry_path(@content_type),
|
|
10
|
+
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700" %>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<% if @entries.any? %>
|
|
15
|
+
<div class="bg-white shadow rounded-lg overflow-hidden">
|
|
16
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
17
|
+
<thead class="bg-gray-50">
|
|
18
|
+
<tr>
|
|
19
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Title</th>
|
|
20
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Slug</th>
|
|
21
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
22
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Updated</th>
|
|
23
|
+
<th class="relative px-6 py-3"><span class="sr-only">Actions</span></th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
27
|
+
<% @entries.each do |entry| %>
|
|
28
|
+
<tr>
|
|
29
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
30
|
+
<%= link_to entry.title, edit_cp_content_type_entry_path(@content_type, entry), class: "text-sm font-medium text-purple-600 hover:text-purple-900" %>
|
|
31
|
+
</td>
|
|
32
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">/<%= entry.slug %></td>
|
|
33
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
34
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= entry.published? ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800' %>">
|
|
35
|
+
<%= entry.status %>
|
|
36
|
+
</span>
|
|
37
|
+
</td>
|
|
38
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= entry.updated_at.strftime("%b %d, %Y") %></td>
|
|
39
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
40
|
+
<%= link_to "Edit", edit_cp_content_type_entry_path(@content_type, entry), class: "text-purple-600 hover:text-purple-900" %>
|
|
41
|
+
</td>
|
|
42
|
+
</tr>
|
|
43
|
+
<% end %>
|
|
44
|
+
</tbody>
|
|
45
|
+
</table>
|
|
46
|
+
</div>
|
|
47
|
+
<% else %>
|
|
48
|
+
<div class="text-center py-12 bg-white rounded-lg shadow">
|
|
49
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
51
|
+
</svg>
|
|
52
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">No entries</h3>
|
|
53
|
+
<p class="mt-1 text-sm text-gray-500">Get started by creating your first entry.</p>
|
|
54
|
+
<div class="mt-6">
|
|
55
|
+
<%= link_to "New Entry", new_cp_content_type_entry_path(@content_type),
|
|
56
|
+
class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700" %>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<% end %>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
|
|
3
|
+
<p class="mt-1 text-sm text-gray-500">Welcome back<%= ", #{current_user_label}" if current_user_label %></p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
|
7
|
+
<% @content_types.each do |ct| %>
|
|
8
|
+
<%= link_to cp_content_type_path(ct), class: "block p-6 bg-white rounded-lg shadow hover:shadow-md transition-shadow" do %>
|
|
9
|
+
<h3 class="text-lg font-medium text-gray-900"><%= ct.name %></h3>
|
|
10
|
+
<p class="mt-1 text-3xl font-bold text-purple-600"><%= ct.entries.count %></p>
|
|
11
|
+
<p class="text-sm text-gray-500">entries</p>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<% if @content_types.empty? %>
|
|
16
|
+
<%= link_to new_cp_content_type_path, class: "block p-6 bg-white rounded-lg shadow border-2 border-dashed border-gray-300 hover:border-purple-400 transition-colors" do %>
|
|
17
|
+
<div class="text-center">
|
|
18
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
19
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
20
|
+
</svg>
|
|
21
|
+
<h3 class="mt-2 text-sm font-medium text-gray-900">Create your first content type</h3>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<% if @recent_entries.any? %>
|
|
28
|
+
<div class="bg-white rounded-lg shadow">
|
|
29
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
|
30
|
+
<h2 class="text-lg font-medium text-gray-900">Recent Entries</h2>
|
|
31
|
+
</div>
|
|
32
|
+
<ul class="divide-y divide-gray-200">
|
|
33
|
+
<% @recent_entries.each do |entry| %>
|
|
34
|
+
<li>
|
|
35
|
+
<%= link_to edit_cp_content_type_entry_path(entry.content_type, entry), class: "block px-6 py-4 hover:bg-gray-50" do %>
|
|
36
|
+
<div class="flex items-center justify-between">
|
|
37
|
+
<div>
|
|
38
|
+
<p class="text-sm font-medium text-gray-900"><%= entry.title %></p>
|
|
39
|
+
<p class="text-xs text-gray-500"><%= entry.content_type.name %></p>
|
|
40
|
+
</div>
|
|
41
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= entry.published? ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800' %>">
|
|
42
|
+
<%= entry.status %>
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
</li>
|
|
47
|
+
<% end %>
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<div class="space-y-6">
|
|
2
|
+
<%= form_with model: [:cp, @content_type, entry], multipart: true, class: "space-y-6", id: "entry-form" do |f| %>
|
|
3
|
+
<% if entry.errors.any? %>
|
|
4
|
+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
|
5
|
+
<ul class="list-disc list-inside">
|
|
6
|
+
<% entry.errors.full_messages.each do |msg| %>
|
|
7
|
+
<li><%= msg %></li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6" style="align-items: start;">
|
|
14
|
+
<!-- Main content -->
|
|
15
|
+
<div class="lg:col-span-2 space-y-6">
|
|
16
|
+
<div class="bg-white shadow rounded-lg p-6">
|
|
17
|
+
<div class="space-y-6">
|
|
18
|
+
<div>
|
|
19
|
+
<%= f.label :title, class: "block text-sm font-medium text-gray-700" %>
|
|
20
|
+
<%= f.text_field :title, 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" %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<%= f.label :slug, class: "block text-sm font-medium text-gray-700" %>
|
|
25
|
+
<%= f.text_field :slug, 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 font-mono",
|
|
26
|
+
placeholder: "auto-generated from title" %>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Dynamic fields based on blueprint -->
|
|
32
|
+
<div class="bg-white shadow rounded-lg p-6 space-y-6">
|
|
33
|
+
<h3 class="text-lg font-medium text-gray-900">Content</h3>
|
|
34
|
+
<% @content_type.fields.each do |field| %>
|
|
35
|
+
<% field_handle = field["handle"].to_s %>
|
|
36
|
+
<% field_id = "entry_data_#{field_handle.parameterize}" %>
|
|
37
|
+
<div>
|
|
38
|
+
<label for="<%= field_id %>" class="block text-sm font-medium text-gray-700"><%= field["label"] || field_handle.titleize %></label>
|
|
39
|
+
<% case field["type"] %>
|
|
40
|
+
<% when "text" %>
|
|
41
|
+
<%= text_field_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle), id: field_id,
|
|
42
|
+
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
|
+
<% when "textarea" %>
|
|
44
|
+
<%= text_area_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle), id: field_id, rows: 4,
|
|
45
|
+
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
|
+
<% when "rich_text" %>
|
|
47
|
+
<div class="mt-1">
|
|
48
|
+
<%= hidden_field_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle), id: "#{field_id}_hidden" %>
|
|
49
|
+
<lexxy-editor value="<%= entry.data&.dig(field_handle) %>"
|
|
50
|
+
data-direct-upload-url="<%= main_app.rails_direct_uploads_url %>"
|
|
51
|
+
data-blob-url-template="<%= main_app.rails_service_blob_url(":signed_id", ":filename") %>"
|
|
52
|
+
data-hidden-field="<%= field_id %>_hidden"
|
|
53
|
+
class="lexxy-content min-h-48 rounded-lg border border-gray-300 bg-white shadow-sm"
|
|
54
|
+
placeholder="Write something…"></lexxy-editor>
|
|
55
|
+
</div>
|
|
56
|
+
<% when "number" %>
|
|
57
|
+
<%= number_field_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle), id: field_id,
|
|
58
|
+
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" %>
|
|
59
|
+
<% when "boolean" %>
|
|
60
|
+
<div class="mt-1">
|
|
61
|
+
<%= hidden_field_tag "entry[data][#{field_handle}]", "false" %>
|
|
62
|
+
<%= check_box_tag "entry[data][#{field_handle}]", "true", ActiveModel::Type::Boolean.new.cast(entry.data&.dig(field_handle)), id: field_id %>
|
|
63
|
+
</div>
|
|
64
|
+
<% when "date" %>
|
|
65
|
+
<%= date_field_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle), id: field_id,
|
|
66
|
+
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" %>
|
|
67
|
+
<% when "select" %>
|
|
68
|
+
<% options = Array(field["options"]) %>
|
|
69
|
+
<%= select_tag "entry[data][#{field_handle}]",
|
|
70
|
+
options_for_select([["— Select —", ""]] + options.map { |o| [o, o] }, entry.data&.dig(field_handle)),
|
|
71
|
+
id: field_id,
|
|
72
|
+
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" %>
|
|
73
|
+
<% when "checkboxes" %>
|
|
74
|
+
<% options = Array(field["options"]) %>
|
|
75
|
+
<% selected = Array(entry.data&.dig(field_handle)) %>
|
|
76
|
+
<div class="mt-1 space-y-2">
|
|
77
|
+
<% options.each do |option| %>
|
|
78
|
+
<label class="flex items-center gap-2 text-sm text-gray-700">
|
|
79
|
+
<%= check_box_tag "entry[data][#{field_handle}][]", option, selected.include?(option),
|
|
80
|
+
class: "h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded" %>
|
|
81
|
+
<%= option %>
|
|
82
|
+
</label>
|
|
83
|
+
<% end %>
|
|
84
|
+
</div>
|
|
85
|
+
<% when "color" %>
|
|
86
|
+
<div class="mt-1 flex items-center gap-3">
|
|
87
|
+
<%= color_field_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle) || "#000000", id: field_id,
|
|
88
|
+
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"><%= entry.data&.dig(field_handle) %></span>
|
|
90
|
+
</div>
|
|
91
|
+
<% when "taxonomy" %>
|
|
92
|
+
<% taxonomy_handle = field["taxonomy"].to_s %>
|
|
93
|
+
<% terms = (@taxonomy_terms_by_field || {}).fetch(field_handle, []) %>
|
|
94
|
+
<% selected_ids = entry.persisted? ? entry.terms.joins(:taxonomy).where(plum_taxonomies: { handle: taxonomy_handle }).pluck(:id) : [] %>
|
|
95
|
+
<div class="mt-1 space-y-2">
|
|
96
|
+
<% terms.each do |term| %>
|
|
97
|
+
<label class="flex items-center gap-2 text-sm text-gray-700">
|
|
98
|
+
<%= check_box_tag "entry[term_ids][]", term.id, selected_ids.include?(term.id),
|
|
99
|
+
class: "h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded" %>
|
|
100
|
+
<%= term.name %>
|
|
101
|
+
</label>
|
|
102
|
+
<% end %>
|
|
103
|
+
<% if terms.empty? %>
|
|
104
|
+
<p class="text-sm text-gray-500">No terms in "<%= taxonomy_handle %>". <%= link_to "Add terms", "#{cp_prefix}/taxonomies", class: "text-purple-600" %>.</p>
|
|
105
|
+
<% end %>
|
|
106
|
+
</div>
|
|
107
|
+
<% when "url" %>
|
|
108
|
+
<%= url_field_tag "entry[data][#{field_handle}]", entry.data&.dig(field_handle), id: field_id,
|
|
109
|
+
placeholder: "https://",
|
|
110
|
+
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
|
+
<% when "image" %>
|
|
112
|
+
<%= render "plum/cp/shared/image_picker",
|
|
113
|
+
input_name: "entry[data][#{field_handle}]",
|
|
114
|
+
value: entry.data&.dig(field_handle).to_s,
|
|
115
|
+
field_id: field_id %>
|
|
116
|
+
<% when "relationship" %>
|
|
117
|
+
<% selected_entry_id = entry.data&.dig(field_handle).to_s %>
|
|
118
|
+
<% relationship_entries = (@relationship_entries_by_field || {}).fetch(field_handle, []) %>
|
|
119
|
+
<%= select_tag "entry[data][#{field_handle}]",
|
|
120
|
+
options_for_select([["No entry", ""]] + relationship_entries.map { |related_entry| ["#{related_entry.title} (#{related_entry.content_type.name})", related_entry.id] }, selected_entry_id),
|
|
121
|
+
id: field_id,
|
|
122
|
+
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" %>
|
|
123
|
+
<% when "blocks" %>
|
|
124
|
+
<% editor_config = Plum::BlockEditorConfig.new(@theme, allowed: field["blocks"]) %>
|
|
125
|
+
<% current_blocks = entry.data&.dig(field_handle) %>
|
|
126
|
+
<% current_blocks = [] unless current_blocks.is_a?(Array) %>
|
|
127
|
+
<% if editor_config.blocks.empty? %>
|
|
128
|
+
<p class="mt-1 text-sm text-gray-500">This theme defines no blocks for this field.</p>
|
|
129
|
+
<%= hidden_field_tag "entry[data][#{field_handle}]", current_blocks.to_json %>
|
|
130
|
+
<% else %>
|
|
131
|
+
<div class="mt-1 space-y-3" data-controller="plum--blocks-editor"
|
|
132
|
+
data-plum--blocks-editor-config-value="<%= editor_config.to_json %>"
|
|
133
|
+
data-plum--blocks-editor-assets-url-value="<%= cp_assets_path(format: :json) %>"
|
|
134
|
+
data-plum--blocks-editor-upload-url-value="<%= cp_assets_path %>">
|
|
135
|
+
<%= hidden_field_tag "entry[data][#{field_handle}]", current_blocks.to_json,
|
|
136
|
+
data: { "plum--blocks-editor-target": "input" } %>
|
|
137
|
+
<p class="hidden text-sm text-gray-400" data-plum--blocks-editor-target="empty">No sections yet. Add one below.</p>
|
|
138
|
+
<div class="space-y-3" data-plum--blocks-editor-target="list"></div>
|
|
139
|
+
<div class="flex items-center gap-2 pt-1">
|
|
140
|
+
<select data-plum--blocks-editor-target="picker"
|
|
141
|
+
class="rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-purple-500 focus:outline-none focus:ring-purple-500">
|
|
142
|
+
<% editor_config.blocks.each do |b| %>
|
|
143
|
+
<option value="<%= b["id"] %>"><%= b["label"] %></option>
|
|
144
|
+
<% end %>
|
|
145
|
+
</select>
|
|
146
|
+
<button type="button" data-action="plum--blocks-editor#add"
|
|
147
|
+
class="rounded-md bg-purple-600 px-3 py-2 text-sm font-medium text-white hover:bg-purple-700">+ Add section</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
<% end %>
|
|
151
|
+
<% end %>
|
|
152
|
+
</div>
|
|
153
|
+
<% end %>
|
|
154
|
+
<% if @content_type.fields.empty? %>
|
|
155
|
+
<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>
|
|
156
|
+
<% end %>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<!-- Sidebar -->
|
|
161
|
+
<div class="space-y-6" style="position: sticky; top: 2rem;">
|
|
162
|
+
<div class="bg-white shadow rounded-lg p-6 space-y-4">
|
|
163
|
+
<h3 class="text-lg font-medium text-gray-900">Publishing</h3>
|
|
164
|
+
|
|
165
|
+
<div>
|
|
166
|
+
<%= f.label :status, class: "block text-sm font-medium text-gray-700" %>
|
|
167
|
+
<%= f.select :status, Plum::Entry.statuses.keys.map { |s| [s.titleize, s] }, {},
|
|
168
|
+
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" %>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div>
|
|
172
|
+
<%= f.label :published_at, class: "block text-sm font-medium text-gray-700" %>
|
|
173
|
+
<%= f.datetime_local_field :published_at,
|
|
174
|
+
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" %>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div class="pt-4 border-t border-gray-200">
|
|
178
|
+
<%= f.submit "Save", class: "w-full px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 cursor-pointer" %>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
<% end %>
|
|
184
|
+
|
|
185
|
+
<% if entry.persisted? %>
|
|
186
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
187
|
+
<div class="lg:col-start-3">
|
|
188
|
+
<div class="bg-white shadow rounded-lg p-6">
|
|
189
|
+
<%= button_to "Delete Entry", cp_content_type_entry_path(@content_type, entry), method: :delete,
|
|
190
|
+
data: { turbo_confirm: "Are you sure you want to delete this entry?" },
|
|
191
|
+
class: "w-full px-4 py-2 border border-red-300 rounded-md shadow-sm text-sm font-medium text-red-700 bg-white hover:bg-red-50" %>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<% end %>
|
|
196
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<div class="flex items-center justify-between mb-8">
|
|
2
|
+
<h1 class="text-2xl font-bold text-gray-900">Edit <%= @entry.title %></h1>
|
|
3
|
+
<% if @entry.published? %>
|
|
4
|
+
<%= link_to "View", "#{root_path.chomp('/')}/#{@entry.slug}", target: "_blank",
|
|
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" %>
|
|
6
|
+
<% end %>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<%= render "form", entry: @entry %>
|