diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-22 15:28:37 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-22 15:28:37 +0200 |
| commit | 028b0ce90e16d488dba291007b9092a6a700ca3a (patch) | |
| tree | 93bd81220d820181756b236de40e39b00fe0f40b | |
| parent | 6893270420c13cf1a0af158cf3d4cc71cd7e2005 (diff) | |
Split nodes#edit into a four-quadrant layout on desktop
| -rw-r--r-- | app/views/nodes/edit.html.erb | 129 | ||||
| -rw-r--r-- | public/javascripts/admin_interface.js | 8 | ||||
| -rw-r--r-- | public/stylesheets/admin.css | 47 |
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 | ||
