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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81464763020bbbb1d3f9ac35df979f34dbeeb004063d54af6703362b23b5e589
4
- data.tar.gz: d3c46388e12a933dac2a55b14a1a8a5f5cd30b95d51545548dd50951d1f4c90c
3
+ metadata.gz: d5cb4cca0c59768b347f78d370c203428f9d663e26fe91a8bc6a7b63c3c14ac7
4
+ data.tar.gz: 706b81555013c29de1e4f4641f70f9a63c39d1231811421867f3beaf3f42073e
5
5
  SHA512:
6
- metadata.gz: 63b48baeedd15716690c0f88d201c692f1ce65ec665c48fecca2088ec5008c028392b5d35c603258ae41da83c8780786d8daa90ca9bf39aedc77fcac424b8063
7
- data.tar.gz: 5c7fae67e6195b6fd2b800c66c5a2a5c53a50ecd1a6b1d283b8b13809b4b03a3dacf4c8998248eaf306978cc8b5ecccc88562384d9a41e527f44c376f4028bb4
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
+ ![RubyCMS logo](docs/assets/ruby_cms_logo.png)
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: "ruby_cms/minimal"
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) || relative_path.start_with?("admin")
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
- total_views = page_view_events.count
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 { |_, c| c == 1 }
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
- .where("started_at < ?", @start_date)
367
- .distinct
368
- .pluck(:visitor_token)
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 h-7 flex-shrink-0 rounded-sm bg-gray-900 flex items-center justify-center text-white font-bold text-sm">
7
- <% if respond_to?(:main_app, true) && main_app.respond_to?(:image_path) %>
8
- <%= image_tag main_app.image_path("logo.png"), alt: "Logo", onerror: "this.style.display='none'", class: "w-full h-full object-contain" rescue nil %>
9
- <% end %>
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:min-h-[min(100vh-12rem,52rem)]">
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
- <%= select_tag(
13
- :page,
14
- options_for_select(@available_pages.map { |k, v| [v[:name], k] }, @current_page),
15
- 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",
16
- onchange: "this.form.requestSubmit()"
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 %>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyCms
4
- VERSION = "0.1.3.1"
4
+ VERSION = "0.1.4"
5
5
  end
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