summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-30 23:22:47 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-30 23:22:47 +0200
commitfd1ef782f7fc85db740fcb2ac1f859725e7280c5 (patch)
tree06f1fae092876616de569f87f6d0f461e4b47833
parentbc116a2acfe2450d85229379d54c3344e485db6a (diff)
Provide the full i18n matrix for public/admin-preview/shared-preview urls
-rw-r--r--app/views/nodes/show.html.erb100
-rw-r--r--config/locales/de.yml2
-rw-r--r--config/locales/en.yml2
-rw-r--r--public/stylesheets/admin.css163
4 files changed, 253 insertions, 14 deletions
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index a337b555..8b12e2eb 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -194,25 +194,97 @@
194 194
195 <div class="layout_row_label"><%= t(".links") %></div> 195 <div class="layout_row_label"><%= t(".links") %></div>
196 <div class="layout_row_content info_group"> 196 <div class="layout_row_content info_group">
197 <div class="info_group_items"> 197 <%
198 <div class="info_item"> 198 # Public addresses come from the head, previews from the draft, and
199 <span class="info_label"><%= t(".public") %></span> 199 # those sets differ routinely -- a German head with a freshly
200 <%= link_to @page.public_link, content_url(@node.unique_path) %> 200 # translated English draft has one public address and two preview
201 # ones. Columns are the union; a kind with no address in a locale
202 # leaves that cell empty.
203 available = I18n.available_locales - [:root]
204 head_locales = (@node.head&.translated_locales || []).map(&:to_sym) & available
205 draft_locales = (@node.draft&.translated_locales || []).map(&:to_sym) & available
206 link_locales = (head_locales | draft_locales)
207 link_locales = [I18n.default_locale] if link_locales.empty?
208 link_locales = [I18n.default_locale] + (link_locales - [I18n.default_locale]).sort
209 locale_param = ->(locale) { locale unless locale == I18n.default_locale }
210 %>
211
212 <div class="link_matrix link_matrix_<%= link_locales.size %>">
213 <div class="link_matrix_head">
214 <span></span>
215 <% link_locales.each do |locale| %>
216 <span class="link_matrix_locale"><%= locale.to_s.upcase %></span>
217 <% end %>
201 </div> 218 </div>
219
220 <% if @node.head %>
221 <div class="link_matrix_row">
222 <span class="link_matrix_label">
223 <span class="info_label"><%= t(".public") %></span>
224 <%= link_to "/#{@node.unique_name}", content_url(@node.unique_path) %>
225 </span>
226 <span class="link_matrix_cells">
227 <% link_locales.each do |locale| %>
228 <span class="link_matrix_cell">
229 <% if head_locales.include?(locale) %>
230 <span class="link_locale_tag"><%= locale.to_s.upcase %></span>
231 <%= render "nodes/link_cell",
232 :url => content_url(@node.unique_path, :locale => locale_param.(locale)) %>
233 <% end %>
234 </span>
235 <% end %>
236 </span>
237 </div>
238 <% end %>
239
202 <% if @node.draft %> 240 <% if @node.draft %>
203 <div class="info_item"> 241 <div class="link_matrix_row">
204 <span class="info_label"><%= t(".admin_preview") %></span> 242 <span class="link_matrix_label">
205 <%= link_to preview_page_path(@node.draft), preview_page_path(@node.draft) %> 243 <span class="info_label"><%= t(".admin_preview") %></span>
244 <%= link_to preview_page_path(@node.draft), preview_page_url(@node.draft) %>
245 </span>
246 <span class="link_matrix_cells">
247 <% link_locales.each do |locale| %>
248 <span class="link_matrix_cell">
249 <% if draft_locales.include?(locale) %>
250 <span class="link_locale_tag"><%= locale.to_s.upcase %></span>
251 <%= render "nodes/link_cell",
252 :url => preview_page_url(@node.draft, :locale => locale_param.(locale)) %>
253 <% end %>
254 </span>
255 <% end %>
256 </span>
206 </div> 257 </div>
207 <div class="info_item"> 258
208 <span class="info_label"><%= t(".public_preview") %></span> 259 <div class="link_matrix_row">
260 <span class="link_matrix_label">
261 <span class="info_label"><%= t(".public_preview") %></span>
262 <% if @node.draft.preview_token.present? %>
263 <%= link_to shared_preview_path(:token => @node.draft.preview_token),
264 shared_preview_url(:token => @node.draft.preview_token),
265 target: "_blank", rel: "noopener" %>
266 <% end %>
267 </span>
209 <% if @node.draft.preview_token.present? %> 268 <% if @node.draft.preview_token.present? %>
210 <div class="aligned_action_row"> 269 <span class="link_matrix_cells">
211 <%= link_to shared_preview_path(token: @node.draft.preview_token), shared_preview_url(token: @node.draft.preview_token), target: "_blank", rel: "noopener" %> 270 <% link_locales.each do |locale| %>
212 <%= button_to t(".revoke"), revoke_shared_preview_node_path(@node), method: :put, form: { data: { confirm: t(".confirm_revoke") }, class: 'button_to state_changing' } %> 271 <span class="link_matrix_cell">
213 </div> 272 <% if draft_locales.include?(locale) %>
273 <span class="link_locale_tag"><%= locale.to_s.upcase %></span>
274 <%= render "nodes/link_cell",
275 :url => shared_preview_url(:token => @node.draft.preview_token,
276 :locale => locale_param.(locale)) %>
277 <% end %>
278 </span>
279 <% end %>
280 </span>
281 <%= button_to t(".revoke"), revoke_shared_preview_node_path(@node), method: :put,
282 form: { data: { confirm: t(".confirm_revoke") },
283 class: 'button_to state_changing link_matrix_actions' } %>
214 <% else %> 284 <% else %>
215 <%= button_to t(".create_preview_link"), generate_shared_preview_node_path(@node), method: :put, form: { class: 'button_to state_changing' } %> 285 <span class="link_matrix_cells"></span>
286 <%= button_to t(".create_preview_link"), generate_shared_preview_node_path(@node), method: :put,
287 form: { class: 'button_to state_changing link_matrix_actions' } %>
216 <% end %> 288 <% end %>
217 </div> 289 </div>
218 <% end %> 290 <% end %>
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 2d5e9685..521aca8b 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -404,6 +404,8 @@ de:
404 confirm_trash: "Diese Seite und alles darunter in den Papierkorb verschieben? Alle veröffentlichten Inhalte darin gehen offline." 404 confirm_trash: "Diese Seite und alles darunter in den Papierkorb verschieben? Alle veröffentlichten Inhalte darin gehen offline."
405 move_to_trash: "In den Papierkorb" 405 move_to_trash: "In den Papierkorb"
406 locked_hint: "Gesperrt — zum Bearbeiten, Veröffentlichen oder Verwerfen erst unter Personen entsperren." 406 locked_hint: "Gesperrt — zum Bearbeiten, Veröffentlichen oder Verwerfen erst unter Personen entsperren."
407 visit: "besuchen"
408 copy_url: "URL kopieren"
407 trash: "Papierkorb" 409 trash: "Papierkorb"
408 was_at: "War unter" 410 was_at: "War unter"
409 restore_to: "Wiederherstellen nach" 411 restore_to: "Wiederherstellen nach"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index da8282cf..9bf51262 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -351,6 +351,8 @@ en:
351 confirm_trash: "Move this page and everything beneath it to the Trash? All published content in it will go offline." 351 confirm_trash: "Move this page and everything beneath it to the Trash? All published content in it will go offline."
352 move_to_trash: "Move to Trash" 352 move_to_trash: "Move to Trash"
353 locked_hint: "Locked — see People below to unlock before editing, publishing, or discarding." 353 locked_hint: "Locked — see People below to unlock before editing, publishing, or discarding."
354 visit: "Visit"
355 copy_url: "Copy URL"
354 trash: "Trash" 356 trash: "Trash"
355 was_at: "Was at" 357 was_at: "Was at"
356 restore_to: "Restore to" 358 restore_to: "Restore to"
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 1d39dd6c..c0496c6e 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -13,6 +13,7 @@
13 --handle-fill: color-mix(in srgb, CanvasText, Canvas 60%); /* #989898 as fill */ 13 --handle-fill: color-mix(in srgb, CanvasText, Canvas 60%); /* #989898 as fill */
14 --link-underline: color-mix(in srgb, CanvasText, Canvas 69%); /* #b0b0b0 */ 14 --link-underline: color-mix(in srgb, CanvasText, Canvas 69%); /* #b0b0b0 */
15 --text-muted: color-mix(in srgb, CanvasText, Canvas 47%); 15 --text-muted: color-mix(in srgb, CanvasText, Canvas 47%);
16 --text-heavy: color-mix(in srgb, CanvasText, Canvas 23%);
16 17
17 --badge-scrim: rgba(255, 255, 255, 0.85); 18 --badge-scrim: rgba(255, 255, 255, 0.85);
18 19
@@ -977,6 +978,168 @@ form.button_to button[type="submit"] {
977 box-sizing: border-box; 978 box-sizing: border-box;
978} 979}
979 980
981/* ============================================================
982 Link matrix (nodes#show)
983 ============================================================
984 One block per link kind, each an independent grid with an identical
985 column template */
986
987.link_matrix_head {
988 display: none;
989}
990
991.link_matrix_row {
992 display: flex;
993 flex-direction: column;
994 margin-bottom: 1.75rem;
995}
996
997.link_matrix_row:last-child {
998 margin-bottom: 0;
999}
1000
1001.link_matrix_cells {
1002 margin-top: 0.7rem;
1003}
1004
1005.link_matrix_row .link_matrix_actions {
1006 margin-top: 1.25rem;
1007}
1008
1009.link_matrix_label { order: 0; }
1010.link_matrix_cells { order: 1; }
1011.link_matrix_actions { order: 2; }
1012
1013.link_matrix_cells {
1014 display: flex;
1015 flex-wrap: wrap;
1016 gap: 0.6rem 1.5rem;
1017}
1018
1019.link_matrix_cell {
1020 display: inline-flex;
1021 align-items: center;
1022 gap: 0.25rem;
1023}
1024
1025.link_matrix_label {
1026 display: flex;
1027 flex-direction: column;
1028 gap: 0.1rem;
1029 min-width: 0;
1030}
1031
1032.link_matrix_label a {
1033 overflow-wrap: anywhere;
1034}
1035
1036.link_locale_tag {
1037 font-size: 0.85rem;
1038 color: var(--text-muted);
1039}
1040
1041.link_matrix_locale {
1042 font-size: 0.8rem;
1043 font-variant: all-small-caps;
1044 letter-spacing: 0.06em;
1045 color: var(--text);
1046}
1047
1048.link_icon_button {
1049 display: inline-flex;
1050 align-items: center;
1051 padding: 0.3rem;
1052 border: 1px solid var(--border);
1053 border-radius: 2px;
1054 background-color: var(--surface);
1055 color: var(--text-heavy);
1056 line-height: 1;
1057 text-decoration: none;
1058}
1059
1060.link_icon_button:hover {
1061 color: var(--on-solid);
1062 background-color: var(--text);
1063 border-color: var(--text);
1064}
1065
1066.link_icon_button svg {
1067 width: 1.15rem;
1068 height: 1.15rem;
1069}
1070
1071/* Transient "Copied!" text; empty at rest. */
1072.link_icon_button .copy_button_label:not(:empty) {
1073 margin-left: 0.35rem;
1074 font-size: 0.85rem;
1075}
1076
1077@media (min-width: 1016px) {
1078 /* ONE grid for the whole matrix, not one per row: separate grids compute
1079 their auto columns independently, so identical templates do not align
1080 unless every width is fixed. display: contents dissolves the row and
1081 cell wrappers, making their children direct grid items that share the
1082 tracks. */
1083 .link_matrix {
1084 display: grid;
1085 align-items: start;
1086 gap: 0.75rem 1rem;
1087 }
1088
1089 /* Label sizes to its longest URL, one auto column per locale, one for
1090 the token actions, then a spacer so the columns cluster near the
1091 label rather than drifting to the right edge. */
1092 .link_matrix_1 { grid-template-columns: auto auto auto minmax(0, 1fr); }
1093 .link_matrix_2 { grid-template-columns: auto auto auto auto minmax(0, 1fr); }
1094 .link_matrix_3 { grid-template-columns: auto auto auto auto auto minmax(0, 1fr); }
1095
1096 .link_matrix_head,
1097 .link_matrix_row,
1098 .link_matrix_cells {
1099 display: contents;
1100 }
1101
1102 /* Auto-placement must follow source order once the wrappers are gone. */
1103 .link_matrix_label,
1104 .link_matrix_cells,
1105 .link_matrix_actions {
1106 order: 0;
1107 }
1108
1109 /* Forces each row to begin a new grid row without placeholder cells. */
1110 .link_matrix_label {
1111 grid-column-start: 1;
1112 }
1113
1114 /* A display: contents element cannot carry a border, so the header rule
1115 lives on the locale labels themselves -- which marks the columns
1116 individually and avoids a stray line under the empty label cell. */
1117 .link_matrix_locale {
1118 padding-bottom: 0.25rem;
1119 border-bottom: 1px solid var(--hairline);
1120 }
1121
1122 .link_matrix_locale,
1123 .link_matrix_cell {
1124 justify-self: center;
1125 }
1126
1127 /* The header sits above the first row's buttons, which are themselves
1128 top-aligned, so it needs no extra offset -- but the locale cells do,
1129 to line up with the URL rather than the label. */
1130 .link_matrix_cell {
1131 padding-top: 0.1rem;
1132 }
1133
1134 .link_locale_tag {
1135 display: none;
1136 }
1137
1138 .link_matrix_row .link_matrix_actions {
1139 margin-top: 0;
1140 }
1141}
1142
980/* Layout only -- the at-rest visibility (wavy underline) for these 1143/* Layout only -- the at-rest visibility (wavy underline) for these
981 links comes from the scoped rule in Base elements above. */ 1144 links comes from the scoped rule in Base elements above. */
982.add_events, 1145.add_events,