diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-08-20 12:50:30 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-08-20 12:50:30 +0200 |
| commit | a8cf725312d245d8d1f1df0fff22718975ce249e (patch) | |
| tree | c5084dfe79e6eac647b0253a305361aa8ff6efbc /app | |
| parent | 4df88b601b1900c287051d827eaff46f498f60d0 (diff) | |
Add redirect ui in in nodes#edit and apply some minor cleanup
Also give search inputs a magnifier and a clear control: input_group
wraps a field with an icon and, where clearing is meaningful, a
button that empties it and any hidden companion named by data-clears.
Clean up some inconistency between field hints presented as p and as
span.
Remove stray console.log.
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/content_controller.rb | 5 | ||||
| -rw-r--r-- | app/models/page.rb | 26 | ||||
| -rw-r--r-- | app/views/nodes/edit.html.erb | 53 |
3 files changed, 60 insertions, 24 deletions
diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb index 8be4bfbd..debb69b9 100644 --- a/app/controllers/content_controller.rb +++ b/app/controllers/content_controller.rb | |||
| @@ -14,6 +14,11 @@ class ContentController < ApplicationController | |||
| 14 | expires_in 20.minutes, :public => true | 14 | expires_in 20.minutes, :public => true |
| 15 | 15 | ||
| 16 | if @page and @page.public? | 16 | if @page and @page.public? |
| 17 | if (target = @page.redirect_target) | ||
| 18 | return redirect_to(target.internal? ? content_path(target.node.unique_name) : target.url, | ||
| 19 | :status => @page.redirect_status) | ||
| 20 | end | ||
| 21 | |||
| 17 | render( | 22 | render( |
| 18 | :template => @page.valid_template, | 23 | :template => @page.valid_template, |
| 19 | :layout => true | 24 | :layout => true |
diff --git a/app/models/page.rb b/app/models/page.rb index 9115ccb0..b17708e7 100644 --- a/app/models/page.rb +++ b/app/models/page.rb | |||
| @@ -124,7 +124,7 @@ class Page < ApplicationRecord | |||
| 124 | end | 124 | end |
| 125 | 125 | ||
| 126 | # One row per non-default locale, read from the actual translation | 126 | # One row per non-default locale, read from the actual translation |
| 127 | # row -- never through the locale-dependent accessor, so a locale | 127 | # row, never through the locale-dependent accessor, so a locale |
| 128 | # with no real translation yet reports as absent rather than quietly | 128 | # with no real translation yet reports as absent rather than quietly |
| 129 | # showing a fallback value borrowed from another locale. | 129 | # showing a fallback value borrowed from another locale. |
| 130 | def translation_summary | 130 | def translation_summary |
| @@ -311,21 +311,27 @@ class Page < ApplicationRecord | |||
| 311 | published_at.nil? ? true : published_at < Time.now | 311 | published_at.nil? ? true : published_at < Time.now |
| 312 | end | 312 | end |
| 313 | 313 | ||
| 314 | # The destination this page sends visitors to, or nil. An internal target | 314 | # Where this page sends visitors, or nil. Internal wins over external. A |
| 315 | # wins over an external one. A target that is restricted or has no head is | 315 | # node that is restricted or has no head is no destination at all, so the |
| 316 | # no destination at all, so the page renders itself rather than linking to | 316 | # page renders itself rather than pointing at nothing. |
| 317 | # nothing. The banner partial calls this too, so the precedence cannot | 317 | RedirectTarget = Struct.new(:node, :url) do |
| 318 | # drift between the redirect and the link. | 318 | def internal? |
| 319 | node.present? | ||
| 320 | end | ||
| 321 | end | ||
| 322 | |||
| 319 | def redirect_target | 323 | def redirect_target |
| 320 | return nil if redirect.blank? | 324 | return nil if redirect.blank? |
| 321 | 325 | ||
| 322 | if redirect_node_id.present? | 326 | if redirect_node_id.present? |
| 323 | node = Node.find_by(:id => redirect_node_id) | 327 | target = Node.find_by(:id => redirect_node_id) |
| 324 | return nil unless node&.head && !node.restricted? | 328 | return nil unless target&.head && !target.restricted? |
| 325 | return node.unique_name | 329 | return RedirectTarget.new(target, nil) |
| 326 | end | 330 | end |
| 327 | 331 | ||
| 328 | external_url.presence | 332 | return RedirectTarget.new(nil, external_url) if external_url.present? |
| 333 | |||
| 334 | nil | ||
| 329 | end | 335 | end |
| 330 | 336 | ||
| 331 | def redirect_status | 337 | def redirect_status |
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb index 927cc648..f629985e 100644 --- a/app/views/nodes/edit.html.erb +++ b/app/views/nodes/edit.html.erb | |||
| @@ -80,7 +80,7 @@ | |||
| 80 | </li> | 80 | </li> |
| 81 | <% end %> | 81 | <% end %> |
| 82 | </ul> | 82 | </ul> |
| 83 | <p class="field_hint"><%= t(".headline_hint") %></p> | 83 | <span class="field_hint"><%= t(".headline_hint") %></span> |
| 84 | <%= text_field_tag nil, nil, id: "related_asset_search_term", placeholder: t(".attach_search_placeholder"), autocomplete: "off" %> | 84 | <%= text_field_tag nil, nil, id: "related_asset_search_term", placeholder: t(".attach_search_placeholder"), autocomplete: "off" %> |
| 85 | <div id="related_asset_search_results" class="search_results"></div> | 85 | <div id="related_asset_search_results" class="search_results"></div> |
| 86 | </div> | 86 | </div> |
| @@ -118,7 +118,7 @@ | |||
| 118 | <div class="layout_row_label"><%= t(".parent") %></div> | 118 | <div class="layout_row_label"><%= t(".parent") %></div> |
| 119 | <div class="layout_row_content"> | 119 | <div class="layout_row_content"> |
| 120 | <%= text_field_tag :move_to_search_term, (Node.find_by(:id => @page.parent_node_id) || @node.parent)&.title %> | 120 | <%= text_field_tag :move_to_search_term, (Node.find_by(:id => @page.parent_node_id) || @node.parent)&.title %> |
| 121 | <p class="field_hint"><%= t(".parent_hint") %></p> | 121 | <span class="field_hint"><%= t(".parent_hint") %></span> |
| 122 | <div id="move_to_search_results" class="search_results"></div> | 122 | <div id="move_to_search_results" class="search_results"></div> |
| 123 | <%= d.hidden_field( | 123 | <%= d.hidden_field( |
| 124 | :parent_node_id, | 124 | :parent_node_id, |
| @@ -129,6 +129,43 @@ | |||
| 129 | </div> | 129 | </div> |
| 130 | 130 | ||
| 131 | <div class="layout_row"> | 131 | <div class="layout_row"> |
| 132 | <div class="layout_row_label"><%= t(".author") %></div> | ||
| 133 | <div class="layout_row_content"> | ||
| 134 | <%= d.select :user_id, user_list, | ||
| 135 | :selected => @page.user_id || @node.draft&.user_id || @node.head&.user_id %> | ||
| 136 | </div> | ||
| 137 | </div> | ||
| 138 | |||
| 139 | <div class="layout_row"> | ||
| 140 | <div class="layout_row_label"><%= t(".publish_at") %></div> | ||
| 141 | <div class="layout_row_content"><%= d.datetime_select :published_at, :value => @page.published_at %></div> | ||
| 142 | </div> | ||
| 143 | |||
| 144 | <div class="layout_row"> | ||
| 145 | <div class="layout_row_label"><%= t(".redirect") %></div> | ||
| 146 | <div class="layout_row_content"> | ||
| 147 | <label> | ||
| 148 | <%= d.check_box :redirect, { :checked => @page.redirect.present? }, | ||
| 149 | "temporary", "" %> | ||
| 150 | <%= t(".redirect_enable") %> | ||
| 151 | </label> | ||
| 152 | <span class="field_hint"><%= t(".redirect_hint") %></span> | ||
| 153 | |||
| 154 | <div id="redirect_target_field" style="<%= "display: none;" unless @page.redirect.present? %>"> | ||
| 155 | <div class="input_group" data-clears="page_redirect_node_id"> | ||
| 156 | <span class="field_search_icon"><%= icon("search", library: "tabler", "aria-hidden": true) %></span> | ||
| 157 | <%= text_field_tag :redirect_search_term, | ||
| 158 | Node.find_by(:id => @page.redirect_node_id)&.title, | ||
| 159 | :placeholder => "—", :class => "clearable_input" %> | ||
| 160 | <div id="redirect_search_results" class="search_results"></div> | ||
| 161 | <button type="button" class="field_clear" aria-label="clear input">×</button> | ||
| 162 | <%= d.hidden_field :redirect_node_id %> | ||
| 163 | </div> | ||
| 164 | </div> | ||
| 165 | </div> | ||
| 166 | </div> | ||
| 167 | |||
| 168 | <div class="layout_row"> | ||
| 132 | <div class="layout_row_label"><%= t(".external_url") %></div> | 169 | <div class="layout_row_label"><%= t(".external_url") %></div> |
| 133 | <div class="layout_row_content"> | 170 | <div class="layout_row_content"> |
| 134 | <%= d.text_field :external_url %> | 171 | <%= d.text_field :external_url %> |
| @@ -144,11 +181,6 @@ | |||
| 144 | </div> | 181 | </div> |
| 145 | 182 | ||
| 146 | <div class="layout_row"> | 183 | <div class="layout_row"> |
| 147 | <div class="layout_row_label"><%= t(".publish_at") %></div> | ||
| 148 | <div class="layout_row_content"><%= d.datetime_select :published_at, :value => @page.published_at %></div> | ||
| 149 | </div> | ||
| 150 | |||
| 151 | <div class="layout_row"> | ||
| 152 | <div class="layout_row_label"><%= t(".template") %></div> | 184 | <div class="layout_row_label"><%= t(".template") %></div> |
| 153 | <div class="layout_row_content"> | 185 | <div class="layout_row_content"> |
| 154 | <%= d.select :template_name, custom_page_templates, {:prompt => 'Select Template'} %> | 186 | <%= d.select :template_name, custom_page_templates, {:prompt => 'Select Template'} %> |
| @@ -156,13 +188,6 @@ | |||
| 156 | </div> | 188 | </div> |
| 157 | </div> | 189 | </div> |
| 158 | 190 | ||
| 159 | <div class="layout_row"> | ||
| 160 | <div class="layout_row_label"><%= t(".author") %></div> | ||
| 161 | <div class="layout_row_content"> | ||
| 162 | <%= d.select :user_id, user_list, | ||
| 163 | :selected => @page.user_id || @node.draft&.user_id || @node.head&.user_id %> | ||
| 164 | </div> | ||
| 165 | </div> | ||
| 166 | </div> | 191 | </div> |
| 167 | </details> | 192 | </details> |
| 168 | 193 | ||
