summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-07 03:48:09 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-07 03:48:09 +0200
commit1bf719d6ac58187cf406d92a40b665d3fa6e658b (patch)
tree214c0095fe06dd204e31285fbd16df4e07821d80
parent7b6af89509e8439fe2474e623ee97e4db67ab011 (diff)
Add context-aware child-creation shortcuts to nodes#show
parent_match Procs on CccConventions::NODE_KINDS, matched against unique_path, decide which "add child" kinds show on a given node. Fixes nodes#new not honoring a pre-selected kind (radio group and parent-field visibility both defaulted to "generic" unconditionally).
-rw-r--r--app/controllers/nodes_controller.rb1
-rw-r--r--app/helpers/nodes_helper.rb5
-rw-r--r--app/views/nodes/new.html.erb4
-rw-r--r--app/views/nodes/show.html.erb29
-rw-r--r--lib/ccc_conventions.rb61
-rw-r--r--public/javascripts/admin_search.js18
-rw-r--r--public/stylesheets/admin.css8
7 files changed, 82 insertions, 44 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
index 69aa268..a72be68 100644
--- a/app/controllers/nodes_controller.rb
+++ b/app/controllers/nodes_controller.rb
@@ -22,6 +22,7 @@ class NodesController < ApplicationController
22 22
23 def new 23 def new
24 @node = Node.new node_params 24 @node = Node.new node_params
25 @selected_kind = CccConventions::NODE_KINDS.key?(params[:kind]) ? params[:kind] : "generic"
25 if params.has_key?(:parent_id) 26 if params.has_key?(:parent_id)
26 @parent_id = params[:parent_id] 27 @parent_id = params[:parent_id]
27 @parent_name = Node.find(@parent_id).title 28 @parent_name = Node.find(@parent_id).title
diff --git a/app/helpers/nodes_helper.rb b/app/helpers/nodes_helper.rb
index 2baf813..a89f879 100644
--- a/app/helpers/nodes_helper.rb
+++ b/app/helpers/nodes_helper.rb
@@ -58,4 +58,9 @@ module NodesHelper
58 t(:event_schedule_none) 58 t(:event_schedule_none)
59 end 59 end
60 end 60 end
61
62 def matching_node_kinds(node)
63 path = node.unique_path
64 CccConventions::NODE_KINDS.select { |_, config| config[:parent_match]&.call(path) }
65 end
61end 66end
diff --git a/app/views/nodes/new.html.erb b/app/views/nodes/new.html.erb
index 0a05325..71f2fbf 100644
--- a/app/views/nodes/new.html.erb
+++ b/app/views/nodes/new.html.erb
@@ -13,7 +13,7 @@
13 <div class="node_content"> 13 <div class="node_content">
14 <% CccConventions::NODE_KINDS.each do |kind, config| %> 14 <% CccConventions::NODE_KINDS.each do |kind, config| %>
15 <p> 15 <p>
16 <%= radio_button_tag :kind, kind, kind == "generic", 16 <%= radio_button_tag :kind, kind, kind == @selected_kind,
17 data: { path_prefix: resolve_kind_text(config[:path_prefix]) } %> 17 data: { path_prefix: resolve_kind_text(config[:path_prefix]) } %>
18 <%= resolve_kind_text(config[:label]) %> 18 <%= resolve_kind_text(config[:label]) %>
19 <% if config[:hint] %> 19 <% if config[:hint] %>
@@ -29,7 +29,7 @@
29 <span class="field_hint">A URL slug will be generated from this automatically. You can review or adjust it afterward under the "metadata" section's Slug field.</span> 29 <span class="field_hint">A URL slug will be generated from this automatically. You can review or adjust it afterward under the "metadata" section's Slug field.</span>
30 </div> 30 </div>
31 31
32 <div id="parent_search_field"> 32 <div id="parent_search_field" style="<%= @selected_kind == "generic" ? "" : "display: none;" %>">
33 <div class="node_description">Parent</div> 33 <div class="node_description">Parent</div>
34 <div class="node_content"> 34 <div class="node_content">
35 <%= text_field_tag :parent_search_term, @parent_name %> 35 <%= text_field_tag :parent_search_term, @parent_name %>
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index 189adb8..963bc37 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -101,17 +101,30 @@
101 <%= link_to 'add event', new_event_path(node_id: @node.id, tag_list: mapping&.last, auto_tag_source: mapping&.first, return_to: request.path) %> 101 <%= link_to 'add event', new_event_path(node_id: @node.id, tag_list: mapping&.last, auto_tag_source: mapping&.first, return_to: request.path) %>
102 </div> 102 </div>
103 103
104 <% if @node.children.any? %> 104 <% matches = matching_node_kinds(@node) %>
105 <% if @node.children.any? || matches.any? %>
105 <div class="node_description">Children</div> 106 <div class="node_description">Children</div>
106 <div class="node_content node_info_group"> 107 <div class="node_content node_info_group">
107 <details> 108 <% if @node.children.any? %>
108 <summary><%= pluralize(@node.children.count, 'child', 'children') %></summary> 109 <details>
109 <ul> 110 <summary><%= pluralize(@node.children.count, 'child', 'children') %></summary>
110 <% @node.children.order(:slug).each do |child| %> 111 <ul>
111 <li><%= link_to (child.head&.title || child.draft&.title || child.slug), node_path(child) %></li> 112 <% @node.children.order(:slug).each do |child| %>
113 <li><%= link_to (child.head&.title || child.draft&.title || child.slug), node_path(child) %></li>
114 <% end %>
115 </ul>
116 </details>
117 <% end %>
118 <% if matches.any? %>
119 <p class="add_child_links">
120 <% matches.each_with_index do |(kind, config), index| %>
121 <%= " &middot; ".html_safe if index > 0 %>
122 <% link_params = { kind: kind } %>
123 <% link_params[:parent_id] = @node.id if kind == "generic" %>
124 <%= link_to "add '#{resolve_kind_text(config[:label])}' child", new_node_path(link_params) %>
112 <% end %> 125 <% end %>
113 </ul> 126 </p>
114 </details> 127 <% end %>
115 </div> 128 </div>
116 <% end %> 129 <% end %>
117 130
diff --git a/lib/ccc_conventions.rb b/lib/ccc_conventions.rb
index c0f9943..b420452 100644
--- a/lib/ccc_conventions.rb
+++ b/lib/ccc_conventions.rb
@@ -4,44 +4,49 @@ module CccConventions
4 4
5 NODE_KINDS = { 5 NODE_KINDS = {
6 "top_level" => { 6 "top_level" => {
7 parent: -> { Node.root }, 7 parent: -> { Node.root },
8 path_prefix: "", 8 parent_match: ->(path) { path == [] },
9 label: "Top Level" 9 path_prefix: "",
10 label: "Top Level"
10 }, 11 },
11 "generic" => { 12 "generic" => {
12 label: "Generic", 13 parent_match: ->(path) { true },
13 hint: "Can be created anywhere - choose the parent below." 14 label: "Generic",
14 # no path_prefix - depends on whatever parent gets chosen; see below 15 hint: "Can be created anywhere - choose the parent below."
15 }, 16 },
16 "update" => { 17 "update" => {
17 parent: -> { Update.find_or_create_parent }, 18 parent: -> { Update.find_or_create_parent },
18 tags: ["update"], 19 parent_match: ->(path) { path[0] == "updates" && (path.length == 1 || path[1] =~ /\A\d{4}\z/) },
19 path_prefix: -> { "updates/#{Time.now.year}" }, 20 tags: ["update"],
20 label: "Update", 21 path_prefix: -> { "updates/#{Time.now.year}" },
21 hint: -> { "Automatically created in /updates/#{Time.now.year}/, gets tag \"update\", and inherits the update template." } 22 label: "Update",
23 hint: -> { "Automatically created in /updates/#{Time.now.year}/, gets tag \"update\", and inherits the update template." }
22 }, 24 },
23 "press_release" => { 25 "press_release" => {
24 parent: -> { Update.find_or_create_parent }, 26 parent: -> { Update.find_or_create_parent },
25 tags: ["update", "pressemitteilung"], 27 parent_match: ->(path) { path[0] == "updates" && (path.length == 1 || path[1] =~ /\A\d{4}\z/) },
26 path_prefix: -> { "updates/#{Time.now.year}" }, 28 tags: ["update", "pressemitteilung"],
27 label: "Pressemitteilung", 29 path_prefix: -> { "updates/#{Time.now.year}" },
28 hint: -> { "Automatically created in /updates/#{Time.now.year}/, gets tags \"update, pressemitteilung\", and inherits the update template." } 30 label: "Pressemitteilung",
31 hint: -> { "Automatically created in /updates/#{Time.now.year}/, gets tags \"update, pressemitteilung\", and inherits the update template." }
29 }, 32 },
30 "erfa" => { 33 "erfa" => {
31 parent: -> { Node.find_by_unique_name!(ERFA_PARENT_NAME) }, 34 parent: -> { Node.find_by_unique_name!(ERFA_PARENT_NAME) },
32 tags: ["erfa-detail"], 35 parent_match: ->(path) { path == ["club", "erfas"] },
33 template: "chapter_detail", 36 tags: ["erfa-detail"],
34 path_prefix: ERFA_PARENT_NAME, 37 template: "chapter_detail",
35 label: "Erfa", 38 path_prefix: ERFA_PARENT_NAME,
36 hint: "Automatically created under the Erfa-Kreise overview page, gets tag \"erfa-detail\", and uses the chapter detail template." 39 label: "Erfa",
40 hint: "Automatically created under the Erfa-Kreise overview page, gets tag \"erfa-detail\", and uses the chapter detail template."
37 }, 41 },
38 "chaostreff" => { 42 "chaostreff" => {
39 parent: -> { Node.find_by_unique_name!(CHAOSTREFF_PARENT_NAME) }, 43 parent: -> { Node.find_by_unique_name!(CHAOSTREFF_PARENT_NAME) },
40 tags: ["chaostreff-detail"], 44 parent_match: ->(path) { path == ["club", "chaostreffs"] },
41 template: "chapter_detail", 45 tags: ["chaostreff-detail"],
42 path_prefix: CHAOSTREFF_PARENT_NAME, 46 template: "chapter_detail",
43 label: "Chaostreff", 47 path_prefix: CHAOSTREFF_PARENT_NAME,
44 hint: "Automatically created under the Chaostreffs overview page, gets tag \"chaostreff-detail\", and uses the chapter detail template." 48 label: "Chaostreff",
49 hint: "Automatically created under the Chaostreffs overview page, gets tag \"chaostreff-detail\", and uses the chapter detail template."
45 } 50 }
46 }.freeze 51 }.freeze
47end 52end
diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js
index 2565929..6ef9087 100644
--- a/public/javascripts/admin_search.js
+++ b/public/javascripts/admin_search.js
@@ -208,14 +208,20 @@ parent_search = {
208 }, 208 },
209 209
210 initialize_radio_buttons : function() { 210 initialize_radio_buttons : function() {
211 $("input[name='kind']").bind("change", function(){ 211 parent_search.sync_parent_field();
212 if ($(this).val() === "generic") { 212 $("input[name='kind']").bind("change", function() {
213 $("#parent_search_field").show(); 213 parent_search.sync_parent_field();
214 } else {
215 $("#parent_search_field").hide();
216 }
217 parent_search.update_resulting_path(); 214 parent_search.update_resulting_path();
218 }); 215 });
216 },
217
218 sync_parent_field : function() {
219 var kind = $("input[name='kind']:checked").val();
220 if (kind === "generic") {
221 $("#parent_search_field").show();
222 } else {
223 $("#parent_search_field").hide();
224 }
219 } 225 }
220} 226}
221 227
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 1196d83..3033798 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -338,6 +338,14 @@ input[type=radio] {
338 border: 1px solid #989898; 338 border: 1px solid #989898;
339} 339}
340 340
341.add_child_links {
342 margin-top: 0.5rem;
343}
344
345.add_child_links a {
346 white-space: nowrap;
347}
348
341div#login_form input[type=text], div#login_form input[type=password] { 349div#login_form input[type=text], div#login_form input[type=password] {
342 width: 150px; 350 width: 150px;
343} 351}