diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-05 21:52:11 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-05 21:52:11 +0200 |
| commit | 9a7ce465b06462443ee82e8418f3c070de080ab2 (patch) | |
| tree | bf68ecc1536af990604cd58e75417073a07f0d1a | |
| parent | 51d491a3d67e8c9efde8019ecb8a8e1a8195ec99 (diff) | |
Surface node-kind conventions in the admin UI
nodes#new: each kind's radio button now shows a label plus a smaller,
muted .field_hint explaining what happens automatically (parent,
tags, template) - previously a single undifferentiated string mixing
a short label with a long parenthetical, cluttering the smaller kinds
(erfa/chaostreff) worst. Title field gets the same hint treatment,
telling editors up front that the slug is auto-generated and where to
adjust it later - the previous silence here meant editors only
discovered slug generation after already committing to a title, with
no visible way to fix it.
New "Resulting path" row live-previews the full URL (origin + kind's
path prefix + a debounced call to the new parameterize_preview route,
reusing the exact same slug_for helper "create" now calls, so the
preview can never diverge from what actually gets created) as the
editor types, plus a copy-to-clipboard button. For "generic", the
prefix comes from whichever parent gets chosen via the search widget,
not the registry - parent_search.link_closure now stashes the chosen
node's unique_name as a data attribute for exactly this.
New read-only admin/conventions view dumps the NODE_KINDS registry as
a reference table for whoever's confused later about how a given kind
behaves - deliberately just renders the same label/hint text used on
nodes#new rather than re-describing parent/tags/template separately,
since decomposing the parent Proc would mean either invoking it
(Update's has a real side effect - creates the year folder) or
re-stating its logic a second time in a different shape, either of
which risks drifting from the actual behavior.
nodes#edit's Template field gets a one-line hint noting the value may
already be pre-filled based on how the node was created - the one
place the previous commit's new inheritance mechanism actually
surfaces on this view.
| -rw-r--r-- | app/helpers/application_helper.rb | 4 | ||||
| -rw-r--r-- | app/views/nodes/edit.html.erb | 5 | ||||
| -rw-r--r-- | app/views/nodes/new.html.erb | 34 | ||||
| -rw-r--r-- | config/routes.rb | 1 | ||||
| -rw-r--r-- | public/stylesheets/admin.css | 12 |
5 files changed, 40 insertions, 16 deletions
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0be66e9..87aa82c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb | |||
| @@ -11,4 +11,8 @@ module ApplicationHelper | |||
| 11 | end | 11 | end |
| 12 | end | 12 | end |
| 13 | end | 13 | end |
| 14 | |||
| 15 | def resolve_kind_text(value) | ||
| 16 | value.respond_to?(:call) ? value.call : value | ||
| 17 | end | ||
| 14 | end | 18 | end |
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb index 31f585c..2d0edc1 100644 --- a/app/views/nodes/edit.html.erb +++ b/app/views/nodes/edit.html.erb | |||
| @@ -44,7 +44,10 @@ | |||
| 44 | <div class="node_content"><%= d.datetime_select :published_at, :value => @draft.published_at %></div> | 44 | <div class="node_content"><%= d.datetime_select :published_at, :value => @draft.published_at %></div> |
| 45 | 45 | ||
| 46 | <div class="node_description">Template</div> | 46 | <div class="node_description">Template</div> |
| 47 | <div class="node_content"><%= d.select :template_name, custom_page_templates, {:prompt => 'Select Template'} %></div> | 47 | <div class="node_content"> |
| 48 | <%= d.select :template_name, custom_page_templates, {:prompt => 'Select Template'} %> | ||
| 49 | <span class="field_hint">Set automatically based on how this node was created - change it if needed.</span> | ||
| 50 | </div> | ||
| 48 | 51 | ||
| 49 | <div class="node_description">Author</div> | 52 | <div class="node_description">Author</div> |
| 50 | <div class="node_content"><%= d.select :user_id, user_list %></div> | 53 | <div class="node_content"><%= d.select :user_id, user_list %></div> |
diff --git a/app/views/nodes/new.html.erb b/app/views/nodes/new.html.erb index 028d727..f557454 100644 --- a/app/views/nodes/new.html.erb +++ b/app/views/nodes/new.html.erb | |||
| @@ -13,27 +13,23 @@ | |||
| 13 | <tr> | 13 | <tr> |
| 14 | <td class="description">Type</td> | 14 | <td class="description">Type</td> |
| 15 | <td> | 15 | <td> |
| 16 | <div> | 16 | <% CccConventions::NODE_KINDS.each do |kind, config| %> |
| 17 | <%= radio_button_tag :kind, "top_level" %> | ||
| 18 | Top Level | ||
| 19 | </div> | ||
| 20 | <p> | ||
| 21 | <%= radio_button_tag :kind, "generic", :selected => true %> | ||
| 22 | Generic ( can be created anywhere ) | ||
| 23 | </p> | ||
| 24 | <p> | ||
| 25 | <%= radio_button_tag :kind, "update" %> | ||
| 26 | Update ( is automatically created in /updates/<%= Time.now.year.to_s %>/ and gets tag "update" ) | ||
| 27 | </p> | ||
| 28 | <p> | 17 | <p> |
| 29 | <%= radio_button_tag :kind, "press_release" %> | 18 | <%= radio_button_tag :kind, kind, kind == "generic", |
| 30 | Pressemitteilung ( is automatically created in /updates/<%= Time.now.year.to_s %>/ and gets tags "update, pressemitteilung" ) | 19 | data: { path_prefix: resolve_kind_text(config[:path_prefix]) } %> |
| 20 | <%= resolve_kind_text(config[:label]) %> | ||
| 21 | <% if config[:hint] %> | ||
| 22 | <span class="field_hint"><%= resolve_kind_text(config[:hint]) %></span> | ||
| 23 | <% end %> | ||
| 31 | </p> | 24 | </p> |
| 25 | <% end %> | ||
| 32 | </td> | 26 | </td> |
| 33 | </tr> | 27 | </tr> |
| 34 | <tr> | 28 | <tr> |
| 35 | <td class="description">Title</td> | 29 | <td class="description">Title</td> |
| 36 | <td><%= text_field_tag :title %></td> | 30 | <td><%= text_field_tag :title %> |
| 31 | <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> | ||
| 32 | </td> | ||
| 37 | </tr> | 33 | </tr> |
| 38 | <tr id="parent_search_field"> | 34 | <tr id="parent_search_field"> |
| 39 | <td class="description">Parent</td> | 35 | <td class="description">Parent</td> |
| @@ -46,6 +42,14 @@ | |||
| 46 | </td> | 42 | </td> |
| 47 | </tr> | 43 | </tr> |
| 48 | <tr> | 44 | <tr> |
| 45 | <td class="description">Resulting path</td> | ||
| 46 | <td> | ||
| 47 | <span id="resulting_path">—</span> | ||
| 48 | <button type="button" id="copy_resulting_path" class="unselected">copy</button> | ||
| 49 | <span class="field_hint">This preview updates as you type. The final path can still be changed afterward by editing the title (for the last segment) or moving the node to a different parent (for everything before it).</span> | ||
| 50 | </td> | ||
| 51 | </tr> | ||
| 52 | <tr> | ||
| 49 | <td></td> | 53 | <td></td> |
| 50 | <td class="right"><%= submit_tag "Create" %></td> | 54 | <td class="right"><%= submit_tag "Create" %></td> |
| 51 | </tr> | 55 | </tr> |
diff --git a/config/routes.rb b/config/routes.rb index cf4733c..6c07414 100644 --- a/config/routes.rb +++ b/config/routes.rb | |||
| @@ -59,6 +59,7 @@ Cccms::Application.routes.draw do | |||
| 59 | match 'admin' => 'admin#index', :as => :admin, :via => :get | 59 | match 'admin' => 'admin#index', :as => :admin, :via => :get |
| 60 | match 'admin/search' => 'admin#search', :as => :admin_search, :via => :get | 60 | match 'admin/search' => 'admin#search', :as => :admin_search, :via => :get |
| 61 | match 'admin/menu_search' => 'admin#menu_search', :as => :admin_menu_search, :via => :get | 61 | match 'admin/menu_search' => 'admin#menu_search', :as => :admin_menu_search, :via => :get |
| 62 | match 'admin/conventions' => 'admin#conventions', :as => :admin_conventions, :via => :get | ||
| 62 | match 'search' => 'search#index', :as => :search, :via => :get | 63 | match 'search' => 'search#index', :as => :search, :via => :get |
| 63 | 64 | ||
| 64 | resources :users | 65 | resources :users |
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index a02bd61..f240bb2 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css | |||
| @@ -508,6 +508,14 @@ table tr.header { | |||
| 508 | text-align: left; | 508 | text-align: left; |
| 509 | } | 509 | } |
| 510 | 510 | ||
| 511 | .field_hint { | ||
| 512 | display: block; | ||
| 513 | margin-top: 2px; | ||
| 514 | font-size: 0.75rem; | ||
| 515 | color: #969696; | ||
| 516 | padding-bottom: 4px; | ||
| 517 | } | ||
| 518 | |||
| 511 | #search_widget { | 519 | #search_widget { |
| 512 | position: absolute; | 520 | position: absolute; |
| 513 | top: 20px; | 521 | top: 20px; |
| @@ -527,18 +535,21 @@ table tr.header { | |||
| 527 | font-size: 18px; | 535 | font-size: 18px; |
| 528 | } | 536 | } |
| 529 | 537 | ||
| 538 | #menu_search_results p, | ||
| 530 | #search_results p { | 539 | #search_results p { |
| 531 | margin: 0; | 540 | margin: 0; |
| 532 | padding: 4px 4px 0 4px; | 541 | padding: 4px 4px 0 4px; |
| 533 | border-bottom: 1px solid #e8e8e8; | 542 | border-bottom: 1px solid #e8e8e8; |
| 534 | } | 543 | } |
| 535 | 544 | ||
| 545 | #menu_search_results p a, | ||
| 536 | #search_results p a { | 546 | #search_results p a { |
| 537 | display: block; | 547 | display: block; |
| 538 | font-weight: bold; | 548 | font-weight: bold; |
| 539 | font-size: 0.95rem; | 549 | font-size: 0.95rem; |
| 540 | } | 550 | } |
| 541 | 551 | ||
| 552 | #menu_search_results p span.result_path, | ||
| 542 | #search_results p span.result_path { | 553 | #search_results p span.result_path { |
| 543 | display: block; | 554 | display: block; |
| 544 | margin-top: 2px; | 555 | margin-top: 2px; |
| @@ -548,6 +559,7 @@ table tr.header { | |||
| 548 | padding-bottom: 4px; | 559 | padding-bottom: 4px; |
| 549 | } | 560 | } |
| 550 | 561 | ||
| 562 | #menu_search_results p.search_more, | ||
| 551 | #search_results p.search_more { | 563 | #search_results p.search_more { |
| 552 | margin: 0; | 564 | margin: 0; |
| 553 | padding: 6px 4px; | 565 | padding: 6px 4px; |
