ruby_cms 0.1.1 → 0.1.2
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 +5 -0
- data/README.md +68 -164
- data/app/components/ruby_cms/admin/admin_page.rb +19 -19
- data/app/components/ruby_cms/admin/admin_page_header.rb +81 -0
- data/app/components/ruby_cms/admin/admin_resource_card.rb +55 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +4 -4
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +5 -5
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_body.rb +1 -1
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_cell.rb +15 -13
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_head.rb +13 -11
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +9 -9
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header.rb +2 -2
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +8 -8
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_pagination.rb +9 -9
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_row.rb +3 -4
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +25 -24
- data/app/controllers/ruby_cms/admin/base_controller.rb +10 -4
- data/app/controllers/ruby_cms/admin/content_blocks_controller.rb +4 -3
- data/app/controllers/ruby_cms/admin/locale_controller.rb +2 -1
- data/app/controllers/ruby_cms/admin/user_permissions_controller.rb +25 -7
- data/app/helpers/ruby_cms/settings_helper.rb +19 -9
- data/app/javascript/controllers/ruby_cms/bulk_action_table_controller.js +53 -12
- data/app/models/ruby_cms/permission.rb +38 -9
- data/app/models/ruby_cms/permittable.rb +0 -2
- data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +2 -2
- data/app/views/layouts/ruby_cms/admin.html.erb +13 -17
- data/app/views/ruby_cms/admin/content_blocks/index.html.erb +0 -11
- data/app/views/ruby_cms/admin/content_blocks/show.html.erb +204 -85
- data/app/views/ruby_cms/admin/settings/index.html.erb +214 -175
- data/app/views/ruby_cms/admin/user_permissions/index.html.erb +32 -2
- data/app/views/ruby_cms/admin/users/_row.html.erb +4 -1
- data/config/locales/en.yml +4 -0
- data/lib/ruby_cms/engine.rb +20 -12
- data/lib/ruby_cms/version.rb +1 -1
- data/lib/ruby_cms.rb +24 -0
- metadata +4 -3
- data/app/views/ruby_cms/admin/content_blocks/edit.html.erb +0 -17
|
@@ -19,20 +19,22 @@ module RubyCms
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def view_template
|
|
22
|
-
td(class: "w-
|
|
22
|
+
td(class: "w-10 px-4 py-3",
|
|
23
23
|
data: { action: "click->#{@controller_name}#stopPropagation" }) do
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
div(class: "inline-flex items-center justify-center") do
|
|
25
|
+
input(
|
|
26
|
+
type: "checkbox",
|
|
27
|
+
role: "checkbox",
|
|
28
|
+
value: @item_id,
|
|
29
|
+
class: "size-4 rounded border-border/80 text-primary focus:ring-primary/30 focus:ring-offset-0 cursor-pointer transition-colors",
|
|
30
|
+
data: {
|
|
31
|
+
"#{@controller_name}-target": "itemCheckbox",
|
|
32
|
+
item_id: @item_id,
|
|
33
|
+
action: "change->#{@controller_name}#updateSelection"
|
|
34
|
+
},
|
|
35
|
+
aria_label: "Select row"
|
|
36
|
+
)
|
|
37
|
+
end
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
end
|
|
@@ -14,17 +14,19 @@ module RubyCms
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def view_template
|
|
17
|
-
th(class: "w-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
th(class: "w-10 px-4 py-3") do
|
|
18
|
+
div(class: "inline-flex items-center justify-center") do
|
|
19
|
+
input(
|
|
20
|
+
type: "checkbox",
|
|
21
|
+
role: "checkbox",
|
|
22
|
+
class: "size-4 rounded border-border/80 text-primary focus:ring-primary/30 focus:ring-offset-0 cursor-pointer transition-colors",
|
|
23
|
+
data: {
|
|
24
|
+
"#{@controller_name}-target": "selectAllCheckbox",
|
|
25
|
+
action: "change->#{@controller_name}#toggleSelectAll"
|
|
26
|
+
},
|
|
27
|
+
aria_label: "Select all"
|
|
28
|
+
)
|
|
29
|
+
end
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
end
|
|
@@ -63,7 +63,7 @@ module RubyCms
|
|
|
63
63
|
|
|
64
64
|
def dialog_content_attributes
|
|
65
65
|
{
|
|
66
|
-
class: "w-full max-w-md rounded-
|
|
66
|
+
class: "w-full max-w-md rounded-xl border border-border/60 bg-white p-6 shadow-lg ring-1 ring-black/[0.03]",
|
|
67
67
|
data: {
|
|
68
68
|
"#{@controller_name}-target": "dialogContent"
|
|
69
69
|
},
|
|
@@ -74,8 +74,8 @@ module RubyCms
|
|
|
74
74
|
def render_close_button
|
|
75
75
|
button(
|
|
76
76
|
type: "button",
|
|
77
|
-
class: "inline-flex
|
|
78
|
-
"hover:bg-
|
|
77
|
+
class: "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground " \
|
|
78
|
+
"hover:bg-muted hover:text-foreground transition-colors",
|
|
79
79
|
data: {
|
|
80
80
|
action: "click->#{@controller_name}#closeDialog"
|
|
81
81
|
},
|
|
@@ -119,7 +119,7 @@ module RubyCms
|
|
|
119
119
|
def render_header
|
|
120
120
|
h3(
|
|
121
121
|
id: "dialog-title",
|
|
122
|
-
class: "text-base font-semibold text-
|
|
122
|
+
class: "text-base font-semibold text-foreground",
|
|
123
123
|
data: {
|
|
124
124
|
"#{@controller_name}-target": "dialogTitle"
|
|
125
125
|
}
|
|
@@ -128,7 +128,7 @@ module RubyCms
|
|
|
128
128
|
|
|
129
129
|
def render_message
|
|
130
130
|
div(
|
|
131
|
-
class: "mt-3 text-sm text-
|
|
131
|
+
class: "mt-3 text-sm text-muted-foreground space-y-1",
|
|
132
132
|
data: {
|
|
133
133
|
"#{@controller_name}-target": "dialogMessage"
|
|
134
134
|
}
|
|
@@ -149,8 +149,8 @@ module RubyCms
|
|
|
149
149
|
button(
|
|
150
150
|
type: "button",
|
|
151
151
|
class: "inline-flex h-9 items-center justify-center rounded-md border " \
|
|
152
|
-
"border-
|
|
153
|
-
"shadow-sm hover:bg-
|
|
152
|
+
"border-border bg-white px-4 text-sm font-medium text-foreground " \
|
|
153
|
+
"shadow-sm hover:bg-muted transition-colors",
|
|
154
154
|
data: {
|
|
155
155
|
action: "click->#{@controller_name}#closeDialog"
|
|
156
156
|
}
|
|
@@ -160,8 +160,8 @@ module RubyCms
|
|
|
160
160
|
def render_confirm_button
|
|
161
161
|
button(
|
|
162
162
|
type: "button",
|
|
163
|
-
class: "inline-flex h-9 items-center justify-center rounded-md bg-
|
|
164
|
-
"text-sm font-medium text-
|
|
163
|
+
class: "inline-flex h-9 items-center justify-center rounded-md bg-destructive px-4 " \
|
|
164
|
+
"text-sm font-medium text-destructive-foreground shadow-sm hover:bg-destructive/90 transition-colors",
|
|
165
165
|
data: {
|
|
166
166
|
"#{@controller_name}-target": "dialogConfirmButton",
|
|
167
167
|
action: "click->#{@controller_name}#confirmAction"
|
|
@@ -22,7 +22,7 @@ module RubyCms
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def view_template
|
|
25
|
-
thead(class: "bg-
|
|
25
|
+
thead(class: "sticky top-0 z-20 bg-muted/40 [&_tr]:border-b [&_tr]:border-border/60") do
|
|
26
26
|
tr do
|
|
27
27
|
render_bulk_checkbox_header
|
|
28
28
|
render_table_headers
|
|
@@ -51,7 +51,7 @@ module RubyCms
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def th_base_classes
|
|
54
|
-
"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-
|
|
54
|
+
"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground"
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -31,7 +31,7 @@ module RubyCms
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def view_template
|
|
34
|
-
div(class: "px-
|
|
34
|
+
div(class: "px-5 py-3 border-b border-border/60 bg-white") do
|
|
35
35
|
div(class: "flex flex-wrap items-center justify-between gap-4") do
|
|
36
36
|
render_title_group if @title.present?
|
|
37
37
|
div(class: "flex items-center gap-2 flex-wrap") do
|
|
@@ -47,7 +47,7 @@ module RubyCms
|
|
|
47
47
|
|
|
48
48
|
def render_title_group
|
|
49
49
|
div(class: "min-w-0") do
|
|
50
|
-
h2(class: "text-sm font-semibold text-
|
|
50
|
+
h2(class: "text-sm font-semibold text-foreground") { @title }
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -88,7 +88,7 @@ module RubyCms
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def render_search_icon
|
|
91
|
-
span(class: "absolute left-3 text-
|
|
91
|
+
span(class: "absolute left-3 text-muted-foreground pointer-events-none") do
|
|
92
92
|
svg(class: "h-4 w-4", fill: "none", stroke: "currentColor",
|
|
93
93
|
viewBox: "0 0 24 24") do |s|
|
|
94
94
|
s.path(
|
|
@@ -106,8 +106,8 @@ module RubyCms
|
|
|
106
106
|
type: "search",
|
|
107
107
|
name: @search_param,
|
|
108
108
|
placeholder: "Search",
|
|
109
|
-
class: "h-9 w-full sm:w-72 rounded-md border border-
|
|
110
|
-
"pr-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-
|
|
109
|
+
class: "h-9 w-full sm:w-72 rounded-md border border-border bg-white pl-9 " \
|
|
110
|
+
"pr-3 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/20",
|
|
111
111
|
value: search_value,
|
|
112
112
|
data: { action: "input->turbo-frame#submit" }
|
|
113
113
|
)
|
|
@@ -125,15 +125,15 @@ module RubyCms
|
|
|
125
125
|
{
|
|
126
126
|
"blue" => "text-blue-600 hover:bg-blue-50",
|
|
127
127
|
"green" => "text-emerald-600 hover:bg-emerald-50",
|
|
128
|
-
"red" => "text-
|
|
128
|
+
"red" => "text-destructive hover:bg-destructive/10",
|
|
129
129
|
"purple" => "text-violet-600 hover:bg-violet-50",
|
|
130
|
-
"gray" => "text-
|
|
130
|
+
"gray" => "text-muted-foreground hover:bg-muted",
|
|
131
131
|
"teal" => "text-teal-600 hover:bg-teal-50"
|
|
132
132
|
}
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
def icon_button_base_classes
|
|
136
|
-
"inline-flex items-center justify-center
|
|
136
|
+
"inline-flex items-center justify-center size-9 rounded-md border border-border " \
|
|
137
137
|
"bg-white shadow-sm transition-colors"
|
|
138
138
|
end
|
|
139
139
|
|
|
@@ -24,7 +24,7 @@ module RubyCms
|
|
|
24
24
|
def view_template
|
|
25
25
|
return unless @pagination[:total_pages] && @pagination[:total_pages] > 1
|
|
26
26
|
|
|
27
|
-
div(class: "px-
|
|
27
|
+
div(class: "px-5 py-3 flex items-center justify-between gap-4 bg-muted/30") do
|
|
28
28
|
render_pagination_info
|
|
29
29
|
render_pagination_controls
|
|
30
30
|
end
|
|
@@ -35,7 +35,7 @@ module RubyCms
|
|
|
35
35
|
def render_pagination_info
|
|
36
36
|
return unless @pagination[:start_item] && @pagination[:end_item] && @pagination[:total_count]
|
|
37
37
|
|
|
38
|
-
div(class: "text-
|
|
38
|
+
div(class: "text-xs text-muted-foreground tabular-nums") { pagination_info_text }
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def pagination_info_text
|
|
@@ -75,14 +75,14 @@ module RubyCms
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def pagination_button_classes
|
|
78
|
-
"inline-flex h-
|
|
79
|
-
"bg-white px-3 text-
|
|
78
|
+
"inline-flex h-8 items-center justify-center rounded-md border border-border " \
|
|
79
|
+
"bg-white px-3 text-xs font-medium text-foreground shadow-sm hover:bg-muted " \
|
|
80
80
|
"transition-colors"
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def pagination_button_disabled_classes
|
|
84
|
-
"inline-flex h-
|
|
85
|
-
"bg-white px-3 text-
|
|
84
|
+
"inline-flex h-8 items-center justify-center rounded-md border border-border " \
|
|
85
|
+
"bg-white px-3 text-xs font-medium text-muted-foreground opacity-50 cursor-not-allowed"
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def render_next_button
|
|
@@ -127,7 +127,7 @@ module RubyCms
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def render_ellipsis
|
|
130
|
-
span(class: "px-2 text-
|
|
130
|
+
span(class: "px-2 py-1.5 text-xs font-medium text-muted-foreground select-none") { "…" }
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def render_current_page(page_num)
|
|
@@ -144,8 +144,8 @@ module RubyCms
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def current_page_classes
|
|
147
|
-
"inline-flex h-
|
|
148
|
-
"bg-
|
|
147
|
+
"inline-flex h-8 items-center justify-center rounded-md border border-primary " \
|
|
148
|
+
"bg-primary px-3 text-xs font-medium text-primary-foreground shadow-sm pointer-events-none"
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
def calculate_pages_to_show(current_page, total_pages)
|
|
@@ -73,7 +73,7 @@ module RubyCms
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def build_row_classes
|
|
76
|
-
classes = ["hover:bg-
|
|
76
|
+
classes = ["border-b border-border/40 hover:bg-muted/50 transition-colors"]
|
|
77
77
|
classes << @row_class if @row_class
|
|
78
78
|
classes << "cursor-pointer" if @click_url
|
|
79
79
|
build_classes(classes)
|
|
@@ -84,9 +84,8 @@ module RubyCms
|
|
|
84
84
|
attrs[:item_id] = @data[:item_id] if @data[:item_id]
|
|
85
85
|
|
|
86
86
|
if @click_url
|
|
87
|
-
attrs[:
|
|
88
|
-
attrs[:
|
|
89
|
-
attrs[:action] = "click->clickable-row#navigate"
|
|
87
|
+
attrs[:click_url] = @click_url
|
|
88
|
+
attrs[:action] = "click->#{@controller_name}#rowClick"
|
|
90
89
|
end
|
|
91
90
|
|
|
92
91
|
attrs
|
|
@@ -26,15 +26,12 @@ module RubyCms
|
|
|
26
26
|
|
|
27
27
|
def view_template
|
|
28
28
|
div(
|
|
29
|
-
class: "hidden px-
|
|
29
|
+
class: "hidden bg-primary/5 border-t border-border/60 px-5 py-3 backdrop-blur-sm",
|
|
30
30
|
data: {
|
|
31
31
|
"#{@controller_name}-target": "bulkBar"
|
|
32
32
|
}
|
|
33
33
|
) do
|
|
34
|
-
div(
|
|
35
|
-
class: "flex items-center justify-between gap-3 rounded-lg border " \
|
|
36
|
-
"border-gray-200/80 bg-white px-4 py-3 shadow-sm"
|
|
37
|
-
) do
|
|
34
|
+
div(class: "flex items-center justify-between gap-4 max-w-full") do
|
|
38
35
|
render_selection_info
|
|
39
36
|
render_action_buttons
|
|
40
37
|
end
|
|
@@ -44,17 +41,22 @@ module RubyCms
|
|
|
44
41
|
private
|
|
45
42
|
|
|
46
43
|
def render_selection_info
|
|
47
|
-
div(class: "flex items-center gap-3
|
|
48
|
-
|
|
49
|
-
class: "
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
div(class: "flex items-center gap-3") do
|
|
45
|
+
div(class: "flex items-center gap-2") do
|
|
46
|
+
div(class: "size-6 rounded-full bg-primary/10 flex items-center justify-center") do
|
|
47
|
+
span(
|
|
48
|
+
class: "text-xs font-bold text-primary tabular-nums",
|
|
49
|
+
data: {
|
|
50
|
+
"#{@controller_name}-target": "selectedCount"
|
|
51
|
+
}
|
|
52
|
+
) { "0" }
|
|
53
|
+
end
|
|
54
|
+
span(class: "text-sm font-medium text-foreground") { "selected" }
|
|
55
|
+
end
|
|
56
|
+
div(class: "h-4 w-px bg-border/60")
|
|
54
57
|
button(
|
|
55
58
|
type: "button",
|
|
56
|
-
class: "text-
|
|
57
|
-
"transition-colors",
|
|
59
|
+
class: "text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
58
60
|
data: {
|
|
59
61
|
"#{@controller_name}-target": "selectAllButton",
|
|
60
62
|
action: "click->#{@controller_name}#selectAll"
|
|
@@ -62,17 +64,16 @@ module RubyCms
|
|
|
62
64
|
) { "Select all" }
|
|
63
65
|
button(
|
|
64
66
|
type: "button",
|
|
65
|
-
class: "text-
|
|
66
|
-
"transition-colors",
|
|
67
|
+
class: "text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
67
68
|
data: {
|
|
68
69
|
action: "click->#{@controller_name}#clearSelection"
|
|
69
70
|
}
|
|
70
|
-
) { "Clear
|
|
71
|
+
) { "Clear" }
|
|
71
72
|
end
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
def render_action_buttons
|
|
75
|
-
div(class: "flex items-center gap-2
|
|
76
|
+
div(class: "flex items-center gap-2") do
|
|
76
77
|
@bulk_action_buttons.each do |button_config|
|
|
77
78
|
render_custom_action_button(button_config)
|
|
78
79
|
end
|
|
@@ -93,9 +94,9 @@ module RubyCms
|
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def build_button_class(config)
|
|
96
|
-
base = "inline-flex items-center justify-center rounded-md border border-
|
|
97
|
-
"bg-white px-3 py-
|
|
98
|
-
"hover:bg-
|
|
97
|
+
base = "inline-flex items-center justify-center rounded-md border border-border " \
|
|
98
|
+
"bg-white px-3 py-1.5 text-sm font-medium text-foreground shadow-sm " \
|
|
99
|
+
"hover:bg-muted transition-colors"
|
|
99
100
|
config[:class].present? ? "#{base} #{config[:class]}" : base
|
|
100
101
|
end
|
|
101
102
|
|
|
@@ -118,9 +119,9 @@ module RubyCms
|
|
|
118
119
|
def render_delete_button
|
|
119
120
|
button(
|
|
120
121
|
type: "button",
|
|
121
|
-
class: "inline-flex items-center justify-center rounded-md border border-
|
|
122
|
-
"bg-white px-3 py-
|
|
123
|
-
"hover:bg-
|
|
122
|
+
class: "inline-flex items-center justify-center rounded-md border border-destructive/30 " \
|
|
123
|
+
"bg-white px-3 py-1.5 text-sm font-medium text-destructive shadow-sm " \
|
|
124
|
+
"hover:bg-destructive/10 transition-colors",
|
|
124
125
|
data: {
|
|
125
126
|
action: "click->#{@controller_name}#showActionDialog",
|
|
126
127
|
action_name: "delete",
|
|
@@ -86,10 +86,16 @@ module RubyCms
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def set_cms_locale
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
locale = session[:ruby_cms_locale].presence || session[:admin_locale].presence
|
|
90
|
+
return if locale.blank?
|
|
91
|
+
|
|
92
|
+
locale = locale.to_sym
|
|
93
|
+
return unless I18n.available_locales.include?(locale)
|
|
94
|
+
|
|
95
|
+
# Keep both session keys in sync for host app + engine controllers.
|
|
96
|
+
session[:ruby_cms_locale] = locale
|
|
97
|
+
session[:admin_locale] = locale
|
|
98
|
+
I18n.locale = locale
|
|
93
99
|
end
|
|
94
100
|
|
|
95
101
|
# Resolve parameter key for model params
|
|
@@ -23,7 +23,7 @@ module RubyCms
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def show
|
|
26
|
-
@blocks_by_locale =
|
|
26
|
+
@blocks_by_locale = load_blocks_by_locale_for_edit
|
|
27
27
|
respond_with_block(@content_block)
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -37,6 +37,7 @@ module RubyCms
|
|
|
37
37
|
|
|
38
38
|
def edit
|
|
39
39
|
@blocks_by_locale = load_blocks_by_locale_for_edit
|
|
40
|
+
render :show
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def create
|
|
@@ -135,12 +136,12 @@ module RubyCms
|
|
|
135
136
|
def handle_locale_update_errors(errors)
|
|
136
137
|
@content_block.errors.add(:base, errors.join("; "))
|
|
137
138
|
@blocks_by_locale = load_blocks_by_locale_for_edit
|
|
138
|
-
render :
|
|
139
|
+
render :show, status: :unprocessable_content
|
|
139
140
|
end
|
|
140
141
|
|
|
141
142
|
def update_single_block
|
|
142
143
|
@content_block.record_update_by(current_user_cms)
|
|
143
|
-
save_and_respond(@content_block, :
|
|
144
|
+
save_and_respond(@content_block, :show)
|
|
144
145
|
end
|
|
145
146
|
|
|
146
147
|
def save_and_respond(block, failure_view)
|
|
@@ -7,9 +7,10 @@ module RubyCms
|
|
|
7
7
|
before_action :ensure_authenticated, only: [:update]
|
|
8
8
|
|
|
9
9
|
def update
|
|
10
|
-
locale = params[:locale].to_sym
|
|
10
|
+
locale = params[:locale].to_s.presence&.to_sym
|
|
11
11
|
if I18n.available_locales.include?(locale)
|
|
12
12
|
session[:ruby_cms_locale] = locale
|
|
13
|
+
session[:admin_locale] = locale
|
|
13
14
|
I18n.locale = locale
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -17,14 +17,11 @@ module RubyCms
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def create
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
if params[:template].present?
|
|
21
|
+
apply_template
|
|
22
|
+
else
|
|
23
|
+
grant_individual_permission
|
|
24
24
|
end
|
|
25
|
-
rescue ActiveRecord::RecordInvalid
|
|
26
|
-
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
27
|
-
alert: t("ruby_cms.admin.user_permissions.could_not_grant")
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
def destroy
|
|
@@ -54,6 +51,27 @@ module RubyCms
|
|
|
54
51
|
def user_class
|
|
55
52
|
Object.const_get(Rails.application.config.ruby_cms.user_class_name.presence || "User")
|
|
56
53
|
end
|
|
54
|
+
|
|
55
|
+
def apply_template
|
|
56
|
+
template_key = params[:template].to_sym
|
|
57
|
+
RubyCms::UserPermission.where(user: @user).destroy_all
|
|
58
|
+
RubyCms::Permission.apply_template!(@user, template_key)
|
|
59
|
+
@user.make_admin! if @user.respond_to?(:make_admin!) && !@user.admin?
|
|
60
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
61
|
+
notice: "Applied #{RubyCms::Permission.templates.dig(template_key, :label)} template."
|
|
62
|
+
rescue ArgumentError => e
|
|
63
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user), alert: e.message
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def grant_individual_permission
|
|
67
|
+
permission = RubyCms::Permission.find(params[:permission_id])
|
|
68
|
+
RubyCms::UserPermission.find_or_create_by!(user: @user, permission: permission)
|
|
69
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
70
|
+
notice: t("ruby_cms.admin.user_permissions.granted")
|
|
71
|
+
rescue ActiveRecord::RecordInvalid
|
|
72
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
73
|
+
alert: t("ruby_cms.admin.user_permissions.could_not_grant")
|
|
74
|
+
end
|
|
57
75
|
end
|
|
58
76
|
end
|
|
59
77
|
end
|
|
@@ -34,6 +34,16 @@ module RubyCms
|
|
|
34
34
|
key.tr("_", " ").humanize
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# SVG path fragment for a nav item (matches sidebar), or nil.
|
|
38
|
+
def settings_nav_visibility_icon(entry)
|
|
39
|
+
key_str = entry.key.to_s
|
|
40
|
+
return nil unless key_str.start_with?("nav_show_")
|
|
41
|
+
|
|
42
|
+
nav_key = key_str.delete_prefix("nav_show_")
|
|
43
|
+
item = RubyCms.nav_registry.find { |e| e[:key].to_s == nav_key }
|
|
44
|
+
item&.dig(:icon)
|
|
45
|
+
end
|
|
46
|
+
|
|
37
47
|
def render_setting_field(entry:, value:, tab:)
|
|
38
48
|
case entry.type.to_sym
|
|
39
49
|
when :integer
|
|
@@ -50,13 +60,13 @@ module RubyCms
|
|
|
50
60
|
private
|
|
51
61
|
|
|
52
62
|
def input_base_classes
|
|
53
|
-
"w-full h-9 rounded-
|
|
54
|
-
"shadow-sm focus:outline-none focus:ring-2 focus:ring-
|
|
63
|
+
"w-full h-9 rounded-lg border border-border bg-background px-3 text-sm text-foreground " \
|
|
64
|
+
"shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
def textarea_base_classes
|
|
58
|
-
"w-full rounded-
|
|
59
|
-
"shadow-sm focus:outline-none focus:ring-2 focus:ring-
|
|
68
|
+
"w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground " \
|
|
69
|
+
"shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
|
|
60
70
|
end
|
|
61
71
|
|
|
62
72
|
def render_integer_setting_field(entry:, value:, tab:)
|
|
@@ -88,18 +98,18 @@ module RubyCms
|
|
|
88
98
|
:label,
|
|
89
99
|
"",
|
|
90
100
|
for: setting_input_id(entry),
|
|
91
|
-
class: "relative inline-flex h-
|
|
92
|
-
"bg-
|
|
101
|
+
class: "relative inline-flex h-7 w-12 cursor-pointer items-center rounded-full " \
|
|
102
|
+
"border border-border bg-muted transition-colors peer-checked:bg-primary peer-checked:border-primary"
|
|
93
103
|
) do
|
|
94
104
|
content_tag(
|
|
95
105
|
:span,
|
|
96
106
|
"",
|
|
97
|
-
class: "inline-block h-5 w-5 transform rounded-full bg-
|
|
98
|
-
"translate-x-
|
|
107
|
+
class: "inline-block h-5 w-5 transform rounded-full bg-background shadow-sm ring-1 ring-border transition " \
|
|
108
|
+
"translate-x-1 peer-checked:translate-x-6"
|
|
99
109
|
)
|
|
100
110
|
end
|
|
101
111
|
|
|
102
|
-
content_tag(:div, class: "inline-flex items-center") { hidden + checkbox + label }
|
|
112
|
+
content_tag(:div, class: "inline-flex items-center shrink-0") { hidden + checkbox + label }
|
|
103
113
|
end
|
|
104
114
|
|
|
105
115
|
def render_json_setting_field(entry:, value:, tab:)
|
|
@@ -112,8 +112,7 @@ export default class extends Controller {
|
|
|
112
112
|
|
|
113
113
|
if (count > 0) {
|
|
114
114
|
bulkBar.classList.remove("hidden");
|
|
115
|
-
|
|
116
|
-
selectedCount.textContent = `${count} ${itemName}${count === 1 ? "" : "s"} selected:`;
|
|
115
|
+
selectedCount.textContent = `${count}`;
|
|
117
116
|
} else {
|
|
118
117
|
bulkBar.classList.add("hidden");
|
|
119
118
|
}
|
|
@@ -143,10 +142,14 @@ export default class extends Controller {
|
|
|
143
142
|
const itemId = String(row.getAttribute("data-item-id") || "");
|
|
144
143
|
if (selectedIdsStr.includes(itemId)) {
|
|
145
144
|
row.setAttribute("data-state", "selected");
|
|
146
|
-
row.classList.add("bg-
|
|
145
|
+
row.classList.add("bg-primary/5");
|
|
146
|
+
row.classList.remove("hover:bg-muted/50");
|
|
147
147
|
} else {
|
|
148
148
|
row.removeAttribute("data-state");
|
|
149
|
-
row.classList.remove("bg-
|
|
149
|
+
row.classList.remove("bg-primary/5");
|
|
150
|
+
if (!row.classList.contains("hover:bg-muted/50")) {
|
|
151
|
+
row.classList.add("hover:bg-muted/50");
|
|
152
|
+
}
|
|
150
153
|
}
|
|
151
154
|
});
|
|
152
155
|
}
|
|
@@ -302,6 +305,41 @@ export default class extends Controller {
|
|
|
302
305
|
this.isProcessing = false;
|
|
303
306
|
}
|
|
304
307
|
|
|
308
|
+
rowClick(event) {
|
|
309
|
+
const row = event.currentTarget;
|
|
310
|
+
const target = event.target;
|
|
311
|
+
|
|
312
|
+
if (
|
|
313
|
+
target.closest('input[type="checkbox"]') ||
|
|
314
|
+
target.closest("button") ||
|
|
315
|
+
target.closest("a") ||
|
|
316
|
+
target.closest("[data-action*='stopPropagation']")
|
|
317
|
+
) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (event.ctrlKey || event.metaKey) {
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
const checkbox = row.querySelector(
|
|
324
|
+
'input[type="checkbox"][data-item-id]',
|
|
325
|
+
);
|
|
326
|
+
if (checkbox) {
|
|
327
|
+
checkbox.checked = !checkbox.checked;
|
|
328
|
+
this.updateBulkBar();
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const clickUrl = row.dataset.clickUrl;
|
|
334
|
+
if (clickUrl) {
|
|
335
|
+
if (window.Turbo) {
|
|
336
|
+
window.Turbo.visit(clickUrl);
|
|
337
|
+
} else {
|
|
338
|
+
window.location.href = clickUrl;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
305
343
|
stopPropagation(event) {
|
|
306
344
|
event.stopPropagation();
|
|
307
345
|
}
|
|
@@ -517,20 +555,23 @@ export default class extends Controller {
|
|
|
517
555
|
|
|
518
556
|
showNotification(message, type = "info") {
|
|
519
557
|
const toast = document.createElement("div");
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
558
|
+
const colorMap = {
|
|
559
|
+
success: "bg-emerald-600",
|
|
560
|
+
error: "bg-destructive",
|
|
561
|
+
info: "bg-primary",
|
|
562
|
+
};
|
|
563
|
+
toast.className = `fixed top-4 right-4 z-50 px-4 py-3 rounded-lg shadow-lg text-white text-sm max-w-sm animate-in slide-in-from-top-2 ${
|
|
564
|
+
colorMap[type] || colorMap.info
|
|
526
565
|
}`;
|
|
527
566
|
toast.textContent = message;
|
|
528
567
|
|
|
529
568
|
document.body.appendChild(toast);
|
|
530
569
|
|
|
531
570
|
setTimeout(() => {
|
|
532
|
-
toast.
|
|
533
|
-
|
|
571
|
+
toast.style.opacity = "0";
|
|
572
|
+
toast.style.transition = "opacity 200ms ease-out";
|
|
573
|
+
setTimeout(() => toast.remove(), 200);
|
|
574
|
+
}, 4000);
|
|
534
575
|
}
|
|
535
576
|
|
|
536
577
|
clearItemIdsFromUrl() {
|