summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/content_controller.rb5
-rw-r--r--app/models/page.rb26
-rw-r--r--app/views/nodes/edit.html.erb53
-rw-r--r--config/locales/de.yml5
-rw-r--r--config/locales/en.yml5
-rw-r--r--public/javascripts/admin_interface.js27
-rw-r--r--public/javascripts/admin_search.js14
-rw-r--r--public/stylesheets/admin.css45
8 files changed, 150 insertions, 30 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
diff --git a/config/locales/de.yml b/config/locales/de.yml
index e94dfb7e..95d6edc2 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -454,6 +454,11 @@ de:
454 set_headline_title: "Dieses Foto als Aufmacherbild der Seite verwenden" 454 set_headline_title: "Dieses Foto als Aufmacherbild der Seite verwenden"
455 remove_image_label: "Bild entfernen" 455 remove_image_label: "Bild entfernen"
456 external_url: "Externe Homepage" 456 external_url: "Externe Homepage"
457 redirect: "Weiterleitung"
458 redirect_enable: "Diese Seite leitet weiter"
459 redirect_hint: "Ohne Ziel-Node wird auf die externe Homepage weitergeleitet."
460 redirect_target_hint: "Bleibt erhalten, wenn die Weiterleitung abgeschaltet wird."
461 redirect_clear: "Ziel entfernen"
457 show: 462 show:
458 status: "Status" 463 status: "Status"
459 head: "Head" 464 head: "Head"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 54d973e2..8b67f0ed 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -406,6 +406,11 @@ en:
406 set_headline_title: "Use this photo as the page's headline image" 406 set_headline_title: "Use this photo as the page's headline image"
407 remove_image_label: "Remove image" 407 remove_image_label: "Remove image"
408 external_url: "External homepage" 408 external_url: "External homepage"
409 redirect: "Redirect"
410 redirect_enable: "This page redirects"
411 redirect_hint: "If the taget node is empty, the external url will be used."
412 redirect_target_hint: "Disabling does not clear the destination"
413 redirect_clear: "Clear redirect"
409 show: 414 show:
410 status: "Status" 415 status: "Status"
411 head: "Head" 416 head: "Head"
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js
index 994d3698..ffebb694 100644
--- a/public/javascripts/admin_interface.js
+++ b/public/javascripts/admin_interface.js
@@ -63,7 +63,6 @@ $(document).ready(function () {
63 search_toggle.addEventListener('click', function (e) { 63 search_toggle.addEventListener('click', function (e) {
64 admin_search.display_toggle(); 64 admin_search.display_toggle();
65 e.preventDefault(); 65 e.preventDefault();
66 console.log("Toggle");
67 }); 66 });
68 } 67 }
69 68
@@ -124,6 +123,32 @@ $(document).ready(function () {
124 }); 123 });
125 } 124 }
126 125
126 document.querySelectorAll('.field_clear').forEach(function (btn) {
127 btn.addEventListener('click', function () {
128 var wrap = btn.closest('.input_group');
129 wrap.querySelector('input[type=text]').value = '';
130 var hidden = wrap.dataset.clears;
131 if (hidden) { document.getElementById(hidden).value = ''; }
132 });
133 });
134
135 if (document.getElementById('redirect_search_term')) {
136 redirect_search.initialize_search();
137
138 var mode = document.getElementById('page_redirect');
139 var field = document.getElementById('redirect_target_field');
140 mode.addEventListener('change', function () {
141 field.style.display = mode.checked ? '' : 'none';
142 });
143
144 var term = document.getElementById('redirect_search_term');
145 term.addEventListener('input', function () {
146 if (term.value === '') {
147 document.getElementById('page_redirect_node_id').value = '';
148 }
149 });
150 }
151
127 var metadata_details = document.getElementById('metadata_details'); 152 var metadata_details = document.getElementById('metadata_details');
128 if (metadata_details) { 153 if (metadata_details) {
129 var desktop_mq = window.matchMedia('(min-width: 1016px)'); 154 var desktop_mq = window.matchMedia('(min-width: 1016px)');
diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js
index e20e511a..746cecc1 100644
--- a/public/javascripts/admin_search.js
+++ b/public/javascripts/admin_search.js
@@ -263,6 +263,20 @@ move_to_search = {
263 } 263 }
264}; 264};
265 265
266redirect_search = {
267 initialize_search : function() {
268 initSearchPicker({
269 inputSelector: "#redirect_search_term",
270 resultsSelector: "#redirect_search_results",
271 showRestricted: true,
272 onSelect: function(node) {
273 $("#redirect_search_term").val(node.title);
274 $("#page_redirect_node_id").val(node.node_id);
275 }
276 });
277 }
278};
279
266restore_search = { 280restore_search = {
267 initialize_search : function() { 281 initialize_search : function() {
268 initSearchPicker({ 282 initSearchPicker({
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 923cac4b..63333d93 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -446,11 +446,8 @@ form.button_to.computation button[type="submit"]:disabled {
446 pointer-events: none; 446 pointer-events: none;
447} 447}
448 448
449/* Native (non-button_to) submit buttons -- Create/Save/Publish forms. 449/* Native (non-button_to) submit buttons used for Create/Save/Publish
450 Lower specificity than form.button_to's own rule, so button_to forms 450 forms. */
451 are correctly unaffected. If a button_to output ever loses its class
452 wrapper, it will silently fall through to this bordered style instead
453 of rendering as a plain link -- worth knowing, not necessarily fixing. */
454input[type="submit"] { 451input[type="submit"] {
455 -webkit-appearance: none; 452 -webkit-appearance: none;
456 appearance: none; 453 appearance: none;
@@ -470,6 +467,44 @@ input[type="submit"]:hover {
470 background-color: var(--text); 467 background-color: var(--text);
471} 468}
472 469
470.input_group {
471 width: 100%;
472 position: relative;
473 display: inline-block;
474}
475
476.input_group .field_search_icon {
477 position: absolute;
478 left: 0.5rem;
479 top: 55%;
480 transform: translateY(-50%);
481 color: var(--text-muted);
482 pointer-events: none;
483}
484
485.input_group .field_search_icon svg {
486 width: 1em;
487 height: 1em;
488}
489
490.input_group input[type="text"].clearable_input {
491 padding-left: 2rem;
492 padding-right: 1.75rem;
493}
494
495.field_clear {
496 position: absolute;
497 right: 0.25rem;
498 top: 50%;
499 transform: translateY(-50%);
500 appearance: none;
501 background: none;
502 border: 0;
503 color: var(--text-muted);
504 cursor: pointer;
505 line-height: 1;
506}
507
473/* ============================================================ 508/* ============================================================
474 Admin dashboard 509 Admin dashboard
475 ============================================================ */ 510 ============================================================ */