ruby_cms 0.1.6 → 0.1.7
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 +4 -0
- data/app/javascript/controllers/ruby_cms/locale_tabs_controller.js +37 -15
- data/app/models/content_block.rb +8 -6
- data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +5 -5
- data/app/views/ruby_cms/admin/content_blocks/_form.html.erb +75 -144
- data/app/views/ruby_cms/admin/content_blocks/new.html.erb +8 -3
- data/app/views/ruby_cms/admin/content_blocks/show.html.erb +12 -7
- data/config/locales/en.yml +13 -0
- data/config/locales/nl.yml +41 -0
- data/lib/generators/ruby_cms/install_generator.rb +67 -4
- data/lib/ruby_cms/version.rb +1 -1
- data/lib/ruby_cms.rb +8 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cf4ff917e02707c3a9d4ed7cec086d357b3c7de708ebc58475a4b5c0276a64bd
|
|
4
|
+
data.tar.gz: be5e98bda847b6b1129210ba575fb29951f7e2ac96d53e9fcdb7cf58af7e97b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97f3797085020d26908c8189324fc3f157241be0aa1eecc4a7575d4a7839ea3920662ee7e9e2f6ad509c1fd7810cc291fc9c09f23630f98c4b2d208ef50dd1af
|
|
7
|
+
data.tar.gz: 7ce9c3b67c447847f407b3fa96ee3dacee1780bd14352f3da9cc310d71868cccbed42e5de0c13cd1f9a6e8edc0bfbf6c96acc0a3cad37e5c2dcbaf7db448715b
|
data/CHANGELOG.md
CHANGED
|
@@ -8,27 +8,49 @@ export default class extends Controller {
|
|
|
8
8
|
const panelId = tab.dataset.panelId;
|
|
9
9
|
if (!panelId) return;
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const hideClasses = ["hidden", "is-hidden"];
|
|
12
|
+
const panels = this.element.querySelectorAll("[data-locale-panel]");
|
|
12
13
|
|
|
13
|
-
// Hide all panels
|
|
14
|
+
// Hide all panels in this tabs component
|
|
15
|
+
panels.forEach((panel) => panel.classList.add(...hideClasses));
|
|
16
|
+
|
|
17
|
+
// Reset all tab states
|
|
14
18
|
this.tabTargets.forEach((t) => {
|
|
15
|
-
|
|
16
|
-
if (pid) {
|
|
17
|
-
const panel = document.getElementById(pid);
|
|
18
|
-
if (panel) panel.classList.add(hideClass);
|
|
19
|
-
}
|
|
20
|
-
t.classList.remove("is-active");
|
|
19
|
+
this.setTabState(t, false);
|
|
21
20
|
});
|
|
22
21
|
|
|
23
22
|
// Show selected panel
|
|
24
|
-
const panel =
|
|
25
|
-
if (panel) panel.classList.remove(
|
|
23
|
+
const panel = this.findPanel(panelId);
|
|
24
|
+
if (panel) panel.classList.remove(...hideClasses);
|
|
26
25
|
|
|
27
26
|
// Highlight selected tab
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
this.setTabState(tab, true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
findPanel(panelId) {
|
|
31
|
+
// Scope lookup to this component first so multiple tab groups never conflict.
|
|
32
|
+
if (window.CSS && typeof window.CSS.escape === "function") {
|
|
33
|
+
const scoped = this.element.querySelector(`#${window.CSS.escape(panelId)}`);
|
|
34
|
+
if (scoped) return scoped;
|
|
35
|
+
}
|
|
36
|
+
return document.getElementById(panelId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setTabState(tab, isActive) {
|
|
40
|
+
const activeClasses = this.splitClasses(tab.dataset.activeClasses);
|
|
41
|
+
const inactiveClasses = this.splitClasses(tab.dataset.inactiveClasses);
|
|
42
|
+
|
|
43
|
+
// Remove both sets first, then apply the desired one.
|
|
44
|
+
tab.classList.remove(...activeClasses, ...inactiveClasses, "is-active");
|
|
45
|
+
tab.classList.add(...(isActive ? activeClasses : inactiveClasses));
|
|
46
|
+
if (isActive) tab.classList.add("is-active");
|
|
47
|
+
tab.setAttribute("aria-selected", isActive ? "true" : "false");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
splitClasses(classListString) {
|
|
51
|
+
return (classListString || "")
|
|
52
|
+
.split(" ")
|
|
53
|
+
.map((klass) => klass.trim())
|
|
54
|
+
.filter(Boolean);
|
|
33
55
|
}
|
|
34
56
|
}
|
data/app/models/content_block.rb
CHANGED
|
@@ -9,26 +9,28 @@ class ContentBlock < ApplicationRecord
|
|
|
9
9
|
|
|
10
10
|
def self.action_text_available?
|
|
11
11
|
return false unless defined?(::ActionText::RichText)
|
|
12
|
-
return false unless ActiveRecord::Base.connected?
|
|
13
12
|
|
|
14
13
|
ActiveRecord::Base.connection.data_source_exists?("action_text_rich_texts")
|
|
15
|
-
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
|
14
|
+
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError,
|
|
15
|
+
ActiveRecord::StatementInvalid
|
|
16
16
|
false
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def self.active_storage_available?
|
|
20
20
|
return false unless defined?(::ActiveStorage::Blob)
|
|
21
|
-
return false unless ActiveRecord::Base.connected?
|
|
22
21
|
|
|
23
22
|
c = ActiveRecord::Base.connection
|
|
24
23
|
c.data_source_exists?("active_storage_blobs") &&
|
|
25
24
|
c.data_source_exists?("active_storage_attachments")
|
|
26
|
-
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
|
25
|
+
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError,
|
|
26
|
+
ActiveRecord::StatementInvalid
|
|
27
27
|
false
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
# Define associations without requiring an active DB connection at boot time.
|
|
31
|
+
# In production, eager loading may happen before AR has connected.
|
|
32
|
+
has_rich_text :rich_content if defined?(::ActionText::RichText)
|
|
33
|
+
has_one_attached :image if defined?(::ActiveStorage::Blob)
|
|
32
34
|
|
|
33
35
|
belongs_to :updated_by, class_name: "User", optional: true
|
|
34
36
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%# Admin Sidebar Navigation %>
|
|
2
|
-
<aside class="w-
|
|
2
|
+
<aside class="w-46 flex-shrink-0 bg-[#FAF9F5] flex flex-col overflow-hidden" aria-label="Admin" data-ruby-cms--mobile-menu-target="sidebar">
|
|
3
3
|
<div class="px-5 py-5 flex-shrink-0 bg-[#FAF9F5]">
|
|
4
4
|
<div class="flex items-center gap-3" data-ruby-cms--mobile-menu-target="sidebarContent">
|
|
5
5
|
<% if respond_to?(:image_tag, true) %>
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
{},
|
|
95
95
|
{
|
|
96
96
|
class: "absolute inset-0 w-full h-full opacity-0 cursor-pointer",
|
|
97
|
-
aria: { label: "Locale" },
|
|
97
|
+
aria: { label: t("ruby_cms.nav.locale", default: "Locale") },
|
|
98
98
|
onchange: "this.form.submit()"
|
|
99
99
|
} %>
|
|
100
100
|
</div>
|
|
@@ -106,16 +106,16 @@
|
|
|
106
106
|
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
107
107
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
|
108
108
|
</svg>
|
|
109
|
-
<span class="flex-1 text-left"
|
|
109
|
+
<span class="flex-1 text-left"><%= t("ruby_cms.nav.view_site", default: "View site") %></span>
|
|
110
110
|
<% end %>
|
|
111
111
|
<% end %>
|
|
112
112
|
|
|
113
113
|
<% if respond_to?(:main_app, true) && main_app.respond_to?(:session_path) %>
|
|
114
|
-
<%= button_to main_app.session_path, method: :delete, class: "flex items-center gap-3 w-full px-3 py-2.5 text-sm font-medium text-gray-600 rounded-md bg-transparent border-0 cursor-pointer transition-colors hover:bg-blue-500 hover:text-white", data: { turbo_confirm: "Are you sure you want to logout?" } do %>
|
|
114
|
+
<%= button_to main_app.session_path, method: :delete, class: "flex items-center gap-3 w-full px-3 py-2.5 text-sm font-medium text-gray-600 rounded-md bg-transparent border-0 cursor-pointer transition-colors hover:bg-blue-500 hover:text-white", data: { turbo_confirm: t("ruby_cms.nav.logout_confirm", default: "Are you sure you want to logout?") } do %>
|
|
115
115
|
<svg class="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
116
116
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
|
|
117
117
|
</svg>
|
|
118
|
-
<span class="flex-1 text-left"
|
|
118
|
+
<span class="flex-1 text-left"><%= t("ruby_cms.nav.logout", default: "Logout") %></span>
|
|
119
119
|
<% end %>
|
|
120
120
|
<% end %>
|
|
121
121
|
</div>
|
|
@@ -1,161 +1,92 @@
|
|
|
1
1
|
<%= form_with model: @content_block,
|
|
2
|
-
url:
|
|
3
|
-
method:
|
|
2
|
+
url: ruby_cms_admin_content_blocks_path,
|
|
3
|
+
method: :post,
|
|
4
4
|
id: "content-block-form",
|
|
5
5
|
data: { turbo: false } do |f| %>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<label class="text-sm font-medium text-gray-700"><%= t("ruby_cms.admin.content_blocks.content_type") %></label>
|
|
22
|
-
<%= select_tag "content_block[content_type]",
|
|
23
|
-
options_for_select(::ContentBlock::CONTENT_TYPES, @content_block.content_type),
|
|
24
|
-
class: "h-9 rounded-md border border-gray-200 bg-white px-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-teal-200" %>
|
|
25
|
-
</div>
|
|
26
|
-
<% end %>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<div class="flex items-center gap-2 flex-shrink-0">
|
|
30
|
-
<%= link_to "Cancel",
|
|
31
|
-
@content_block.persisted? ? ruby_cms_admin_content_block_path(@content_block) : ruby_cms_admin_content_blocks_path,
|
|
32
|
-
class: "inline-flex h-9 items-center justify-center rounded-md border border-gray-200 bg-white px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 transition-colors no-underline" %>
|
|
33
|
-
<%= f.submit "Save", class: "inline-flex h-9 items-center justify-center rounded-md bg-gray-900 px-4 text-sm font-medium text-white shadow-sm hover:bg-gray-800 transition-colors cursor-pointer" %>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<% if @content_block.errors.any? %>
|
|
38
|
-
<div class="mx-6 mt-4 rounded-md border border-rose-200 bg-rose-50 p-4">
|
|
39
|
-
<ul class="list-disc pl-4 space-y-1">
|
|
40
|
-
<% @content_block.errors.full_messages.each do |msg| %>
|
|
41
|
-
<li class="text-sm text-rose-700"><%= msg %></li>
|
|
42
|
-
<% end %>
|
|
43
|
-
</ul>
|
|
44
|
-
</div>
|
|
45
|
-
<% end %>
|
|
46
|
-
|
|
47
|
-
<% if @content_block.persisted? && @blocks_by_locale.present? %>
|
|
48
|
-
<%# Multi-locale layout %>
|
|
49
|
-
|
|
50
|
-
<div class="flex items-center gap-2 px-6 py-4 border-b border-gray-100">
|
|
51
|
-
<%= hidden_field_tag "content_block[published]", "0" %>
|
|
52
|
-
<%= check_box_tag "content_block[published]", "1",
|
|
53
|
-
@blocks_by_locale.values.any?(&:published?),
|
|
54
|
-
class: "h-4 w-4 rounded border-gray-300 text-teal-600 focus:ring-teal-200" %>
|
|
55
|
-
<label class="text-sm font-medium text-gray-700"><%= t("ruby_cms.admin.content_blocks.published") %> (all translations)</label>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<div class="rounded-md border border-gray-200 overflow-hidden mx-6 my-4" data-controller="ruby-cms--locale-tabs">
|
|
59
|
-
<div class="flex gap-1 p-2 bg-gray-50 border-b border-gray-200" role="tablist">
|
|
60
|
-
<% @blocks_by_locale.each_with_index do |(locale_s, _), idx| %>
|
|
61
|
-
<button type="button"
|
|
62
|
-
role="tab"
|
|
63
|
-
aria-selected="<%= idx == 0 %>"
|
|
64
|
-
aria-controls="locale-panel-<%= locale_s %>"
|
|
65
|
-
id="locale-tab-<%= locale_s %>"
|
|
66
|
-
data-ruby-cms--locale-tabs-target="tab"
|
|
67
|
-
data-panel-id="locale-panel-<%= locale_s %>"
|
|
68
|
-
data-action="click->ruby-cms--locale-tabs#switchTab"
|
|
69
|
-
class="px-3 py-1.5 rounded text-xs font-medium border-none cursor-pointer transition-colors <%= idx == 0 ? 'bg-white text-blue-600 shadow-sm ring-1 ring-gray-200' : 'bg-transparent text-gray-500 hover:bg-gray-100 hover:text-gray-900' %>">
|
|
70
|
-
<%= ruby_cms_locale_display_name(locale_s) %>
|
|
71
|
-
</button>
|
|
72
|
-
<% end %>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
<% @blocks_by_locale.each_with_index do |(locale_s, block), idx| %>
|
|
76
|
-
<div id="locale-panel-<%= locale_s %>"
|
|
77
|
-
role="tabpanel"
|
|
78
|
-
aria-labelledby="locale-tab-<%= locale_s %>"
|
|
79
|
-
class="p-5 <%= idx > 0 ? 'hidden' : '' %>"
|
|
80
|
-
data-locale-panel>
|
|
81
|
-
|
|
82
|
-
<%= hidden_field_tag "content_block[locales][#{locale_s}][locale]", locale_s %>
|
|
83
|
-
|
|
84
|
-
<div class="grid grid-cols-2 gap-4">
|
|
85
|
-
<div class="flex flex-col gap-1">
|
|
86
|
-
<label class="text-sm font-medium text-gray-700"><%= t("ruby_cms.admin.content_blocks.title") %></label>
|
|
87
|
-
<%= text_field_tag "content_block[locales][#{locale_s}][title]",
|
|
88
|
-
block.title,
|
|
89
|
-
class: "h-9 rounded-md border border-gray-200 bg-white px-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-teal-200" %>
|
|
6
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::CARD_CLASS %>">
|
|
7
|
+
<div class="p-6 space-y-6">
|
|
8
|
+
<% if @content_block.errors.any? %>
|
|
9
|
+
<div class="rounded-lg border border-destructive/30 bg-destructive/5 p-4">
|
|
10
|
+
<div class="flex gap-3">
|
|
11
|
+
<svg class="size-5 text-destructive shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
|
12
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
13
|
+
</svg>
|
|
14
|
+
<div>
|
|
15
|
+
<p class="text-sm font-medium text-destructive"><%= t("ruby_cms.admin.content_blocks.errors_title", default: "Please fix the following errors:") %></p>
|
|
16
|
+
<ul class="mt-1.5 text-sm text-destructive/80 list-disc list-inside">
|
|
17
|
+
<% @content_block.errors.full_messages.each do |message| %>
|
|
18
|
+
<li><%= message %></li>
|
|
19
|
+
<% end %>
|
|
20
|
+
</ul>
|
|
90
21
|
</div>
|
|
91
|
-
|
|
92
|
-
<div class="flex flex-col gap-1">
|
|
93
|
-
<label class="text-sm font-medium text-gray-700"><%= t("ruby_cms.admin.content_blocks.content") %></label>
|
|
94
|
-
<%= text_area_tag "content_block[locales][#{locale_s}][content]",
|
|
95
|
-
block.content,
|
|
96
|
-
rows: 5,
|
|
97
|
-
class: "rounded-md border border-gray-200 bg-white px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-teal-200 resize-y" %>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<% if ::ContentBlock.respond_to?(:action_text_available?) && ::ContentBlock.action_text_available? && block.persisted? && block.respond_to?(:rich_content) %>
|
|
101
|
-
<div class="col-span-2 flex flex-col gap-1">
|
|
102
|
-
<label class="text-sm font-medium text-gray-700"><%= t("ruby_cms.admin.content_blocks.rich_content") %></label>
|
|
103
|
-
<%= rich_text_area_tag "content_block[locales][#{locale_s}][rich_content]",
|
|
104
|
-
block.rich_content.present? ? block.rich_content.to_s : "",
|
|
105
|
-
class: "rounded-md border border-gray-200 bg-white text-sm shadow-sm focus:outline-none" %>
|
|
106
|
-
</div>
|
|
107
|
-
<% end %>
|
|
108
22
|
</div>
|
|
109
23
|
</div>
|
|
110
24
|
<% end %>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<% else %>
|
|
114
|
-
<%# Single-locale (new block) %>
|
|
115
|
-
|
|
116
|
-
<div class="grid grid-cols-2 gap-4 p-6">
|
|
117
|
-
<div class="flex flex-col gap-1">
|
|
118
|
-
<%= f.label :locale, class: "text-sm font-medium text-gray-700" %>
|
|
119
|
-
<%= f.select :locale,
|
|
120
|
-
I18n.available_locales.map { |l| [ruby_cms_locale_display_name(l), l.to_s] },
|
|
121
|
-
{ include_blank: t("ruby_cms.admin.content_blocks.select_locale") },
|
|
122
|
-
class: "h-9 rounded-md border border-gray-200 bg-white px-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-teal-200",
|
|
123
|
-
name: "content_block[locale]" %>
|
|
124
|
-
</div>
|
|
125
25
|
|
|
126
|
-
<div class="
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
26
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
|
27
|
+
<div>
|
|
28
|
+
<%= f.label :key, class: RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>
|
|
29
|
+
<%= f.text_field :key,
|
|
30
|
+
class: RubyCms::Admin::AdminResourceCard::INPUT_CLASS,
|
|
31
|
+
placeholder: "homepage_hero_title",
|
|
32
|
+
name: "content_block[key]" %>
|
|
33
|
+
</div>
|
|
130
34
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
35
|
+
<div>
|
|
36
|
+
<%= f.label :locale, class: RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>
|
|
37
|
+
<%= f.select :locale,
|
|
38
|
+
I18n.available_locales.map { |l| [ruby_cms_locale_display_name(l), l.to_s] },
|
|
39
|
+
{ include_blank: t("ruby_cms.admin.content_blocks.select_locale", default: "Select locale") },
|
|
40
|
+
class: RubyCms::Admin::AdminResourceCard::INPUT_CLASS,
|
|
41
|
+
name: "content_block[locale]" %>
|
|
42
|
+
</div>
|
|
135
43
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
44
|
+
<div>
|
|
45
|
+
<%= f.label :title, class: RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>
|
|
46
|
+
<%= f.text_field :title, class: RubyCms::Admin::AdminResourceCard::INPUT_CLASS %>
|
|
47
|
+
</div>
|
|
140
48
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<%= f.
|
|
144
|
-
<%= f.rich_text_area :rich_content, class: "rounded-md border border-gray-200 bg-white text-sm shadow-sm focus:outline-none" %>
|
|
49
|
+
<div>
|
|
50
|
+
<%= f.label :content_type, class: RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>
|
|
51
|
+
<%= f.select :content_type, ::ContentBlock::CONTENT_TYPES, {}, class: RubyCms::Admin::AdminResourceCard::INPUT_CLASS %>
|
|
145
52
|
</div>
|
|
146
|
-
|
|
147
|
-
<div class="col-span-2
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
53
|
+
|
|
54
|
+
<div class="sm:col-span-2">
|
|
55
|
+
<%= f.label :content, class: RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>
|
|
56
|
+
<%= f.text_area :content,
|
|
57
|
+
rows: 5,
|
|
58
|
+
class: "#{RubyCms::Admin::AdminResourceCard::INPUT_CLASS} resize-y" %>
|
|
152
59
|
</div>
|
|
153
|
-
<% end %>
|
|
154
60
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
61
|
+
<% if ::ContentBlock.respond_to?(:action_text_available?) && ::ContentBlock.action_text_available? %>
|
|
62
|
+
<div class="sm:col-span-2">
|
|
63
|
+
<%= f.label :rich_content, class: RubyCms::Admin::AdminResourceCard::LABEL_CLASS %>
|
|
64
|
+
<%= f.rich_text_area :rich_content, class: "rounded-lg border border-border bg-background text-sm shadow-sm focus:outline-none" %>
|
|
65
|
+
</div>
|
|
66
|
+
<% else %>
|
|
67
|
+
<div class="sm:col-span-2 rounded-lg border border-border/60 bg-muted/30 p-4">
|
|
68
|
+
<p class="text-sm text-muted-foreground">
|
|
69
|
+
Rich text requires Action Text. Run:
|
|
70
|
+
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">bin/rails action_text:install</code>
|
|
71
|
+
and
|
|
72
|
+
<code class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">bin/rails db:migrate</code>.
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
<% end %>
|
|
158
76
|
</div>
|
|
77
|
+
|
|
78
|
+
<label class="inline-flex items-center gap-3 cursor-pointer">
|
|
79
|
+
<%= f.check_box :published, class: "size-4 rounded border-border text-primary focus:ring-primary/30 cursor-pointer" %>
|
|
80
|
+
<span class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.content_blocks.published", default: "Published") %></span>
|
|
81
|
+
</label>
|
|
159
82
|
</div>
|
|
160
|
-
|
|
83
|
+
|
|
84
|
+
<div class="<%= RubyCms::Admin::AdminResourceCard::ACTIONS_CLASS %>">
|
|
85
|
+
<%= link_to t("ruby_cms.admin.common.cancel", default: "Cancel"),
|
|
86
|
+
ruby_cms_admin_content_blocks_path,
|
|
87
|
+
class: RubyCms::Admin::AdminResourceCard::CANCEL_CLASS %>
|
|
88
|
+
<%= f.submit t("ruby_cms.admin.content_blocks.create", default: "Save"),
|
|
89
|
+
class: RubyCms::Admin::AdminResourceCard::SUBMIT_CLASS %>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
161
92
|
<% end %>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
<%= render RubyCms::Admin::AdminPageHeader.new(
|
|
2
|
+
title: t("ruby_cms.admin.content_blocks.new_title", default: "New content block"),
|
|
3
|
+
breadcrumbs: [
|
|
4
|
+
{ label: t("ruby_cms.admin.nav.admin", default: "Admin"), url: ruby_cms_admin_root_path },
|
|
5
|
+
{ label: t("ruby_cms.admin.nav.content_blocks", default: "Content Blocks"), url: ruby_cms_admin_content_blocks_path },
|
|
6
|
+
{ label: t("ruby_cms.admin.content_blocks.new_title", default: "New content block") }
|
|
7
|
+
]
|
|
8
|
+
) %>
|
|
2
9
|
|
|
3
10
|
<%= render "form" %>
|
|
4
|
-
|
|
5
|
-
<p class="mt-4"><%= link_to "Back", ruby_cms_admin_content_blocks_path, class: "text-blue-600 hover:text-blue-800" %></p>
|
|
@@ -78,28 +78,33 @@
|
|
|
78
78
|
|
|
79
79
|
<%# ── Locale Tabs ── %>
|
|
80
80
|
<% if @blocks_by_locale.present? %>
|
|
81
|
+
<% active_locale = @blocks_by_locale.key?(@content_block.locale.to_s) ? @content_block.locale.to_s : @blocks_by_locale.keys.first %>
|
|
81
82
|
<div class="rounded-xl border border-border/60 overflow-hidden" data-controller="ruby-cms--locale-tabs">
|
|
82
83
|
<div class="flex gap-1 p-2 bg-muted/30 border-b border-border/60" role="tablist">
|
|
83
|
-
<% @blocks_by_locale.
|
|
84
|
+
<% @blocks_by_locale.each do |locale_s, _| %>
|
|
85
|
+
<% selected = locale_s == active_locale %>
|
|
84
86
|
<button type="button"
|
|
85
87
|
role="tab"
|
|
86
|
-
aria-selected="<%=
|
|
88
|
+
aria-selected="<%= selected %>"
|
|
87
89
|
aria-controls="locale-panel-<%= locale_s %>"
|
|
88
90
|
id="locale-tab-<%= locale_s %>"
|
|
89
91
|
data-ruby-cms--locale-tabs-target="tab"
|
|
90
92
|
data-panel-id="locale-panel-<%= locale_s %>"
|
|
93
|
+
data-active-classes="bg-background text-primary shadow-sm ring-1 ring-border/60"
|
|
94
|
+
data-inactive-classes="bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
91
95
|
data-action="click->ruby-cms--locale-tabs#switchTab"
|
|
92
|
-
class="px-3 py-1.5 rounded-md text-xs font-medium border-none cursor-pointer transition-colors <%=
|
|
96
|
+
class="px-3 py-1.5 rounded-md text-xs font-medium border-none cursor-pointer transition-colors <%= selected ? 'bg-background text-primary shadow-sm ring-1 ring-border/60' : 'bg-transparent text-muted-foreground hover:bg-muted hover:text-foreground' %>">
|
|
93
97
|
<%= ruby_cms_locale_display_name(locale_s) %>
|
|
94
98
|
</button>
|
|
95
99
|
<% end %>
|
|
96
100
|
</div>
|
|
97
101
|
|
|
98
|
-
<% @blocks_by_locale.
|
|
102
|
+
<% @blocks_by_locale.each do |locale_s, block| %>
|
|
103
|
+
<% selected = locale_s == active_locale %>
|
|
99
104
|
<div id="locale-panel-<%= locale_s %>"
|
|
100
105
|
role="tabpanel"
|
|
101
106
|
aria-labelledby="locale-tab-<%= locale_s %>"
|
|
102
|
-
class="p-5 <%=
|
|
107
|
+
class="p-5 <%= selected ? '' : 'hidden' %>"
|
|
103
108
|
data-locale-panel>
|
|
104
109
|
|
|
105
110
|
<%= hidden_field_tag "content_block[locales][#{locale_s}][locale]", locale_s %>
|
|
@@ -204,8 +209,8 @@
|
|
|
204
209
|
<div class="space-y-3">
|
|
205
210
|
<% @blocks_by_locale.each do |locale_s, block| %>
|
|
206
211
|
<% next if block.title.blank? && block.content_body.blank? %>
|
|
207
|
-
<div class="
|
|
208
|
-
<span class="
|
|
212
|
+
<div class="py-1">
|
|
213
|
+
<span class="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"><%= ruby_cms_locale_display_name(locale_s) %></span>
|
|
209
214
|
<% if block.title.present? %>
|
|
210
215
|
<p class="text-xs font-medium text-foreground"><%= block.title %></p>
|
|
211
216
|
<% end %>
|
data/config/locales/en.yml
CHANGED
|
@@ -14,7 +14,20 @@ en:
|
|
|
14
14
|
|
|
15
15
|
ruby_cms:
|
|
16
16
|
nav:
|
|
17
|
+
locale: "Locale"
|
|
17
18
|
settings: "Settings"
|
|
19
|
+
view_site: "View site"
|
|
20
|
+
logout: "Logout"
|
|
21
|
+
logout_confirm: "Are you sure you want to logout?"
|
|
22
|
+
items:
|
|
23
|
+
dashboard: "Dashboard"
|
|
24
|
+
visual_editor: "Visual editor"
|
|
25
|
+
content_blocks: "Content blocks"
|
|
26
|
+
analytics: "Analytics"
|
|
27
|
+
permissions: "Permissions"
|
|
28
|
+
visitor_errors: "Visitor errors"
|
|
29
|
+
users: "Users"
|
|
30
|
+
settings: "Settings"
|
|
18
31
|
admin:
|
|
19
32
|
breadcrumb:
|
|
20
33
|
admin: "Admin"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
nl:
|
|
4
|
+
ruby_cms:
|
|
5
|
+
nav:
|
|
6
|
+
locale: "Taal"
|
|
7
|
+
settings: "Instellingen"
|
|
8
|
+
view_site: "Bekijk site"
|
|
9
|
+
logout: "Uitloggen"
|
|
10
|
+
logout_confirm: "Weet je zeker dat je wilt uitloggen?"
|
|
11
|
+
items:
|
|
12
|
+
dashboard: "Dashboard"
|
|
13
|
+
visual_editor: "Visuele editor"
|
|
14
|
+
content_blocks: "Contentblokken"
|
|
15
|
+
analytics: "Analytics"
|
|
16
|
+
permissions: "Rechten"
|
|
17
|
+
visitor_errors: "Bezoekersfouten"
|
|
18
|
+
users: "Gebruikers"
|
|
19
|
+
settings: "Instellingen"
|
|
20
|
+
admin:
|
|
21
|
+
breadcrumb:
|
|
22
|
+
admin: "Beheer"
|
|
23
|
+
base:
|
|
24
|
+
authentication_required: "Authenticatie vereist."
|
|
25
|
+
not_authorized: "Niet geautoriseerd."
|
|
26
|
+
locales:
|
|
27
|
+
en: "Engels"
|
|
28
|
+
nl: "Nederlands"
|
|
29
|
+
de: "Duits"
|
|
30
|
+
fr: "Frans"
|
|
31
|
+
es: "Spaans"
|
|
32
|
+
content_blocks:
|
|
33
|
+
created: "Contentblok aangemaakt."
|
|
34
|
+
updated: "Contentblok bijgewerkt."
|
|
35
|
+
deleted: "Contentblok verwijderd."
|
|
36
|
+
select_locale: "Selecteer taal"
|
|
37
|
+
content_type: "Inhoudstype"
|
|
38
|
+
title: "Titel"
|
|
39
|
+
content: "Inhoud"
|
|
40
|
+
rich_content: "Rich content"
|
|
41
|
+
published: "Gepubliceerd"
|
|
@@ -260,11 +260,17 @@ module RubyCms
|
|
|
260
260
|
def install_action_text
|
|
261
261
|
migrate_dir = Rails.root.join("db/migrate")
|
|
262
262
|
return unless migrate_dir.directory?
|
|
263
|
-
return if action_text_already_installed?(migrate_dir)
|
|
264
263
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
if action_text_already_installed?(migrate_dir)
|
|
265
|
+
say "ℹ Task action_text: Existing Action Text setup detected. Skipping action_text:install.",
|
|
266
|
+
:cyan
|
|
267
|
+
else
|
|
268
|
+
say "ℹ Task action_text: Installing Action Text for rich text/image content blocks.", :cyan
|
|
269
|
+
run "bin/rails action_text:install"
|
|
270
|
+
say "✓ Task action_text: Installed Action Text", :green
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
configure_action_text_assets
|
|
268
274
|
rescue StandardError => e
|
|
269
275
|
say "⚠ Task action_text: Could not install: #{e.message}. Rich text will be disabled.",
|
|
270
276
|
:yellow
|
|
@@ -380,6 +386,63 @@ module RubyCms
|
|
|
380
386
|
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
|
381
387
|
false
|
|
382
388
|
end
|
|
389
|
+
|
|
390
|
+
def configure_action_text_assets
|
|
391
|
+
add_action_text_importmap_pins
|
|
392
|
+
add_action_text_imports_to_admin_entrypoint
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def add_action_text_importmap_pins
|
|
396
|
+
importmap_path = Rails.root.join("config/importmap.rb")
|
|
397
|
+
return unless File.exist?(importmap_path)
|
|
398
|
+
|
|
399
|
+
content = File.read(importmap_path)
|
|
400
|
+
lines_to_add = []
|
|
401
|
+
lines_to_add << %(pin "trix") unless content.include?('pin "trix"')
|
|
402
|
+
unless content.include?('pin "@rails/actiontext"')
|
|
403
|
+
lines_to_add << %(pin "@rails/actiontext", to: "actiontext.esm.js")
|
|
404
|
+
end
|
|
405
|
+
return if lines_to_add.empty?
|
|
406
|
+
|
|
407
|
+
inject_into_file importmap_path.to_s, before: /^end/ do
|
|
408
|
+
"\n # RubyCMS rich text (Action Text + Trix)\n #{lines_to_add.join("\n ")}\n"
|
|
409
|
+
end
|
|
410
|
+
say "✓ Task action_text/importmap: Added trix/actiontext pins to importmap.rb.", :green
|
|
411
|
+
rescue StandardError => e
|
|
412
|
+
say "⚠ Task action_text/importmap: Could not add pins: #{e.message}. " \
|
|
413
|
+
"Add pins for trix and @rails/actiontext manually.",
|
|
414
|
+
:yellow
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def add_action_text_imports_to_admin_entrypoint
|
|
418
|
+
admin_js_path = Rails.root.join("app/javascript/admin.js")
|
|
419
|
+
content = File.exist?(admin_js_path) ? File.read(admin_js_path) : ""
|
|
420
|
+
|
|
421
|
+
lines_to_add = []
|
|
422
|
+
lines_to_add << %(import "trix") unless content.include?('import "trix"') || content.include?("import 'trix'")
|
|
423
|
+
unless content.include?('import "@rails/actiontext"') ||
|
|
424
|
+
content.include?("import '@rails/actiontext'")
|
|
425
|
+
lines_to_add << %(import "@rails/actiontext")
|
|
426
|
+
end
|
|
427
|
+
return if lines_to_add.empty?
|
|
428
|
+
|
|
429
|
+
if File.exist?(admin_js_path)
|
|
430
|
+
append_to_file admin_js_path.to_s, "\n#{lines_to_add.join("\n")}\n"
|
|
431
|
+
say "✓ Task action_text/js: Added Action Text imports to app/javascript/admin.js.", :green
|
|
432
|
+
else
|
|
433
|
+
content = <<~JS
|
|
434
|
+
// RubyCMS admin entrypoint
|
|
435
|
+
import "trix"
|
|
436
|
+
import "@rails/actiontext"
|
|
437
|
+
JS
|
|
438
|
+
File.write(admin_js_path, content)
|
|
439
|
+
say "✓ Task action_text/js: Created app/javascript/admin.js with Action Text imports.", :green
|
|
440
|
+
end
|
|
441
|
+
rescue StandardError => e
|
|
442
|
+
say "⚠ Task action_text/js: Could not update admin.js: #{e.message}. " \
|
|
443
|
+
"Import trix and @rails/actiontext manually.",
|
|
444
|
+
:yellow
|
|
445
|
+
end
|
|
383
446
|
end
|
|
384
447
|
|
|
385
448
|
def install_tailwind
|
data/lib/ruby_cms/version.rb
CHANGED
data/lib/ruby_cms.rb
CHANGED
|
@@ -100,7 +100,7 @@ module RubyCms
|
|
|
100
100
|
list = nav_registry
|
|
101
101
|
.select {|item| nav_entry_visible?(item, view_context:, user:) }
|
|
102
102
|
.sort_by {|item| nav_sort_tuple(item) }
|
|
103
|
-
apply_nav_order(list)
|
|
103
|
+
localize_nav_labels(apply_nav_order(list))
|
|
104
104
|
rescue StandardError => e
|
|
105
105
|
Rails.logger.error("[RubyCMS] Error filtering navigation: #{e.message}") if defined?(Rails.logger)
|
|
106
106
|
nav_registry
|
|
@@ -131,6 +131,13 @@ module RubyCms
|
|
|
131
131
|
[priority, item[:order] || 1000, item[:label].to_s]
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
+
def localize_nav_labels(list)
|
|
135
|
+
list.map do |item|
|
|
136
|
+
translated = I18n.t("ruby_cms.nav.items.#{item[:key]}", default: item[:label].to_s)
|
|
137
|
+
item.merge(label: translated)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
134
141
|
def nav_section_priority(section)
|
|
135
142
|
section.to_s == NAV_SECTION_MAIN ? 0 : 2
|
|
136
143
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_cms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Codebyjob
|
|
@@ -174,6 +174,7 @@ files:
|
|
|
174
174
|
- config/database.yml
|
|
175
175
|
- config/importmap.rb
|
|
176
176
|
- config/locales/en.yml
|
|
177
|
+
- config/locales/nl.yml
|
|
177
178
|
- config/routes.rb
|
|
178
179
|
- db/migrate/20260125000001_create_ruby_cms_permissions.rb
|
|
179
180
|
- db/migrate/20260125000002_create_ruby_cms_user_permissions.rb
|