diff options
| -rw-r--r-- | app/controllers/menu_items_controller.rb | 10 | ||||
| -rw-r--r-- | app/views/menu_items/index.html.erb | 49 | ||||
| -rw-r--r-- | config/locales/de.yml | 4 | ||||
| -rw-r--r-- | config/locales/en.yml | 4 | ||||
| -rw-r--r-- | config/routes.rb | 2 | ||||
| -rw-r--r-- | public/javascripts/admin_interface.js | 13 | ||||
| -rw-r--r-- | public/stylesheets/admin.css | 144 |
7 files changed, 196 insertions, 30 deletions
diff --git a/app/controllers/menu_items_controller.rb b/app/controllers/menu_items_controller.rb index 48018800..f169e0ca 100644 --- a/app/controllers/menu_items_controller.rb +++ b/app/controllers/menu_items_controller.rb | |||
| @@ -54,6 +54,16 @@ class MenuItemsController < ApplicationController | |||
| 54 | head :ok | 54 | head :ok |
| 55 | end | 55 | end |
| 56 | 56 | ||
| 57 | def move_up | ||
| 58 | MenuItem.find(params[:id]).move_higher | ||
| 59 | redirect_to menu_items_path | ||
| 60 | end | ||
| 61 | |||
| 62 | def move_down | ||
| 63 | MenuItem.find(params[:id]).move_lower | ||
| 64 | redirect_to menu_items_path | ||
| 65 | end | ||
| 66 | |||
| 57 | private | 67 | private |
| 58 | 68 | ||
| 59 | def menu_item_params | 69 | def menu_item_params |
diff --git a/app/views/menu_items/index.html.erb b/app/views/menu_items/index.html.erb index cdc60c63..0eacc34a 100644 --- a/app/views/menu_items/index.html.erb +++ b/app/views/menu_items/index.html.erb | |||
| @@ -4,18 +4,55 @@ | |||
| 4 | <%= icon("plus", library: "tabler", "aria-hidden": true) %> <%= t(".create_item") %> | 4 | <%= icon("plus", library: "tabler", "aria-hidden": true) %> <%= t(".create_item") %> |
| 5 | <% end %> | 5 | <% end %> |
| 6 | 6 | ||
| 7 | <p class="field_hint"><%= t(".reorder_hint") %></p> | ||
| 8 | |||
| 7 | <table id="menu_item_list"> | 9 | <table id="menu_item_list"> |
| 8 | <% @menu_items.each do |menu_item| %> | 10 | <% @menu_items.each do |menu_item| %> |
| 9 | <tr id="menu_items-<%= menu_item.id %>"> | 11 | <tr id="menu_items-<%= menu_item.id %>"> |
| 10 | <td class="menu_sort_handle"> | 12 | <td class="menu_sort_handle"> |
| 11 | <div></div> | 13 | <span class="menu_grip"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span> |
| 14 | |||
| 15 | <%= button_to move_up_menu_item_path(menu_item), method: :post, | ||
| 16 | disabled: menu_item.first?, | ||
| 17 | form: { class: "button_to menu_move" }, | ||
| 18 | "aria-label" => t(".move_up"), title: t(".move_up") do %> | ||
| 19 | <%= icon("chevron-up", library: "tabler", "aria-hidden": true) %> | ||
| 20 | <% end %> | ||
| 21 | |||
| 22 | <%= button_to move_down_menu_item_path(menu_item), method: :post, | ||
| 23 | disabled: menu_item.last?, | ||
| 24 | form: { class: "button_to menu_move" }, | ||
| 25 | "aria-label" => t(".move_down"), title: t(".move_down") do %> | ||
| 26 | <%= icon("chevron-down", library: "tabler", "aria-hidden": true) %> | ||
| 27 | <% end %> | ||
| 28 | </td> | ||
| 29 | <td class="menu_item_title"> | ||
| 30 | <%= menu_item.title %> | ||
| 31 | <% (I18n.available_locales - [:root, I18n.default_locale]).each do |locale| %> | ||
| 32 | <% translated = menu_item.translations.find_by(:locale => locale)&.title %> | ||
| 33 | <span class="menu_item_translation"> | ||
| 34 | <span class="menu_item_translation_locale"><%= locale.to_s.upcase %></span> | ||
| 35 | <% if translated.present? %> | ||
| 36 | <%= translated %> | ||
| 37 | <% else %> | ||
| 38 | <span class="menu_item_translation_absent"><%= t(".falls_back") %></span> | ||
| 39 | <% end %> | ||
| 40 | </span> | ||
| 41 | <% end %> | ||
| 42 | </td> | ||
| 43 | <td class="menu_item_action"> | ||
| 44 | <%= link_to edit_menu_item_path(menu_item), | ||
| 45 | "aria-label" => t("admin.common.edit"), | ||
| 46 | title: t("admin.common.edit") do %> | ||
| 47 | <%= icon("edit", library: "tabler", "aria-hidden": true) %> | ||
| 48 | <% end %> | ||
| 12 | </td> | 49 | </td> |
| 13 | <td class="menu_item_title"><%= menu_item.title %></td> | 50 | <td class="menu_item_action"> |
| 14 | <td><%= link_to t("admin.common.edit"), edit_menu_item_path(menu_item) %></td> | ||
| 15 | <td> | ||
| 16 | <%= button_to menu_item_path(menu_item), method: :delete, | 51 | <%= button_to menu_item_path(menu_item), method: :delete, |
| 17 | form: { data: { confirm: t(".confirm_destroy") }, class: 'button_to destructive' } do %> | 52 | form: { data: { confirm: t(".confirm_destroy") }, class: 'button_to destructive' }, |
| 18 | <%= icon("trash", library: "tabler", "aria-hidden": true) %> <%= t("admin.common.destroy") %> | 53 | "aria-label" => t("admin.common.destroy"), |
| 54 | title: t("admin.common.destroy") do %> | ||
| 55 | <%= icon("trash", library: "tabler", "aria-hidden": true) %> | ||
| 19 | <% end %> | 56 | <% end %> |
| 20 | </td> | 57 | </td> |
| 21 | </tr> | 58 | </tr> |
diff --git a/config/locales/de.yml b/config/locales/de.yml index 647caf57..2d5e9685 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml | |||
| @@ -669,6 +669,10 @@ de: | |||
| 669 | title: "Menüeinträge" | 669 | title: "Menüeinträge" |
| 670 | create_item: "Menüeintrag anlegen" | 670 | create_item: "Menüeintrag anlegen" |
| 671 | confirm_destroy: "Diesen Menüeintrag wirklich löschen?" | 671 | confirm_destroy: "Diesen Menüeintrag wirklich löschen?" |
| 672 | reorder_hint: "Die Einträge im Navi-Menü kann man draggen" | ||
| 673 | falls_back: "… Fallback auf deutsch" | ||
| 674 | move_up: "Nach oben" | ||
| 675 | move_down: "Nach unten" | ||
| 672 | new: | 676 | new: |
| 673 | title: "Menüeintrag hinzufügen" | 677 | title: "Menüeintrag hinzufügen" |
| 674 | edit: | 678 | edit: |
diff --git a/config/locales/en.yml b/config/locales/en.yml index 1b112e3d..da8282cf 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml | |||
| @@ -481,6 +481,10 @@ en: | |||
| 481 | title: "Menu Items" | 481 | title: "Menu Items" |
| 482 | create_item: "Create menu item" | 482 | create_item: "Create menu item" |
| 483 | confirm_destroy: "Do you really want to destroy the menu entry?" | 483 | confirm_destroy: "Do you really want to destroy the menu entry?" |
| 484 | reorder_hint: "Drag the handles to change the order" | ||
| 485 | falls_back: "… falls back to German" | ||
| 486 | move_up: "Move up" | ||
| 487 | move_down: "Move down" | ||
| 484 | new: | 488 | new: |
| 485 | title: "Add Menu Item" | 489 | title: "Add Menu Item" |
| 486 | edit: | 490 | edit: |
diff --git a/config/routes.rb b/config/routes.rb index 9793c6a6..4b5d15c9 100644 --- a/config/routes.rb +++ b/config/routes.rb | |||
| @@ -108,6 +108,8 @@ Cccms::Application.routes.draw do | |||
| 108 | resources :menu_items, :except => :show do | 108 | resources :menu_items, :except => :show do |
| 109 | member do | 109 | member do |
| 110 | post :sort | 110 | post :sort |
| 111 | post :move_up | ||
| 112 | post :move_down | ||
| 111 | end | 113 | end |
| 112 | end | 114 | end |
| 113 | 115 | ||
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js index ee0b34cd..d94fb623 100644 --- a/public/javascripts/admin_interface.js +++ b/public/javascripts/admin_interface.js | |||
| @@ -386,9 +386,16 @@ menu_item_sorter = { | |||
| 386 | type: "POST", | 386 | type: "POST", |
| 387 | url: "/menu_items/0/sort", | 387 | url: "/menu_items/0/sort", |
| 388 | data: $(this).sortable("serialize"), | 388 | data: $(this).sortable("serialize"), |
| 389 | dataType: "json", | 389 | success : function() { |
| 390 | success : function(results) { | 390 | // The controller answers head :ok, so there is nothing to parse |
| 391 | alert(results); | 391 | // and dataType: "json" would send every success down the error |
| 392 | // path. Reload rather than patch the DOM: first?/last? decide the | ||
| 393 | // disabled chevrons server-side, and after a drag they describe | ||
| 394 | // the old order. | ||
| 395 | window.location.reload(); | ||
| 396 | }, | ||
| 397 | error : function() { | ||
| 398 | alert("Reihenfolge konnte nicht gespeichert werden."); | ||
| 392 | } | 399 | } |
| 393 | }); | 400 | }); |
| 394 | } | 401 | } |
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index 49e2362b..1d39dd6c 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css | |||
| @@ -9,7 +9,6 @@ | |||
| 9 | --hairline: color-mix(in srgb, CanvasText, Canvas 91%); /* #e8e8e8 */ | 9 | --hairline: color-mix(in srgb, CanvasText, Canvas 91%); /* #e8e8e8 */ |
| 10 | --hairline-faint: color-mix(in srgb, CanvasText, Canvas 94%); /* #f1f1f1 */ | 10 | --hairline-faint: color-mix(in srgb, CanvasText, Canvas 94%); /* #f1f1f1 */ |
| 11 | --border: color-mix(in srgb, CanvasText, Canvas 87%); /* #ddd */ | 11 | --border: color-mix(in srgb, CanvasText, Canvas 87%); /* #ddd */ |
| 12 | --frame-fill: color-mix(in srgb, CanvasText, Canvas 83%); /* lightgrey */ | ||
| 13 | --field-border: color-mix(in srgb, CanvasText, Canvas 60%); /* #989898 */ | 12 | --field-border: color-mix(in srgb, CanvasText, Canvas 60%); /* #989898 */ |
| 14 | --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 */ |
| 15 | --link-underline: color-mix(in srgb, CanvasText, Canvas 69%); /* #b0b0b0 */ | 14 | --link-underline: color-mix(in srgb, CanvasText, Canvas 69%); /* #b0b0b0 */ |
| @@ -1295,11 +1294,15 @@ input#menu_item_title { | |||
| 1295 | ============================================================ */ | 1294 | ============================================================ */ |
| 1296 | 1295 | ||
| 1297 | #menu_item_list { | 1296 | #menu_item_list { |
| 1298 | border-collapse: collapse; | 1297 | border-collapse: separate; |
| 1299 | padding: 5px 5px 5px 5px; | 1298 | border-spacing: 0; |
| 1300 | border: solid 5px var(--frame-fill); | 1299 | width: 100%; |
| 1301 | background-color: var(--frame-fill); | 1300 | max-width: 44rem; |
| 1302 | border-radius: 5px; | 1301 | |
| 1302 | border: 1px solid var(--hairline); | ||
| 1303 | border-radius: var(--radius); | ||
| 1304 | |||
| 1305 | padding: 0.5rem; | ||
| 1303 | } | 1306 | } |
| 1304 | 1307 | ||
| 1305 | #menu_item_list tr:hover { | 1308 | #menu_item_list tr:hover { |
| @@ -1307,33 +1310,132 @@ input#menu_item_title { | |||
| 1307 | } | 1310 | } |
| 1308 | 1311 | ||
| 1309 | #menu_item_list td { | 1312 | #menu_item_list td { |
| 1310 | height: 20px; | 1313 | padding: 0.4rem 0.5rem; |
| 1314 | border-bottom: 1px solid var(--hairline); | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | #menu_item_list tr:last-child td { | ||
| 1318 | border-bottom: none; | ||
| 1311 | } | 1319 | } |
| 1312 | 1320 | ||
| 1313 | #menu_item_list td.menu_item_title { | 1321 | #menu_item_list td.menu_item_title { |
| 1314 | width: 200px; | 1322 | width: 100%; |
| 1323 | } | ||
| 1324 | |||
| 1325 | .menu_item_translation { | ||
| 1326 | display: block; | ||
| 1327 | font-size: 0.85rem; | ||
| 1328 | color: var(--text-muted); | ||
| 1315 | } | 1329 | } |
| 1316 | 1330 | ||
| 1317 | #menu_item_list td.menu_sort_handle div { | 1331 | .menu_item_translation_locale { |
| 1318 | background-color: var(--handle-fill); | 1332 | font-variant: all-small-caps; |
| 1319 | height: 26px; | 1333 | letter-spacing: 0.04em; |
| 1320 | width: 26px; | 1334 | margin-right: 0.35rem; |
| 1321 | } | 1335 | } |
| 1322 | 1336 | ||
| 1323 | #menu_item_list td.menu_sort_handle div:before { | 1337 | .menu_item_translation_absent { |
| 1324 | content: "⇳"; | 1338 | font-style: italic; |
| 1325 | font-size: 20px; | 1339 | } |
| 1326 | font-weight: bold; | 1340 | |
| 1341 | #menu_item_list td.menu_item_action { | ||
| 1342 | width: 1px; | ||
| 1343 | white-space: nowrap; | ||
| 1327 | text-align: center; | 1344 | text-align: center; |
| 1328 | vertical-align: center; | ||
| 1329 | } | 1345 | } |
| 1330 | 1346 | ||
| 1331 | #menu_item_list td.menu_sort_handle div:hover { | 1347 | #menu_item_list td.menu_item_action a { |
| 1332 | cursor: grab; | 1348 | text-decoration: none; |
| 1333 | } | 1349 | } |
| 1334 | 1350 | ||
| 1335 | .ui-state-highlight td { | 1351 | #menu_item_list td.menu_item_action svg { |
| 1336 | height: 20px; | 1352 | width: 1.25rem; |
| 1353 | height: 1.25rem; | ||
| 1354 | vertical-align: middle; | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | #menu_item_list td.menu_sort_handle { | ||
| 1358 | width: 1px; | ||
| 1359 | white-space: nowrap; | ||
| 1360 | color: var(--text-muted); | ||
| 1361 | } | ||
| 1362 | |||
| 1363 | /* jQuery UI sortable binds mouse events, so drag never works on touch. | ||
| 1364 | Below the desktop breakpoint the grip would advertise something that | ||
| 1365 | cannot happen, and the width is better spent on the chevrons. */ | ||
| 1366 | #menu_item_list .menu_grip { | ||
| 1367 | display: none; | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | @media (min-width: 1016px) { | ||
| 1371 | #menu_item_list .menu_grip { | ||
| 1372 | display: inline-block; | ||
| 1373 | cursor: grab; | ||
| 1374 | vertical-align: middle; | ||
| 1375 | } | ||
| 1376 | |||
| 1377 | #menu_item_list td.menu_sort_handle:active .menu_grip { | ||
| 1378 | cursor: grabbing; | ||
| 1379 | } | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | #menu_item_list form.menu_move { | ||
| 1383 | display: inline-block; | ||
| 1384 | margin: 0; | ||
| 1385 | } | ||
| 1386 | |||
| 1387 | #menu_item_list form.menu_move button[type="submit"] { | ||
| 1388 | padding: 0.15rem 0.2rem; | ||
| 1389 | border: 1px solid transparent; | ||
| 1390 | background-color: transparent; | ||
| 1391 | color: var(--text-muted); | ||
| 1392 | line-height: 1; | ||
| 1393 | } | ||
| 1394 | |||
| 1395 | #menu_item_list form.menu_move button[type="submit"]:hover { | ||
| 1396 | color: var(--text); | ||
| 1397 | background-color: var(--surface-tint); | ||
| 1398 | } | ||
| 1399 | |||
| 1400 | #menu_item_list form.menu_move button[type="submit"]:disabled { | ||
| 1401 | opacity: 0.35; | ||
| 1402 | cursor: default; | ||
| 1403 | background-color: transparent; | ||
| 1404 | color: var(--text-muted); | ||
| 1405 | } | ||
| 1406 | |||
| 1407 | #menu_item_list td.menu_sort_handle svg { | ||
| 1408 | width: 1.1rem; | ||
| 1409 | height: 1.1rem; | ||
| 1410 | vertical-align: middle; | ||
| 1411 | } | ||
| 1412 | |||
| 1413 | #menu_item_list td.menu_sort_handle:active { | ||
| 1414 | cursor: grabbing; | ||
| 1415 | } | ||
| 1416 | |||
| 1417 | #menu_item_list td.menu_sort_handle svg { | ||
| 1418 | width: 1.25rem; | ||
| 1419 | height: 1.25rem; | ||
| 1420 | vertical-align: middle; | ||
| 1421 | } | ||
| 1422 | |||
| 1423 | #menu_item_list tr.ui-state-highlight td { | ||
| 1424 | height: 2.5rem; | ||
| 1425 | background-color: var(--accent-surface); | ||
| 1426 | border: 1px dashed var(--accent); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | #menu_item_list form.button_to.destructive input[type="submit"], | ||
| 1430 | #menu_item_list form.button_to.destructive button[type="submit"] { | ||
| 1431 | color: var(--danger); | ||
| 1432 | background-color: transparent; | ||
| 1433 | } | ||
| 1434 | |||
| 1435 | #menu_item_list form.button_to.destructive input[type="submit"]:hover, | ||
| 1436 | #menu_item_list form.button_to.destructive button[type="submit"]:hover { | ||
| 1437 | color: var(--on-solid); | ||
| 1438 | background-color: var(--danger-solid); | ||
| 1337 | } | 1439 | } |
| 1338 | 1440 | ||
| 1339 | /* ============================================================ | 1441 | /* ============================================================ |
