summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-06 16:49:45 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-06 16:49:45 +0200
commit36b5324900968afc7498950c14ad13be5c061a7c (patch)
treea717b115e308a940cc69d2b979ab03f90e5aeb80
parent889e15eabbe107d2642fdd8aa3f03821058c00dc (diff)
Restructure nodes#show and nodes#new around node_description/node_content
nodes#show's flat <table> of label/value pairs grew unreadable once Admin Preview and Public Preview were added alongside Public Link - switched to the div.node_description/div.node_content pattern already used by nodes#edit, grouped into People/Dates/Links/Revisions/Tags/ Events/Children sections rather than one undifferentiated list. People/Dates/Links use a flex-based item layout, not a second <table> - multiple independent tables sharing one outer width but different column counts produced misaligned columns with no shared grid. Flex items size to their own content with a shared minimum instead. Revisions and Children are both collapsed via <details>, no JS needed - previously nodes#show didn't list a node's children at all, blocking the ability to find/navigate erfa and chaostreff pages without already knowing they're now standalone nodes. Revision list items link to node_revision_path(@node, page) - the actual per-revision diff/restore view, not the plain index. nodes#new converted to the same pattern for visual consistency, plus a few things surfaced along the way: submit buttons were unstyled site-wide (a bare input[type=submit] with no border, fill, or hover state - Create on this exact page was easy to miss entirely), fixed with a bordered/bold treatment reusing the existing form.button_to visual language rather than introducing a fourth button style. Title regained the bold weight it had before the table rewrite dropped it silently. Tag list grouping and full link/button semantic taxonomy (show vs edit/add vs publish/revoke vs destroy, applied consistently across every link on the page, not just buttons) are known follow-ups, not attempted here.
-rw-r--r--app/views/nodes/new.html.erb55
-rw-r--r--app/views/nodes/show.html.erb204
-rw-r--r--public/stylesheets/admin.css64
3 files changed, 191 insertions, 132 deletions
diff --git a/app/views/nodes/new.html.erb b/app/views/nodes/new.html.erb
index f557454..0a05325 100644
--- a/app/views/nodes/new.html.erb
+++ b/app/views/nodes/new.html.erb
@@ -6,13 +6,11 @@
6 </div> 6 </div>
7<% end %> 7<% end %>
8 8
9<p>What kind of node do you want to create?</p>
10
11<%= form_tag nodes_path do %> 9<%= form_tag nodes_path do %>
12<table id="new_node"> 10<div id="new_node">
13 <tr> 11
14 <td class="description">Type</td> 12 <div class="node_description">Type</div>
15 <td> 13 <div class="node_content">
16 <% CccConventions::NODE_KINDS.each do |kind, config| %> 14 <% CccConventions::NODE_KINDS.each do |kind, config| %>
17 <p> 15 <p>
18 <%= radio_button_tag :kind, kind, kind == "generic", 16 <%= radio_button_tag :kind, kind, kind == "generic",
@@ -23,35 +21,32 @@
23 <% end %> 21 <% end %>
24 </p> 22 </p>
25 <% end %> 23 <% end %>
26 </td> 24 </div>
27 </tr> 25
28 <tr> 26 <div class="node_description">Title</div>
29 <td class="description">Title</td> 27 <div class="node_content">
30 <td><%= text_field_tag :title %> 28 <%= 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> 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>
32 </td> 30 </div>
33 </tr> 31
34 <tr id="parent_search_field"> 32 <div id="parent_search_field">
35 <td class="description">Parent</td> 33 <div class="node_description">Parent</div>
36 <td> 34 <div class="node_content">
37 <%= text_field_tag :parent_search_term, @parent_name %> 35 <%= text_field_tag :parent_search_term, @parent_name %>
38 <%= hidden_field_tag :parent_id, @parent_id %> 36 <%= hidden_field_tag :parent_id, @parent_id %>
39 <div id="search_results"> 37 <div id="search_results"></div>
38 </div>
39 </div>
40 40
41 </div> 41 <div class="node_description">Resulting path</div>
42 </td> 42 <div class="node_content">
43 </tr>
44 <tr>
45 <td class="description">Resulting path</td>
46 <td>
47 <span id="resulting_path">—</span> 43 <span id="resulting_path">—</span>
48 <button type="button" id="copy_resulting_path" class="unselected">copy</button> 44 <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> 45 <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> 46 </div>
51 </tr> 47
52 <tr> 48 <div class="node_description"></div>
53 <td></td> 49 <div class="node_content right"><%= submit_tag "Create" %></div>
54 <td class="right"><%= submit_tag "Create" %></td> 50
55 </tr> 51</div>
56</table>
57<% end %> 52<% end %>
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index d63df86..189adb8 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -1,95 +1,129 @@
1<% content_for :subnavigation do %> 1<% content_for :subnavigation do %>
2 <%= link_to 'Edit', edit_node_path(@node), :class => "unselected" %> 2 <%= link_to 'Edit', edit_node_path(@node), :class => "unselected" %>
3 <%= link_to 'Preview', preview_page_path(@page) %> 3 <%= link_to 'Preview', preview_page_path(@page) %>
4 <%= link_to 'Revisions', node_revisions_path(@node) %>
5 <%= unlock_link if @node.locked? %>
6<% end %> 4<% end %>
7 5
8
9<div id="page_editor" class="show_node"> 6<div id="page_editor" class="show_node">
10 <table id="content"> 7 <div id="content">
11 <tr> 8
12 <td class="description">Public Link</td> 9 <div class="node_description">People</div>
13 <td><%= link_to @page.public_link, content_url(@node.unique_path) %></td> 10 <div class="node_content node_info_group">
14 </tr> 11 <div class="node_info_group_items">
15 <% if @node.draft %> 12 <div class="node_info_item">
16 <tr> 13 <span class="node_info_label">Author</span>
17 <td class="description">Admin Preview</td> 14 <%= @page.user.try(:login) %>
18 <td><%= link_to preview_page_path(@node.draft), preview_page_path(@node.draft) %></td> 15 </div>
19 </tr> 16 <div class="node_info_item">
20 <tr> 17 <span class="node_info_label">Editor</span>
21 <td class="description">Public Preview</td> 18 <%= @page.editor.try(:login) %>
22 <td> 19 </div>
23 <% if @node.draft.preview_token.present? %> 20 <% if @node.locked? %>
24 <%= link_to shared_preview_path(token: @node.draft.preview_token), shared_preview_url(token: @node.draft.preview_token), target: "_blank", rel: "noopener" %> 21 <div class="node_info_item">
25 <%= button_to 'Revoke', revoke_shared_preview_node_path(@node), method: :put, form: { data: { confirm: "Revoke this preview link? Anyone currently using it will immediately lose access." } } %> 22 <span class="node_info_label">Locked by</span>
26 <% else %> 23 <span class="warning"><%= @node.lock_owner.login %></span>
27 <%= button_to 'Create public preview link', generate_shared_preview_node_path(@node), method: :put %> 24 <%= unlock_link %>
25 </div>
28 <% end %> 26 <% end %>
29 </td> 27 </div>
30 </tr> 28 </div>
31 <% end %> 29
32 <tr> 30 <div class="node_description">Dates</div>
33 <td class="description">Author</td> 31 <div class="node_content node_info_group">
34 <td><%= @page.user.try(:login) %></td> 32 <div class="node_info_group_items">
35 </tr> 33 <div class="node_info_item">
36 <tr> 34 <span class="node_info_label">Last updated</span>
37 <td class="description">Editor</td> 35 <%= @page.updated_at %>
38 <td><%= @page.editor.try(:login) %></td> 36 </div>
39 </tr> 37 <% if @page.published_at.present? %>
40 <% if @page.node.locked? %> 38 <div class="node_info_item">
41 <tr> 39 <span class="node_info_label"><%= @page.public? ? 'Published at' : 'Will publish at' %></span>
42 <td class="description">Locked by</td> 40 <%= @page.published_at %>
43 <td><span class="warning"><%= @page.node.lock_owner.login %></span></td> 41 </div>
44 </tr> 42 <% end %>
45 <% end %> 43 </div>
46 <tr> 44 </div>
47 <td class="description">Last updated</td> 45
48 <td><%= @page.updated_at %></td> 46 <div class="node_description">Links</div>
49 </tr> 47 <div class="node_content node_info_group">
50 <tr> 48 <div class="node_info_group_items">
51 <td class="description">Published at</td> 49 <div class="node_info_item">
52 <td><%= @page.published_at %></td> 50 <span class="node_info_label">Public</span>
53 </tr> 51 <%= link_to @page.public_link, content_url(@node.unique_path) %>
54 <tr> 52 </div>
55 <td class="description">Revision</td> 53 <% if @node.draft %>
56 <td><%= @page.revision %></td> 54 <div class="node_info_item">
57 </tr> 55 <span class="node_info_label">Admin Preview</span>
58 <tr> 56 <%= link_to preview_page_path(@node.draft), preview_page_path(@node.draft) %>
59 <td class="description">Tagged with:</td> 57 </div>
60 <td><%= @page.tag_list %></td> 58 <div class="node_info_item">
61 </tr> 59 <span class="node_info_label">Public Preview</span>
62 <tr> 60 <% if @node.draft.preview_token.present? %>
63 <td class="description">Events</td> 61 <%= link_to shared_preview_path(token: @node.draft.preview_token), shared_preview_url(token: @node.draft.preview_token), target: "_blank", rel: "noopener" %>
64 <td> 62 <%= button_to 'Revoke', revoke_shared_preview_node_path(@node), method: :put, form: { data: { confirm: "Revoke this preview link? Anyone currently using it will immediately lose access." } } %>
65 <ul> 63 <% else %>
66 <% @node.events.order(:start_time).each do |event| %> 64 <%= button_to 'Create public preview link', generate_shared_preview_node_path(@node), method: :put %>
67 <li> 65 <% end %>
68 <%= event_schedule_text(event) %> 66 </div>
69 [<%= link_to 'show', event_path(event, return_to: request.path) %>
70 | <%= link_to 'edit', edit_event_path(event, return_to: request.path) %>]
71 </li>
72 <% end %> 67 <% end %>
68 </div>
69 </div>
70
71 <div class="node_description">Revisions</div>
72 <div class="node_content node_info_group">
73 <details>
74 <summary>
75 <%= pluralize(@node.pages.count, 'revision', 'revisions') %>
76 &middot; <%= link_to 'full history (diff / restore)', node_revisions_path(@node) %>
77 </summary>
78 <ul>
79 <% @node.pages.order(:revision).each do |page| %>
80 <li><%= link_to "##{page.revision} — #{page.title} (#{page.user.try(:login)}, #{page.updated_at})", node_revision_path(@node, page) %></li>
81 <% end %>
73 </ul> 82 </ul>
74 <% mapping = default_event_tag_mapping(@page) %> 83 </details>
75 <%= link_to 'add event', new_event_path(node_id: @node.id, tag_list: mapping&.last, auto_tag_source: mapping&.first, return_to: request.path) %> 84 </div>
76 </td> 85
77 </tr> 86 <div class="node_description">Tags</div>
78 <tr> 87 <div class="node_content node_info_group"><%= @page.tag_list %></div>
79 <td class="description"><strong>Title</strong></td> 88
80 <td><%= sanitize( @page.title ) %></td> 89 <div class="node_description">Events</div>
81 </tr> 90 <div class="node_content node_info_group">
82 <tr> 91 <ul>
83 <td class="description"><strong>Abstract</strong></td> 92 <% @node.events.order(:start_time).each do |event| %>
84 <td><%= sanitize( @page.abstract ) %></td> 93 <li>
85 </tr> 94 <%= event_schedule_text(event) %>
86 <tr> 95 [<%= link_to 'show', event_path(event, return_to: request.path) %>
87 <td class="description"><strong>Body</strong></td> 96 | <%= link_to 'edit', edit_event_path(event, return_to: request.path) %>]
88 <td><%= sanitize( @page.body ) %></td> 97 </li>
89 </tr> 98 <% end %>
90 <tr> 99 </ul>
91 <td></td> 100 <% mapping = default_event_tag_mapping(@page) %>
92 <td class="right"></td> 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) %>
93 </tr> 102 </div>
94 </table> 103
104 <% if @node.children.any? %>
105 <div class="node_description">Children</div>
106 <div class="node_content node_info_group">
107 <details>
108 <summary><%= pluralize(@node.children.count, 'child', 'children') %></summary>
109 <ul>
110 <% @node.children.order(:slug).each do |child| %>
111 <li><%= link_to (child.head&.title || child.draft&.title || child.slug), node_path(child) %></li>
112 <% end %>
113 </ul>
114 </details>
115 </div>
116 <% end %>
117
118 <div class="node_description"><strong>Title</strong></div>
119 <div class="node_content"><strong><%= sanitize(@page.title) %></strong></div>
120
121 <div class="node_description">Abstract</div>
122 <div class="node_content"><%= sanitize(@page.abstract) %></div>
123
124 <div class="node_description">Body</div>
125 <div class="node_content"><%= sanitize(@page.body) %></div>
126 <%# Assets not yet addressed - no confirmed model/association seen for this page's attached assets %>
127
128 </div>
95</div> 129</div>
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index f240bb2..1196d83 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -106,7 +106,6 @@ div.pagination span.current, div.pagination a:hover {
106#main_navigation a, 106#main_navigation a,
107#sub_navigation a, 107#sub_navigation a,
108#metadata a, 108#metadata a,
109input[type=submit],
110#overview_toggle a { 109#overview_toggle a {
111 letter-spacing: 1px; 110 letter-spacing: 1px;
112 padding-left: 5px; 111 padding-left: 5px;
@@ -316,17 +315,23 @@ input[type=password] {
316 border: 1px solid #989898; 315 border: 1px solid #989898;
317} 316}
318 317
319input[type=submit] { 318input[type="submit"] {
320 border: none; 319 -webkit-appearance: none;
321 padding-top: 0px; 320 appearance: none;
322 padding-bottom: 1px; 321 background: none;
323 background-color: #ffffff; 322 border: 1px solid #000000;
323 border-radius: 2px;
324 padding: 4px 12px;
325 font: inherit;
326 font-weight: bold;
327 color: inherit;
328 cursor: pointer;
329 text-decoration: none;
324} 330}
325 331
326input[type=submit]:hover { 332input[type="submit"]:hover {
327 border: none;
328 color: #ffffff; 333 color: #ffffff;
329 background-color: #ff9600; 334 background-color: #000000;
330} 335}
331 336
332input[type=radio] { 337input[type=radio] {
@@ -516,6 +521,39 @@ table tr.header {
516 padding-bottom: 4px; 521 padding-bottom: 4px;
517} 522}
518 523
524.node_content.node_info_group {
525 border: 1px solid #e8e8e8;
526 border-radius: 6px;
527 padding: 0.5rem 0.75rem;
528}
529
530.node_info_group_items {
531 display: flex;
532 flex-wrap: wrap;
533 gap: 0.5rem 2rem;
534}
535
536.node_info_item {
537 min-width: 140px;
538}
539
540.node_info_label {
541 display: block;
542 font-weight: normal;
543 color: #969696;
544 font-size: 0.85rem;
545 text-transform: lowercase;
546}
547
548.node_info_group ul {
549 margin: 0;
550 padding-left: 1.25rem;
551}
552
553.node_info_group li {
554 margin-bottom: 0.25rem;
555}
556
519#search_widget { 557#search_widget {
520 position: absolute; 558 position: absolute;
521 top: 20px; 559 top: 20px;
@@ -569,14 +607,6 @@ table tr.header {
569 border-bottom: none; 607 border-bottom: none;
570} 608}
571 609
572table#content th.description {
573 width: 100px;
574}
575
576table#content th.content {
577 width: 690px;
578}
579
580#menu_item_list { 610#menu_item_list {
581 border-collapse: collapse; 611 border-collapse: collapse;
582 padding: 5px 5px 5px 5px; 612 padding: 5px 5px 5px 5px;