diff options
| -rw-r--r-- | app/views/nodes/show.html.erb | 100 | ||||
| -rw-r--r-- | config/locales/de.yml | 2 | ||||
| -rw-r--r-- | config/locales/en.yml | 2 | ||||
| -rw-r--r-- | public/stylesheets/admin.css | 163 |
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, |
