summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-22 15:28:37 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-22 15:28:37 +0200
commit028b0ce90e16d488dba291007b9092a6a700ca3a (patch)
tree93bd81220d820181756b236de40e39b00fe0f40b
parent6893270420c13cf1a0af158cf3d4cc71cd7e2005 (diff)
Split nodes#edit into a four-quadrant layout on desktop
-rw-r--r--app/views/nodes/edit.html.erb129
-rw-r--r--public/javascripts/admin_interface.js8
-rw-r--r--public/stylesheets/admin.css47
3 files changed, 112 insertions, 72 deletions
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb
index 12dc000..fc1d18f 100644
--- a/app/views/nodes/edit.html.erb
+++ b/app/views/nodes/edit.html.erb
@@ -33,48 +33,17 @@
33 </div> 33 </div>
34 <% end %> 34 <% end %>
35 35
36 <details id="metadata_details"> 36 <%= fields_for @page do |d| %>
37 <summary>Metadata (slug, parent, tags, template, author, images)</summary> 37 <div id="edit_grid">
38 <div id="metadata">
39 <div class="node_description">Slug</div>
40 <div class="node_content">
41 <%= f.text_field(
42 :staged_slug, :value => @node.staged_slug || @node.slug
43 )
44 %>
45 </div>
46 38
47 <div class="node_description">parent</div> 39 <div id="main_fields">
48 <div class="node_content"> 40 <div class="node_description">Title</div>
49 <%= text_field_tag :move_to_search_term, @node.parent.title rescue "" %> 41 <div class="node_content"><%= d.text_field :title %></div>
50 <div id="move_to_search_results" class="search_results"></div>
51 <%= f.hidden_field(
52 :staged_parent_id,
53 :value => @node.staged_parent_id || @node.parent_id
54 )
55 %>
56 </div>
57 42
58 <%= fields_for @page do |d| %> 43 <div class="node_description">Abstract</div>
59 <div class="node_description">Tags - comma seperated</div> 44 <div class="node_content"><%= d.text_area :abstract %></div>
60 <div class="node_content"><%= text_field_tag :tag_list, @page.tag_list.join(', ') %></div>
61 45
62 <div class="node_description">Publish at</div> 46 <div class="node_description">Attachments</div>
63 <div class="node_content"><%= d.datetime_select :published_at, :value => @page.published_at %></div>
64
65 <div class="node_description">Template</div>
66 <div class="node_content">
67 <%= d.select :template_name, custom_page_templates, {:prompt => 'Select Template'} %>
68 <span class="field_hint">Set automatically based on how this node was created - change it if needed.</span>
69 </div>
70
71 <div class="node_description">Author</div>
72 <div class="node_content">
73 <%= d.select :user_id, user_list,
74 :selected => @page.user_id || @node.draft&.user_id || @node.head&.user_id %>
75 </div>
76
77 <div class="node_description">Images</div>
78 <div class="node_content"> 47 <div class="node_content">
79 <div id="related_assets" 48 <div id="related_assets"
80 data-search-url="<%= search_node_related_assets_path(@node) %>" 49 data-search-url="<%= search_node_related_assets_path(@node) %>"
@@ -120,33 +89,73 @@
120 </button> 89 </button>
121 </li> 90 </li>
122 </template> 91 </template>
123
124 </div> 92 </div>
125 </details>
126 93
127 <div id="content"> 94 <details id="metadata_details">
128 <div class="node_description">Title</div> 95 <summary>Metadata (slug, parent, tags, template, author, images)</summary>
129 <div class="node_content"><%= d.text_field :title %></div> 96 <div id="metadata">
97 <div class="node_description">Slug</div>
98 <div class="node_content">
99 <%= f.text_field(
100 :staged_slug, :value => @node.staged_slug || @node.slug
101 )
102 %>
103 </div>
104
105 <div class="node_description">parent</div>
106 <div class="node_content">
107 <%= text_field_tag :move_to_search_term, @node.parent.title rescue "" %>
108 <p class="field_hint">Start typing to find a new parent for this node.</p>
109 <div id="move_to_search_results" class="search_results"></div>
110 <%= f.hidden_field(
111 :staged_parent_id,
112 :value => @node.staged_parent_id || @node.parent_id
113 )
114 %>
115 </div>
130 116
131 <div class="node_description">Abstract</div> 117 <div class="node_description">Tags</div>
132 <div class="node_content"><%= d.text_area :abstract %></div> 118 <div class="node_content">
119 <%= text_field_tag :tag_list, @page.tag_list.join(', ') %>
120 <span class="field_hint">Comma separated.</span>
121 </div>
133 122
134 <div class="node_description">Body</div> 123 <div class="node_description">Publish at</div>
135 <div class="body_toolbar_row"> 124 <div class="node_content"><%= d.datetime_select :published_at, :value => @page.published_at %></div>
136 <button type="button" id="preview_toggle" class="view_toggle" aria-pressed="false" aria-label="Toggle live preview" title="Toggle live preview">
137 <%= icon("layout-sidebar-right", library: "tabler", "aria-hidden": true) %> <span>Live preview</span>
138 </button>
139 <button type="button" id="preview_force_render" class="view_toggle" style="display:none" aria-label="Force preview render" title="Force preview render">
140 <%= icon("refresh", library: "tabler", "aria-hidden": true) %> <span>Force refresh</span>
141 </button>
142 </div>
143 125
144 <div id="editor_and_preview"> 126 <div class="node_description">Template</div>
145 <div class="preview_content"> 127 <div class="node_content">
146 <div class="node_content"><%= d.text_area :body, :class => 'with_editor' %></div> 128 <%= d.select :template_name, custom_page_templates, {:prompt => 'Select Template'} %>
129 <span class="field_hint">Set automatically based on how this node was created - change it if needed.</span>
130 </div>
131
132 <div class="node_description">Author</div>
133 <div class="node_content">
134 <%= d.select :user_id, user_list,
135 :selected => @page.user_id || @node.draft&.user_id || @node.head&.user_id %>
136 </div>
137
138 </div>
139 </details>
140
141 <div id="content">
142 <div class="node_description">Body</div>
143 <div class="body_toolbar_row">
144 <button type="button" id="preview_toggle" class="view_toggle" aria-pressed="false" aria-label="Toggle live preview" title="Toggle live preview">
145 <%= icon("layout-sidebar-right", library: "tabler", "aria-hidden": true) %> <span>Live preview</span>
146 </button>
147 <button type="button" id="preview_force_render" class="view_toggle" style="display:none" aria-label="Force preview render" title="Force preview render">
148 <%= icon("refresh", library: "tabler", "aria-hidden": true) %> <span>Force refresh</span>
149 </button>
147 </div> 150 </div>
148 <div id="preview_panel" style="display:none"> 151
149 <iframe id="live_preview_iframe" title="Live preview" data-src="<%= preview_page_path(@page, :locale => I18n.default_locale) %>"></iframe> 152 <div id="editor_and_preview">
153 <div class="preview_content">
154 <div class="node_content"><%= d.text_area :body, :class => 'with_editor' %></div>
155 </div>
156 <div id="preview_panel" style="display:none">
157 <iframe id="live_preview_iframe" title="Live preview" data-src="<%= preview_page_path(@page, :locale => I18n.default_locale) %>"></iframe>
158 </div>
150 </div> 159 </div>
151 </div> 160 </div>
152 </div> 161 </div>
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js
index f6d915f..64da5a8 100644
--- a/public/javascripts/admin_interface.js
+++ b/public/javascripts/admin_interface.js
@@ -73,6 +73,14 @@ $(document).ready(function () {
73 cccms.preview.initialize(); 73 cccms.preview.initialize();
74 } 74 }
75 75
76 var metadata_details = document.getElementById('metadata_details');
77 if (metadata_details) {
78 var desktop_mq = window.matchMedia('(min-width: 1016px)');
79 var sync_metadata = function() { metadata_details.open = desktop_mq.matches; };
80 sync_metadata();
81 desktop_mq.addEventListener('change', sync_metadata);
82 }
83
76 jQuery.ajaxSetup({ 84 jQuery.ajaxSetup({
77 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");} 85 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");}
78 }); 86 });
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 7b06c7d..5c0a8c0 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -97,7 +97,8 @@ input[type=radio] {
97} 97}
98 98
99#metadata, 99#metadata,
100#content { 100#content,
101#main_fields {
101 margin-left: 5px; 102 margin-left: 5px;
102} 103}
103 104
@@ -892,6 +893,26 @@ form.button_to button[type="submit"] {
892 Page editor / metadata forms 893 Page editor / metadata forms
893 ============================================================ */ 894 ============================================================ */
894 895
896@media(min-width:1016px) {
897 #edit_grid {
898 display: grid;
899 grid-template-columns: 1fr 1fr;
900 grid-template-areas:
901 "main meta"
902 "body body";
903 column-gap: 2rem;
904 align-items: start;
905 margin-left: -125px;
906 }
907 #main_fields { grid-area: main; }
908 #metadata_details { grid-area: meta; }
909 #page_editor #content { grid-area: body; }
910
911 /* Details is force-opened by JS on desktop; hide the summary only
912 once open, so a JS failure still leaves a working toggle. */
913 #metadata_details[open] > summary { display: none; }
914}
915
895#new_node { 916#new_node {
896 margin-left: -118px; 917 margin-left: -118px;
897} 918}
@@ -947,18 +968,16 @@ div#page_editor {
947} 968}
948 969
949@media(min-width:1016px) { 970@media(min-width:1016px) {
950 input#tag_list,
951 input#menu_search_term, 971 input#menu_search_term,
952 input#menu_item_node_id, 972 input#menu_item_node_id,
953 input#menu_item_path, 973 input#menu_item_path,
954 input#menu_item_title, 974 input#menu_item_title {
955 input#node_staged_slug,
956 input#move_to_search_term {
957 width: 690px; 975 width: 690px;
958 } 976 }
959 977
960 input[type=text]#page_title { 978 input[type=text]#page_title {
961 width: 690px; 979 width: 100%;
980 box-sizing: border-box;
962 font-size: 1rem; 981 font-size: 1rem;
963 padding-top: 6px; 982 padding-top: 6px;
964 padding-bottom: 4px; 983 padding-bottom: 4px;
@@ -968,24 +987,27 @@ div#page_editor {
968 } 987 }
969 988
970 textarea#page_abstract { 989 textarea#page_abstract {
971 width: 690px; 990 width: 100%;
972 height: 150px; 991 box-sizing: border-box;
992 height: 250px;
973 padding: 5px; 993 padding: 5px;
974 } 994 }
975 995
976 #page_editor textarea#page_body { 996 #page_editor textarea#page_body {
977 width: 700px; 997 width: 100%;
998 box-sizing: border-box;
978 height: 600px; 999 height: 600px;
979 } 1000 }
980 1001
981 #page_editor #metadata, #page_editor #content, #admin_overview { 1002 #admin_overview {
982 margin-left: -125px; 1003 margin-left: -125px;
983 } 1004 }
984 1005
985 #page_editor input[type=text], 1006 #page_editor input[type=text],
986 #page_editor input[type=password], 1007 #page_editor input[type=password],
987 #page_editor textarea { 1008 #page_editor textarea {
988 width: 690px; 1009 width: 100%;
1010 box-sizing: border-box;
989 } 1011 }
990} 1012}
991 1013
@@ -998,7 +1020,8 @@ div#page_editor {
998 } 1020 }
999 1021
1000 #page_editor #content, 1022 #page_editor #content,
1001 #page_editor #metadata { 1023 #page_editor #metadata,
1024 #page_editor #main_fields {
1002 padding-right: 5px; 1025 padding-right: 5px;
1003 } 1026 }
1004 1027