ruby_cms 0.1.3.1 → 0.1.4
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 +6 -0
- data/README.md +2 -0
- data/app/controllers/ruby_cms/admin/visual_editor_controller.rb +12 -3
- data/app/services/ruby_cms/analytics/report.rb +35 -31
- data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +5 -4
- data/app/views/ruby_cms/admin/settings/index.html.erb +4 -4
- data/app/views/ruby_cms/admin/visual_editor/index.html.erb +20 -7
- data/lib/ruby_cms/version.rb +1 -1
- data/lib/tasks/admin.rake +3 -1
- data/log/test.log +3672 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5cb4cca0c59768b347f78d370c203428f9d663e26fe91a8bc6a7b63c3c14ac7
|
|
4
|
+
data.tar.gz: 706b81555013c29de1e4f4641f70f9a63c39d1231811421867f3beaf3f42073e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c175d5c394f5f66f5581c417cd6a6029c543319b91ffa55b5410fd62885cf0dfffa22b700594d05a801816f4adcf3df7046222637f46e9ae0f08b8bf81d81cd
|
|
7
|
+
data.tar.gz: d24e55dea55ae02cac49473e28a3ed0ac2d3ba7849fd4bc923ca0dab68046af86bf2a3e9f293ffe027f8988ed897c561d0e8043de60e90ac5fadcd8d4c999951
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.4] - 2026-03-23
|
|
4
|
+
|
|
5
|
+
- Improve visual editor and settings admin UX
|
|
6
|
+
- Update sidebar branding to use the new RubyCMS logo
|
|
7
|
+
- Refresh README with project logo
|
|
8
|
+
|
|
3
9
|
## [0.1.3] - 2026-03-23
|
|
4
10
|
|
|
5
11
|
- Update analytics pages (dashboard/detail views)
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# RubyCMS
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Reusable Rails engine for a CMS-style admin: permissions, admin UI shell, content blocks, and a visual editor.
|
|
4
6
|
|
|
5
7
|
Vision: your app owns product features (pages, models, business logic); RubyCMS manages content workflows and admin screens.
|
|
@@ -22,7 +22,7 @@ module RubyCms
|
|
|
22
22
|
return render_invalid_page unless template
|
|
23
23
|
|
|
24
24
|
load_preview_data(@page_key)
|
|
25
|
-
render template: template, layout: "
|
|
25
|
+
render template: template, layout: "admin/minimal"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def quick_update
|
|
@@ -186,7 +186,13 @@ module RubyCms
|
|
|
186
186
|
pages = {}
|
|
187
187
|
add_config_pages(pages)
|
|
188
188
|
add_auto_detected_pages(pages) if pages.empty?
|
|
189
|
-
pages
|
|
189
|
+
prioritize_home_page(pages)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def prioritize_home_page(pages)
|
|
193
|
+
return pages unless pages.key?("home")
|
|
194
|
+
|
|
195
|
+
{ "home" => pages["home"] }.merge(pages.except("home"))
|
|
190
196
|
end
|
|
191
197
|
|
|
192
198
|
def add_auto_detected_pages(pages)
|
|
@@ -273,7 +279,10 @@ module RubyCms
|
|
|
273
279
|
%w[
|
|
274
280
|
layouts shared mailers components
|
|
275
281
|
admin
|
|
276
|
-
].include?(dir_name) ||
|
|
282
|
+
].include?(dir_name) ||
|
|
283
|
+
dir_name.end_with?("_mailer") ||
|
|
284
|
+
dir_name.end_with?("_mailers") ||
|
|
285
|
+
relative_path.start_with?("admin")
|
|
277
286
|
end
|
|
278
287
|
|
|
279
288
|
def depth_exceeded?(relative_path)
|
|
@@ -25,33 +25,7 @@ module RubyCms
|
|
|
25
25
|
|
|
26
26
|
def dashboard_stats
|
|
27
27
|
Rails.cache.fetch(cache_key("dashboard"), expires_in: cache_duration) do
|
|
28
|
-
|
|
29
|
-
total_sessions = visits.distinct.count(:visit_token)
|
|
30
|
-
unique_visitors = visits.distinct.count(:visitor_token)
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
total_page_views: total_views,
|
|
34
|
-
unique_visitors: unique_visitors,
|
|
35
|
-
total_sessions: total_sessions,
|
|
36
|
-
pages_per_session: total_sessions.positive? ? (total_views.to_f / total_sessions).round(1) : 0,
|
|
37
|
-
bounce_rate: compute_bounce_rate,
|
|
38
|
-
new_visitor_percentage: compute_new_visitor_percentage,
|
|
39
|
-
avg_daily_views: days_in_range.positive? ? (total_views.to_f / days_in_range).round(0).to_i : 0,
|
|
40
|
-
popular_pages: popular_pages_data,
|
|
41
|
-
top_visitors: top_visitors_data,
|
|
42
|
-
hourly_activity: hourly_activity_data,
|
|
43
|
-
daily_activity: daily_activity_data,
|
|
44
|
-
daily_visitors: daily_visitors_data,
|
|
45
|
-
top_referrers: referrer_data,
|
|
46
|
-
landing_pages: landing_pages_data,
|
|
47
|
-
utm_sources: utm_sources_data,
|
|
48
|
-
browser_stats: visits.where.not(browser: [nil, ""]).group(:browser).count,
|
|
49
|
-
device_stats: visits.where.not(device_type: [nil, ""]).group(:device_type).count,
|
|
50
|
-
os_stats: visits.where.not(os: [nil, ""]).group(:os).count,
|
|
51
|
-
suspicious_activity: suspicious_activity_data,
|
|
52
|
-
recent_page_views: page_view_events.order(time: :desc).limit(recent_page_views_limit),
|
|
53
|
-
extra_cards: extra_cards_data
|
|
54
|
-
}
|
|
28
|
+
dashboard_stats_payload
|
|
55
29
|
end
|
|
56
30
|
end
|
|
57
31
|
|
|
@@ -352,7 +326,7 @@ module RubyCms
|
|
|
352
326
|
return 0 unless total.positive?
|
|
353
327
|
|
|
354
328
|
event_counts = page_view_events.group(:visit_id).count
|
|
355
|
-
single_page = event_counts.count {
|
|
329
|
+
single_page = event_counts.count {|_, c| c == 1 }
|
|
356
330
|
((single_page.to_f / total) * 100).round(1)
|
|
357
331
|
rescue StandardError
|
|
358
332
|
0
|
|
@@ -363,9 +337,9 @@ module RubyCms
|
|
|
363
337
|
return 0 unless total.positive?
|
|
364
338
|
|
|
365
339
|
returning_tokens = Ahoy::Visit
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
340
|
+
.where(started_at: ...@start_date)
|
|
341
|
+
.distinct
|
|
342
|
+
.pluck(:visitor_token)
|
|
369
343
|
|
|
370
344
|
new_count = visits.where.not(visitor_token: returning_tokens).distinct.count(:visitor_token)
|
|
371
345
|
((new_count.to_f / total) * 100).round(0).to_i
|
|
@@ -391,6 +365,36 @@ module RubyCms
|
|
|
391
365
|
RubyCms::Settings.get(:analytics_visitor_details_limit,
|
|
392
366
|
default: DEFAULT_VISITOR_DETAILS_LIMIT).to_i
|
|
393
367
|
end
|
|
368
|
+
|
|
369
|
+
def dashboard_stats_payload
|
|
370
|
+
total_views = page_view_events.count
|
|
371
|
+
total_sessions = visits.distinct.count(:visit_token)
|
|
372
|
+
unique_visitors = visits.distinct.count(:visitor_token)
|
|
373
|
+
|
|
374
|
+
{
|
|
375
|
+
total_page_views: total_views,
|
|
376
|
+
unique_visitors: unique_visitors,
|
|
377
|
+
total_sessions: total_sessions,
|
|
378
|
+
pages_per_session: total_sessions.positive? ? (total_views.to_f / total_sessions).round(1) : 0,
|
|
379
|
+
bounce_rate: compute_bounce_rate,
|
|
380
|
+
new_visitor_percentage: compute_new_visitor_percentage,
|
|
381
|
+
avg_daily_views: days_in_range.positive? ? (total_views.to_f / days_in_range).round(0).to_i : 0,
|
|
382
|
+
popular_pages: popular_pages_data,
|
|
383
|
+
top_visitors: top_visitors_data,
|
|
384
|
+
hourly_activity: hourly_activity_data,
|
|
385
|
+
daily_activity: daily_activity_data,
|
|
386
|
+
daily_visitors: daily_visitors_data,
|
|
387
|
+
top_referrers: referrer_data,
|
|
388
|
+
landing_pages: landing_pages_data,
|
|
389
|
+
utm_sources: utm_sources_data,
|
|
390
|
+
browser_stats: visits.where.not(browser: [nil, ""]).group(:browser).count,
|
|
391
|
+
device_stats: visits.where.not(device_type: [nil, ""]).group(:device_type).count,
|
|
392
|
+
os_stats: visits.where.not(os: [nil, ""]).group(:os).count,
|
|
393
|
+
suspicious_activity: suspicious_activity_data,
|
|
394
|
+
recent_page_views: page_view_events.order(time: :desc).limit(recent_page_views_limit),
|
|
395
|
+
extra_cards: extra_cards_data
|
|
396
|
+
}
|
|
397
|
+
end
|
|
394
398
|
end
|
|
395
399
|
end
|
|
396
400
|
end
|
|
@@ -3,10 +3,11 @@
|
|
|
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) %>
|
|
6
|
-
<div class="w-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
<div class="w-9 h-9 flex-shrink-0 rounded-md bg-white border border-gray-200 overflow-hidden">
|
|
7
|
+
<%= image_tag "ruby_cms/logo.png",
|
|
8
|
+
alt: "RubyCMS logo",
|
|
9
|
+
class: "w-full h-full object-contain",
|
|
10
|
+
onerror: "this.style.display='none'; this.parentElement.classList.add('bg-gray-900','text-white','font-bold','text-xs','flex','items-center','justify-center'); this.parentElement.innerHTML='R';" %>
|
|
10
11
|
</div>
|
|
11
12
|
<% end %>
|
|
12
13
|
<p class="text-base font-semibold text-gray-900 m-0">RubyCMS</p>
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
|
|
23
23
|
<%# Single card: category rail | separator | content (Todoist-like) %>
|
|
24
24
|
<div class="rounded-2xl border border-border bg-card shadow-sm ring-1 ring-black/[0.03] overflow-hidden">
|
|
25
|
-
<div class="flex flex-col lg:flex-row lg:
|
|
25
|
+
<div class="flex flex-col lg:flex-row lg:h-[min(100vh-12rem,64rem)]">
|
|
26
26
|
<%# Left: sections (icons from TAB_CONFIG) %>
|
|
27
27
|
<aside class="shrink-0 border-b border-border lg:w-56 lg:border-b-0 lg:border-r bg-muted/25">
|
|
28
|
-
<div class="p-3 sm:p-4">
|
|
28
|
+
<div class="p-3 sm:p-4 lg:h-full lg:overflow-y-auto">
|
|
29
29
|
<p class="mb-3 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
30
30
|
<%= t("ruby_cms.admin.settings.categories_title", default: "Sections") %>
|
|
31
31
|
</p>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
</aside>
|
|
51
51
|
|
|
52
52
|
<%# Right: active section %>
|
|
53
|
-
<div class="flex min-w-0 flex-1 flex-col bg-background">
|
|
53
|
+
<div class="flex min-w-0 flex-1 flex-col bg-background min-h-0">
|
|
54
54
|
<% tab_description = settings_tab_description(@active_tab) %>
|
|
55
55
|
|
|
56
56
|
<div class="border-b border-border/80 px-5 py-4 sm:px-6 sm:py-5">
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
<% end %>
|
|
89
89
|
</div>
|
|
90
90
|
|
|
91
|
-
<div class="flex flex-1 flex-col px-5 py-5 sm:px-6 sm:py-6">
|
|
91
|
+
<div class="flex flex-1 flex-col px-5 py-5 sm:px-6 sm:py-6 min-h-0 overflow-y-auto">
|
|
92
92
|
<% if is_navigation %>
|
|
93
93
|
<% if nav_sub == "order" %>
|
|
94
94
|
<%= form_with url: settings_url, method: :patch, local: true, html: { id: "nav_order_form", class: "flex flex-1 flex-col space-y-6", data: { turbo: false } } do %>
|
|
@@ -5,16 +5,29 @@
|
|
|
5
5
|
<!-- Header -->
|
|
6
6
|
<div class="flex flex-wrap items-center justify-between gap-4">
|
|
7
7
|
<h1 class="text-lg font-semibold tracking-tight text-gray-900">Visual Editor</h1>
|
|
8
|
-
<%= form_with url: ruby_cms_admin_visual_editor_path, method: :get, class: "flex items-center gap-2", data: { turbo: false } do %>
|
|
8
|
+
<%= form_with url: ruby_cms_admin_visual_editor_path, method: :get, class: "flex items-center gap-2", data: { turbo: false, controller: "visual-editor-page-select" } do %>
|
|
9
9
|
<%= hidden_field_tag :edit_mode, @edit_mode %>
|
|
10
10
|
|
|
11
11
|
<% if @available_pages && @available_pages.size > 1 %>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class: "h-9
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
<% selected_page_name = @available_pages.dig(@current_page, :name) || @current_page.to_s.humanize %>
|
|
13
|
+
<%= Select(class: "min-w-56") do %>
|
|
14
|
+
<%= SelectInput(name: :page, value: @current_page, data: { action: "change->visual-editor-page-select#submit" }) %>
|
|
15
|
+
<%= SelectTrigger(class: "h-9 bg-white font-medium") do %>
|
|
16
|
+
<%= SelectValue(placeholder: selected_page_name) { selected_page_name } %>
|
|
17
|
+
<% end %>
|
|
18
|
+
<%= SelectContent do %>
|
|
19
|
+
<%= SelectGroup do %>
|
|
20
|
+
<% @available_pages.each do |key, page| %>
|
|
21
|
+
<% is_selected = key.to_s == @current_page.to_s %>
|
|
22
|
+
<%= SelectItem(
|
|
23
|
+
value: key,
|
|
24
|
+
aria_selected: is_selected.to_s,
|
|
25
|
+
class: (is_selected ? "bg-primary/10 text-primary ring-1 ring-primary/20" : nil)
|
|
26
|
+
) { page[:name] } %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
18
31
|
<% else %>
|
|
19
32
|
<%= hidden_field_tag :page, @current_page %>
|
|
20
33
|
<% end %>
|
data/lib/ruby_cms/version.rb
CHANGED
data/lib/tasks/admin.rake
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
namespace :admin do
|
|
3
|
+
namespace :admin do # rubocop:disable Metrics/BlockLength
|
|
4
4
|
def ruby_cms_user_class
|
|
5
5
|
Object.const_get(Rails.application.config.ruby_cms.user_class_name.presence ||
|
|
6
6
|
"User")
|
|
@@ -118,3 +118,5 @@ namespace :admin do
|
|
|
118
118
|
puts "Logged out #{count} session(s)"
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
|
+
|
|
122
|
+
# rubocop:enable Metrics/BlockLength
|