summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-08 22:52:00 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-08 22:52:00 +0200
commit168ff3f8b6037bc3c2b5bce74adac37e48366dac (patch)
tree3d33fa85125266f3ec827528f73da4704b948f45
parent9427dc462e68eb4b902cd5b2ace5607b036504ef (diff)
Add the Status section to nodes#show and rebuild nodes#edit's action bar
Retires content_for :subnavigation on nodes#show entirely -- Preview was fully redundant with Links' own preview URLs in every state, and Edit is now a permanent, always-rendered action inside the new Status section rather than a link floating at the top, consistent with the "action lives next to the info" pattern People already established for Unlock. Status surfaces head/draft/autosave plainly and gates Edit/Publish/Destroy/Discard on lock ownership specifically, not mere lock presence -- @node.locked? alone would have blocked the lock owner's own session, caught by the click-test and fixed here rather than shipped. nodes#edit's action bar is rebuilt to sit outside form_for (both button_to calls render their own nested form, invalid HTML the browser was silently stripping) with Save wired back in via form="..." rather than needing to live inside the form tag at all. Also brings the locked-and-ready-to-edit flash message, and the visual polish from this session's click-test: consistent button heights across the bordered and pill-shaped variants sharing a row, spacing between Status's data and its actions, and error_messages styling reusing the destructive-red vocabulary already established elsewhere.
-rw-r--r--app/controllers/nodes_controller.rb2
-rw-r--r--app/views/nodes/edit.html.erb45
-rw-r--r--app/views/nodes/show.html.erb58
-rw-r--r--public/stylesheets/admin.css88
4 files changed, 159 insertions, 34 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
index d8586e2..72d4a3e 100644
--- a/app/controllers/nodes_controller.rb
+++ b/app/controllers/nodes_controller.rb
@@ -68,6 +68,8 @@ class NodesController < ApplicationController
68 @node.lock_for_editing!( current_user ) 68 @node.lock_for_editing!( current_user )
69 @page = @node.autosave || @node.draft || @node.head 69 @page = @node.autosave || @node.draft || @node.head
70 70
71 flash.now[:notice] = "Node locked and ready to edit" unless @node.autosave
72
71 if @node.autosave 73 if @node.autosave
72 flash.now[:notice] = 74 flash.now[:notice] =
73 "This page has unsaved changes from a previous session, shown below. " \ 75 "This page has unsaved changes from a previous session, shown below. " \
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb
index feba92a..cdc9b36 100644
--- a/app/views/nodes/edit.html.erb
+++ b/app/views/nodes/edit.html.erb
@@ -1,3 +1,21 @@
1<h1><%= title_for_node(@node) %></h1>
2
3<div class="node_action_bar">
4 <%= button_to 'Unlock + Back', unlock_node_path(@node), method: :put,
5 form: { class: 'button_to state_changing' },
6 disabled: @node.autosave.present? %>
7
8 <% if @node.autosave || (@node.draft && @node.head) %>
9 <%= button_to (@node.draft && !@node.autosave ? 'Destroy Draft' : 'Discard changes'),
10 revert_node_path(@node), method: :put,
11 form: { data: { confirm: "This cannot be undone. Continue?" }, class: 'button_to destructive' } %>
12 <% end %>
13
14 <%= submit_tag "Save Draft", form: dom_id(@node, :edit) %>
15 <%= submit_tag "Save + Unlock + Exit", form: dom_id(@node, :edit) %>
16 <%= link_to "Preview ↗", preview_page_path(@page), target: "_blank", rel: "noopener", class: "preview_link" %>
17</div>
18
1<div id="page_editor"> 19<div id="page_editor">
2 <%= form_for(@node, html: { data: { autosave_url: autosave_node_path(@node), show_url: node_path(@node) } }) do |f| %> 20 <%= form_for(@node, html: { data: { autosave_url: autosave_node_path(@node), show_url: node_path(@node) } }) do |f| %>
3 <% if @node.errors.any? %> 21 <% if @node.errors.any? %>
@@ -6,25 +24,6 @@
6 </div> 24 </div>
7 <% end %> 25 <% end %>
8 26
9 <h1><%= title_for_node(@node) %></h1>
10
11 <div id="node_action_bar">
12 <%= button_to 'Unlock + Back', unlock_node_path(@node), method: :put,
13 form: { class: 'button_to state_changing' },
14 disabled: @node.autosave.present? %>
15
16 <% if @node.autosave || (@node.draft && @node.head) %>
17 <%= button_to (@node.draft && !@node.autosave ? 'Destroy Draft' : 'Discard changes'),
18 revert_node_path(@node), method: :put,
19 form: { data: { confirm: "This cannot be undone. Continue?" }, class: 'button_to destructive' } %>
20 <% end %>
21
22 <%= link_to 'Preview', preview_page_path(@page) %>
23
24 <%= f.submit 'Save Draft' %>
25 <%= f.submit 'Save + Unlock + Exit' %>
26 </div>
27
28 <details id="metadata_details"> 27 <details id="metadata_details">
29 <summary>Metadata (slug, parent, tags, template, author, images)</summary> 28 <summary>Metadata (slug, parent, tags, template, author, images)</summary>
30 <div id="metadata"> 29 <div id="metadata">
@@ -102,9 +101,9 @@
102 <div class="node_content"><%= d.text_area :body, :class => 'with_editor' %></div> 101 <div class="node_content"><%= d.text_area :body, :class => 'with_editor' %></div>
103 <% end %> 102 <% end %>
104 103
105 <div id="node_action_bar"> 104 <div class="node_action_bar node_action_bar_save">
106 <%= f.submit 'Save Draft' %> 105 <%= f.submit 'Save Draft' %>
107 <%= f.submit 'Save + Unlock + Exit' %> 106 <%= f.submit 'Save + Unlock + Exit' %>
108 </div> 107 </div>
109 <% end %> 108 <% end %>
110</div> 109</div>
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index 2ab7986..036caf2 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -1,11 +1,59 @@
1<% content_for :subnavigation do %> 1<% locked_by_other = @node.locked? && @node.lock_owner != current_user %>
2 <%= link_to 'Edit', edit_node_path(@node), :class => "unselected" %>
3 <%= link_to 'Preview', preview_page_path(@page) %>
4<% end %>
5
6<div id="page_editor" class="show_node"> 2<div id="page_editor" class="show_node">
7 <h1><%= title_for_node(@node) %></h1> 3 <h1><%= title_for_node(@node) %></h1>
8 <div id="content"> 4 <div id="content">
5 <div class="node_description">Status</div>
6 <div class="node_content node_info_group node_status">
7 <div class="node_info_group_items">
8 <div class="node_info_item">
9 <span class="node_info_label">Head</span>
10 <%= @node.head ? "#{@node.head.title} (rev #{@node.head.revision}, #{@node.head.updated_at})" : "none — never published" %>
11 </div>
12 <div class="node_info_item">
13 <span class="node_info_label">Draft</span>
14 <%= @node.draft ? "#{@node.draft.title} (rev #{@node.draft.revision}, saved #{@node.draft.updated_at})" : "none" %>
15 </div>
16 <div class="node_info_item">
17 <span class="node_info_label">Autosave</span>
18 <%= @node.autosave ? "#{@node.autosave.title} (unsaved, #{@node.autosave.updated_at})" : "none" %>
19 </div>
20 </div>
21
22 <% edit_label = @node.autosave ? "Continue Editing" : (@node.draft ? "Edit Draft" : "Edit") %>
23 <div class="node_info_group_items">
24 <div class="node_info_item">
25 <% if locked_by_other %>
26 <span class="disabled_action"><%= edit_label %></span>
27 <% else %>
28 <%= link_to (@node.autosave ? "Continue Editing" : (@node.draft ? "Edit Draft" : "Lock + Edit")), edit_node_path(@node), class: "action_button" %>
29 <% if !@node.draft && !@node.autosave %>
30 <span class="field_hint">Nothing pending — this will start a fresh draft.</span>
31 <% end %>
32 <% end %>
33 </div>
34
35 <% unless locked_by_other %>
36 <% if @node.draft && !@node.autosave %>
37 <div class="node_info_item">
38 <%= button_to 'Publish', publish_node_path(@node), method: :put,
39 form: { data: { confirm: "Publish this draft?" }, class: 'button_to state_changing' } %>
40 </div>
41 <% end %>
42 <% if @node.draft || @node.autosave %>
43 <div class="node_info_item">
44 <%= button_to (@node.draft && !@node.autosave ? 'Destroy Draft' : 'Discard changes'),
45 revert_node_path(@node), method: :put,
46 form: { data: { confirm: "This cannot be undone. Continue?" }, class: 'button_to destructive' } %>
47 </div>
48 <% end %>
49 <% end %>
50 </div>
51
52 <% if locked_by_other %>
53 <span class="field_hint">Locked — see People below to unlock before editing, publishing, or discarding.</span>
54 <% end %>
55 </div>
56
9 <div class="node_description">People</div> 57 <div class="node_description">People</div>
10 <div class="node_content node_info_group"> 58 <div class="node_content node_info_group">
11 <div class="node_info_group_items"> 59 <div class="node_info_group_items">
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index ceacf17..a6e8bf6 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -79,6 +79,9 @@ input[type=radio] {
79 #wrapper { 79 #wrapper {
80 margin: 0 125px; 80 margin: 0 125px;
81 } 81 }
82 .node_action_bar.node_action_bar_save {
83 margin-left: 120px;
84 }
82} 85}
83@media(max-width:1015px) { 86@media(max-width:1015px) {
84 #wrapper { 87 #wrapper {
@@ -165,12 +168,14 @@ input[type=radio] {
165 margin-bottom: 40px; 168 margin-bottom: 40px;
166} 169}
167 170
168#node_action_bar { 171.node_action_bar {
169 margin-bottom: 1rem; 172 display: flex;
170} 173 flex-wrap: wrap;
171 174 align-items: center;
172#node_action_bar > * { 175 gap: 0.75rem;
173 margin-right: 0.5rem; 176 margin: 1rem 0 1.5rem;
177 padding-bottom: 1rem;
178 border-bottom: 1px solid #e8e8e8;
174} 179}
175 180
176/* ============================================================ 181/* ============================================================
@@ -213,6 +218,18 @@ span.warning a {
213 text-decoration: underline; 218 text-decoration: underline;
214} 219}
215 220
221.error_messages {
222 border-left: 3px solid #cc0000;
223 background-color: #fdecea;
224 padding: 6px 10px;
225 margin-bottom: 1rem;
226}
227
228.error_messages ul {
229 margin: 0;
230 padding-left: 1.25rem;
231}
232
216/* ============================================================ 233/* ============================================================
217 Pagination 234 Pagination
218 ============================================================ */ 235 ============================================================ */
@@ -487,6 +504,16 @@ table.user_table td.user_login {
487 padding: 0.5rem 0.75rem; 504 padding: 0.5rem 0.75rem;
488} 505}
489 506
507.node_info_group .disabled_action {
508 display: inline-block;
509 border: 1px solid #c0c0c0;
510 border-radius: 2px;
511 padding: 4px 12px;
512 font-weight: bold;
513 color: #969696;
514 cursor: not-allowed;
515}
516
490.node_info_group_items { 517.node_info_group_items {
491 display: flex; 518 display: flex;
492 flex-wrap: wrap; 519 flex-wrap: wrap;
@@ -514,6 +541,38 @@ table.user_table td.user_login {
514 margin-bottom: 0.25rem; 541 margin-bottom: 0.25rem;
515} 542}
516 543
544.node_content.node_status {
545 border: 2px solid #000000;
546 background-color: #fafafa;
547}
548
549.node_status .node_info_group_items:first-child {
550 margin-bottom: 0.75rem;
551}
552
553.node_status .node_info_group_items + .node_info_group_items {
554 margin-top: 0.5rem;
555 margin-bottom: 0.5rem;
556}
557
558.node_status .field_hint {
559 margin-top: 0.5rem;
560 display: block;
561}
562
563#page_editor a.action_button,
564.node_status form.button_to input[type="submit"],
565.node_status form.button_to button[type="submit"] {
566 padding: 4px 12px;
567 line-height: 1.2;
568 box-sizing: border-box;
569}
570
571.node_status form.button_to input[type="submit"],
572.node_status form.button_to button[type="submit"] {
573 border: 1px solid transparent;
574}
575
517.field_hint { 576.field_hint {
518 display: block; 577 display: block;
519 margin-top: 2px; 578 margin-top: 2px;
@@ -562,6 +621,23 @@ div#page_editor {
562 margin-left: 10px; 621 margin-left: 10px;
563} 622}
564 623
624#page_editor a.action_button {
625 display: inline-block;
626 -webkit-appearance: none;
627 appearance: none;
628 border: 1px solid #000000;
629 border-radius: 2px;
630 padding: 4px 12px;
631 font-weight: bold;
632 text-decoration: none;
633 color: #000000;
634}
635
636#page_editor a.action_button:hover {
637 color: #ffffff;
638 background-color: #000000;
639}
640
565@media(min-width:1016px) { 641@media(min-width:1016px) {
566 input#tag_list, 642 input#tag_list,
567 input#node_staged_slug, 643 input#node_staged_slug,