Browse Source

Merge branch 'main' of https://dev.duhaz.fr/MrDuhaz/Blog-Duhaz

Laurent Hazart 1 month ago
parent
commit
62f734ecdd
53 changed files with 2543 additions and 3607 deletions
  1. 31 0
      core/.gitignore
  2. 40 21
      static/admin/css/autocomplete.css
  3. 372 159
      static/admin/css/base.css
  4. 111 123
      static/admin/css/changelists.css
  5. 130 0
      static/admin/css/dark_mode.css
  6. 3 0
      static/admin/css/dashboard.css
  7. 0 20
      static/admin/css/fonts.css
  8. 111 126
      static/admin/css/forms.css
  9. 10 28
      static/admin/css/login.css
  10. 45 14
      static/admin/css/nav_sidebar.css
  11. 73 110
      static/admin/css/responsive.css
  12. 37 6
      static/admin/css/responsive_rtl.css
  13. 83 41
      static/admin/css/rtl.css
  14. 19 0
      static/admin/css/unusable_password_field.css
  15. 118 99
      static/admin/css/vendor/select2/LICENSE-SELECT2.md
  16. 0 202
      static/admin/fonts/LICENSE.txt
  17. 0 3
      static/admin/fonts/README.txt
  18. BIN
      static/admin/fonts/Roboto-Bold-webfont.woff
  19. BIN
      static/admin/fonts/Roboto-Light-webfont.woff
  20. BIN
      static/admin/fonts/Roboto-Regular-webfont.woff
  21. 1 1
      static/admin/img/LICENSE
  22. 59 10
      static/admin/img/calendar-icons.svg
  23. 1 1
      static/admin/img/gis/move_vertex_off.svg
  24. 1 1
      static/admin/img/icon-alert.svg
  25. 3 0
      static/admin/img/icon-clock.svg
  26. 7 1
      static/admin/img/icon-no.svg
  27. 91 41
      static/admin/js/SelectFilter2.js
  28. 184 134
      static/admin/js/actions.js
  29. 0 7
      static/admin/js/actions.min.js
  30. 5 14
      static/admin/js/admin/DateTimeShortcuts.js
  31. 94 13
      static/admin/js/admin/RelatedObjectLookups.js
  32. 17 22
      static/admin/js/autocomplete.js
  33. 33 1
      static/admin/js/calendar.js
  34. 4 3
      static/admin/js/cancel.js
  35. 0 43
      static/admin/js/change_form.js
  36. 0 2
      static/admin/js/collapse.min.js
  37. 24 3
      static/admin/js/core.js
  38. 30 0
      static/admin/js/filters.js
  39. 22 11
      static/admin/js/inlines.js
  40. 0 11
      static/admin/js/inlines.min.js
  41. 57 17
      static/admin/js/jquery.init.js
  42. 0 1
      static/admin/js/popup_response.js
  43. 0 1
      static/admin/js/prepopulate.js
  44. 5 1
      static/admin/js/prepopulate_init.js
  45. 51 0
      static/admin/js/theme.js
  46. 29 0
      static/admin/js/unusable_password_field.js
  47. 2 18
      static/admin/js/urlify.js
  48. 1 1
      static/admin/js/vendor/jquery/LICENSE.txt
  49. 354 472
      static/admin/js/vendor/jquery/jquery.js
  50. 0 1
      static/admin/js/vendor/jquery/jquery.min.js
  51. 1 1
      static/admin/js/vendor/select2/LICENSE.md
  52. 284 1754
      static/admin/js/vendor/xregexp/xregexp.js
  53. 0 69
      static/admin/js/vendor/xregexp/xregexp.min.js

+ 31 - 0
core/.gitignore

@@ -99,3 +99,34 @@ migrations/__pycache__/0024_auto_20240730_2235.cpython-39.pyc
 migrations/__pycache__/0024_auto_20240730_2235.cpython-312.pyc
 migrations/__pycache__/0024_auto_20240730_2235.cpython-312.pyc
 migrations/__pycache__/0025_auto_20240730_2241.cpython-39.pyc
 migrations/__pycache__/0025_auto_20240730_2241.cpython-39.pyc
 migrations/__pycache__/0025_auto_20240730_2241.cpython-312.pyc
 migrations/__pycache__/0025_auto_20240730_2241.cpython-312.pyc
+__pycache__/__init__.cpython-313.pyc
+__pycache__/admin.cpython-313.pyc
+__pycache__/apps.cpython-313.pyc
+__pycache__/models.cpython-313.pyc
+__pycache__/views.cpython-313.pyc
+migrations/__pycache__/__init__.cpython-313.pyc
+migrations/__pycache__/0001_initial.cpython-313.pyc
+migrations/__pycache__/0002_auto_20220422_0914.cpython-313.pyc
+migrations/__pycache__/0003_data.cpython-313.pyc
+migrations/__pycache__/0004_auto_20221121_1234.cpython-313.pyc
+migrations/__pycache__/0005_page_p_menu_stack.cpython-313.pyc
+migrations/__pycache__/0006_speed_dial.cpython-313.pyc
+migrations/__pycache__/0007_auto_20231201_1455.cpython-313.pyc
+migrations/__pycache__/0008_speed_dial_sd_icone.cpython-313.pyc
+migrations/__pycache__/0009_speed_dial_sd_color.cpython-313.pyc
+migrations/__pycache__/0010_auto_20231222_1114.cpython-313.pyc
+migrations/__pycache__/0011_alter_page_p_menu_parent.cpython-313.pyc
+migrations/__pycache__/0012_page_p_menu_est_parent.cpython-313.pyc
+migrations/__pycache__/0013_alter_page_p_menu_parent.cpython-313.pyc
+migrations/__pycache__/0014_fichier.cpython-313.pyc
+migrations/__pycache__/0015_auto_20240119_1346.cpython-313.pyc
+migrations/__pycache__/0016_groupe.cpython-313.pyc
+migrations/__pycache__/0017_groupe_g_description.cpython-313.pyc
+migrations/__pycache__/0018_speed_dial_sd_groupe.cpython-313.pyc
+migrations/__pycache__/0019_alter_speed_dial_sd_groupe.cpython-313.pyc
+migrations/__pycache__/0020_alter_speed_dial_sd_titre.cpython-313.pyc
+migrations/__pycache__/0021_auto_20240423_1338.cpython-313.pyc
+migrations/__pycache__/0022_page_p_proteger.cpython-313.pyc
+migrations/__pycache__/0023_auto_20240730_2056.cpython-313.pyc
+migrations/__pycache__/0024_auto_20240730_2235.cpython-313.pyc
+migrations/__pycache__/0025_auto_20240730_2241.cpython-313.pyc

+ 40 - 21
static/admin/css/autocomplete.css

@@ -14,7 +14,7 @@ select.admin-autocomplete {
 
 
 .select2-container--admin-autocomplete.select2-container--focus .select2-selection,
 .select2-container--admin-autocomplete.select2-container--focus .select2-selection,
 .select2-container--admin-autocomplete.select2-container--open .select2-selection {
 .select2-container--admin-autocomplete.select2-container--open .select2-selection {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
     min-height: 30px;
     min-height: 30px;
 }
 }
 
 
@@ -29,13 +29,13 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--single {
 .select2-container--admin-autocomplete .select2-selection--single {
-    background-color: #fff;
-    border: 1px solid #ccc;
+    background-color: var(--body-bg);
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     border-radius: 4px;
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
-    color: #444;
+    color: var(--body-fg);
     line-height: 30px;
     line-height: 30px;
 }
 }
 
 
@@ -46,7 +46,7 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
@@ -80,7 +80,7 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
 .select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
-    background-color: #eee;
+    background-color: var(--darkened-bg);
     cursor: default;
     cursor: default;
 }
 }
 
 
@@ -94,8 +94,8 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--multiple {
 .select2-container--admin-autocomplete .select2-selection--multiple {
-    background-color: white;
-    border: 1px solid #ccc;
+    background-color: var(--body-bg);
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     border-radius: 4px;
     cursor: text;
     cursor: text;
 }
 }
@@ -104,8 +104,10 @@ select.admin-autocomplete {
     box-sizing: border-box;
     box-sizing: border-box;
     list-style: none;
     list-style: none;
     margin: 0;
     margin: 0;
-    padding: 0 5px;
+    padding: 0 10px 5px 5px;
     width: 100%;
     width: 100%;
+    display: flex;
+    flex-wrap: wrap;
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
@@ -113,7 +115,7 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
-    color: #999;
+    color: var(--body-quiet-color);
     margin-top: 5px;
     margin-top: 5px;
     float: left;
     float: left;
 }
 }
@@ -123,11 +125,13 @@ select.admin-autocomplete {
     float: right;
     float: right;
     font-weight: bold;
     font-weight: bold;
     margin: 5px;
     margin: 5px;
+    position: absolute;
+    right: 0;
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
-    background-color: #e4e4e4;
-    border: 1px solid #ccc;
+    background-color: var(--darkened-bg);
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     border-radius: 4px;
     cursor: default;
     cursor: default;
     float: left;
     float: left;
@@ -137,7 +141,7 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
-    color: #999;
+    color: var(--body-quiet-color);
     cursor: pointer;
     cursor: pointer;
     display: inline-block;
     display: inline-block;
     font-weight: bold;
     font-weight: bold;
@@ -145,7 +149,7 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
-    color: #333;
+    color: var(--body-fg);
 }
 }
 
 
 .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
 .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
@@ -163,12 +167,12 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
 .select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
-    border: solid #999 1px;
+    border: solid var(--body-quiet-color) 1px;
     outline: 0;
     outline: 0;
 }
 }
 
 
 .select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
 .select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
-    background-color: #eee;
+    background-color: var(--darkened-bg);
     cursor: default;
     cursor: default;
 }
 }
 
 
@@ -186,12 +190,20 @@ select.admin-autocomplete {
     border-bottom-right-radius: 0;
     border-bottom-right-radius: 0;
 }
 }
 
 
+.select2-container--admin-autocomplete .select2-search--dropdown {
+    background: var(--darkened-bg);
+}
+
 .select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
 .select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
-    border: 1px solid #ccc;
+    background: var(--body-bg);
+    color: var(--body-fg);
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
 .select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
     background: transparent;
     background: transparent;
+    color: var(--body-fg);
     border: none;
     border: none;
     outline: 0;
     outline: 0;
     box-shadow: none;
     box-shadow: none;
@@ -201,6 +213,8 @@ select.admin-autocomplete {
 .select2-container--admin-autocomplete .select2-results > .select2-results__options {
 .select2-container--admin-autocomplete .select2-results > .select2-results__options {
     max-height: 200px;
     max-height: 200px;
     overflow-y: auto;
     overflow-y: auto;
+    color: var(--body-fg);
+    background: var(--body-bg);
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-results__option[role=group] {
 .select2-container--admin-autocomplete .select2-results__option[role=group] {
@@ -208,11 +222,12 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
 .select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
 .select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
-    background-color: #ddd;
+    background-color: var(--selected-bg);
+    color: var(--body-fg);
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-results__option .select2-results__option {
 .select2-container--admin-autocomplete .select2-results__option .select2-results__option {
@@ -249,8 +264,8 @@ select.admin-autocomplete {
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
 .select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
-    background-color: #79aec8;
-    color: white;
+    background-color: var(--primary);
+    color: var(--primary-fg);
 }
 }
 
 
 .select2-container--admin-autocomplete .select2-results__group {
 .select2-container--admin-autocomplete .select2-results__group {
@@ -258,3 +273,7 @@ select.admin-autocomplete {
     display: block;
     display: block;
     padding: 6px;
     padding: 6px;
 }
 }
+
+.errors .select2-selection {
+    border: 1px solid var(--error-fg);
+}

+ 372 - 159
static/admin/css/base.css

@@ -2,7 +2,92 @@
     DJANGO Admin styles
     DJANGO Admin styles
 */
 */
 
 
-@import url(fonts.css);
+/* VARIABLE DEFINITIONS */
+html[data-theme="light"],
+:root {
+    --primary: #79aec8;
+    --secondary: #417690;
+    --accent: #f5dd5d;
+    --primary-fg: #fff;
+
+    --body-fg: #333;
+    --body-bg: #fff;
+    --body-quiet-color: #666;
+    --body-medium-color: #444;
+    --body-loud-color: #000;
+
+    --header-color: #ffc;
+    --header-branding-color: var(--accent);
+    --header-bg: var(--secondary);
+    --header-link-color: var(--primary-fg);
+
+    --breadcrumbs-fg: #c4dce8;
+    --breadcrumbs-link-fg: var(--body-bg);
+    --breadcrumbs-bg: #264b5d;
+
+    --link-fg: #417893;
+    --link-hover-color: #036;
+    --link-selected-fg: var(--secondary);
+
+    --hairline-color: #e8e8e8;
+    --border-color: #ccc;
+
+    --error-fg: #ba2121;
+
+    --message-success-bg: #dfd;
+    --message-warning-bg: #ffc;
+    --message-error-bg: #ffefef;
+
+    --darkened-bg: #f8f8f8; /* A bit darker than --body-bg */
+    --selected-bg: #e4e4e4; /* E.g. selected table cells */
+    --selected-row: #ffc;
+
+    --button-fg: #fff;
+    --button-bg: var(--secondary);
+    --button-hover-bg: #205067;
+    --default-button-bg: #205067;
+    --default-button-hover-bg: var(--secondary);
+    --close-button-bg: #747474;
+    --close-button-hover-bg: #333;
+    --delete-button-bg: #ba2121;
+    --delete-button-hover-bg: #a41515;
+
+    --object-tools-fg: var(--button-fg);
+    --object-tools-bg: var(--close-button-bg);
+    --object-tools-hover-bg: var(--close-button-hover-bg);
+
+    --font-family-primary:
+        "Segoe UI",
+        system-ui,
+        Roboto,
+        "Helvetica Neue",
+        Arial,
+        sans-serif,
+        "Apple Color Emoji",
+        "Segoe UI Emoji",
+        "Segoe UI Symbol",
+        "Noto Color Emoji";
+    --font-family-monospace:
+        ui-monospace,
+        Menlo,
+        Monaco,
+        "Cascadia Mono",
+        "Segoe UI Mono",
+        "Roboto Mono",
+        "Oxygen Mono",
+        "Ubuntu Monospace",
+        "Source Code Pro",
+        "Fira Mono",
+        "Droid Sans Mono",
+        "Courier New",
+        monospace,
+        "Apple Color Emoji",
+        "Segoe UI Emoji",
+        "Segoe UI Symbol",
+        "Noto Color Emoji";
+
+    color-scheme: light;
+}
 
 
 html, body {
 html, body {
     height: 100%;
     height: 100%;
@@ -11,21 +96,22 @@ html, body {
 body {
 body {
     margin: 0;
     margin: 0;
     padding: 0;
     padding: 0;
-    font-size: 14px;
-    font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
-    color: #333;
-    background: #fff;
+    font-size: 0.875rem;
+    font-family: var(--font-family-primary);
+    color: var(--body-fg);
+    background: var(--body-bg);
 }
 }
 
 
 /* LINKS */
 /* LINKS */
 
 
 a:link, a:visited {
 a:link, a:visited {
-    color: #447e9b;
+    color: var(--link-fg);
     text-decoration: none;
     text-decoration: none;
+    transition: color 0.15s, background 0.15s;
 }
 }
 
 
 a:focus, a:hover {
 a:focus, a:hover {
-    color: #036;
+    color: var(--link-hover-color);
 }
 }
 
 
 a:focus {
 a:focus {
@@ -37,7 +123,7 @@ a img {
 }
 }
 
 
 a.section:link, a.section:visited {
 a.section:link, a.section:visited {
-    color: #fff;
+    color: var(--header-link-color);
     text-decoration: none;
     text-decoration: none;
 }
 }
 
 
@@ -63,12 +149,11 @@ h1,h2,h3,h4,h5 {
 h1 {
 h1 {
     margin: 0 0 20px;
     margin: 0 0 20px;
     font-weight: 300;
     font-weight: 300;
-    font-size: 20px;
-    color: #666;
+    font-size: 1.25rem;
 }
 }
 
 
 h2 {
 h2 {
-    font-size: 16px;
+    font-size: 1rem;
     margin: 1em 0 .5em 0;
     margin: 1em 0 .5em 0;
 }
 }
 
 
@@ -78,22 +163,23 @@ h2.subhead {
 }
 }
 
 
 h3 {
 h3 {
-    font-size: 14px;
+    font-size: 0.875rem;
     margin: .8em 0 .3em 0;
     margin: .8em 0 .3em 0;
-    color: #666;
+    color: var(--body-medium-color);
     font-weight: bold;
     font-weight: bold;
 }
 }
 
 
 h4 {
 h4 {
-    font-size: 12px;
+    font-size: 0.75rem;
     margin: 1em 0 .8em 0;
     margin: 1em 0 .8em 0;
     padding-bottom: 3px;
     padding-bottom: 3px;
+    color: var(--body-medium-color);
 }
 }
 
 
 h5 {
 h5 {
-    font-size: 10px;
+    font-size: 0.625rem;
     margin: 1.5em 0 .5em 0;
     margin: 1.5em 0 .5em 0;
-    color: #666;
+    color: var(--body-quiet-color);
     text-transform: uppercase;
     text-transform: uppercase;
     letter-spacing: 1px;
     letter-spacing: 1px;
 }
 }
@@ -108,8 +194,8 @@ li ul {
 }
 }
 
 
 li, dt, dd {
 li, dt, dd {
-    font-size: 13px;
-    line-height: 20px;
+    font-size: 0.8125rem;
+    line-height: 1.25rem;
 }
 }
 
 
 dt {
 dt {
@@ -131,11 +217,15 @@ fieldset {
     min-width: 0;
     min-width: 0;
     padding: 0;
     padding: 0;
     border: none;
     border: none;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
+}
+
+details summary {
+    cursor: pointer;
 }
 }
 
 
 blockquote {
 blockquote {
-    font-size: 11px;
+    font-size: 0.6875rem;
     color: #777;
     color: #777;
     margin-left: 2px;
     margin-left: 2px;
     padding-left: 10px;
     padding-left: 10px;
@@ -143,15 +233,15 @@ blockquote {
 }
 }
 
 
 code, pre {
 code, pre {
-    font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
-    color: #666;
-    font-size: 12px;
+    font-family: var(--font-family-monospace);
+    color: var(--body-quiet-color);
+    font-size: 0.75rem;
     overflow-x: auto;
     overflow-x: auto;
 }
 }
 
 
 pre.literal-block {
 pre.literal-block {
     margin: 10px;
     margin: 10px;
-    background: #eee;
+    background: var(--darkened-bg);
     padding: 6px 8px;
     padding: 6px 8px;
 }
 }
 
 
@@ -161,29 +251,28 @@ code strong {
 
 
 hr {
 hr {
     clear: both;
     clear: both;
-    color: #eee;
-    background-color: #eee;
+    color: var(--hairline-color);
+    background-color: var(--hairline-color);
     height: 1px;
     height: 1px;
     border: none;
     border: none;
     margin: 0;
     margin: 0;
     padding: 0;
     padding: 0;
-    font-size: 1px;
     line-height: 1px;
     line-height: 1px;
 }
 }
 
 
 /* TEXT STYLES & MODIFIERS */
 /* TEXT STYLES & MODIFIERS */
 
 
 .small {
 .small {
-    font-size: 11px;
+    font-size: 0.6875rem;
 }
 }
 
 
 .mini {
 .mini {
-    font-size: 10px;
+    font-size: 0.625rem;
 }
 }
 
 
 .help, p.help, form p.help, div.help, form div.help, div.help li {
 .help, p.help, form p.help, div.help, form div.help, div.help li {
-    font-size: 11px;
-    color: #999;
+    font-size: 0.6875rem;
+    color: var(--body-quiet-color);
 }
 }
 
 
 div.help ul {
 div.help ul {
@@ -199,7 +288,7 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
 }
 }
 
 
 .quiet, a.quiet:link, a.quiet:visited {
 .quiet, a.quiet:link, a.quiet:visited {
-    color: #999;
+    color: var(--body-quiet-color);
     font-weight: normal;
     font-weight: normal;
 }
 }
 
 
@@ -211,60 +300,63 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
     white-space: nowrap;
     white-space: nowrap;
 }
 }
 
 
+.hidden {
+    display: none !important;
+}
+
 /* TABLES */
 /* TABLES */
 
 
 table {
 table {
     border-collapse: collapse;
     border-collapse: collapse;
-    border-color: #ccc;
+    border-color: var(--border-color);
 }
 }
 
 
 td, th {
 td, th {
-    font-size: 13px;
-    line-height: 16px;
-    border-bottom: 1px solid #eee;
+    font-size: 0.8125rem;
+    line-height: 1rem;
+    border-bottom: 1px solid var(--hairline-color);
     vertical-align: top;
     vertical-align: top;
     padding: 8px;
     padding: 8px;
-    font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
 }
 }
 
 
 th {
 th {
-    font-weight: 600;
+    font-weight: 500;
     text-align: left;
     text-align: left;
 }
 }
 
 
 thead th,
 thead th,
 tfoot td {
 tfoot td {
-    color: #666;
+    color: var(--body-quiet-color);
     padding: 5px 10px;
     padding: 5px 10px;
-    font-size: 11px;
-    background: #fff;
+    font-size: 0.6875rem;
+    background: var(--body-bg);
     border: none;
     border: none;
-    border-top: 1px solid #eee;
-    border-bottom: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 tfoot td {
 tfoot td {
     border-bottom: none;
     border-bottom: none;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
 }
 }
 
 
 thead th.required {
 thead th.required {
-    color: #000;
+    font-weight: bold;
 }
 }
 
 
 tr.alt {
 tr.alt {
-    background: #f6f6f6;
+    background: var(--darkened-bg);
 }
 }
 
 
 tr:nth-child(odd), .row-form-errors {
 tr:nth-child(odd), .row-form-errors {
-    background: #fff;
+    background: var(--body-bg);
 }
 }
 
 
 tr:nth-child(even),
 tr:nth-child(even),
 tr:nth-child(even) .errorlist,
 tr:nth-child(even) .errorlist,
 tr:nth-child(odd) + .row-form-errors,
 tr:nth-child(odd) + .row-form-errors,
 tr:nth-child(odd) + .row-form-errors .errorlist {
 tr:nth-child(odd) + .row-form-errors .errorlist {
-    background: #f9f9f9;
+    background: var(--darkened-bg);
 }
 }
 
 
 /* SORTABLE TABLES */
 /* SORTABLE TABLES */
@@ -273,15 +365,15 @@ thead th {
     padding: 5px 10px;
     padding: 5px 10px;
     line-height: normal;
     line-height: normal;
     text-transform: uppercase;
     text-transform: uppercase;
-    background: #f6f6f6;
+    background: var(--darkened-bg);
 }
 }
 
 
 thead th a:link, thead th a:visited {
 thead th a:link, thead th a:visited {
-    color: #666;
+    color: var(--body-quiet-color);
 }
 }
 
 
 thead th.sorted {
 thead th.sorted {
-    background: #eee;
+    background: var(--selected-bg);
 }
 }
 
 
 thead th.sorted .text {
 thead th.sorted .text {
@@ -300,7 +392,7 @@ table thead th .text a {
 }
 }
 
 
 table thead th .text a:focus, table thead th .text a:hover {
 table thead th .text a:focus, table thead th .text a:hover {
-    background: #eee;
+    background: var(--selected-bg);
 }
 }
 
 
 thead th.sorted a.sortremove {
 thead th.sorted a.sortremove {
@@ -346,13 +438,13 @@ table thead th.sorted .sortoptions a.sortremove:after {
     top: -6px;
     top: -6px;
     left: 3px;
     left: 3px;
     font-weight: 200;
     font-weight: 200;
-    font-size: 18px;
-    color: #999;
+    font-size: 1.125rem;
+    color: var(--body-quiet-color);
 }
 }
 
 
 table thead th.sorted .sortoptions a.sortremove:focus:after,
 table thead th.sorted .sortoptions a.sortremove:focus:after,
 table thead th.sorted .sortoptions a.sortremove:hover:after {
 table thead th.sorted .sortoptions a.sortremove:hover:after {
-    color: #447e9b;
+    color: var(--link-fg);
 }
 }
 
 
 table thead th.sorted .sortoptions a.sortremove:focus,
 table thead th.sorted .sortoptions a.sortremove:focus,
@@ -385,9 +477,9 @@ input, textarea, select, .form-row p, form .button {
     margin: 2px 0;
     margin: 2px 0;
     padding: 2px 3px;
     padding: 2px 3px;
     vertical-align: middle;
     vertical-align: middle;
-    font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
+    font-family: var(--font-family-primary);
     font-weight: normal;
     font-weight: normal;
-    font-size: 13px;
+    font-size: 0.8125rem;
 }
 }
 .form-row div.help {
 .form-row div.help {
     padding: 2px 3px;
     padding: 2px 3px;
@@ -397,22 +489,33 @@ textarea {
     vertical-align: top;
     vertical-align: top;
 }
 }
 
 
-input[type=text], input[type=password], input[type=email], input[type=url],
-input[type=number], input[type=tel], textarea, select, .vTextField {
-    border: 1px solid #ccc;
+/*
+Minifiers remove the default (text) "type" attribute from "input" HTML tags.
+Add input:not([type]) to make the CSS stylesheet work the same.
+*/
+input:not([type]), input[type=text], input[type=password], input[type=email],
+input[type=url], input[type=number], input[type=tel], textarea, select,
+.vTextField {
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     border-radius: 4px;
     padding: 5px 6px;
     padding: 5px 6px;
     margin-top: 0;
     margin-top: 0;
+    color: var(--body-fg);
+    background-color: var(--body-bg);
 }
 }
 
 
-input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
-input[type=url]:focus, input[type=number]:focus, input[type=tel]:focus,
-textarea:focus, select:focus, .vTextField:focus {
-    border-color: #999;
+/*
+Minifiers remove the default (text) "type" attribute from "input" HTML tags.
+Add input:not([type]) to make the CSS stylesheet work the same.
+*/
+input:not([type]):focus, input[type=text]:focus, input[type=password]:focus,
+input[type=email]:focus, input[type=url]:focus, input[type=number]:focus,
+input[type=tel]:focus, textarea:focus, select:focus, .vTextField:focus {
+    border-color: var(--body-quiet-color);
 }
 }
 
 
 select {
 select {
-    height: 30px;
+    height: 1.875rem;
 }
 }
 
 
 select[multiple] {
 select[multiple] {
@@ -424,12 +527,13 @@ select[multiple] {
 /* FORM BUTTONS */
 /* FORM BUTTONS */
 
 
 .button, input[type=submit], input[type=button], .submit-row input, a.button {
 .button, input[type=submit], input[type=button], .submit-row input, a.button {
-    background: #79aec8;
+    background: var(--button-bg);
     padding: 10px 15px;
     padding: 10px 15px;
     border: none;
     border: none;
     border-radius: 4px;
     border-radius: 4px;
-    color: #fff;
+    color: var(--button-fg);
     cursor: pointer;
     cursor: pointer;
+    transition: background 0.15s;
 }
 }
 
 
 a.button {
 a.button {
@@ -439,7 +543,7 @@ a.button {
 .button:active, input[type=submit]:active, input[type=button]:active,
 .button:active, input[type=submit]:active, input[type=button]:active,
 .button:focus, input[type=submit]:focus, input[type=button]:focus,
 .button:focus, input[type=submit]:focus, input[type=button]:focus,
 .button:hover, input[type=submit]:hover, input[type=button]:hover {
 .button:hover, input[type=submit]:hover, input[type=button]:hover {
-    background: #609ab6;
+    background: var(--button-hover-bg);
 }
 }
 
 
 .button[disabled], input[type=submit][disabled], input[type=button][disabled] {
 .button[disabled], input[type=submit][disabled], input[type=button][disabled] {
@@ -447,16 +551,15 @@ a.button {
 }
 }
 
 
 .button.default, input[type=submit].default, .submit-row input.default {
 .button.default, input[type=submit].default, .submit-row input.default {
-    float: right;
     border: none;
     border: none;
     font-weight: 400;
     font-weight: 400;
-    background: #417690;
+    background: var(--default-button-bg);
 }
 }
 
 
 .button.default:active, input[type=submit].default:active,
 .button.default:active, input[type=submit].default:active,
 .button.default:focus, input[type=submit].default:focus,
 .button.default:focus, input[type=submit].default:focus,
 .button.default:hover, input[type=submit].default:hover {
 .button.default:hover, input[type=submit].default:hover {
-    background: #205067;
+    background: var(--default-button-hover-bg);
 }
 }
 
 
 .button[disabled].default,
 .button[disabled].default,
@@ -471,7 +574,7 @@ input[type=button][disabled].default {
 .module {
 .module {
     border: none;
     border: none;
     margin-bottom: 30px;
     margin-bottom: 30px;
-    background: #fff;
+    background: var(--body-bg);
 }
 }
 
 
 .module p, .module ul, .module h3, .module h4, .module dl, .module pre {
 .module p, .module ul, .module h3, .module h4, .module dl, .module pre {
@@ -495,15 +598,15 @@ input[type=button][disabled].default {
     margin: 0;
     margin: 0;
     padding: 8px;
     padding: 8px;
     font-weight: 400;
     font-weight: 400;
-    font-size: 13px;
+    font-size: 0.8125rem;
     text-align: left;
     text-align: left;
-    background: #79aec8;
-    color: #fff;
+    background: var(--header-bg);
+    color: var(--header-link-color);
 }
 }
 
 
 .module caption,
 .module caption,
 .inline-group h2 {
 .inline-group h2 {
-    font-size: 12px;
+    font-size: 0.75rem;
     letter-spacing: 0.5px;
     letter-spacing: 0.5px;
     text-transform: uppercase;
     text-transform: uppercase;
 }
 }
@@ -522,48 +625,51 @@ ul.messagelist {
 ul.messagelist li {
 ul.messagelist li {
     display: block;
     display: block;
     font-weight: 400;
     font-weight: 400;
-    font-size: 13px;
+    font-size: 0.8125rem;
     padding: 10px 10px 10px 65px;
     padding: 10px 10px 10px 65px;
     margin: 0 0 10px 0;
     margin: 0 0 10px 0;
-    background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat;
+    background: var(--message-success-bg) url(../img/icon-yes.svg) 40px 12px no-repeat;
     background-size: 16px auto;
     background-size: 16px auto;
-    color: #333;
+    color: var(--body-fg);
+    word-break: break-word;
 }
 }
 
 
 ul.messagelist li.warning {
 ul.messagelist li.warning {
-    background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat;
+    background: var(--message-warning-bg) url(../img/icon-alert.svg) 40px 14px no-repeat;
     background-size: 14px auto;
     background-size: 14px auto;
 }
 }
 
 
 ul.messagelist li.error {
 ul.messagelist li.error {
-    background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat;
+    background: var(--message-error-bg) url(../img/icon-no.svg) 40px 12px no-repeat;
     background-size: 16px auto;
     background-size: 16px auto;
 }
 }
 
 
 .errornote {
 .errornote {
-    font-size: 14px;
+    font-size: 0.875rem;
     font-weight: 700;
     font-weight: 700;
     display: block;
     display: block;
     padding: 10px 12px;
     padding: 10px 12px;
     margin: 0 0 10px 0;
     margin: 0 0 10px 0;
-    color: #ba2121;
-    border: 1px solid #ba2121;
+    color: var(--error-fg);
+    border: 1px solid var(--error-fg);
     border-radius: 4px;
     border-radius: 4px;
-    background-color: #fff;
+    background-color: var(--body-bg);
     background-position: 5px 12px;
     background-position: 5px 12px;
+    overflow-wrap: break-word;
 }
 }
 
 
 ul.errorlist {
 ul.errorlist {
     margin: 0 0 4px;
     margin: 0 0 4px;
     padding: 0;
     padding: 0;
-    color: #ba2121;
-    background: #fff;
+    color: var(--error-fg);
+    background: var(--body-bg);
 }
 }
 
 
 ul.errorlist li {
 ul.errorlist li {
-    font-size: 13px;
+    font-size: 0.8125rem;
     display: block;
     display: block;
     margin-bottom: 4px;
     margin-bottom: 4px;
+    overflow-wrap: break-word;
 }
 }
 
 
 ul.errorlist li:first-child {
 ul.errorlist li:first-child {
@@ -587,7 +693,7 @@ td ul.errorlist li {
 .form-row.errors {
 .form-row.errors {
     margin: 0;
     margin: 0;
     border: none;
     border: none;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
     background: none;
     background: none;
 }
 }
 
 
@@ -597,31 +703,30 @@ td ul.errorlist li {
 
 
 .errors input, .errors select, .errors textarea,
 .errors input, .errors select, .errors textarea,
 td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
 td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
-    border: 1px solid #ba2121;
+    border: 1px solid var(--error-fg);
 }
 }
 
 
 .description {
 .description {
-    font-size: 12px;
+    font-size: 0.75rem;
     padding: 5px 0 0 12px;
     padding: 5px 0 0 12px;
 }
 }
 
 
 /* BREADCRUMBS */
 /* BREADCRUMBS */
 
 
 div.breadcrumbs {
 div.breadcrumbs {
-    background: #79aec8;
+    background: var(--breadcrumbs-bg);
     padding: 10px 40px;
     padding: 10px 40px;
     border: none;
     border: none;
-    font-size: 14px;
-    color: #c4dce8;
+    color: var(--breadcrumbs-fg);
     text-align: left;
     text-align: left;
 }
 }
 
 
 div.breadcrumbs a {
 div.breadcrumbs a {
-    color: #fff;
+    color: var(--breadcrumbs-link-fg);
 }
 }
 
 
 div.breadcrumbs a:focus, div.breadcrumbs a:hover {
 div.breadcrumbs a:focus, div.breadcrumbs a:hover {
-    color: #c4dce8;
+    color: var(--breadcrumbs-fg);
 }
 }
 
 
 /* ACTION ICONS */
 /* ACTION ICONS */
@@ -631,6 +736,11 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover {
     background: url(../img/icon-viewlink.svg) 0 1px no-repeat;
     background: url(../img/icon-viewlink.svg) 0 1px no-repeat;
 }
 }
 
 
+.hidelink {
+    padding-left: 16px;
+    background: url(../img/icon-hidelink.svg) 0 1px no-repeat;
+}
+
 .addlink {
 .addlink {
     padding-left: 16px;
     padding-left: 16px;
     background: url(../img/icon-addlink.svg) 0 1px no-repeat;
     background: url(../img/icon-addlink.svg) 0 1px no-repeat;
@@ -647,18 +757,18 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover {
 }
 }
 
 
 a.deletelink:link, a.deletelink:visited {
 a.deletelink:link, a.deletelink:visited {
-    color: #CC3434;
+    color: #CC3434; /* XXX Probably unused? */
 }
 }
 
 
 a.deletelink:focus, a.deletelink:hover {
 a.deletelink:focus, a.deletelink:hover {
-    color: #993333;
+    color: #993333; /* XXX Probably unused? */
     text-decoration: none;
     text-decoration: none;
 }
 }
 
 
 /* OBJECT TOOLS */
 /* OBJECT TOOLS */
 
 
 .object-tools {
 .object-tools {
-    font-size: 10px;
+    font-size: 0.625rem;
     font-weight: bold;
     font-weight: bold;
     padding-left: 0;
     padding-left: 0;
     float: right;
     float: right;
@@ -666,19 +776,11 @@ a.deletelink:focus, a.deletelink:hover {
     margin-top: -48px;
     margin-top: -48px;
 }
 }
 
 
-.form-row .object-tools {
-    margin-top: 5px;
-    margin-bottom: 5px;
-    float: none;
-    height: 2em;
-    padding-left: 3.5em;
-}
-
 .object-tools li {
 .object-tools li {
     display: block;
     display: block;
     float: left;
     float: left;
     margin-left: 5px;
     margin-left: 5px;
-    height: 16px;
+    height: 1rem;
 }
 }
 
 
 .object-tools a {
 .object-tools a {
@@ -689,29 +791,29 @@ a.deletelink:focus, a.deletelink:hover {
     display: block;
     display: block;
     float: left;
     float: left;
     padding: 3px 12px;
     padding: 3px 12px;
-    background: #999;
+    background: var(--object-tools-bg);
+    color: var(--object-tools-fg);
     font-weight: 400;
     font-weight: 400;
-    font-size: 11px;
+    font-size: 0.6875rem;
     text-transform: uppercase;
     text-transform: uppercase;
     letter-spacing: 0.5px;
     letter-spacing: 0.5px;
-    color: #fff;
 }
 }
 
 
 .object-tools a:focus, .object-tools a:hover {
 .object-tools a:focus, .object-tools a:hover {
-    background-color: #417690;
+    background-color: var(--object-tools-hover-bg);
 }
 }
 
 
 .object-tools a:focus{
 .object-tools a:focus{
     text-decoration: none;
     text-decoration: none;
 }
 }
 
 
-.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink {
+.object-tools a.viewsitelink, .object-tools a.addlink {
     background-repeat: no-repeat;
     background-repeat: no-repeat;
     background-position: right 7px center;
     background-position: right 7px center;
     padding-right: 26px;
     padding-right: 26px;
 }
 }
 
 
-.object-tools a.viewsitelink, .object-tools a.golink {
+.object-tools a.viewsitelink {
     background-image: url(../img/tooltag-arrowright.svg);
     background-image: url(../img/tooltag-arrowright.svg);
 }
 }
 
 
@@ -721,14 +823,21 @@ a.deletelink:focus, a.deletelink:hover {
 
 
 /* OBJECT HISTORY */
 /* OBJECT HISTORY */
 
 
-table#change-history {
+#change-history table {
     width: 100%;
     width: 100%;
 }
 }
 
 
-table#change-history tbody th {
+#change-history table tbody th {
     width: 16em;
     width: 16em;
 }
 }
 
 
+#change-history .paginator {
+    color: var(--body-quiet-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--body-bg);
+    overflow: hidden;
+}
+
 /* PAGE STRUCTURE */
 /* PAGE STRUCTURE */
 
 
 #container {
 #container {
@@ -741,10 +850,6 @@ table#change-history tbody th {
     height: 100%;
     height: 100%;
 }
 }
 
 
-#container > div {
-    flex-shrink: 0;
-}
-
 #container > .main {
 #container > .main {
     display: flex;
     display: flex;
     flex: 1 0 auto;
     flex: 1 0 auto;
@@ -755,6 +860,20 @@ table#change-history tbody th {
     max-width: 100%;
     max-width: 100%;
 }
 }
 
 
+.skip-to-content-link {
+    position: absolute;
+    top: -999px;
+    margin: 5px;
+    padding: 5px;
+    background: var(--body-bg);
+    z-index: 1;
+}
+
+.skip-to-content-link:focus {
+    left: 0px;
+    top: 0px;
+}
+
 #content {
 #content {
     padding: 20px 40px;
     padding: 20px 40px;
 }
 }
@@ -775,9 +894,10 @@ table#change-history tbody th {
     margin-right: -300px;
     margin-right: -300px;
 }
 }
 
 
-#footer {
-    clear: both;
-    padding: 10px;
+@media (forced-colors: active) {
+  #content-related {
+      border: 1px solid;
+  }
 }
 }
 
 
 /* COLUMN TYPES */
 /* COLUMN TYPES */
@@ -813,72 +933,96 @@ table#change-history tbody th {
     justify-content: space-between;
     justify-content: space-between;
     align-items: center;
     align-items: center;
     padding: 10px 40px;
     padding: 10px 40px;
-    background: #417690;
-    color: #ffc;
-    overflow: hidden;
+    background: var(--header-bg);
+    color: var(--header-color);
 }
 }
 
 
-#header a:link, #header a:visited {
-    color: #fff;
+#header a:link, #header a:visited, #logout-form button {
+    color: var(--header-link-color);
 }
 }
 
 
 #header a:focus , #header a:hover {
 #header a:focus , #header a:hover {
     text-decoration: underline;
     text-decoration: underline;
 }
 }
 
 
+@media (forced-colors: active) {
+  #header {
+      border-bottom: 1px solid;
+  }
+}
+
 #branding {
 #branding {
-    float: left;
+    display: flex;
 }
 }
 
 
-#branding h1 {
+#site-name {
     padding: 0;
     padding: 0;
-    margin: 0 20px 0 0;
+    margin: 0;
+    margin-inline-end: 20px;
     font-weight: 300;
     font-weight: 300;
-    font-size: 24px;
-    color: #f5dd5d;
+    font-size: 1.5rem;
+    color: var(--header-branding-color);
 }
 }
 
 
-#branding h1, #branding h1 a:link, #branding h1 a:visited {
-    color: #f5dd5d;
+#site-name a:link, #site-name a:visited {
+    color: var(--accent);
 }
 }
 
 
 #branding h2 {
 #branding h2 {
     padding: 0 10px;
     padding: 0 10px;
-    font-size: 14px;
+    font-size: 0.875rem;
     margin: -8px 0 8px 0;
     margin: -8px 0 8px 0;
     font-weight: normal;
     font-weight: normal;
-    color: #ffc;
+    color: var(--header-color);
 }
 }
 
 
 #branding a:hover {
 #branding a:hover {
     text-decoration: none;
     text-decoration: none;
 }
 }
 
 
+#logout-form {
+    display: inline;
+}
+
+#logout-form button {
+    background: none;
+    border: 0;
+    cursor: pointer;
+    font-family: var(--font-family-primary);
+}
+
 #user-tools {
 #user-tools {
     float: right;
     float: right;
-    padding: 0;
     margin: 0 0 0 20px;
     margin: 0 0 0 20px;
+    text-align: right;
+}
+
+#user-tools, #logout-form button{
+    padding: 0;
     font-weight: 300;
     font-weight: 300;
-    font-size: 11px;
+    font-size: 0.6875rem;
     letter-spacing: 0.5px;
     letter-spacing: 0.5px;
     text-transform: uppercase;
     text-transform: uppercase;
-    text-align: right;
 }
 }
 
 
-#user-tools a {
+#user-tools a, #logout-form button {
     border-bottom: 1px solid rgba(255, 255, 255, 0.25);
     border-bottom: 1px solid rgba(255, 255, 255, 0.25);
 }
 }
 
 
-#user-tools a:focus, #user-tools a:hover {
+#user-tools a:focus, #user-tools a:hover,
+#logout-form button:active, #logout-form button:hover {
     text-decoration: none;
     text-decoration: none;
-    border-bottom-color: #79aec8;
-    color: #79aec8;
+    border-bottom: 0;
+}
+
+#logout-form button:active, #logout-form button:hover {
+    margin-bottom: 1px;
 }
 }
 
 
 /* SIDEBAR */
 /* SIDEBAR */
 
 
 #content-related {
 #content-related {
-    background: #f8f8f8;
+    background: var(--darkened-bg);
 }
 }
 
 
 #content-related .module {
 #content-related .module {
@@ -886,14 +1030,13 @@ table#change-history tbody th {
 }
 }
 
 
 #content-related h3 {
 #content-related h3 {
-    font-size: 14px;
-    color: #666;
+    color: var(--body-quiet-color);
     padding: 0 16px;
     padding: 0 16px;
     margin: 0 0 16px;
     margin: 0 0 16px;
 }
 }
 
 
 #content-related h4 {
 #content-related h4 {
-    font-size: 13px;
+    font-size: 0.8125rem;
 }
 }
 
 
 #content-related p {
 #content-related p {
@@ -916,40 +1059,40 @@ table#change-history tbody th {
     background: none;
     background: none;
     padding: 16px;
     padding: 16px;
     margin-bottom: 16px;
     margin-bottom: 16px;
-    border-bottom: 1px solid #eaeaea;
-    font-size: 18px;
-    color: #333;
+    border-bottom: 1px solid var(--hairline-color);
+    font-size: 1.125rem;
+    color: var(--body-fg);
 }
 }
 
 
 .delete-confirmation form input[type="submit"] {
 .delete-confirmation form input[type="submit"] {
-    background: #ba2121;
+    background: var(--delete-button-bg);
     border-radius: 4px;
     border-radius: 4px;
     padding: 10px 15px;
     padding: 10px 15px;
-    color: #fff;
+    color: var(--button-fg);
 }
 }
 
 
 .delete-confirmation form input[type="submit"]:active,
 .delete-confirmation form input[type="submit"]:active,
 .delete-confirmation form input[type="submit"]:focus,
 .delete-confirmation form input[type="submit"]:focus,
 .delete-confirmation form input[type="submit"]:hover {
 .delete-confirmation form input[type="submit"]:hover {
-    background: #a41515;
+    background: var(--delete-button-hover-bg);
 }
 }
 
 
 .delete-confirmation form .cancel-link {
 .delete-confirmation form .cancel-link {
     display: inline-block;
     display: inline-block;
     vertical-align: middle;
     vertical-align: middle;
-    height: 15px;
-    line-height: 15px;
-    background: #ddd;
+    height: 0.9375rem;
+    line-height: 0.9375rem;
     border-radius: 4px;
     border-radius: 4px;
     padding: 10px 15px;
     padding: 10px 15px;
-    color: #333;
+    color: var(--button-fg);
+    background: var(--close-button-bg);
     margin: 0 0 0 10px;
     margin: 0 0 0 10px;
 }
 }
 
 
 .delete-confirmation form .cancel-link:active,
 .delete-confirmation form .cancel-link:active,
 .delete-confirmation form .cancel-link:focus,
 .delete-confirmation form .cancel-link:focus,
 .delete-confirmation form .cancel-link:hover {
 .delete-confirmation form .cancel-link:hover {
-    background: #ccc;
+    background: var(--close-button-hover-bg);
 }
 }
 
 
 /* POPUP */
 /* POPUP */
@@ -964,3 +1107,73 @@ table#change-history tbody th {
 .popup #header {
 .popup #header {
     padding: 10px 20px;
     padding: 10px 20px;
 }
 }
+
+/* PAGINATOR */
+
+.paginator {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    font-size: 0.8125rem;
+    padding-top: 10px;
+    padding-bottom: 10px;
+    line-height: 22px;
+    margin: 0;
+    border-top: 1px solid var(--hairline-color);
+    width: 100%;
+}
+
+.paginator a:link, .paginator a:visited {
+    padding: 2px 6px;
+    background: var(--button-bg);
+    text-decoration: none;
+    color: var(--button-fg);
+}
+
+.paginator a.showall {
+    border: none;
+    background: none;
+    color: var(--link-fg);
+}
+
+.paginator a.showall:focus, .paginator a.showall:hover {
+    background: none;
+    color: var(--link-hover-color);
+}
+
+.paginator .end {
+    margin-right: 6px;
+}
+
+.paginator .this-page {
+    padding: 2px 6px;
+    font-weight: bold;
+    font-size: 0.8125rem;
+    vertical-align: top;
+}
+
+.paginator a:focus, .paginator a:hover {
+    color: white;
+    background: var(--link-hover-color);
+}
+
+.paginator input {
+    margin-left: auto;
+}
+
+.base-svgs {
+    display: none;
+}
+
+.visually-hidden {
+    position: absolute;
+    width: 1px;
+    height: 1px;
+    padding: 0;
+    overflow: hidden;
+    clip: rect(0,0,0,0);
+    white-space: nowrap;
+    border: 0;
+    color: var(--body-fg);
+    background-color: var(--body-bg);
+}

+ 111 - 123
static/admin/css/changelists.css

@@ -40,13 +40,13 @@
 }
 }
 
 
 #changelist .toplinks {
 #changelist .toplinks {
-    border-bottom: 1px solid #ddd;
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 #changelist .paginator {
 #changelist .paginator {
-    color: #666;
-    border-bottom: 1px solid #eee;
-    background: #fff;
+    color: var(--body-quiet-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--body-bg);
     overflow: hidden;
     overflow: hidden;
 }
 }
 
 
@@ -68,7 +68,7 @@
 }
 }
 
 
 #changelist table tfoot {
 #changelist table tfoot {
-    color: #666;
+    color: var(--body-quiet-color);
 }
 }
 
 
 /* TOOLBAR */
 /* TOOLBAR */
@@ -76,48 +76,48 @@
 #toolbar {
 #toolbar {
     padding: 8px 10px;
     padding: 8px 10px;
     margin-bottom: 15px;
     margin-bottom: 15px;
-    border-top: 1px solid #eee;
-    border-bottom: 1px solid #eee;
-    background: #f8f8f8;
-    color: #666;
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
 }
 }
 
 
 #toolbar form input {
 #toolbar form input {
     border-radius: 4px;
     border-radius: 4px;
-    font-size: 14px;
+    font-size: 0.875rem;
     padding: 5px;
     padding: 5px;
-    color: #333;
+    color: var(--body-fg);
 }
 }
 
 
 #toolbar #searchbar {
 #toolbar #searchbar {
-    height: 19px;
-    border: 1px solid #ccc;
+    height: 1.1875rem;
+    border: 1px solid var(--border-color);
     padding: 2px 5px;
     padding: 2px 5px;
     margin: 0;
     margin: 0;
     vertical-align: top;
     vertical-align: top;
-    font-size: 13px;
+    font-size: 0.8125rem;
     max-width: 100%;
     max-width: 100%;
 }
 }
 
 
 #toolbar #searchbar:focus {
 #toolbar #searchbar:focus {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 }
 
 
 #toolbar form input[type="submit"] {
 #toolbar form input[type="submit"] {
-    border: 1px solid #ccc;
-    font-size: 13px;
+    border: 1px solid var(--border-color);
+    font-size: 0.8125rem;
     padding: 4px 8px;
     padding: 4px 8px;
     margin: 0;
     margin: 0;
     vertical-align: middle;
     vertical-align: middle;
-    background: #fff;
+    background: var(--body-bg);
     box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
     box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
     cursor: pointer;
     cursor: pointer;
-    color: #333;
+    color: var(--body-fg);
 }
 }
 
 
 #toolbar form input[type="submit"]:focus,
 #toolbar form input[type="submit"]:focus,
 #toolbar form input[type="submit"]:hover {
 #toolbar form input[type="submit"]:hover {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 }
 
 
 #changelist-search img {
 #changelist-search img {
@@ -125,19 +125,28 @@
     margin-right: 4px;
     margin-right: 4px;
 }
 }
 
 
+#changelist-search .help {
+    word-break: break-word;
+}
+
 /* FILTER COLUMN */
 /* FILTER COLUMN */
 
 
 #changelist-filter {
 #changelist-filter {
     flex: 0 0 240px;
     flex: 0 0 240px;
     order: 1;
     order: 1;
-    width: 240px;
-    background: #f8f8f8;
+    background: var(--darkened-bg);
     border-left: none;
     border-left: none;
     margin: 0 0 0 30px;
     margin: 0 0 0 30px;
 }
 }
 
 
+@media (forced-colors: active) {
+  #changelist-filter {
+      border: 1px solid;
+  }
+}
+
 #changelist-filter h2 {
 #changelist-filter h2 {
-    font-size: 14px;
+    font-size: 0.875rem;
     text-transform: uppercase;
     text-transform: uppercase;
     letter-spacing: 0.5px;
     letter-spacing: 0.5px;
     padding: 5px 15px;
     padding: 5px 15px;
@@ -145,17 +154,39 @@
     border-bottom: none;
     border-bottom: none;
 }
 }
 
 
-#changelist-filter h3 {
+#changelist-filter h3,
+#changelist-filter details summary {
     font-weight: 400;
     font-weight: 400;
-    font-size: 14px;
     padding: 0 15px;
     padding: 0 15px;
     margin-bottom: 10px;
     margin-bottom: 10px;
 }
 }
 
 
+#changelist-filter details summary > * {
+    display: inline;
+}
+
+#changelist-filter details > summary {
+    list-style-type: none;
+}
+
+#changelist-filter details > summary::-webkit-details-marker {
+    display: none;
+}
+
+#changelist-filter details > summary::before {
+    content: '→';
+    font-weight: bold;
+    color: var(--link-hover-color);
+}
+
+#changelist-filter details[open] > summary::before {
+    content: '↓';
+}
+
 #changelist-filter ul {
 #changelist-filter ul {
     margin: 5px 0;
     margin: 5px 0;
     padding: 0 15px 15px;
     padding: 0 15px 15px;
-    border-bottom: 1px solid #eaeaea;
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 #changelist-filter ul:last-child {
 #changelist-filter ul:last-child {
@@ -170,103 +201,53 @@
 
 
 #changelist-filter a {
 #changelist-filter a {
     display: block;
     display: block;
-    color: #999;
-    text-overflow: ellipsis;
-    overflow-x: hidden;
+    color: var(--body-quiet-color);
+    word-break: break-word;
 }
 }
 
 
 #changelist-filter li.selected {
 #changelist-filter li.selected {
-    border-left: 5px solid #eaeaea;
+    border-left: 5px solid var(--hairline-color);
     padding-left: 10px;
     padding-left: 10px;
     margin-left: -15px;
     margin-left: -15px;
 }
 }
 
 
 #changelist-filter li.selected a {
 #changelist-filter li.selected a {
-    color: #5b80b2;
+    color: var(--link-selected-fg);
 }
 }
 
 
 #changelist-filter a:focus, #changelist-filter a:hover,
 #changelist-filter a:focus, #changelist-filter a:hover,
 #changelist-filter li.selected a:focus,
 #changelist-filter li.selected a:focus,
 #changelist-filter li.selected a:hover {
 #changelist-filter li.selected a:hover {
-    color: #036;
+    color: var(--link-hover-color);
 }
 }
 
 
-#changelist-filter #changelist-filter-clear a {
-    font-size: 13px;
-    padding-bottom: 10px;
-    border-bottom: 1px solid #eaeaea;
+#changelist-filter #changelist-filter-extra-actions {
+    font-size: 0.8125rem;
+    margin-bottom: 10px;
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 /* DATE DRILLDOWN */
 /* DATE DRILLDOWN */
 
 
-.change-list ul.toplinks {
-    display: block;
-    float: left;
-    padding: 0;
-    margin: 0;
-    width: 100%;
-}
-
-.change-list ul.toplinks li {
-    padding: 3px 6px;
+.change-list .toplinks {
+    display: flex;
+    padding-bottom: 5px;
+    flex-wrap: wrap;
+    gap: 3px 17px;
     font-weight: bold;
     font-weight: bold;
-    list-style-type: none;
-    display: inline-block;
 }
 }
 
 
-.change-list ul.toplinks .date-back a {
-    color: #999;
+.change-list .toplinks a {
+    font-size: 0.8125rem;
 }
 }
 
 
-.change-list ul.toplinks .date-back a:focus,
-.change-list ul.toplinks .date-back a:hover {
-    color: #036;
+.change-list .toplinks .date-back {
+    color: var(--body-quiet-color);
 }
 }
 
 
-/* PAGINATOR */
-
-.paginator {
-    font-size: 13px;
-    padding-top: 10px;
-    padding-bottom: 10px;
-    line-height: 22px;
-    margin: 0;
-    border-top: 1px solid #ddd;
-    width: 100%;
-}
-
-.paginator a:link, .paginator a:visited {
-    padding: 2px 6px;
-    background: #79aec8;
-    text-decoration: none;
-    color: #fff;
-}
-
-.paginator a.showall {
-    border: none;
-    background: none;
-    color: #5b80b2;
-}
-
-.paginator a.showall:focus, .paginator a.showall:hover {
-    background: none;
-    color: #036;
-}
-
-.paginator .end {
-    margin-right: 6px;
-}
-
-.paginator .this-page {
-    padding: 2px 6px;
-    font-weight: bold;
-    font-size: 13px;
-    vertical-align: top;
-}
-
-.paginator a:focus, .paginator a:hover {
-    color: white;
-    background: #036;
+.change-list .toplinks .date-back:focus,
+.change-list .toplinks .date-back:hover {
+    color: var(--link-hover-color);
 }
 }
 
 
 /* ACTIONS */
 /* ACTIONS */
@@ -280,33 +261,41 @@
     vertical-align: baseline;
     vertical-align: baseline;
 }
 }
 
 
-#changelist table tbody tr.selected {
-    background-color: #FFFFCC;
+/* Once the :has() pseudo-class is supported by all browsers, the tr.selected
+   selector and the JS adding the class can be removed. */
+#changelist tbody tr.selected {
+    background-color: var(--selected-row);
+}
+
+#changelist tbody tr:has(.action-select:checked) {
+    background-color: var(--selected-row);
+}
+
+@media (forced-colors: active) {
+    #changelist tbody tr.selected {
+        background-color: SelectedItem;
+    }
+    #changelist tbody tr:has(.action-select:checked) {
+        background-color: SelectedItem;
+    }
 }
 }
 
 
 #changelist .actions {
 #changelist .actions {
     padding: 10px;
     padding: 10px;
-    background: #fff;
+    background: var(--body-bg);
     border-top: none;
     border-top: none;
     border-bottom: none;
     border-bottom: none;
-    line-height: 24px;
-    color: #999;
+    line-height: 1.5rem;
+    color: var(--body-quiet-color);
     width: 100%;
     width: 100%;
 }
 }
 
 
-#changelist .actions.selected {
-    background: #fffccf;
-    border-top: 1px solid #fffee8;
-    border-bottom: 1px solid #edecd6;
-}
-
 #changelist .actions span.all,
 #changelist .actions span.all,
 #changelist .actions span.action-counter,
 #changelist .actions span.action-counter,
 #changelist .actions span.clear,
 #changelist .actions span.clear,
 #changelist .actions span.question {
 #changelist .actions span.question {
-    font-size: 13px;
+    font-size: 0.8125rem;
     margin: 0 0.5em;
     margin: 0 0.5em;
-    display: none;
 }
 }
 
 
 #changelist .actions:last-child {
 #changelist .actions:last-child {
@@ -315,41 +304,40 @@
 
 
 #changelist .actions select {
 #changelist .actions select {
     vertical-align: top;
     vertical-align: top;
-    height: 24px;
-    background: none;
-    color: #000;
-    border: 1px solid #ccc;
+    height: 1.5rem;
+    color: var(--body-fg);
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     border-radius: 4px;
-    font-size: 14px;
+    font-size: 0.875rem;
     padding: 0 0 0 4px;
     padding: 0 0 0 4px;
     margin: 0;
     margin: 0;
     margin-left: 10px;
     margin-left: 10px;
 }
 }
 
 
 #changelist .actions select:focus {
 #changelist .actions select:focus {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 }
 
 
 #changelist .actions label {
 #changelist .actions label {
     display: inline-block;
     display: inline-block;
     vertical-align: middle;
     vertical-align: middle;
-    font-size: 13px;
+    font-size: 0.8125rem;
 }
 }
 
 
 #changelist .actions .button {
 #changelist .actions .button {
-    font-size: 13px;
-    border: 1px solid #ccc;
+    font-size: 0.8125rem;
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     border-radius: 4px;
-    background: #fff;
+    background: var(--body-bg);
     box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
     box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
     cursor: pointer;
     cursor: pointer;
-    height: 24px;
+    height: 1.5rem;
     line-height: 1;
     line-height: 1;
     padding: 4px 8px;
     padding: 4px 8px;
     margin: 0;
     margin: 0;
-    color: #333;
+    color: var(--body-fg);
 }
 }
 
 
 #changelist .actions .button:focus, #changelist .actions .button:hover {
 #changelist .actions .button:focus, #changelist .actions .button:hover {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 }

+ 130 - 0
static/admin/css/dark_mode.css

@@ -0,0 +1,130 @@
+@media (prefers-color-scheme: dark) {
+    :root {
+      --primary: #264b5d;
+      --primary-fg: #f7f7f7;
+  
+      --body-fg: #eeeeee;
+      --body-bg: #121212;
+      --body-quiet-color: #d0d0d0;
+      --body-medium-color: #e0e0e0;
+      --body-loud-color: #ffffff;
+  
+      --breadcrumbs-link-fg: #e0e0e0;
+      --breadcrumbs-bg: var(--primary);
+  
+      --link-fg: #81d4fa;
+      --link-hover-color: #4ac1f7;
+      --link-selected-fg: #6f94c6;
+  
+      --hairline-color: #272727;
+      --border-color: #353535;
+  
+      --error-fg: #e35f5f;
+      --message-success-bg: #006b1b;
+      --message-warning-bg: #583305;
+      --message-error-bg: #570808;
+  
+      --darkened-bg: #212121;
+      --selected-bg: #1b1b1b;
+      --selected-row: #00363a;
+  
+      --close-button-bg: #333333;
+      --close-button-hover-bg: #666666;
+
+      color-scheme: dark;
+    }
+  }
+
+
+html[data-theme="dark"] {
+    --primary: #264b5d;
+    --primary-fg: #f7f7f7;
+
+    --body-fg: #eeeeee;
+    --body-bg: #121212;
+    --body-quiet-color: #d0d0d0;
+    --body-medium-color: #e0e0e0;
+    --body-loud-color: #ffffff;
+
+    --breadcrumbs-link-fg: #e0e0e0;
+    --breadcrumbs-bg: var(--primary);
+
+    --link-fg: #81d4fa;
+    --link-hover-color: #4ac1f7;
+    --link-selected-fg: #6f94c6;
+
+    --hairline-color: #272727;
+    --border-color: #353535;
+
+    --error-fg: #e35f5f;
+    --message-success-bg: #006b1b;
+    --message-warning-bg: #583305;
+    --message-error-bg: #570808;
+
+    --darkened-bg: #212121;
+    --selected-bg: #1b1b1b;
+    --selected-row: #00363a;
+
+    --close-button-bg: #333333;
+    --close-button-hover-bg: #666666;
+
+    color-scheme: dark;
+}
+
+/* THEME SWITCH */
+.theme-toggle {
+    cursor: pointer;
+    border: none;
+    padding: 0;
+    background: transparent;
+    vertical-align: middle;
+    margin-inline-start: 5px;
+    margin-top: -1px;
+}
+
+.theme-toggle svg {
+    vertical-align: middle;
+    height: 1rem;
+    width: 1rem;
+    display: none;
+}
+
+/*
+Fully hide screen reader text so we only show the one matching the current
+theme.
+*/
+.theme-toggle .visually-hidden {
+    display: none;
+}
+
+html[data-theme="auto"] .theme-toggle .theme-label-when-auto {
+    display: block;
+}
+
+html[data-theme="dark"] .theme-toggle .theme-label-when-dark {
+    display: block;
+}
+
+html[data-theme="light"] .theme-toggle .theme-label-when-light {
+    display: block;
+}
+
+/* ICONS */
+.theme-toggle svg.theme-icon-when-auto,
+.theme-toggle svg.theme-icon-when-dark,
+.theme-toggle svg.theme-icon-when-light {
+    fill: var(--header-link-color);
+    color: var(--header-bg);
+}
+
+html[data-theme="auto"] .theme-toggle svg.theme-icon-when-auto {
+    display: block;
+}
+
+html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark {
+    display: block;
+}
+
+html[data-theme="light"] .theme-toggle svg.theme-icon-when-light {
+    display: block;
+}

+ 3 - 0
static/admin/css/dashboard.css

@@ -1,4 +1,7 @@
 /* DASHBOARD */
 /* DASHBOARD */
+.dashboard td, .dashboard th {
+    word-break: break-word;
+}
 
 
 .dashboard .module table th {
 .dashboard .module table th {
     width: 100%;
     width: 100%;

+ 0 - 20
static/admin/css/fonts.css

@@ -1,20 +0,0 @@
-@font-face {
-    font-family: 'Roboto';
-    src: url('../fonts/Roboto-Bold-webfont.woff');
-    font-weight: 700;
-    font-style: normal;
-}
-
-@font-face {
-    font-family: 'Roboto';
-    src: url('../fonts/Roboto-Regular-webfont.woff');
-    font-weight: 400;
-    font-style: normal;
-}
-
-@font-face {
-    font-family: 'Roboto';
-    src: url('../fonts/Roboto-Light-webfont.woff');
-    font-weight: 300;
-    font-style: normal;
-}

+ 111 - 126
static/admin/css/forms.css

@@ -5,8 +5,8 @@
 .form-row {
 .form-row {
     overflow: hidden;
     overflow: hidden;
     padding: 10px;
     padding: 10px;
-    font-size: 13px;
-    border-bottom: 1px solid #eee;
+    font-size: 0.8125rem;
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 .form-row img, .form-row input {
 .form-row img, .form-row input {
@@ -22,35 +22,45 @@ form .form-row p {
     padding-left: 0;
     padding-left: 0;
 }
 }
 
 
-.hidden {
-    display: none;
+.flex-container {
+    display: flex;
+}
+
+.form-multiline {
+    flex-wrap: wrap;
+}
+
+.form-multiline > div {
+    padding-bottom: 10px;
 }
 }
 
 
 /* FORM LABELS */
 /* FORM LABELS */
 
 
 label {
 label {
     font-weight: normal;
     font-weight: normal;
-    color: #666;
-    font-size: 13px;
+    color: var(--body-quiet-color);
+    font-size: 0.8125rem;
 }
 }
 
 
 .required label, label.required {
 .required label, label.required {
     font-weight: bold;
     font-weight: bold;
-    color: #333;
 }
 }
 
 
 /* RADIO BUTTONS */
 /* RADIO BUTTONS */
 
 
-form ul.radiolist li {
-    list-style-type: none;
+form div.radiolist div {
+    padding-right: 7px;
 }
 }
 
 
-form ul.radiolist label {
-    float: none;
-    display: inline;
+form div.radiolist.inline div {
+    display: inline-block;
 }
 }
 
 
-form ul.radiolist input[type="radio"] {
+form div.radiolist label {
+    width: auto;
+}
+
+form div.radiolist input[type="radio"] {
     margin: -2px 4px 0 0;
     margin: -2px 4px 0 0;
     padding: 0;
     padding: 0;
 }
 }
@@ -65,29 +75,42 @@ form ul.inline li {
     padding-right: 7px;
     padding-right: 7px;
 }
 }
 
 
+/* FIELDSETS */
+
+fieldset .fieldset-heading,
+fieldset .inline-heading,
+:not(.inline-related) .collapse summary {
+    border: 1px solid var(--header-bg);
+    margin: 0;
+    padding: 8px;
+    font-weight: 400;
+    font-size: 0.8125rem;
+    background: var(--header-bg);
+    color: var(--header-link-color);
+}
+
 /* ALIGNED FIELDSETS */
 /* ALIGNED FIELDSETS */
 
 
 .aligned label {
 .aligned label {
     display: block;
     display: block;
     padding: 4px 10px 0 0;
     padding: 4px 10px 0 0;
-    float: left;
+    min-width: 160px;
     width: 160px;
     width: 160px;
     word-wrap: break-word;
     word-wrap: break-word;
-    line-height: 1;
 }
 }
 
 
 .aligned label:not(.vCheckboxLabel):after {
 .aligned label:not(.vCheckboxLabel):after {
     content: '';
     content: '';
     display: inline-block;
     display: inline-block;
     vertical-align: middle;
     vertical-align: middle;
-    height: 26px;
 }
 }
 
 
-.aligned label + p, .aligned label + div.help, .aligned label + div.readonly {
+.aligned label + p, .aligned .checkbox-row + div.help, .aligned label + div.readonly {
     padding: 6px 0;
     padding: 6px 0;
     margin-top: 0;
     margin-top: 0;
     margin-bottom: 0;
     margin-bottom: 0;
-    margin-left: 170px;
+    margin-left: 0;
+    overflow-wrap: break-word;
 }
 }
 
 
 .aligned ul label {
 .aligned ul label {
@@ -109,7 +132,7 @@ form .aligned ul {
     padding-left: 10px;
     padding-left: 10px;
 }
 }
 
 
-form .aligned ul.radiolist {
+form .aligned div.radiolist {
     display: inline-block;
     display: inline-block;
     margin: 0;
     margin: 0;
     padding: 0;
     padding: 0;
@@ -117,16 +140,17 @@ form .aligned ul.radiolist {
 
 
 form .aligned p.help,
 form .aligned p.help,
 form .aligned div.help {
 form .aligned div.help {
-    clear: left;
     margin-top: 0;
     margin-top: 0;
     margin-left: 160px;
     margin-left: 160px;
     padding-left: 10px;
     padding-left: 10px;
 }
 }
 
 
-form .aligned label + p.help,
-form .aligned label + div.help {
+form .aligned p.date div.help.timezonewarning,
+form .aligned p.datetime div.help.timezonewarning,
+form .aligned p.time div.help.timezonewarning {
     margin-left: 0;
     margin-left: 0;
     padding-left: 0;
     padding-left: 0;
+    font-weight: normal;
 }
 }
 
 
 form .aligned p.help:last-child,
 form .aligned p.help:last-child,
@@ -145,6 +169,10 @@ form .aligned select + div.help {
     padding-left: 10px;
     padding-left: 10px;
 }
 }
 
 
+form .aligned select option:checked {
+    background-color: var(--selected-row);
+}
+
 form .aligned ul li {
 form .aligned ul li {
     list-style: none;
     list-style: none;
 }
 }
@@ -155,11 +183,7 @@ form .aligned table p {
 }
 }
 
 
 .aligned .vCheckboxLabel {
 .aligned .vCheckboxLabel {
-    float: none;
-    width: auto;
-    display: inline-block;
-    vertical-align: -3px;
-    padding: 0 0 5px 5px;
+    padding: 1px 0 0 5px;
 }
 }
 
 
 .aligned .vCheckboxLabel + p.help,
 .aligned .vCheckboxLabel + p.help,
@@ -171,14 +195,7 @@ form .aligned table p {
     width: 610px;
     width: 610px;
 }
 }
 
 
-.checkbox-row p.help,
-.checkbox-row div.help {
-    margin-left: 0;
-    padding-left: 0;
-}
-
 fieldset .fieldBox {
 fieldset .fieldBox {
-    float: left;
     margin-right: 20px;
     margin-right: 20px;
 }
 }
 
 
@@ -188,15 +205,10 @@ fieldset .fieldBox {
     width: 200px;
     width: 200px;
 }
 }
 
 
-form .wide p,
-form .wide input + p.help,
-form .wide input + div.help {
-    margin-left: 200px;
-}
-
 form .wide p.help,
 form .wide p.help,
+form .wide ul.errorlist,
 form .wide div.help {
 form .wide div.help {
-    padding-left: 38px;
+    padding-left: 50px;
 }
 }
 
 
 form div.help ul {
 form div.help ul {
@@ -208,53 +220,36 @@ form div.help ul {
     width: 450px;
     width: 450px;
 }
 }
 
 
-/* COLLAPSED FIELDSETS */
+/* COLLAPSIBLE FIELDSETS */
 
 
-fieldset.collapsed * {
-    display: none;
-}
-
-fieldset.collapsed h2, fieldset.collapsed {
-    display: block;
-}
-
-fieldset.collapsed {
-    border: 1px solid #eee;
-    border-radius: 4px;
-    overflow: hidden;
-}
-
-fieldset.collapsed h2 {
-    background: #f8f8f8;
-    color: #666;
-}
-
-fieldset .collapse-toggle {
-    color: #fff;
-}
-
-fieldset.collapsed .collapse-toggle {
+.collapse summary .fieldset-heading,
+.collapse summary .inline-heading {
     background: transparent;
     background: transparent;
+    border: none;
+    color: currentColor;
     display: inline;
     display: inline;
-    color: #447e9b;
+    margin: 0;
+    padding: 0;
 }
 }
 
 
 /* MONOSPACE TEXTAREAS */
 /* MONOSPACE TEXTAREAS */
 
 
 fieldset.monospace textarea {
 fieldset.monospace textarea {
-    font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
+    font-family: var(--font-family-monospace);
 }
 }
 
 
 /* SUBMIT ROW */
 /* SUBMIT ROW */
 
 
 .submit-row {
 .submit-row {
-    padding: 12px 14px;
+    padding: 12px 14px 12px;
     margin: 0 0 20px;
     margin: 0 0 20px;
-    background: #f8f8f8;
-    border: 1px solid #eee;
+    background: var(--darkened-bg);
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     border-radius: 4px;
-    text-align: right;
     overflow: hidden;
     overflow: hidden;
+    display: flex;
+    gap: 10px;
+    flex-wrap: wrap;
 }
 }
 
 
 body.popup .submit-row {
 body.popup .submit-row {
@@ -262,56 +257,54 @@ body.popup .submit-row {
 }
 }
 
 
 .submit-row input {
 .submit-row input {
-    height: 35px;
-    line-height: 15px;
-    margin: 0 0 0 5px;
+    height: 2.1875rem;
+    line-height: 0.9375rem;
 }
 }
 
 
-.submit-row input.default {
-    margin: 0 0 0 8px;
-    text-transform: uppercase;
+.submit-row input, .submit-row a {
+    margin: 0;
 }
 }
 
 
-.submit-row p {
-    margin: 0.3em;
+.submit-row input.default {
+    text-transform: uppercase;
 }
 }
 
 
-.submit-row p.deletelink-box {
-    float: left;
-    margin: 0;
+.submit-row a.deletelink {
+    margin-left: auto;
 }
 }
 
 
 .submit-row a.deletelink {
 .submit-row a.deletelink {
     display: block;
     display: block;
-    background: #ba2121;
+    background: var(--delete-button-bg);
     border-radius: 4px;
     border-radius: 4px;
-    padding: 10px 15px;
-    height: 15px;
-    line-height: 15px;
-    color: #fff;
+    padding: 0.625rem 0.9375rem;
+    height: 0.9375rem;
+    line-height: 0.9375rem;
+    color: var(--button-fg);
 }
 }
 
 
 .submit-row a.closelink {
 .submit-row a.closelink {
     display: inline-block;
     display: inline-block;
-    background: #bbbbbb;
+    background: var(--close-button-bg);
     border-radius: 4px;
     border-radius: 4px;
     padding: 10px 15px;
     padding: 10px 15px;
-    height: 15px;
-    line-height: 15px;
-    margin: 0 0 0 5px;
-    color: #fff;
+    height: 0.9375rem;
+    line-height: 0.9375rem;
+    color: var(--button-fg);
 }
 }
 
 
 .submit-row a.deletelink:focus,
 .submit-row a.deletelink:focus,
 .submit-row a.deletelink:hover,
 .submit-row a.deletelink:hover,
 .submit-row a.deletelink:active {
 .submit-row a.deletelink:active {
-    background: #a41515;
+    background: var(--delete-button-hover-bg);
+    text-decoration: none;
 }
 }
 
 
 .submit-row a.closelink:focus,
 .submit-row a.closelink:focus,
 .submit-row a.closelink:hover,
 .submit-row a.closelink:hover,
 .submit-row a.closelink:active {
 .submit-row a.closelink:active {
-    background: #aaaaaa;
+    background: var(--close-button-hover-bg);
+    text-decoration: none;
 }
 }
 
 
 /* CUSTOM FORM FIELDS */
 /* CUSTOM FORM FIELDS */
@@ -353,10 +346,6 @@ body.popup .submit-row {
     width: 2.2em;
     width: 2.2em;
 }
 }
 
 
-.vTextField, .vUUIDField {
-    width: 20em;
-}
-
 .vIntegerField {
 .vIntegerField {
     width: 5em;
     width: 5em;
 }
 }
@@ -369,6 +358,10 @@ body.popup .submit-row {
     width: 5em;
     width: 5em;
 }
 }
 
 
+.vTextField, .vUUIDField {
+    width: 20em;
+}
+
 /* INLINES */
 /* INLINES */
 
 
 .inline-group {
 .inline-group {
@@ -388,14 +381,16 @@ body.popup .submit-row {
     position: relative;
     position: relative;
 }
 }
 
 
-.inline-related h3 {
+.inline-related h4,
+.inline-related:not(.tabular) .collapse summary {
     margin: 0;
     margin: 0;
-    color: #666;
+    color: var(--body-medium-color);
     padding: 5px;
     padding: 5px;
-    font-size: 13px;
-    background: #f8f8f8;
-    border-top: 1px solid #eee;
-    border-bottom: 1px solid #eee;
+    font-size: 0.8125rem;
+    background: var(--darkened-bg);
+    border: 1px solid var(--hairline-color);
+    border-left-color: var(--darkened-bg);
+    border-right-color: var(--darkened-bg);
 }
 }
 
 
 .inline-related h3 span.delete {
 .inline-related h3 span.delete {
@@ -404,26 +399,16 @@ body.popup .submit-row {
 
 
 .inline-related h3 span.delete label {
 .inline-related h3 span.delete label {
     margin-left: 2px;
     margin-left: 2px;
-    font-size: 11px;
+    font-size: 0.6875rem;
 }
 }
 
 
 .inline-related fieldset {
 .inline-related fieldset {
     margin: 0;
     margin: 0;
-    background: #fff;
+    background: var(--body-bg);
     border: none;
     border: none;
     width: 100%;
     width: 100%;
 }
 }
 
 
-.inline-related fieldset.module h3 {
-    margin: 0;
-    padding: 2px 5px 3px 5px;
-    font-size: 11px;
-    text-align: left;
-    font-weight: bold;
-    background: #bcd;
-    color: #fff;
-}
-
 .inline-group .tabular fieldset.module {
 .inline-group .tabular fieldset.module {
     border: none;
     border: none;
 }
 }
@@ -458,9 +443,9 @@ body.popup .submit-row {
     height: 1.1em;
     height: 1.1em;
     padding: 2px 9px;
     padding: 2px 9px;
     overflow: hidden;
     overflow: hidden;
-    font-size: 9px;
+    font-size: 0.5625rem;
     font-weight: bold;
     font-weight: bold;
-    color: #666;
+    color: var(--body-quiet-color);
     _width: 700px;
     _width: 700px;
 }
 }
 
 
@@ -477,15 +462,15 @@ body.popup .submit-row {
 
 
 .inline-group div.add-row,
 .inline-group div.add-row,
 .inline-group .tabular tr.add-row td {
 .inline-group .tabular tr.add-row td {
-    color: #666;
-    background: #f8f8f8;
+    color: var(--body-quiet-color);
+    background: var(--darkened-bg);
     padding: 8px 10px;
     padding: 8px 10px;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 .inline-group .tabular tr.add-row td {
 .inline-group .tabular tr.add-row td {
     padding: 8px 10px;
     padding: 8px 10px;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
 }
 }
 
 
 .inline-group ul.tools a.add,
 .inline-group ul.tools a.add,
@@ -493,7 +478,7 @@ body.popup .submit-row {
 .inline-group .tabular tr.add-row td a {
 .inline-group .tabular tr.add-row td a {
     background: url(../img/icon-addlink.svg) 0 1px no-repeat;
     background: url(../img/icon-addlink.svg) 0 1px no-repeat;
     padding-left: 16px;
     padding-left: 16px;
-    font-size: 12px;
+    font-size: 0.75rem;
 }
 }
 
 
 .empty-form {
 .empty-form {
@@ -511,8 +496,8 @@ body.popup .submit-row {
 }
 }
 
 
 .related-lookup {
 .related-lookup {
-    width: 16px;
-    height: 16px;
+    width: 1rem;
+    height: 1rem;
     background-image: url(../img/search.svg);
     background-image: url(../img/search.svg);
 }
 }
 
 

+ 10 - 28
static/admin/css/login.css

@@ -1,7 +1,7 @@
 /* LOGIN FORM */
 /* LOGIN FORM */
 
 
 .login {
 .login {
-    background: #f8f8f8;
+    background: var(--darkened-bg);
     height: auto;
     height: auto;
 }
 }
 
 
@@ -12,20 +12,21 @@
 }
 }
 
 
 .login #header h1 {
 .login #header h1 {
-    font-size: 18px;
+    font-size: 1.125rem;
+    margin: 0;
 }
 }
 
 
 .login #header h1 a {
 .login #header h1 a {
-    color: #fff;
+    color: var(--header-link-color);
 }
 }
 
 
 .login #content {
 .login #content {
-    padding: 20px 20px 0;
+    padding: 20px;
 }
 }
 
 
 .login #container {
 .login #container {
-    background: #fff;
-    border: 1px solid #eaeaea;
+    background: var(--body-bg);
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     border-radius: 4px;
     overflow: hidden;
     overflow: hidden;
     width: 28em;
     width: 28em;
@@ -34,44 +35,25 @@
     height: auto;
     height: auto;
 }
 }
 
 
-.login #content-main {
-    width: 100%;
-}
-
 .login .form-row {
 .login .form-row {
     padding: 4px 0;
     padding: 4px 0;
-    float: left;
-    width: 100%;
-    border-bottom: none;
 }
 }
 
 
 .login .form-row label {
 .login .form-row label {
-    padding-right: 0.5em;
+    display: block;
     line-height: 2em;
     line-height: 2em;
-    font-size: 1em;
-    clear: both;
-    color: #333;
 }
 }
 
 
 .login .form-row #id_username, .login .form-row #id_password {
 .login .form-row #id_username, .login .form-row #id_password {
-    clear: both;
     padding: 8px;
     padding: 8px;
     width: 100%;
     width: 100%;
     box-sizing: border-box;
     box-sizing: border-box;
 }
 }
 
 
-.login span.help {
-    font-size: 10px;
-    display: block;
-}
-
 .login .submit-row {
 .login .submit-row {
-    clear: both;
-    padding: 1em 0 0 9.4em;
+    padding: 1em 0 0 0;
     margin: 0;
     margin: 0;
-    border: none;
-    background: none;
-    text-align: left;
+    text-align: center;
 }
 }
 
 
 .login .password-reset-link {
 .login .password-reset-link {

+ 45 - 14
static/admin/css/nav_sidebar.css

@@ -12,22 +12,23 @@
     justify-content: center;
     justify-content: center;
     flex: 0 0 23px;
     flex: 0 0 23px;
     width: 23px;
     width: 23px;
-    border-right: 1px solid #eaeaea;
-    background-color: #ffffff;
+    border: 0;
+    border-right: 1px solid var(--hairline-color);
+    background-color: var(--body-bg);
     cursor: pointer;
     cursor: pointer;
-    font-size: 20px;
-    color: #447e9b;
+    font-size: 1.25rem;
+    color: var(--link-fg);
     padding: 0;
     padding: 0;
 }
 }
 
 
 [dir="rtl"] .toggle-nav-sidebar {
 [dir="rtl"] .toggle-nav-sidebar {
-    border-left: 1px solid #eaeaea;
+    border-left: 1px solid var(--hairline-color);
     border-right: 0;
     border-right: 0;
 }
 }
 
 
 .toggle-nav-sidebar:hover,
 .toggle-nav-sidebar:hover,
 .toggle-nav-sidebar:focus {
 .toggle-nav-sidebar:focus {
-    background-color: #f6f6f6;
+    background-color: var(--darkened-bg);
 }
 }
 
 
 #nav-sidebar {
 #nav-sidebar {
@@ -36,13 +37,13 @@
     left: -276px;
     left: -276px;
     margin-left: -276px;
     margin-left: -276px;
     border-top: 1px solid transparent;
     border-top: 1px solid transparent;
-    border-right: 1px solid #eaeaea;
-    background-color: #ffffff;
+    border-right: 1px solid var(--hairline-color);
+    background-color: var(--body-bg);
     overflow: auto;
     overflow: auto;
 }
 }
 
 
 [dir="rtl"] #nav-sidebar {
 [dir="rtl"] #nav-sidebar {
-    border-left: 1px solid #eaeaea;
+    border-left: 1px solid var(--hairline-color);
     border-right: 0;
     border-right: 0;
     left: 0;
     left: 0;
     margin-left: 0;
     margin-left: 0;
@@ -58,14 +59,16 @@
     content: '\00AB';
     content: '\00AB';
 }
 }
 
 
+.main > #nav-sidebar {
+    visibility: hidden;
+}
+
 .main.shifted > #nav-sidebar {
 .main.shifted > #nav-sidebar {
-    left: 24px;
     margin-left: 0;
     margin-left: 0;
+    visibility: visible;
 }
 }
 
 
 [dir="rtl"] .main.shifted > #nav-sidebar {
 [dir="rtl"] .main.shifted > #nav-sidebar {
-    left: 0;
-    right: 24px;
     margin-right: 0;
     margin-right: 0;
 }
 }
 
 
@@ -91,12 +94,18 @@
 
 
 #nav-sidebar .current-app .section:link,
 #nav-sidebar .current-app .section:link,
 #nav-sidebar .current-app .section:visited {
 #nav-sidebar .current-app .section:visited {
-    color: #ffc;
+    color: var(--header-color);
     font-weight: bold;
     font-weight: bold;
 }
 }
 
 
 #nav-sidebar .current-model {
 #nav-sidebar .current-model {
-    background: #ffc;
+    background: var(--selected-row);
+}
+
+@media (forced-colors: active) {
+    #nav-sidebar .current-model {
+        background-color: SelectedItem;
+    }
 }
 }
 
 
 .main > #nav-sidebar + .content {
 .main > #nav-sidebar + .content {
@@ -117,3 +126,25 @@
         max-width: 100%;
         max-width: 100%;
     }
     }
 }
 }
+
+#nav-filter {
+    width: 100%;
+    box-sizing: border-box;
+    padding: 2px 5px;
+    margin: 5px 0;
+    border: 1px solid var(--border-color);
+    background-color: var(--darkened-bg);
+    color: var(--body-fg);
+}
+
+#nav-filter:focus {
+    border-color: var(--body-quiet-color);
+}
+
+#nav-filter.no-results {
+    background: var(--message-error-bg);
+}
+
+#nav-sidebar table {
+    width: 100%;
+}

+ 73 - 110
static/admin/css/responsive.css

@@ -14,11 +14,11 @@ input[type="submit"], button {
 
 
     td, th {
     td, th {
         padding: 10px;
         padding: 10px;
-        font-size: 14px;
+        font-size: 0.875rem;
     }
     }
 
 
     .small {
     .small {
-        font-size: 12px;
+        font-size: 0.75rem;
     }
     }
 
 
     /* Layout */
     /* Layout */
@@ -28,7 +28,7 @@ input[type="submit"], button {
     }
     }
 
 
     #content {
     #content {
-        padding: 20px 30px 30px;
+        padding: 15px 20px 20px;
     }
     }
 
 
     div.breadcrumbs {
     div.breadcrumbs {
@@ -43,9 +43,8 @@ input[type="submit"], button {
         justify-content: flex-start;
         justify-content: flex-start;
     }
     }
 
 
-    #branding h1 {
+    #site-name {
         margin: 0 0 8px;
         margin: 0 0 8px;
-        font-size: 20px;
         line-height: 1.2;
         line-height: 1.2;
     }
     }
 
 
@@ -88,7 +87,7 @@ input[type="submit"], button {
     }
     }
 
 
     td .changelink, td .addlink {
     td .changelink, td .addlink {
-        font-size: 13px;
+        font-size: 0.8125rem;
     }
     }
 
 
     /* Changelist */
     /* Changelist */
@@ -105,13 +104,13 @@ input[type="submit"], button {
     }
     }
 
 
     #changelist-search label {
     #changelist-search label {
-        line-height: 22px;
+        line-height: 1.375rem;
     }
     }
 
 
     #toolbar form #searchbar {
     #toolbar form #searchbar {
         flex: 1 0 auto;
         flex: 1 0 auto;
         width: 0;
         width: 0;
-        height: 22px;
+        height: 1.375rem;
         margin: 0 10px 0 6px;
         margin: 0 10px 0 6px;
     }
     }
 
 
@@ -131,16 +130,12 @@ input[type="submit"], button {
         padding: 15px 0;
         padding: 15px 0;
     }
     }
 
 
-    #changelist .actions.selected {
-        border: none;
-    }
-
     #changelist .actions label {
     #changelist .actions label {
         display: flex;
         display: flex;
     }
     }
 
 
     #changelist .actions select {
     #changelist .actions select {
-        background: #fff;
+        background: var(--body-bg);
     }
     }
 
 
     #changelist .actions .button {
     #changelist .actions .button {
@@ -152,7 +147,7 @@ input[type="submit"], button {
     #changelist .actions span.clear,
     #changelist .actions span.clear,
     #changelist .actions span.question,
     #changelist .actions span.question,
     #changelist .actions span.action-counter {
     #changelist .actions span.action-counter {
-        font-size: 11px;
+        font-size: 0.6875rem;
         margin: 0 10px 0 0;
         margin: 0 10px 0 0;
     }
     }
 
 
@@ -166,7 +161,7 @@ input[type="submit"], button {
     .filtered .actions,
     .filtered .actions,
 
 
     #changelist .paginator {
     #changelist .paginator {
-        border-top-color: #eee;
+        border-top-color: var(--hairline-color); /* XXX Is this used at all? */
     }
     }
 
 
     #changelist .results + .paginator {
     #changelist .results + .paginator {
@@ -176,9 +171,14 @@ input[type="submit"], button {
     /* Forms */
     /* Forms */
 
 
     label {
     label {
-        font-size: 14px;
+        font-size: 1rem;
     }
     }
 
 
+    /*
+    Minifiers remove the default (text) "type" attribute from "input" HTML
+    tags. Add input:not([type]) to make the CSS stylesheet work the same.
+    */
+    .form-row input:not([type]),
     .form-row input[type=text],
     .form-row input[type=text],
     .form-row input[type=password],
     .form-row input[type=password],
     .form-row input[type=email],
     .form-row input[type=email],
@@ -191,12 +191,12 @@ input[type="submit"], button {
         box-sizing: border-box;
         box-sizing: border-box;
         margin: 0;
         margin: 0;
         padding: 6px 8px;
         padding: 6px 8px;
-        min-height: 36px;
-        font-size: 14px;
+        min-height: 2.25rem;
+        font-size: 1rem;
     }
     }
 
 
     .form-row select {
     .form-row select {
-        height: 36px;
+        height: 2.25rem;
     }
     }
 
 
     .form-row select[multiple] {
     .form-row select[multiple] {
@@ -204,16 +204,10 @@ input[type="submit"], button {
         min-height: 0;
         min-height: 0;
     }
     }
 
 
-    fieldset .fieldBox {
-        float: none;
-        margin: 0 -10px;
-        padding: 0 10px;
-    }
-
     fieldset .fieldBox + .fieldBox {
     fieldset .fieldBox + .fieldBox {
         margin-top: 10px;
         margin-top: 10px;
         padding-top: 10px;
         padding-top: 10px;
-        border-top: 1px solid #eee;
+        border-top: 1px solid var(--hairline-color);
     }
     }
 
 
     textarea {
     textarea {
@@ -232,24 +226,20 @@ input[type="submit"], button {
         margin-left: 15px;
         margin-left: 15px;
     }
     }
 
 
-    form .aligned ul.radiolist {
+    form .aligned div.radiolist {
         margin-left: 2px;
         margin-left: 2px;
     }
     }
 
 
-    /* Related widget */
-
-    .related-widget-wrapper {
-        float: none;
+    .submit-row {
+        padding: 8px;
     }
     }
 
 
-    .related-widget-wrapper-link + .selector {
-        max-width: calc(100% - 30px);
-        margin-right: 15px;
+    .submit-row a.deletelink {
+        padding: 10px 7px;
     }
     }
 
 
-    select + .related-widget-wrapper-link,
-    .related-widget-wrapper-link + .related-widget-wrapper-link {
-        margin-left: 10px;
+    .button, input[type=submit], input[type=button], .submit-row input, a.button {
+        padding: 7px;
     }
     }
 
 
     /* Selector */
     /* Selector */
@@ -269,7 +259,7 @@ input[type="submit"], button {
     }
     }
 
 
     .selector .selector-filter input {
     .selector .selector-filter input {
-        width: auto;
+        width: 100%;
         min-height: 0;
         min-height: 0;
         flex: 1 1;
         flex: 1 1;
     }
     }
@@ -291,7 +281,6 @@ input[type="submit"], button {
         width: 26px;
         width: 26px;
         height: 52px;
         height: 52px;
         padding: 2px 0;
         padding: 2px 0;
-        margin: auto 15px;
         border-radius: 20px;
         border-radius: 20px;
         transform: translateY(-10px);
         transform: translateY(-10px);
     }
     }
@@ -335,7 +324,6 @@ input[type="submit"], button {
         width: 52px;
         width: 52px;
         height: 26px;
         height: 26px;
         padding: 0 2px;
         padding: 0 2px;
-        margin: 15px auto;
         transform: none;
         transform: none;
     }
     }
 
 
@@ -383,27 +371,23 @@ input[type="submit"], button {
         display: none;
         display: none;
     }
     }
 
 
-    form .form-row p.datetime {
-        width: 100%;
-    }
-
     .datetime input {
     .datetime input {
         width: 50%;
         width: 50%;
         max-width: 120px;
         max-width: 120px;
     }
     }
 
 
     .datetime span {
     .datetime span {
-        font-size: 13px;
+        font-size: 0.8125rem;
     }
     }
 
 
     .datetime .timezonewarning {
     .datetime .timezonewarning {
         display: block;
         display: block;
-        font-size: 11px;
-        color: #999;
+        font-size: 0.6875rem;
+        color: var(--body-quiet-color);
     }
     }
 
 
     .datetimeshortcuts {
     .datetimeshortcuts {
-        color: #ccc;
+        color: var(--border-color); /* XXX Redundant, .datetime span also sets #ccc */
     }
     }
 
 
     .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
     .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
@@ -435,7 +419,7 @@ input[type="submit"], button {
         padding: 15px 20px;
         padding: 15px 20px;
     }
     }
 
 
-    .login #branding h1 {
+    .login #site-name {
         margin: 0;
         margin: 0;
     }
     }
 
 
@@ -467,14 +451,10 @@ input[type="submit"], button {
 @media (max-width: 767px) {
 @media (max-width: 767px) {
     /* Layout */
     /* Layout */
 
 
-    #header, #content, #footer {
+    #header, #content {
         padding: 15px;
         padding: 15px;
     }
     }
 
 
-    #footer:empty {
-        padding: 0;
-    }
-
     div.breadcrumbs {
     div.breadcrumbs {
         padding: 10px 15px;
         padding: 10px 15px;
     }
     }
@@ -496,7 +476,7 @@ input[type="submit"], button {
 
 
     #content-related .module h2 {
     #content-related .module h2 {
         padding: 10px 15px;
         padding: 10px 15px;
-        font-size: 16px;
+        font-size: 1rem;
     }
     }
 
 
     /* Changelist */
     /* Changelist */
@@ -557,8 +537,6 @@ input[type="submit"], button {
 
 
     .aligned .form-row,
     .aligned .form-row,
     .aligned .form-row > div {
     .aligned .form-row > div {
-        display: flex;
-        flex-wrap: wrap;
         max-width: 100vw;
         max-width: 100vw;
     }
     }
 
 
@@ -566,6 +544,14 @@ input[type="submit"], button {
         width: calc(100vw - 30px);
         width: calc(100vw - 30px);
     }
     }
 
 
+    .flex-container {
+        flex-flow: column;
+    }
+
+    .flex-container.checkbox-row {
+        flex-flow: row;
+    }
+
     textarea {
     textarea {
         max-width: none;
         max-width: none;
     }
     }
@@ -579,12 +565,9 @@ input[type="submit"], button {
         padding-top: 15px;
         padding-top: 15px;
     }
     }
 
 
-    fieldset.collapsed .form-row {
-        display: none;
-    }
-
     .aligned label {
     .aligned label {
         width: 100%;
         width: 100%;
+        min-width: auto;
         padding: 0 0 10px;
         padding: 0 0 10px;
     }
     }
 
 
@@ -599,10 +582,6 @@ input[type="submit"], button {
         max-width: 100%;
         max-width: 100%;
     }
     }
 
 
-    .aligned .checkbox-row {
-        align-items: center;
-    }
-
     .aligned .checkbox-row input {
     .aligned .checkbox-row input {
         flex: 0 1 auto;
         flex: 0 1 auto;
         margin: 0;
         margin: 0;
@@ -621,8 +600,7 @@ input[type="submit"], button {
     }
     }
 
 
     .aligned p.file-upload {
     .aligned p.file-upload {
-        margin-left: 0;
-        font-size: 13px;
+        font-size: 0.8125rem;
     }
     }
 
 
     span.clearable-file-input {
     span.clearable-file-input {
@@ -630,7 +608,7 @@ input[type="submit"], button {
     }
     }
 
 
     span.clearable-file-input label {
     span.clearable-file-input label {
-        font-size: 13px;
+        font-size: 0.8125rem;
         padding-bottom: 0;
         padding-bottom: 0;
     }
     }
 
 
@@ -645,17 +623,19 @@ input[type="submit"], button {
         padding: 0;
         padding: 0;
     }
     }
 
 
-    form .aligned ul {
+    form .aligned ul,
+    form .aligned ul.errorlist {
         margin-left: 0;
         margin-left: 0;
         padding-left: 0;
         padding-left: 0;
     }
     }
 
 
-    form .aligned ul.radiolist {
+    form .aligned div.radiolist {
+        margin-top: 5px;
         margin-right: 15px;
         margin-right: 15px;
         margin-bottom: -3px;
         margin-bottom: -3px;
     }
     }
 
 
-    form .aligned ul.radiolist li + li {
+    form .aligned div.radiolist:not(.inline) div + div {
         margin-top: 5px;
         margin-top: 5px;
     }
     }
 
 
@@ -683,23 +663,14 @@ input[type="submit"], button {
         align-self: center;
         align-self: center;
     }
     }
 
 
-    select + .related-widget-wrapper-link,
-    .related-widget-wrapper-link + .related-widget-wrapper-link {
-        margin-left: 15px;
-    }
-
     /* Selector */
     /* Selector */
 
 
     .selector {
     .selector {
         flex-direction: column;
         flex-direction: column;
-    }
-
-    .selector > * {
-        float: none;
+        gap: 10px 0;
     }
     }
 
 
     .selector-available, .selector-chosen {
     .selector-available, .selector-chosen {
-        margin-bottom: 0;
         flex: 1 1 auto;
         flex: 1 1 auto;
     }
     }
 
 
@@ -709,11 +680,9 @@ input[type="submit"], button {
 
 
     .selector ul.selector-chooser {
     .selector ul.selector-chooser {
         display: block;
         display: block;
-        float: none;
         width: 52px;
         width: 52px;
         height: 26px;
         height: 26px;
         padding: 0 2px;
         padding: 0 2px;
-        margin: 15px auto 20px;
         transform: none;
         transform: none;
     }
     }
 
 
@@ -740,7 +709,7 @@ input[type="submit"], button {
     /* Inlines */
     /* Inlines */
 
 
     .inline-group[data-inline-type="stacked"] .inline-related {
     .inline-group[data-inline-type="stacked"] .inline-related {
-        border: 2px solid #eee;
+        border: 1px solid var(--hairline-color);
         border-radius: 4px;
         border-radius: 4px;
         margin-top: 15px;
         margin-top: 15px;
         overflow: auto;
         overflow: auto;
@@ -750,18 +719,19 @@ input[type="submit"], button {
         box-sizing: border-box;
         box-sizing: border-box;
     }
     }
 
 
-    .inline-group[data-inline-type="stacked"] .inline-related + .inline-related {
-        margin-top: 30px;
-    }
-
     .inline-group[data-inline-type="stacked"] .inline-related .module {
     .inline-group[data-inline-type="stacked"] .inline-related .module {
         padding: 0 10px;
         padding: 0 10px;
     }
     }
 
 
-    .inline-group[data-inline-type="stacked"] .inline-related .module .form-row:last-child {
+    .inline-group[data-inline-type="stacked"] .inline-related .module .form-row {
+        border-top: 1px solid var(--hairline-color);
         border-bottom: none;
         border-bottom: none;
     }
     }
 
 
+    .inline-group[data-inline-type="stacked"] .inline-related .module .form-row:first-child {
+        border-top: none;
+    }
+
     .inline-group[data-inline-type="stacked"] .inline-related h3 {
     .inline-group[data-inline-type="stacked"] .inline-related h3 {
         padding: 10px;
         padding: 10px;
         border-top-width: 0;
         border-top-width: 0;
@@ -791,7 +761,7 @@ input[type="submit"], button {
 
 
     .inline-group[data-inline-type="stacked"] div.add-row {
     .inline-group[data-inline-type="stacked"] div.add-row {
         margin-top: 15px;
         margin-top: 15px;
-        border: 1px solid #eee;
+        border: 1px solid var(--hairline-color);
         border-radius: 4px;
         border-radius: 4px;
     }
     }
 
 
@@ -810,28 +780,23 @@ input[type="submit"], button {
     /* Submit row */
     /* Submit row */
 
 
     .submit-row {
     .submit-row {
-        padding: 10px 10px 0;
+        padding: 10px;
         margin: 0 0 15px;
         margin: 0 0 15px;
-        display: flex;
         flex-direction: column;
         flex-direction: column;
+        gap: 8px;
     }
     }
 
 
-    .submit-row > * {
-        width: 100%;
-    }
-
-    .submit-row input, .submit-row input.default, .submit-row a, .submit-row a.closelink {
-        float: none;
-        margin: 0 0 10px;
+    .submit-row input, .submit-row input.default, .submit-row a {
         text-align: center;
         text-align: center;
     }
     }
 
 
     .submit-row a.closelink {
     .submit-row a.closelink {
         padding: 10px 0;
         padding: 10px 0;
+        text-align: center;
     }
     }
 
 
-    .submit-row p.deletelink-box {
-        order: 4;
+    .submit-row a.deletelink {
+        margin: 0;
     }
     }
 
 
     /* Messages */
     /* Messages */
@@ -885,9 +850,7 @@ input[type="submit"], button {
     }
     }
 
 
     .login .form-row label {
     .login .form-row label {
-        display: block;
         margin: 0 0 5px;
         margin: 0 0 5px;
-        padding: 0;
         line-height: 1.2;
         line-height: 1.2;
     }
     }
 
 
@@ -895,7 +858,7 @@ input[type="submit"], button {
         padding: 15px 0 0;
         padding: 15px 0 0;
     }
     }
 
 
-    .login br, .login .submit-row label {
+    .login br {
         display: none;
         display: none;
     }
     }
 
 
@@ -907,7 +870,7 @@ input[type="submit"], button {
     .errornote {
     .errornote {
         margin: 0 0 20px;
         margin: 0 0 20px;
         padding: 8px 12px;
         padding: 8px 12px;
-        font-size: 13px;
+        font-size: 0.8125rem;
     }
     }
 
 
     /* Calendar and clock */
     /* Calendar and clock */
@@ -954,8 +917,8 @@ input[type="submit"], button {
 
 
     .calendar-shortcuts {
     .calendar-shortcuts {
         padding: 10px 0;
         padding: 10px 0;
-        font-size: 12px;
-        line-height: 12px;
+        font-size: 0.75rem;
+        line-height: 0.75rem;
     }
     }
 
 
     .calendar-shortcuts a {
     .calendar-shortcuts a {
@@ -963,7 +926,7 @@ input[type="submit"], button {
     }
     }
 
 
     .timelist a {
     .timelist a {
-        background: #fff;
+        background: var(--body-bg);
         padding: 4px;
         padding: 4px;
     }
     }
 
 
@@ -987,7 +950,7 @@ input[type="submit"], button {
     /* History */
     /* History */
 
 
     table#change-history tbody th, table#change-history tbody td {
     table#change-history tbody th, table#change-history tbody td {
-        font-size: 13px;
+        font-size: 0.8125rem;
         word-break: break-word;
         word-break: break-word;
     }
     }
 
 
@@ -998,7 +961,7 @@ input[type="submit"], button {
     /* Docs */
     /* Docs */
 
 
     table.model tbody th, table.model tbody td {
     table.model tbody th, table.model tbody td {
-        font-size: 13px;
+        font-size: 0.8125rem;
         word-break: break-word;
         word-break: break-word;
     }
     }
 }
 }

+ 37 - 6
static/admin/css/responsive_rtl.css

@@ -35,11 +35,6 @@
         background-position: calc(100% - 8px) 9px;
         background-position: calc(100% - 8px) 9px;
     }
     }
 
 
-    [dir="rtl"] .related-widget-wrapper-link + .selector {
-        margin-right: 0;
-        margin-left: 15px;
-    }
-
     [dir="rtl"] .selector .selector-filter label {
     [dir="rtl"] .selector .selector-filter label {
         margin-right: 0;
         margin-right: 0;
         margin-left: 8px;
         margin-left: 8px;
@@ -58,6 +53,22 @@
         padding-left: 0;
         padding-left: 0;
         padding-right: 16px;
         padding-right: 16px;
     }
     }
+
+    [dir="rtl"] .selector-add {
+        background-position: 0 -80px;
+    }
+
+    [dir="rtl"] .selector-remove {
+        background-position: 0 -120px;
+    }
+
+    [dir="rtl"] .active.selector-add:focus, .active.selector-add:hover {
+        background-position: 0 -100px;
+    }
+
+    [dir="rtl"] .active.selector-remove:focus, .active.selector-remove:hover {
+        background-position: 0 -140px;
+    }
 }
 }
 
 
 /* MOBILE */
 /* MOBILE */
@@ -69,7 +80,8 @@
         margin-right: 15px;
         margin-right: 15px;
     }
     }
 
 
-    [dir="rtl"] .aligned ul {
+    [dir="rtl"] .aligned ul,
+    [dir="rtl"] form .aligned ul.errorlist {
         margin-right: 0;
         margin-right: 0;
     }
     }
 
 
@@ -77,4 +89,23 @@
         margin-left: 0;
         margin-left: 0;
         margin-right: 0;
         margin-right: 0;
     }
     }
+    [dir="rtl"] .aligned .vCheckboxLabel {
+        padding: 1px 5px 0 0;
+    }
+
+    [dir="rtl"] .selector-remove {
+        background-position: 0 0;
+    }
+
+    [dir="rtl"] .active.selector-remove:focus, .active.selector-remove:hover {
+        background-position: 0 -20px;
+    }
+
+    [dir="rtl"] .selector-add  {
+        background-position: 0 -40px;
+    }
+
+    [dir="rtl"] .active.selector-add:focus, .active.selector-add:hover {
+        background-position: 0 -60px;
+    }
 }
 }

+ 83 - 41
static/admin/css/rtl.css

@@ -1,25 +1,3 @@
-body {
-    direction: rtl;
-}
-
-/* LOGIN */
-
-.login .form-row {
-    float: right;
-}
-
-.login .form-row label {
-    float: right;
-    padding-left: 0.5em;
-    padding-right: 0;
-    text-align: left;
-}
-
-.login .submit-row {
-    clear: both;
-    padding: 1em 9.4em 0 0;
-}
-
 /* GLOBAL */
 /* GLOBAL */
 
 
 th {
 th {
@@ -35,7 +13,7 @@ th {
     margin-right: 1.5em;
     margin-right: 1.5em;
 }
 }
 
 
-.viewlink, .addlink, .changelink {
+.viewlink, .addlink, .changelink, .hidelink {
     padding-left: 0;
     padding-left: 0;
     padding-right: 16px;
     padding-right: 16px;
     background-position: 100% 1px;
     background-position: 100% 1px;
@@ -119,7 +97,7 @@ thead th.sorted .text {
     border-left: none;
     border-left: none;
     padding-left: 10px;
     padding-left: 10px;
     margin-left: 0;
     margin-left: 0;
-    border-right: 5px solid #eaeaea;
+    border-right: 5px solid var(--hairline-color);
     padding-right: 10px;
     padding-right: 10px;
     margin-right: -15px;
     margin-right: -15px;
 }
 }
@@ -129,23 +107,25 @@ thead th.sorted .text {
     border-left: none;
     border-left: none;
 }
 }
 
 
-/* FORMS */
-
-.aligned label {
-    padding: 0 0 3px 1em;
-    float: right;
+.paginator .end {
+    margin-left: 6px;
+    margin-right: 0;
 }
 }
 
 
-.submit-row {
-    text-align: left
+.paginator input {
+    margin-left: 0;
+    margin-right: auto;
 }
 }
 
 
-.submit-row p.deletelink-box {
-    float: right;
+/* FORMS */
+
+.aligned label {
+    padding: 0 0 3px 1em;
 }
 }
 
 
-.submit-row input.default {
+.submit-row a.deletelink {
     margin-left: 0;
     margin-left: 0;
+    margin-right: auto;
 }
 }
 
 
 .vDateField, .vTimeField {
 .vDateField, .vTimeField {
@@ -156,13 +136,11 @@ thead th.sorted .text {
     margin-left: 5px;
     margin-left: 5px;
 }
 }
 
 
-form .aligned p.help, form .aligned div.help {
-    clear: right;
-}
-
 form .aligned ul {
 form .aligned ul {
     margin-right: 163px;
     margin-right: 163px;
+    padding-right: 10px;
     margin-left: 0;
     margin-left: 0;
+    padding-left: 0;
 }
 }
 
 
 form ul.inline li {
 form ul.inline li {
@@ -171,12 +149,34 @@ form ul.inline li {
     padding-left: 7px;
     padding-left: 7px;
 }
 }
 
 
-input[type=submit].default, .submit-row input.default {
-    float: left;
+form .aligned p.help,
+form .aligned div.help {
+    margin-left: 0;
+    margin-right: 160px;
+    padding-right: 10px;
+}
+
+form div.help ul,
+form .aligned .checkbox-row + .help,
+form .aligned p.date div.help.timezonewarning,
+form .aligned p.datetime div.help.timezonewarning,
+form .aligned p.time div.help.timezonewarning {
+    margin-right: 0;
+    padding-right: 0;
+}
+
+form .wide p.help,
+form .wide ul.errorlist,
+form .wide div.help {
+    padding-left: 0;
+    padding-right: 50px;
+}
+
+.submit-row {
+    text-align: right;
 }
 }
 
 
 fieldset .fieldBox {
 fieldset .fieldBox {
-    float: right;
     margin-left: 20px;
     margin-left: 20px;
     margin-right: 0;
     margin-right: 0;
 }
 }
@@ -197,12 +197,14 @@ fieldset .fieldBox {
     top: 0;
     top: 0;
     left: auto;
     left: auto;
     right: 10px;
     right: 10px;
+    background: url(../img/calendar-icons.svg) 0 -15px no-repeat;
 }
 }
 
 
 .calendarnav-next {
 .calendarnav-next {
     top: 0;
     top: 0;
     right: auto;
     right: auto;
     left: 10px;
     left: 10px;
+    background: url(../img/calendar-icons.svg) 0 0 no-repeat;
 }
 }
 
 
 .calendar caption, .calendarbox h2 {
 .calendar caption, .calendarbox h2 {
@@ -217,6 +219,38 @@ fieldset .fieldBox {
     text-align: right;
     text-align: right;
 }
 }
 
 
+.selector-add {
+  background: url(../img/selector-icons.svg) 0 -64px no-repeat;
+}
+
+.active.selector-add:focus, .active.selector-add:hover {
+  background-position: 0 -80px;
+}
+
+.selector-remove {
+  background: url(../img/selector-icons.svg) 0 -96px no-repeat;
+}
+
+.active.selector-remove:focus, .active.selector-remove:hover {
+  background-position: 0 -112px;
+}
+
+a.selector-chooseall {
+    background: url(../img/selector-icons.svg) right -128px no-repeat;
+}
+
+a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
+    background-position: 100% -144px;
+}
+
+a.selector-clearall {
+    background: url(../img/selector-icons.svg) 0 -160px no-repeat;
+}
+
+a.active.selector-clearall:focus, a.active.selector-clearall:hover {
+    background-position: 0 -176px;
+}
+
 .inline-deletelink {
 .inline-deletelink {
     float: left;
     float: left;
 }
 }
@@ -247,3 +281,11 @@ form .form-row p.datetime {
     margin-left: inherit;
     margin-left: inherit;
     margin-right: 2px;
     margin-right: 2px;
 }
 }
+
+.inline-group .tabular td.original p {
+    right: 0;
+}
+
+.selector .selector-chooser {
+    margin: 0;
+}

+ 19 - 0
static/admin/css/unusable_password_field.css

@@ -0,0 +1,19 @@
+/* Hide warnings fields if usable password is selected */
+form:has(#id_usable_password input[value="true"]:checked) .messagelist {
+    display: none;
+}
+
+/* Hide password fields if unusable password is selected */
+form:has(#id_usable_password input[value="false"]:checked) .field-password1,
+form:has(#id_usable_password input[value="false"]:checked) .field-password2 {
+    display: none;
+}
+
+/* Select appropriate submit button */
+form:has(#id_usable_password input[value="true"]:checked) input[type="submit"].unset-password {
+    display: none;
+}
+
+form:has(#id_usable_password input[value="false"]:checked) input[type="submit"].set-password {
+    display: none;
+}

+ 118 - 99
static/admin/css/vendor/select2/LICENSE-SELECT2.md

@@ -1,50 +1,64 @@
 /* SELECTOR (FILTER INTERFACE) */
 /* SELECTOR (FILTER INTERFACE) */
 
 
 .selector {
 .selector {
-    width: 800px;
-    float: left;
+    display: flex;
+    flex-grow: 1;
+    gap: 0 10px;
 }
 }
 
 
 .selector select {
 .selector select {
-    width: 380px;
     height: 17.2em;
     height: 17.2em;
+    flex: 1 0 auto;
+    overflow: scroll;
+    width: 100%;
 }
 }
 
 
 .selector-available, .selector-chosen {
 .selector-available, .selector-chosen {
-    float: left;
-    width: 380px;
     text-align: center;
     text-align: center;
-    margin-bottom: 5px;
-}
-
-.selector-chosen select {
-    border-top: none;
+    display: flex;
+    flex-direction: column;
+    flex: 1 1;
 }
 }
 
 
 .selector-available h2, .selector-chosen h2 {
 .selector-available h2, .selector-chosen h2 {
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-radius: 4px 4px 0 0;
     border-radius: 4px 4px 0 0;
 }
 }
 
 
+.selector-chosen .list-footer-display {
+    border: 1px solid var(--border-color);
+    border-top: none;
+    border-radius: 0 0 4px 4px;
+    margin: 0 0 10px;
+    padding: 8px;
+    text-align: center;
+    background: var(--primary);
+    color: var(--header-link-color);
+    cursor: pointer;
+}
+.selector-chosen .list-footer-display__clear {
+    color: var(--breadcrumbs-fg);
+}
+
 .selector-chosen h2 {
 .selector-chosen h2 {
-    background: #79aec8;
-    color: #fff;
+    background: var(--secondary);
+    color: var(--header-link-color);
 }
 }
 
 
 .selector .selector-available h2 {
 .selector .selector-available h2 {
-    background: #f8f8f8;
-    color: #666;
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
 }
 }
 
 
 .selector .selector-filter {
 .selector .selector-filter {
-    background: white;
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-width: 0 1px;
     border-width: 0 1px;
     padding: 8px;
     padding: 8px;
-    color: #999;
-    font-size: 10px;
+    color: var(--body-quiet-color);
+    font-size: 0.625rem;
     margin: 0;
     margin: 0;
     text-align: left;
     text-align: left;
+    display: flex;
 }
 }
 
 
 .selector .selector-filter label,
 .selector .selector-filter label,
@@ -56,20 +70,26 @@
     padding: 0;
     padding: 0;
     overflow: hidden;
     overflow: hidden;
     line-height: 1;
     line-height: 1;
+    min-width: auto;
 }
 }
 
 
-.selector .selector-available input {
-    width: 320px;
+.selector-filter input {
+    flex-grow: 1;
+}
+
+.selector .selector-available input,
+.selector .selector-chosen input {
     margin-left: 8px;
     margin-left: 8px;
 }
 }
 
 
 .selector ul.selector-chooser {
 .selector ul.selector-chooser {
-    float: left;
+    align-self: center;
     width: 22px;
     width: 22px;
-    background-color: #eee;
+    background-color: var(--selected-bg);
     border-radius: 10px;
     border-radius: 10px;
-    margin: 10em 5px 0 5px;
+    margin: 0;
     padding: 0;
     padding: 0;
+    transform: translateY(-17px);
 }
 }
 
 
 .selector-chooser li {
 .selector-chooser li {
@@ -83,6 +103,15 @@
     margin: 0 0 10px;
     margin: 0 0 10px;
     border-radius: 0 0 4px 4px;
     border-radius: 0 0 4px 4px;
 }
 }
+.selector .selector-chosen--with-filtered select {
+    margin: 0;
+    border-radius: 0;
+    height: 14em;
+}
+
+.selector .selector-chosen:not(.selector-chosen--with-filtered) .list-footer-display {
+    display: none;
+}
 
 
 .selector-add, .selector-remove {
 .selector-add, .selector-remove {
     width: 16px;
     width: 16px;
@@ -91,7 +120,7 @@
     text-indent: -3000px;
     text-indent: -3000px;
     overflow: hidden;
     overflow: hidden;
     cursor: default;
     cursor: default;
-    opacity: 0.3;
+    opacity: 0.55;
 }
 }
 
 
 .active.selector-add, .active.selector-remove {
 .active.selector-add, .active.selector-remove {
@@ -122,18 +151,18 @@ a.selector-chooseall, a.selector-clearall {
     display: inline-block;
     display: inline-block;
     height: 16px;
     height: 16px;
     text-align: left;
     text-align: left;
-    margin: 1px auto 3px;
+    margin: 0 auto;
     overflow: hidden;
     overflow: hidden;
     font-weight: bold;
     font-weight: bold;
     line-height: 16px;
     line-height: 16px;
-    color: #666;
+    color: var(--body-quiet-color);
     text-decoration: none;
     text-decoration: none;
-    opacity: 0.3;
+    opacity: 0.55;
 }
 }
 
 
 a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
 a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
 a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
 a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
-    color: #447e9b;
+    color: var(--link-fg);
 }
 }
 
 
 a.active.selector-chooseall, a.active.selector-clearall {
 a.active.selector-chooseall, a.active.selector-clearall {
@@ -169,6 +198,7 @@ a.active.selector-clearall:focus, a.active.selector-clearall:hover {
 .stacked {
 .stacked {
     float: left;
     float: left;
     width: 490px;
     width: 490px;
+    display: block;
 }
 }
 
 
 .stacked select {
 .stacked select {
@@ -194,6 +224,7 @@ a.active.selector-clearall:focus, a.active.selector-clearall:hover {
     margin: 0 0 10px 40%;
     margin: 0 0 10px 40%;
     background-color: #eee;
     background-color: #eee;
     border-radius: 10px;
     border-radius: 10px;
+    transform: none;
 }
 }
 
 
 .stacked .selector-chooser li {
 .stacked .selector-chooser li {
@@ -251,8 +282,8 @@ a.active.selector-clearall:focus, a.active.selector-clearall:hover {
 .selector .search-label-icon {
 .selector .search-label-icon {
     background: url(../img/search.svg) 0 0 no-repeat;
     background: url(../img/search.svg) 0 0 no-repeat;
     display: inline-block;
     display: inline-block;
-    height: 18px;
-    width: 18px;
+    height: 1.125rem;
+    width: 1.125rem;
 }
 }
 
 
 /* DATE AND TIME */
 /* DATE AND TIME */
@@ -261,15 +292,15 @@ p.datetime {
     line-height: 20px;
     line-height: 20px;
     margin: 0;
     margin: 0;
     padding: 0;
     padding: 0;
-    color: #666;
+    color: var(--body-quiet-color);
     font-weight: bold;
     font-weight: bold;
 }
 }
 
 
 .datetime span {
 .datetime span {
     white-space: nowrap;
     white-space: nowrap;
     font-weight: normal;
     font-weight: normal;
-    font-size: 11px;
-    color: #ccc;
+    font-size: 0.6875rem;
+    color: var(--body-quiet-color);
 }
 }
 
 
 .datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
 .datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
@@ -278,7 +309,7 @@ p.datetime {
 }
 }
 
 
 table p.datetime {
 table p.datetime {
-    font-size: 11px;
+    font-size: 0.6875rem;
     margin-left: 0;
     margin-left: 0;
     padding-left: 0;
     padding-left: 0;
 }
 }
@@ -312,8 +343,8 @@ table p.datetime {
 }
 }
 
 
 .timezonewarning {
 .timezonewarning {
-    font-size: 11px;
-    color: #999;
+    font-size: 0.6875rem;
+    color: var(--body-quiet-color);
 }
 }
 
 
 /* URL */
 /* URL */
@@ -322,8 +353,8 @@ p.url {
     line-height: 20px;
     line-height: 20px;
     margin: 0;
     margin: 0;
     padding: 0;
     padding: 0;
-    color: #666;
-    font-size: 11px;
+    color: var(--body-quiet-color);
+    font-size: 0.6875rem;
     font-weight: bold;
     font-weight: bold;
 }
 }
 
 
@@ -337,15 +368,11 @@ p.file-upload {
     line-height: 20px;
     line-height: 20px;
     margin: 0;
     margin: 0;
     padding: 0;
     padding: 0;
-    color: #666;
-    font-size: 11px;
+    color: var(--body-quiet-color);
+    font-size: 0.6875rem;
     font-weight: bold;
     font-weight: bold;
 }
 }
 
 
-.aligned p.file-upload {
-    margin-left: 170px;
-}
-
 .file-upload a {
 .file-upload a {
     font-weight: normal;
     font-weight: normal;
 }
 }
@@ -355,8 +382,8 @@ p.file-upload {
 }
 }
 
 
 span.clearable-file-input label {
 span.clearable-file-input label {
-    color: #333;
-    font-size: 11px;
+    color: var(--body-fg);
+    font-size: 0.6875rem;
     display: inline;
     display: inline;
     float: none;
     float: none;
 }
 }
@@ -365,11 +392,12 @@ span.clearable-file-input label {
 
 
 .calendarbox, .clockbox {
 .calendarbox, .clockbox {
     margin: 5px auto;
     margin: 5px auto;
-    font-size: 12px;
+    font-size: 0.75rem;
     width: 19em;
     width: 19em;
     text-align: center;
     text-align: center;
-    background: white;
-    border: 1px solid #ddd;
+    background: var(--body-bg);
+    color: var(--body-fg);
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     border-radius: 4px;
     box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
     box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
     overflow: hidden;
     overflow: hidden;
@@ -397,38 +425,38 @@ span.clearable-file-input label {
     margin: 0;
     margin: 0;
     text-align: center;
     text-align: center;
     border-top: none;
     border-top: none;
-    background: #f5dd5d;
     font-weight: 700;
     font-weight: 700;
-    font-size: 12px;
+    font-size: 0.75rem;
     color: #333;
     color: #333;
+    background: var(--accent);
 }
 }
 
 
 .calendar th {
 .calendar th {
     padding: 8px 5px;
     padding: 8px 5px;
-    background: #f8f8f8;
-    border-bottom: 1px solid #ddd;
+    background: var(--darkened-bg);
+    border-bottom: 1px solid var(--border-color);
     font-weight: 400;
     font-weight: 400;
-    font-size: 12px;
+    font-size: 0.75rem;
     text-align: center;
     text-align: center;
-    color: #666;
+    color: var(--body-quiet-color);
 }
 }
 
 
 .calendar td {
 .calendar td {
     font-weight: 400;
     font-weight: 400;
-    font-size: 12px;
+    font-size: 0.75rem;
     text-align: center;
     text-align: center;
     padding: 0;
     padding: 0;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
     border-bottom: none;
     border-bottom: none;
 }
 }
 
 
 .calendar td.selected a {
 .calendar td.selected a {
-    background: #79aec8;
-    color: #fff;
+    background: var(--secondary);
+    color: var(--button-fg);
 }
 }
 
 
 .calendar td.nonday {
 .calendar td.nonday {
-    background: #f8f8f8;
+    background: var(--darkened-bg);
 }
 }
 
 
 .calendar td.today a {
 .calendar td.today a {
@@ -440,22 +468,22 @@ span.clearable-file-input label {
     font-weight: 400;
     font-weight: 400;
     padding: 6px;
     padding: 6px;
     text-decoration: none;
     text-decoration: none;
-    color: #444;
+    color: var(--body-quiet-color);
 }
 }
 
 
 .calendar td a:focus, .timelist a:focus,
 .calendar td a:focus, .timelist a:focus,
 .calendar td a:hover, .timelist a:hover {
 .calendar td a:hover, .timelist a:hover {
-    background: #79aec8;
+    background: var(--primary);
     color: white;
     color: white;
 }
 }
 
 
 .calendar td a:active, .timelist a:active {
 .calendar td a:active, .timelist a:active {
-    background: #417690;
+    background: var(--header-bg);
     color: white;
     color: white;
 }
 }
 
 
 .calendarnav {
 .calendarnav {
-    font-size: 10px;
+    font-size: 0.625rem;
     text-align: center;
     text-align: center;
     color: #ccc;
     color: #ccc;
     margin: 0;
     margin: 0;
@@ -464,16 +492,16 @@ span.clearable-file-input label {
 
 
 .calendarnav a:link, #calendarnav a:visited,
 .calendarnav a:link, #calendarnav a:visited,
 #calendarnav a:focus, #calendarnav a:hover {
 #calendarnav a:focus, #calendarnav a:hover {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 }
 
 
 .calendar-shortcuts {
 .calendar-shortcuts {
-    background: white;
-    font-size: 11px;
-    line-height: 11px;
-    border-top: 1px solid #eee;
+    background: var(--body-bg);
+    color: var(--body-quiet-color);
+    font-size: 0.6875rem;
+    line-height: 0.6875rem;
+    border-top: 1px solid var(--hairline-color);
     padding: 8px 0;
     padding: 8px 0;
-    color: #ccc;
 }
 }
 
 
 .calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
 .calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
@@ -491,36 +519,26 @@ span.clearable-file-input label {
     background: url(../img/calendar-icons.svg) 0 0 no-repeat;
     background: url(../img/calendar-icons.svg) 0 0 no-repeat;
 }
 }
 
 
-.calendarbox .calendarnav-previous:focus,
-.calendarbox .calendarnav-previous:hover {
-    background-position: 0 -15px;
-}
-
 .calendarnav-next {
 .calendarnav-next {
     right: 10px;
     right: 10px;
-    background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
-}
-
-.calendarbox .calendarnav-next:focus,
-.calendarbox .calendarnav-next:hover {
-    background-position: 0 -45px;
+    background: url(../img/calendar-icons.svg) 0 -15px no-repeat;
 }
 }
 
 
 .calendar-cancel {
 .calendar-cancel {
     margin: 0;
     margin: 0;
     padding: 4px 0;
     padding: 4px 0;
-    font-size: 12px;
-    background: #eee;
-    border-top: 1px solid #ddd;
-    color: #333;
+    font-size: 0.75rem;
+    background: var(--close-button-bg);
+    border-top: 1px solid var(--border-color);
+    color: var(--button-fg);
 }
 }
 
 
 .calendar-cancel:focus, .calendar-cancel:hover {
 .calendar-cancel:focus, .calendar-cancel:hover {
-    background: #ddd;
+    background: var(--close-button-hover-bg);
 }
 }
 
 
 .calendar-cancel a {
 .calendar-cancel a {
-    color: black;
+    color: var(--button-fg);
     display: block;
     display: block;
 }
 }
 
 
@@ -551,24 +569,25 @@ ul.timelist, .timelist li {
 
 
 /* RELATED WIDGET WRAPPER */
 /* RELATED WIDGET WRAPPER */
 .related-widget-wrapper {
 .related-widget-wrapper {
-    float: left;       /* display properly in form rows with multiple fields */
-    overflow: hidden;  /* clear floated contents */
+    display: flex;
+    gap: 0 10px;
+    flex-grow: 1;
+    flex-wrap: wrap;
+    margin-bottom: 5px;
 }
 }
 
 
 .related-widget-wrapper-link {
 .related-widget-wrapper-link {
-    opacity: 0.3;
+    opacity: .6;
+    filter: grayscale(1);
 }
 }
 
 
 .related-widget-wrapper-link:link {
 .related-widget-wrapper-link:link {
-    opacity: .8;
-}
-
-.related-widget-wrapper-link:link:focus,
-.related-widget-wrapper-link:link:hover {
     opacity: 1;
     opacity: 1;
+    filter: grayscale(0);
 }
 }
 
 
-select + .related-widget-wrapper-link,
-.related-widget-wrapper-link + .related-widget-wrapper-link {
-    margin-left: 7px;
+/* GIS MAPS */
+.dj_map {
+    width: 600px;
+    height: 400px;
 }
 }

+ 0 - 202
static/admin/fonts/LICENSE.txt

@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.

+ 0 - 3
static/admin/fonts/README.txt

@@ -1,3 +0,0 @@
-Roboto webfont source: https://www.google.com/fonts/specimen/Roboto
-WOFF files extracted using https://github.com/majodev/google-webfonts-helper
-Weights used in this project: Light (300), Regular (400), Bold (700)

BIN
static/admin/fonts/Roboto-Bold-webfont.woff


BIN
static/admin/fonts/Roboto-Light-webfont.woff


BIN
static/admin/fonts/Roboto-Regular-webfont.woff


+ 1 - 1
static/admin/img/LICENSE

@@ -1,4 +1,4 @@
-All icons are taken from Font Awesome (http://fontawesome.io/) project.
+All icons are taken from Font Awesome (https://fontawesome.com/) project.
 The Font Awesome font is licensed under the SIL OFL 1.1:
 The Font Awesome font is licensed under the SIL OFL 1.1:
 - https://scripts.sil.org/OFL
 - https://scripts.sil.org/OFL
 
 

+ 59 - 10
static/admin/img/calendar-icons.svg

@@ -1,14 +1,63 @@
-<svg width="15" height="60" viewBox="0 0 1792 7168" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-  <defs>
-    <g id="previous">
-      <path d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="15"
+   height="30"
+   viewBox="0 0 1792 3584"
+   version="1.1"
+   id="svg5"
+   sodipodi:docname="calendar-icons.svg"
+   inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview5"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     showgrid="false"
+     inkscape:zoom="13.3"
+     inkscape:cx="15.526316"
+     inkscape:cy="20.977444"
+     inkscape:window-width="1920"
+     inkscape:window-height="1011"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg5" />
+  <defs
+     id="defs2">
+    <g
+       id="previous">
+      <path
+         d="m 1037,1395 102,-102 q 19,-19 19,-45 0,-26 -19,-45 L 832,896 1139,589 q 19,-19 19,-45 0,-26 -19,-45 L 1037,397 q -19,-19 -45,-19 -26,0 -45,19 L 493,851 q -19,19 -19,45 0,26 19,45 l 454,454 q 19,19 45,19 26,0 45,-19 z m 627,-499 q 0,209 -103,385.5 Q 1458,1458 1281.5,1561 1105,1664 896,1664 687,1664 510.5,1561 334,1458 231,1281.5 128,1105 128,896 128,687 231,510.5 334,334 510.5,231 687,128 896,128 1105,128 1281.5,231 1458,334 1561,510.5 1664,687 1664,896 Z"
+         id="path1" />
     </g>
     </g>
-    <g id="next">
-      <path d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
+    <g
+       id="next">
+      <path
+         d="m 845,1395 454,-454 q 19,-19 19,-45 0,-26 -19,-45 L 845,397 q -19,-19 -45,-19 -26,0 -45,19 L 653,499 q -19,19 -19,45 0,26 19,45 l 307,307 -307,307 q -19,19 -19,45 0,26 19,45 l 102,102 q 19,19 45,19 26,0 45,-19 z m 819,-499 q 0,209 -103,385.5 Q 1458,1458 1281.5,1561 1105,1664 896,1664 687,1664 510.5,1561 334,1458 231,1281.5 128,1105 128,896 128,687 231,510.5 334,334 510.5,231 687,128 896,128 1105,128 1281.5,231 1458,334 1561,510.5 1664,687 1664,896 Z"
+         id="path2" />
     </g>
     </g>
   </defs>
   </defs>
-  <use xlink:href="#previous" x="0" y="0" fill="#333333" />
-  <use xlink:href="#previous" x="0" y="1792" fill="#000000" />
-  <use xlink:href="#next" x="0" y="3584" fill="#333333" />
-  <use xlink:href="#next" x="0" y="5376" fill="#000000" />
+  <use
+     xlink:href="#next"
+     x="0"
+     y="5376"
+     fill="#000000"
+     id="use5"
+     transform="translate(0,-3584)" />
+  <use
+     xlink:href="#previous"
+     x="0"
+     y="0"
+     fill="#333333"
+     id="use2"
+     style="fill:#000000;fill-opacity:1" />
 </svg>
 </svg>

+ 1 - 1
static/admin/img/gis/move_vertex_off.svg

@@ -1,3 +1,3 @@
 <svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
 <svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
-  <path fill="#70bf2b" d="M1600 796v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"/>
+  <path fill="#5fa225" d="M1600 796v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"/>
 </svg>
 </svg>

+ 1 - 1
static/admin/img/icon-alert.svg

@@ -1,3 +1,3 @@
 <svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
 <svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
-  <path fill="#efb80b" d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/>
+  <path fill="#b48c08" d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/>
 </svg>
 </svg>

+ 3 - 0
static/admin/img/icon-clock.svg

@@ -0,0 +1,3 @@
+<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
+  <path fill="#2b70bf" d="m555 1335 78-141q-87-63-136-159t-49-203q0-121 61-225-229 117-381 353 167 258 427 375zm389-759q0-20-14-34t-34-14q-125 0-214.5 89.5T592 832q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm363-191q0 7-1 9-105 188-315 566t-316 567l-49 89q-10 16-28 16-12 0-134-70-16-10-16-28 0-12 44-87-143-65-263.5-173T20 1029Q0 998 0 960t20-69q153-235 380-371t496-136q89 0 180 17l54-97q10-16 28-16 5 0 18 6t31 15.5 33 18.5 31.5 18.5T1291 358q16 10 16 27zm37 447q0 139-79 253.5T1056 1250l280-502q8 45 8 84zm448 128q0 35-20 69-39 64-109 145-150 172-347.5 267T896 1536l74-132q212-18 392.5-137T1664 960q-115-179-282-294l63-112q95 64 182.5 153T1772 891q20 34 20 69z"/>
+</svg>

+ 7 - 1
static/admin/img/icon-no.svg

@@ -13,6 +13,7 @@
         redisplay: function(id) {
         redisplay: function(id) {
             // Repopulate HTML select box from cache
             // Repopulate HTML select box from cache
             const box = document.getElementById(id);
             const box = document.getElementById(id);
+            const scroll_value_from_top = box.scrollTop;
             box.innerHTML = '';
             box.innerHTML = '';
             for (const node of SelectBox.cache[id]) {
             for (const node of SelectBox.cache[id]) {
                 if (node.displayed) {
                 if (node.displayed) {
@@ -22,6 +23,7 @@
                     box.appendChild(new_option);
                     box.appendChild(new_option);
                 }
                 }
             }
             }
+            box.scrollTop = scroll_value_from_top;
         },
         },
         filter: function(id, text) {
         filter: function(id, text) {
             // Redisplay the HTML select box, displaying only the choices containing ALL
             // Redisplay the HTML select box, displaying only the choices containing ALL
@@ -31,7 +33,7 @@
                 node.displayed = 1;
                 node.displayed = 1;
                 const node_text = node.text.toLowerCase();
                 const node_text = node.text.toLowerCase();
                 for (const token of tokens) {
                 for (const token of tokens) {
-                    if (node_text.indexOf(token) === -1) {
+                    if (!node_text.includes(token)) {
                         node.displayed = 0;
                         node.displayed = 0;
                         break; // Once the first token isn't found we're done
                         break; // Once the first token isn't found we're done
                     }
                     }
@@ -39,6 +41,10 @@
             }
             }
             SelectBox.redisplay(id);
             SelectBox.redisplay(id);
         },
         },
+        get_hidden_node_count(id) {
+            const cache = SelectBox.cache[id] || [];
+            return cache.filter(node => node.displayed === 0).length;
+        },
         delete_from_cache: function(id, value) {
         delete_from_cache: function(id, value) {
             let delete_index = null;
             let delete_index = null;
             const cache = SelectBox.cache[id];
             const cache = SelectBox.cache[id];

+ 91 - 41
static/admin/js/SelectFilter2.js

@@ -1,4 +1,4 @@
-/*global SelectBox, gettext, interpolate, quickElement, SelectFilter*/
+/*global SelectBox, gettext, ngettext, interpolate, quickElement, SelectFilter*/
 /*
 /*
 SelectFilter2 - Turns a multiple-select box into a filter interface.
 SelectFilter2 - Turns a multiple-select box into a filter interface.
 
 
@@ -30,6 +30,9 @@ Requires core.js and SelectBox.js.
 
 
             // <div class="selector"> or <div class="selector stacked">
             // <div class="selector"> or <div class="selector stacked">
             const selector_div = quickElement('div', from_box.parentNode);
             const selector_div = quickElement('div', from_box.parentNode);
+            // Make sure the selector div is at the beginning so that the
+            // add link would be displayed to the right of the widget.
+            from_box.parentNode.prepend(selector_div);
             selector_div.className = is_stacked ? 'selector stacked' : 'selector';
             selector_div.className = is_stacked ? 'selector stacked' : 'selector';
 
 
             // <div class="selector-available">
             // <div class="selector-available">
@@ -78,7 +81,7 @@ Requires core.js and SelectBox.js.
             remove_link.className = 'selector-remove';
             remove_link.className = 'selector-remove';
 
 
             // <div class="selector-chosen">
             // <div class="selector-chosen">
-            const selector_chosen = quickElement('div', selector_div);
+            const selector_chosen = quickElement('div', selector_div, '', 'id', field_id + '_selector_chosen');
             selector_chosen.className = 'selector-chosen';
             selector_chosen.className = 'selector-chosen';
             const title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
             const title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
             quickElement(
             quickElement(
@@ -93,9 +96,30 @@ Requires core.js and SelectBox.js.
                     [field_name]
                     [field_name]
                 )
                 )
             );
             );
+            
+            const filter_selected_p = quickElement('p', selector_chosen, '', 'id', field_id + '_filter_selected');
+            filter_selected_p.className = 'selector-filter';
+
+            const search_filter_selected_label = quickElement('label', filter_selected_p, '', 'for', field_id + '_selected_input');
+
+            quickElement(
+                'span', search_filter_selected_label, '',
+                'class', 'help-tooltip search-label-icon',
+                'title', interpolate(gettext("Type into this box to filter down the list of selected %s."), [field_name])
+            );
+
+            filter_selected_p.appendChild(document.createTextNode(' '));
+
+            const filter_selected_input = quickElement('input', filter_selected_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
+            filter_selected_input.id = field_id + '_selected_input';
 
 
             const to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', '', 'size', from_box.size, 'name', from_box.name);
             const to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', '', 'size', from_box.size, 'name', from_box.name);
             to_box.className = 'filtered';
             to_box.className = 'filtered';
+            
+            const warning_footer = quickElement('div', selector_chosen, '', 'class', 'list-footer-display');
+            quickElement('span', warning_footer, '', 'id', field_id + '_list-footer-display-text');
+            quickElement('span', warning_footer, ' (click to clear)', 'class', 'list-footer-display__clear');
+            
             const clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_remove_all_link');
             const clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', '#', 'id', field_id + '_remove_all_link');
             clear_all.className = 'selector-clearall';
             clear_all.className = 'selector-clearall';
 
 
@@ -106,6 +130,8 @@ Requires core.js and SelectBox.js.
                 if (elem.classList.contains('active')) {
                 if (elem.classList.contains('active')) {
                     move_func(from, to);
                     move_func(from, to);
                     SelectFilter.refresh_icons(field_id);
                     SelectFilter.refresh_icons(field_id);
+                    SelectFilter.refresh_filtered_selects(field_id);
+                    SelectFilter.refresh_filtered_warning(field_id);
                 }
                 }
                 e.preventDefault();
                 e.preventDefault();
             };
             };
@@ -121,14 +147,29 @@ Requires core.js and SelectBox.js.
             clear_all.addEventListener('click', function(e) {
             clear_all.addEventListener('click', function(e) {
                 move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from');
                 move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from');
             });
             });
+            warning_footer.addEventListener('click', function(e) {
+                filter_selected_input.value = '';
+                SelectBox.filter(field_id + '_to', '');
+                SelectFilter.refresh_filtered_warning(field_id);
+                SelectFilter.refresh_icons(field_id);
+            });
             filter_input.addEventListener('keypress', function(e) {
             filter_input.addEventListener('keypress', function(e) {
-                SelectFilter.filter_key_press(e, field_id);
+                SelectFilter.filter_key_press(e, field_id, '_from', '_to');
             });
             });
             filter_input.addEventListener('keyup', function(e) {
             filter_input.addEventListener('keyup', function(e) {
-                SelectFilter.filter_key_up(e, field_id);
+                SelectFilter.filter_key_up(e, field_id, '_from');
             });
             });
             filter_input.addEventListener('keydown', function(e) {
             filter_input.addEventListener('keydown', function(e) {
-                SelectFilter.filter_key_down(e, field_id);
+                SelectFilter.filter_key_down(e, field_id, '_from', '_to');
+            });
+            filter_selected_input.addEventListener('keypress', function(e) {
+                SelectFilter.filter_key_press(e, field_id, '_to', '_from');
+            });
+            filter_selected_input.addEventListener('keyup', function(e) {
+                SelectFilter.filter_key_up(e, field_id, '_to', '_selected_input');
+            });
+            filter_selected_input.addEventListener('keydown', function(e) {
+                SelectFilter.filter_key_down(e, field_id, '_to', '_from');
             });
             });
             selector_div.addEventListener('change', function(e) {
             selector_div.addEventListener('change', function(e) {
                 if (e.target.tagName === 'SELECT') {
                 if (e.target.tagName === 'SELECT') {
@@ -146,6 +187,7 @@ Requires core.js and SelectBox.js.
                 }
                 }
             });
             });
             from_box.closest('form').addEventListener('submit', function() {
             from_box.closest('form').addEventListener('submit', function() {
+                SelectBox.filter(field_id + '_to', '');
                 SelectBox.select_all(field_id + '_to');
                 SelectBox.select_all(field_id + '_to');
             });
             });
             SelectBox.init(field_id + '_from');
             SelectBox.init(field_id + '_from');
@@ -153,24 +195,6 @@ Requires core.js and SelectBox.js.
             // Move selected from_box options to to_box
             // Move selected from_box options to to_box
             SelectBox.move(field_id + '_from', field_id + '_to');
             SelectBox.move(field_id + '_from', field_id + '_to');
 
 
-            if (!is_stacked) {
-                // In horizontal mode, give the same height to the two boxes.
-                const j_from_box = document.getElementById(field_id + '_from');
-                const j_to_box = document.getElementById(field_id + '_to');
-                let height = filter_p.offsetHeight + j_from_box.offsetHeight;
-
-                const j_to_box_style = window.getComputedStyle(j_to_box);
-                if (j_to_box_style.getPropertyValue('box-sizing') === 'border-box') {
-                    // Add the padding and border to the final height.
-                    height += parseInt(j_to_box_style.getPropertyValue('padding-top'), 10)
-                        + parseInt(j_to_box_style.getPropertyValue('padding-bottom'), 10)
-                        + parseInt(j_to_box_style.getPropertyValue('border-top-width'), 10)
-                        + parseInt(j_to_box_style.getPropertyValue('border-bottom-width'), 10);
-                }
-
-                j_to_box.style.height = height + 'px';
-            }
-
             // Initial icon refresh
             // Initial icon refresh
             SelectFilter.refresh_icons(field_id);
             SelectFilter.refresh_icons(field_id);
         },
         },
@@ -181,6 +205,24 @@ Requires core.js and SelectBox.js.
             field.required = false;
             field.required = false;
             return any_selected;
             return any_selected;
         },
         },
+        refresh_filtered_warning: function(field_id) {
+            const count = SelectBox.get_hidden_node_count(field_id + '_to');
+            const selector = document.getElementById(field_id + '_selector_chosen');
+            const warning = document.getElementById(field_id + '_list-footer-display-text');
+            selector.className = selector.className.replace('selector-chosen--with-filtered', '');
+            warning.textContent = interpolate(ngettext(
+                '%s selected option not visible',
+                '%s selected options not visible',
+                count
+            ), [count]);
+            if(count > 0) {
+                selector.className += ' selector-chosen--with-filtered';
+            }
+        },
+        refresh_filtered_selects: function(field_id) {
+            SelectBox.filter(field_id + '_from', document.getElementById(field_id + "_input").value);
+            SelectBox.filter(field_id + '_to', document.getElementById(field_id + "_selected_input").value);
+        },
         refresh_icons: function(field_id) {
         refresh_icons: function(field_id) {
             const from = document.getElementById(field_id + '_from');
             const from = document.getElementById(field_id + '_from');
             const to = document.getElementById(field_id + '_to');
             const to = document.getElementById(field_id + '_to');
@@ -190,39 +232,47 @@ Requires core.js and SelectBox.js.
             // Active if the corresponding box isn't empty
             // Active if the corresponding box isn't empty
             document.getElementById(field_id + '_add_all_link').classList.toggle('active', from.querySelector('option'));
             document.getElementById(field_id + '_add_all_link').classList.toggle('active', from.querySelector('option'));
             document.getElementById(field_id + '_remove_all_link').classList.toggle('active', to.querySelector('option'));
             document.getElementById(field_id + '_remove_all_link').classList.toggle('active', to.querySelector('option'));
+            SelectFilter.refresh_filtered_warning(field_id);
         },
         },
-        filter_key_press: function(event, field_id) {
-            const from = document.getElementById(field_id + '_from');
+        filter_key_press: function(event, field_id, source, target) {
+            const source_box = document.getElementById(field_id + source);
             // don't submit form if user pressed Enter
             // don't submit form if user pressed Enter
             if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
             if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
-                from.selectedIndex = 0;
-                SelectBox.move(field_id + '_from', field_id + '_to');
-                from.selectedIndex = 0;
+                source_box.selectedIndex = 0;
+                SelectBox.move(field_id + source, field_id + target);
+                source_box.selectedIndex = 0;
                 event.preventDefault();
                 event.preventDefault();
             }
             }
         },
         },
-        filter_key_up: function(event, field_id) {
-            const from = document.getElementById(field_id + '_from');
-            const temp = from.selectedIndex;
-            SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
-            from.selectedIndex = temp;
+        filter_key_up: function(event, field_id, source, filter_input) {
+            const input = filter_input || '_input';
+            const source_box = document.getElementById(field_id + source);
+            const temp = source_box.selectedIndex;
+            SelectBox.filter(field_id + source, document.getElementById(field_id + input).value);
+            source_box.selectedIndex = temp;
+            SelectFilter.refresh_filtered_warning(field_id);
+            SelectFilter.refresh_icons(field_id);
         },
         },
-        filter_key_down: function(event, field_id) {
-            const from = document.getElementById(field_id + '_from');
+        filter_key_down: function(event, field_id, source, target) {
+            const source_box = document.getElementById(field_id + source);
+            // right key (39) or left key (37)
+            const direction = source === '_from' ? 39 : 37;
             // right arrow -- move across
             // right arrow -- move across
-            if ((event.which && event.which === 39) || (event.keyCode && event.keyCode === 39)) {
-                const old_index = from.selectedIndex;
-                SelectBox.move(field_id + '_from', field_id + '_to');
-                from.selectedIndex = (old_index === from.length) ? from.length - 1 : old_index;
+            if ((event.which && event.which === direction) || (event.keyCode && event.keyCode === direction)) {
+                const old_index = source_box.selectedIndex;
+                SelectBox.move(field_id + source, field_id + target);
+                SelectFilter.refresh_filtered_selects(field_id);
+                SelectFilter.refresh_filtered_warning(field_id);
+                source_box.selectedIndex = (old_index === source_box.length) ? source_box.length - 1 : old_index;
                 return;
                 return;
             }
             }
             // down arrow -- wrap around
             // down arrow -- wrap around
             if ((event.which && event.which === 40) || (event.keyCode && event.keyCode === 40)) {
             if ((event.which && event.which === 40) || (event.keyCode && event.keyCode === 40)) {
-                from.selectedIndex = (from.length === from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
+                source_box.selectedIndex = (source_box.length === source_box.selectedIndex + 1) ? 0 : source_box.selectedIndex + 1;
             }
             }
             // up arrow -- wrap around
             // up arrow -- wrap around
             if ((event.which && event.which === 38) || (event.keyCode && event.keyCode === 38)) {
             if ((event.which && event.which === 38) || (event.keyCode && event.keyCode === 38)) {
-                from.selectedIndex = (from.selectedIndex === 0) ? from.length - 1 : from.selectedIndex - 1;
+                source_box.selectedIndex = (source_box.selectedIndex === 0) ? source_box.length - 1 : source_box.selectedIndex - 1;
             }
             }
         }
         }
     };
     };

+ 184 - 134
static/admin/js/actions.js

@@ -1,154 +1,204 @@
-/*global gettext, interpolate, ngettext*/
+/*global gettext, interpolate, ngettext, Actions*/
 'use strict';
 'use strict';
 {
 {
-    const $ = django.jQuery;
-    let lastChecked;
+    function show(selector) {
+        document.querySelectorAll(selector).forEach(function(el) {
+            el.classList.remove('hidden');
+        });
+    }
 
 
-    $.fn.actions = function(opts) {
-        const options = $.extend({}, $.fn.actions.defaults, opts);
-        const actionCheckboxes = $(this);
-        let list_editable_changed = false;
-        const showQuestion = function() {
-                $(options.acrossClears).hide();
-                $(options.acrossQuestions).show();
-                $(options.allContainer).hide();
-            },
-            showClear = function() {
-                $(options.acrossClears).show();
-                $(options.acrossQuestions).hide();
-                $(options.actionContainer).toggleClass(options.selectedClass);
-                $(options.allContainer).show();
-                $(options.counterContainer).hide();
-            },
-            reset = function() {
-                $(options.acrossClears).hide();
-                $(options.acrossQuestions).hide();
-                $(options.allContainer).hide();
-                $(options.counterContainer).show();
-            },
-            clearAcross = function() {
-                reset();
-                $(options.acrossInput).val(0);
-                $(options.actionContainer).removeClass(options.selectedClass);
-            },
-            checker = function(checked) {
-                if (checked) {
-                    showQuestion();
-                } else {
-                    reset();
-                }
-                $(actionCheckboxes).prop("checked", checked)
-                    .parent().parent().toggleClass(options.selectedClass, checked);
-            },
-            updateCounter = function() {
-                const sel = $(actionCheckboxes).filter(":checked").length;
-                // data-actions-icnt is defined in the generated HTML
-                // and contains the total amount of objects in the queryset
-                const actions_icnt = $('.action-counter').data('actionsIcnt');
-                $(options.counterContainer).html(interpolate(
-                    ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
-                        sel: sel,
-                        cnt: actions_icnt
-                    }, true));
-                $(options.allToggle).prop("checked", function() {
-                    let value;
-                    if (sel === actionCheckboxes.length) {
-                        value = true;
-                        showQuestion();
-                    } else {
-                        value = false;
-                        clearAcross();
-                    }
-                    return value;
-                });
-            };
-        // Show counter by default
-        $(options.counterContainer).show();
-        // Check state of checkboxes and reinit state if needed
-        $(this).filter(":checked").each(function(i) {
-            $(this).parent().parent().toggleClass(options.selectedClass);
-            updateCounter();
-            if ($(options.acrossInput).val() === 1) {
-                showClear();
-            }
+    function hide(selector) {
+        document.querySelectorAll(selector).forEach(function(el) {
+            el.classList.add('hidden');
+        });
+    }
+
+    function showQuestion(options) {
+        hide(options.acrossClears);
+        show(options.acrossQuestions);
+        hide(options.allContainer);
+    }
+
+    function showClear(options) {
+        show(options.acrossClears);
+        hide(options.acrossQuestions);
+        document.querySelector(options.actionContainer).classList.remove(options.selectedClass);
+        show(options.allContainer);
+        hide(options.counterContainer);
+    }
+
+    function reset(options) {
+        hide(options.acrossClears);
+        hide(options.acrossQuestions);
+        hide(options.allContainer);
+        show(options.counterContainer);
+    }
+
+    function clearAcross(options) {
+        reset(options);
+        const acrossInputs = document.querySelectorAll(options.acrossInput);
+        acrossInputs.forEach(function(acrossInput) {
+            acrossInput.value = 0;
         });
         });
-        $(options.allToggle).show().on('click', function() {
-            checker($(this).prop("checked"));
-            updateCounter();
+        document.querySelector(options.actionContainer).classList.remove(options.selectedClass);
+    }
+
+    function checker(actionCheckboxes, options, checked) {
+        if (checked) {
+            showQuestion(options);
+        } else {
+            reset(options);
+        }
+        actionCheckboxes.forEach(function(el) {
+            el.checked = checked;
+            el.closest('tr').classList.toggle(options.selectedClass, checked);
         });
         });
-        $("a", options.acrossQuestions).on('click', function(event) {
-            event.preventDefault();
-            $(options.acrossInput).val(1);
-            showClear();
+    }
+
+    function updateCounter(actionCheckboxes, options) {
+        const sel = Array.from(actionCheckboxes).filter(function(el) {
+            return el.checked;
+        }).length;
+        const counter = document.querySelector(options.counterContainer);
+        // data-actions-icnt is defined in the generated HTML
+        // and contains the total amount of objects in the queryset
+        const actions_icnt = Number(counter.dataset.actionsIcnt);
+        counter.textContent = interpolate(
+            ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
+                sel: sel,
+                cnt: actions_icnt
+            }, true);
+        const allToggle = document.getElementById(options.allToggleId);
+        allToggle.checked = sel === actionCheckboxes.length;
+        if (allToggle.checked) {
+            showQuestion(options);
+        } else {
+            clearAcross(options);
+        }
+    }
+
+    const defaults = {
+        actionContainer: "div.actions",
+        counterContainer: "span.action-counter",
+        allContainer: "div.actions span.all",
+        acrossInput: "div.actions input.select-across",
+        acrossQuestions: "div.actions span.question",
+        acrossClears: "div.actions span.clear",
+        allToggleId: "action-toggle",
+        selectedClass: "selected"
+    };
+
+    window.Actions = function(actionCheckboxes, options) {
+        options = Object.assign({}, defaults, options);
+        let list_editable_changed = false;
+        let lastChecked = null;
+        let shiftPressed = false;
+
+        document.addEventListener('keydown', (event) => {
+            shiftPressed = event.shiftKey;
         });
         });
-        $("a", options.acrossClears).on('click', function(event) {
-            event.preventDefault();
-            $(options.allToggle).prop("checked", false);
-            clearAcross();
-            checker(0);
-            updateCounter();
+
+        document.addEventListener('keyup', (event) => {
+            shiftPressed = event.shiftKey;
         });
         });
-        lastChecked = null;
-        $(actionCheckboxes).on('click', function(event) {
-            if (!event) { event = window.event; }
-            const target = event.target ? event.target : event.srcElement;
-            if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) {
-                let inrange = false;
-                $(lastChecked).prop("checked", target.checked)
-                    .parent().parent().toggleClass(options.selectedClass, target.checked);
-                $(actionCheckboxes).each(function() {
-                    if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) {
-                        inrange = (inrange) ? false : true;
-                    }
-                    if (inrange) {
-                        $(this).prop("checked", target.checked)
-                            .parent().parent().toggleClass(options.selectedClass, target.checked);
-                    }
+
+        document.getElementById(options.allToggleId).addEventListener('click', function(event) {
+            checker(actionCheckboxes, options, this.checked);
+            updateCounter(actionCheckboxes, options);
+        });
+
+        document.querySelectorAll(options.acrossQuestions + " a").forEach(function(el) {
+            el.addEventListener('click', function(event) {
+                event.preventDefault();
+                const acrossInputs = document.querySelectorAll(options.acrossInput);
+                acrossInputs.forEach(function(acrossInput) {
+                    acrossInput.value = 1;
                 });
                 });
-            }
-            $(target).parent().parent().toggleClass(options.selectedClass, target.checked);
-            lastChecked = target;
-            updateCounter();
+                showClear(options);
+            });
         });
         });
-        $('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() {
-            list_editable_changed = true;
+
+        document.querySelectorAll(options.acrossClears + " a").forEach(function(el) {
+            el.addEventListener('click', function(event) {
+                event.preventDefault();
+                document.getElementById(options.allToggleId).checked = false;
+                clearAcross(options);
+                checker(actionCheckboxes, options, false);
+                updateCounter(actionCheckboxes, options);
+            });
         });
         });
-        $('form#changelist-form button[name="index"]').on('click', function(event) {
-            if (list_editable_changed) {
-                return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
+
+        function affectedCheckboxes(target, withModifier) {
+            const multiSelect = (lastChecked && withModifier && lastChecked !== target);
+            if (!multiSelect) {
+                return [target];
             }
             }
-        });
-        $('form#changelist-form input[name="_save"]').on('click', function(event) {
-            let action_changed = false;
-            $('select option:selected', options.actionContainer).each(function() {
-                if ($(this).val()) {
-                    action_changed = true;
+            const checkboxes = Array.from(actionCheckboxes);
+            const targetIndex = checkboxes.findIndex(el => el === target);
+            const lastCheckedIndex = checkboxes.findIndex(el => el === lastChecked);
+            const startIndex = Math.min(targetIndex, lastCheckedIndex);
+            const endIndex = Math.max(targetIndex, lastCheckedIndex);
+            const filtered = checkboxes.filter((el, index) => (startIndex <= index) && (index <= endIndex));
+            return filtered;
+        };
+
+        Array.from(document.getElementById('result_list').tBodies).forEach(function(el) {
+            el.addEventListener('change', function(event) {
+                const target = event.target;
+                if (target.classList.contains('action-select')) {
+                    const checkboxes = affectedCheckboxes(target, shiftPressed);
+                    checker(checkboxes, options, target.checked);
+                    updateCounter(actionCheckboxes, options);
+                    lastChecked = target;
+                } else {
+                    list_editable_changed = true;
                 }
                 }
             });
             });
-            if (action_changed) {
-                if (list_editable_changed) {
-                    return confirm(gettext("You have selected an action, but you haven’t saved your changes to individual fields yet. Please click OK to save. You’ll need to re-run the action."));
-                } else {
-                    return confirm(gettext("You have selected an action, and you haven’t made any changes on individual fields. You’re probably looking for the Go button rather than the Save button."));
+        });
+
+        document.querySelector('#changelist-form button[name=index]').addEventListener('click', function(event) {
+            if (list_editable_changed) {
+                const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
+                if (!confirmed) {
+                    event.preventDefault();
                 }
                 }
             }
             }
         });
         });
+
+        const el = document.querySelector('#changelist-form input[name=_save]');
+        // The button does not exist if no fields are editable.
+        if (el) {
+            el.addEventListener('click', function(event) {
+                if (document.querySelector('[name=action]').value) {
+                    const text = list_editable_changed
+                        ? gettext("You have selected an action, but you haven’t saved your changes to individual fields yet. Please click OK to save. You’ll need to re-run the action.")
+                        : gettext("You have selected an action, and you haven’t made any changes on individual fields. You’re probably looking for the Go button rather than the Save button.");
+                    if (!confirm(text)) {
+                        event.preventDefault();
+                    }
+                }
+            });
+        }
+        // Sync counter when navigating to the page, such as through the back
+        // button.
+        window.addEventListener('pageshow', (event) => updateCounter(actionCheckboxes, options));
     };
     };
-    /* Setup plugin defaults */
-    $.fn.actions.defaults = {
-        actionContainer: "div.actions",
-        counterContainer: "span.action-counter",
-        allContainer: "div.actions span.all",
-        acrossInput: "div.actions input.select-across",
-        acrossQuestions: "div.actions span.question",
-        acrossClears: "div.actions span.clear",
-        allToggle: "#action-toggle",
-        selectedClass: "selected"
-    };
-    $(document).ready(function() {
-        const $actionsEls = $('tr input.action-select');
-        if ($actionsEls.length > 0) {
-            $actionsEls.actions();
+
+    // Call function fn when the DOM is loaded and ready. If it is already
+    // loaded, call the function now.
+    // http://youmightnotneedjquery.com/#ready
+    function ready(fn) {
+        if (document.readyState !== 'loading') {
+            fn();
+        } else {
+            document.addEventListener('DOMContentLoaded', fn);
+        }
+    }
+
+    ready(function() {
+        const actionsEls = document.querySelectorAll('tr input.action-select');
+        if (actionsEls.length > 0) {
+            Actions(actionsEls);
         }
         }
     });
     });
 }
 }

+ 0 - 7
static/admin/js/actions.min.js

@@ -1,7 +0,0 @@
-'use strict';{const a=django.jQuery;let e;a.fn.actions=function(g){const b=a.extend({},a.fn.actions.defaults,g),f=a(this);let k=!1;const l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},n=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},
-p=function(){n();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(f).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){const c=a(f).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){let a;c===f.length?(a=!0,l()):(a=!1,p());return a})};
-a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().on("click",function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).on("click",function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).on("click",function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});e=null;a(f).on("click",function(c){c||(c=window.event);
-const d=c.target?c.target:c.srcElement;if(e&&a.data(e)!==a.data(d)&&!0===c.shiftKey){let c=!1;a(e).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)===a.data(e)||a.data(this)===a.data(d))c=c?!1:!0;c&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);e=d;h()});a("form#changelist-form table#result_list tr").on("change","td:gt(0) :input",
-function(){k=!0});a('form#changelist-form button[name="index"]').on("click",function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').on("click",function(c){let d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?confirm(gettext("You have selected an action, but you haven\u2019t saved your changes to individual fields yet. Please click OK to save. You\u2019ll need to re-run the action.")):
-confirm(gettext("You have selected an action, and you haven\u2019t made any changes on individual fields. You\u2019re probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"};a(document).ready(function(){const g=
-a("tr input.action-select");0<g.length&&g.actions()})};

+ 5 - 14
static/admin/js/admin/DateTimeShortcuts.js

@@ -28,8 +28,7 @@
         timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch
         timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch
         timezoneOffset: 0,
         timezoneOffset: 0,
         init: function() {
         init: function() {
-            const body = document.getElementsByTagName('body')[0];
-            const serverOffset = body.dataset.adminUtcOffset;
+            const serverOffset = document.body.dataset.adminUtcOffset;
             if (serverOffset) {
             if (serverOffset) {
                 const localOffset = new Date().getTimezoneOffset() * -60;
                 const localOffset = new Date().getTimezoneOffset() * -60;
                 DateTimeShortcuts.timezoneOffset = localOffset - serverOffset;
                 DateTimeShortcuts.timezoneOffset = localOffset - serverOffset;
@@ -48,8 +47,7 @@
         },
         },
         // Return the current time while accounting for the server timezone.
         // Return the current time while accounting for the server timezone.
         now: function() {
         now: function() {
-            const body = document.getElementsByTagName('body')[0];
-            const serverOffset = body.dataset.adminUtcOffset;
+            const serverOffset = document.body.dataset.adminUtcOffset;
             if (serverOffset) {
             if (serverOffset) {
                 const localNow = new Date();
                 const localNow = new Date();
                 const localOffset = localNow.getTimezoneOffset() * -60;
                 const localOffset = localNow.getTimezoneOffset() * -60;
@@ -92,10 +90,9 @@
             }
             }
             message = interpolate(message, [timezoneOffset]);
             message = interpolate(message, [timezoneOffset]);
 
 
-            const warning = document.createElement('span');
-            warning.className = warningClass;
+            const warning = document.createElement('div');
+            warning.classList.add('help', warningClass);
             warning.textContent = message;
             warning.textContent = message;
-            inp.parentNode.appendChild(document.createElement('br'));
             inp.parentNode.appendChild(warning);
             inp.parentNode.appendChild(warning);
         },
         },
         // Add clock widget to a given field
         // Add clock widget to a given field
@@ -390,13 +387,7 @@
             DateTimeShortcuts.calendars[num].drawNextMonth();
             DateTimeShortcuts.calendars[num].drawNextMonth();
         },
         },
         handleCalendarCallback: function(num) {
         handleCalendarCallback: function(num) {
-            let format = get_format('DATE_INPUT_FORMATS')[0];
-            // the format needs to be escaped a little
-            format = format.replace('\\', '\\\\')
-                .replace('\r', '\\r')
-                .replace('\n', '\\n')
-                .replace('\t', '\\t')
-                .replace("'", "\\'");
+            const format = get_format('DATE_INPUT_FORMATS')[0];
             return function(y, m, d) {
             return function(y, m, d) {
                 DateTimeShortcuts.calendarInputs[num].value = new Date(y, m - 1, d).strftime(format);
                 DateTimeShortcuts.calendarInputs[num].value = new Date(y, m - 1, d).strftime(format);
                 DateTimeShortcuts.calendarInputs[num].focus();
                 DateTimeShortcuts.calendarInputs[num].focus();

+ 94 - 13
static/admin/js/admin/RelatedObjectLookups.js

@@ -4,18 +4,43 @@
 'use strict';
 'use strict';
 {
 {
     const $ = django.jQuery;
     const $ = django.jQuery;
+    let popupIndex = 0;
+    const relatedWindows = [];
+
+    function dismissChildPopups() {
+        relatedWindows.forEach(function(win) {
+            if(!win.closed) {
+                win.dismissChildPopups();
+                win.close();    
+            }
+        });
+    }
+
+    function setPopupIndex() {
+        if(document.getElementsByName("_popup").length > 0) {
+            const index = window.name.lastIndexOf("__") + 2;
+            popupIndex = parseInt(window.name.substring(index));   
+        } else {
+            popupIndex = 0;
+        }
+    }
+
+    function addPopupIndex(name) {
+        return name + "__" + (popupIndex + 1);
+    }
+
+    function removePopupIndex(name) {
+        return name.replace(new RegExp("__" + (popupIndex + 1) + "$"), '');
+    }
 
 
     function showAdminPopup(triggeringLink, name_regexp, add_popup) {
     function showAdminPopup(triggeringLink, name_regexp, add_popup) {
-        const name = triggeringLink.id.replace(name_regexp, '');
-        let href = triggeringLink.href;
+        const name = addPopupIndex(triggeringLink.id.replace(name_regexp, ''));
+        const href = new URL(triggeringLink.href);
         if (add_popup) {
         if (add_popup) {
-            if (href.indexOf('?') === -1) {
-                href += '?_popup=1';
-            } else {
-                href += '&_popup=1';
-            }
+            href.searchParams.set('_popup', 1);
         }
         }
         const win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
         const win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
+        relatedWindows.push(win);
         win.focus();
         win.focus();
         return false;
         return false;
     }
     }
@@ -25,13 +50,17 @@
     }
     }
 
 
     function dismissRelatedLookupPopup(win, chosenId) {
     function dismissRelatedLookupPopup(win, chosenId) {
-        const name = win.name;
+        const name = removePopupIndex(win.name);
         const elem = document.getElementById(name);
         const elem = document.getElementById(name);
         if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
         if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
             elem.value += ',' + chosenId;
             elem.value += ',' + chosenId;
         } else {
         } else {
             document.getElementById(name).value = chosenId;
             document.getElementById(name).value = chosenId;
         }
         }
+        const index = relatedWindows.indexOf(win);
+        if (index > -1) {
+            relatedWindows.splice(index, 1);
+        }
         win.close();
         win.close();
     }
     }
 
 
@@ -50,19 +79,52 @@
             siblings.each(function() {
             siblings.each(function() {
                 const elm = $(this);
                 const elm = $(this);
                 elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
                 elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
+                elm.removeAttr('aria-disabled');
             });
             });
         } else {
         } else {
             siblings.removeAttr('href');
             siblings.removeAttr('href');
+            siblings.attr('aria-disabled', true);
         }
         }
     }
     }
 
 
+    function updateRelatedSelectsOptions(currentSelect, win, objId, newRepr, newId) {
+        // After create/edit a model from the options next to the current
+        // select (+ or :pencil:) update ForeignKey PK of the rest of selects
+        // in the page.
+
+        const path = win.location.pathname;
+        // Extract the model from the popup url '.../<model>/add/' or
+        // '.../<model>/<id>/change/' depending the action (add or change).
+        const modelName = path.split('/')[path.split('/').length - (objId ? 4 : 3)];
+        // Select elements with a specific model reference and context of "available-source".
+        const selectsRelated = document.querySelectorAll(`[data-model-ref="${modelName}"] [data-context="available-source"]`);
+
+        selectsRelated.forEach(function(select) {
+            if (currentSelect === select) {
+                return;
+            }
+
+            let option = select.querySelector(`option[value="${objId}"]`);
+
+            if (!option) {
+                option = new Option(newRepr, newId);
+                select.options.add(option);
+                return;
+            }
+
+            option.textContent = newRepr;
+            option.value = newId;
+        });
+    }
+
     function dismissAddRelatedObjectPopup(win, newId, newRepr) {
     function dismissAddRelatedObjectPopup(win, newId, newRepr) {
-        const name = win.name;
+        const name = removePopupIndex(win.name);
         const elem = document.getElementById(name);
         const elem = document.getElementById(name);
         if (elem) {
         if (elem) {
             const elemName = elem.nodeName.toUpperCase();
             const elemName = elem.nodeName.toUpperCase();
             if (elemName === 'SELECT') {
             if (elemName === 'SELECT') {
                 elem.options[elem.options.length] = new Option(newRepr, newId, true, true);
                 elem.options[elem.options.length] = new Option(newRepr, newId, true, true);
+                updateRelatedSelectsOptions(elem, win, null, newRepr, newId);
             } else if (elemName === 'INPUT') {
             } else if (elemName === 'INPUT') {
                 if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
                 if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
                     elem.value += ',' + newId;
                     elem.value += ',' + newId;
@@ -78,11 +140,15 @@
             SelectBox.add_to_cache(toId, o);
             SelectBox.add_to_cache(toId, o);
             SelectBox.redisplay(toId);
             SelectBox.redisplay(toId);
         }
         }
+        const index = relatedWindows.indexOf(win);
+        if (index > -1) {
+            relatedWindows.splice(index, 1);
+        }
         win.close();
         win.close();
     }
     }
 
 
     function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
     function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
-        const id = win.name.replace(/^edit_/, '');
+        const id = removePopupIndex(win.name.replace(/^edit_/, ''));
         const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
         const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
         const selects = $(selectsSelector);
         const selects = $(selectsSelector);
         selects.find('option').each(function() {
         selects.find('option').each(function() {
@@ -90,18 +156,23 @@
                 this.textContent = newRepr;
                 this.textContent = newRepr;
                 this.value = newId;
                 this.value = newId;
             }
             }
-        });
+        }).trigger('change');
+        updateRelatedSelectsOptions(selects[0], win, objId, newRepr, newId);
         selects.next().find('.select2-selection__rendered').each(function() {
         selects.next().find('.select2-selection__rendered').each(function() {
             // The element can have a clear button as a child.
             // The element can have a clear button as a child.
             // Use the lastChild to modify only the displayed value.
             // Use the lastChild to modify only the displayed value.
             this.lastChild.textContent = newRepr;
             this.lastChild.textContent = newRepr;
             this.title = newRepr;
             this.title = newRepr;
         });
         });
+        const index = relatedWindows.indexOf(win);
+        if (index > -1) {
+            relatedWindows.splice(index, 1);
+        }
         win.close();
         win.close();
     }
     }
 
 
     function dismissDeleteRelatedObjectPopup(win, objId) {
     function dismissDeleteRelatedObjectPopup(win, objId) {
-        const id = win.name.replace(/^delete_/, '');
+        const id = removePopupIndex(win.name.replace(/^delete_/, ''));
         const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
         const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
         const selects = $(selectsSelector);
         const selects = $(selectsSelector);
         selects.find('option').each(function() {
         selects.find('option').each(function() {
@@ -109,6 +180,10 @@
                 $(this).remove();
                 $(this).remove();
             }
             }
         }).trigger('change');
         }).trigger('change');
+        const index = relatedWindows.indexOf(win);
+        if (index > -1) {
+            relatedWindows.splice(index, 1);
+        }
         win.close();
         win.close();
     }
     }
 
 
@@ -119,17 +194,23 @@
     window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup;
     window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup;
     window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup;
     window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup;
     window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup;
     window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup;
+    window.dismissChildPopups = dismissChildPopups;
 
 
     // Kept for backward compatibility
     // Kept for backward compatibility
     window.showAddAnotherPopup = showRelatedObjectPopup;
     window.showAddAnotherPopup = showRelatedObjectPopup;
     window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
     window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
 
 
+    window.addEventListener('unload', function(evt) {
+        window.dismissChildPopups();
+    });
+
     $(document).ready(function() {
     $(document).ready(function() {
+        setPopupIndex();
         $("a[data-popup-opener]").on('click', function(event) {
         $("a[data-popup-opener]").on('click', function(event) {
             event.preventDefault();
             event.preventDefault();
             opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
             opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
         });
         });
-        $('body').on('click', '.related-widget-wrapper-link', function(e) {
+        $('body').on('click', '.related-widget-wrapper-link[data-popup="yes"]', function(e) {
             e.preventDefault();
             e.preventDefault();
             if (this.href) {
             if (this.href) {
                 const event = $.Event('django:show-related', {href: this.href});
                 const event = $.Event('django:show-related', {href: this.href});

+ 17 - 22
static/admin/js/autocomplete.js

@@ -1,25 +1,22 @@
 'use strict';
 'use strict';
 {
 {
     const $ = django.jQuery;
     const $ = django.jQuery;
-    const init = function($element, options) {
-        const settings = $.extend({
-            ajax: {
-                data: function(params) {
-                    return {
-                        term: params.term,
-                        page: params.page
-                    };
-                }
-            }
-        }, options);
-        $element.select2(settings);
-    };
 
 
-    $.fn.djangoAdminSelect2 = function(options) {
-        const settings = $.extend({}, options);
+    $.fn.djangoAdminSelect2 = function() {
         $.each(this, function(i, element) {
         $.each(this, function(i, element) {
-            const $element = $(element);
-            init($element, settings);
+            $(element).select2({
+                ajax: {
+                    data: (params) => {
+                        return {
+                            term: params.term,
+                            page: params.page,
+                            app_label: element.dataset.appLabel,
+                            model_name: element.dataset.modelName,
+                            field_name: element.dataset.fieldName
+                        };
+                    }
+                }
+            });
         });
         });
         return this;
         return this;
     };
     };
@@ -30,9 +27,7 @@
         $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
         $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
     });
     });
 
 
-    $(document).on('formset:added', (function() {
-        return function(event, $newFormset) {
-            return $newFormset.find('.admin-autocomplete').djangoAdminSelect2();
-        };
-    })(this));
+    document.addEventListener('formset:added', (event) => {
+        $(event.target).find('.admin-autocomplete').djangoAdminSelect2();
+    });
 }
 }

+ 33 - 1
static/admin/js/calendar.js

@@ -21,7 +21,39 @@ depends on core.js for utility functions like removeChildren or quickElement
             gettext('November'),
             gettext('November'),
             gettext('December')
             gettext('December')
         ],
         ],
+        monthsOfYearAbbrev: [
+            pgettext('abbrev. month January', 'Jan'),
+            pgettext('abbrev. month February', 'Feb'),
+            pgettext('abbrev. month March', 'Mar'),
+            pgettext('abbrev. month April', 'Apr'),
+            pgettext('abbrev. month May', 'May'),
+            pgettext('abbrev. month June', 'Jun'),
+            pgettext('abbrev. month July', 'Jul'),
+            pgettext('abbrev. month August', 'Aug'),
+            pgettext('abbrev. month September', 'Sep'),
+            pgettext('abbrev. month October', 'Oct'),
+            pgettext('abbrev. month November', 'Nov'),
+            pgettext('abbrev. month December', 'Dec')
+        ],
         daysOfWeek: [
         daysOfWeek: [
+            gettext('Sunday'),
+            gettext('Monday'),
+            gettext('Tuesday'),
+            gettext('Wednesday'),
+            gettext('Thursday'),
+            gettext('Friday'),
+            gettext('Saturday')
+        ],
+        daysOfWeekAbbrev: [
+            pgettext('abbrev. day Sunday', 'Sun'),
+            pgettext('abbrev. day Monday', 'Mon'),
+            pgettext('abbrev. day Tuesday', 'Tue'),
+            pgettext('abbrev. day Wednesday', 'Wed'),
+            pgettext('abbrev. day Thursday', 'Thur'),
+            pgettext('abbrev. day Friday', 'Fri'),
+            pgettext('abbrev. day Saturday', 'Sat')
+        ],
+        daysOfWeekInitial: [
             pgettext('one letter Sunday', 'S'),
             pgettext('one letter Sunday', 'S'),
             pgettext('one letter Monday', 'M'),
             pgettext('one letter Monday', 'M'),
             pgettext('one letter Tuesday', 'T'),
             pgettext('one letter Tuesday', 'T'),
@@ -84,7 +116,7 @@ depends on core.js for utility functions like removeChildren or quickElement
             // Draw days-of-week header
             // Draw days-of-week header
             let tableRow = quickElement('tr', tableBody);
             let tableRow = quickElement('tr', tableBody);
             for (let i = 0; i < 7; i++) {
             for (let i = 0; i < 7; i++) {
-                quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]);
+                quickElement('th', tableRow, CalendarNamespace.daysOfWeekInitial[(i + CalendarNamespace.firstDayOfWeek) % 7]);
             }
             }
 
 
             const startingPos = new Date(year, month - 1, 1 - CalendarNamespace.firstDayOfWeek).getDay();
             const startingPos = new Date(year, month - 1, 1 - CalendarNamespace.firstDayOfWeek).getDay();

+ 4 - 3
static/admin/js/cancel.js

@@ -14,10 +14,11 @@
     ready(function() {
     ready(function() {
         function handleClick(event) {
         function handleClick(event) {
             event.preventDefault();
             event.preventDefault();
-            if (window.location.search.indexOf('&_popup=1') === -1) {
-                window.history.back(); // Go back if not a popup.
+            const params = new URLSearchParams(window.location.search);
+            if (params.has('_popup')) {
+                window.close(); // Close the popup.
             } else {
             } else {
-                window.close(); // Otherwise, close the popup.
+                window.history.back(); // Otherwise, go back.
             }
             }
         }
         }
 
 

+ 0 - 43
static/admin/js/change_form.js

@@ -1,43 +0,0 @@
-/*global gettext*/
-'use strict';
-{
-    window.addEventListener('load', function() {
-        // Add anchor tag for Show/Hide link
-        const fieldsets = document.querySelectorAll('fieldset.collapse');
-        for (const [i, elem] of fieldsets.entries()) {
-            // Don't hide if fields in this fieldset have errors
-            if (elem.querySelectorAll('div.errors, ul.errorlist').length === 0) {
-                elem.classList.add('collapsed');
-                const h2 = elem.querySelector('h2');
-                const link = document.createElement('a');
-                link.id = 'fieldsetcollapser' + i;
-                link.className = 'collapse-toggle';
-                link.href = '#';
-                link.textContent = gettext('Show');
-                h2.appendChild(document.createTextNode(' ('));
-                h2.appendChild(link);
-                h2.appendChild(document.createTextNode(')'));
-            }
-        }
-        // Add toggle to hide/show anchor tag
-        const toggleFunc = function(ev) {
-            if (ev.target.matches('.collapse-toggle')) {
-                ev.preventDefault();
-                ev.stopPropagation();
-                const fieldset = ev.target.closest('fieldset');
-                if (fieldset.classList.contains('collapsed')) {
-                    // Show
-                    ev.target.textContent = gettext('Hide');
-                    fieldset.classList.remove('collapsed');
-                } else {
-                    // Hide
-                    ev.target.textContent = gettext('Show');
-                    fieldset.classList.add('collapsed');
-                }
-            }
-        };
-        document.querySelectorAll('fieldset.module').forEach(function(el) {
-            el.addEventListener('click', toggleFunc);
-        });
-    });
-}

+ 0 - 2
static/admin/js/collapse.min.js

@@ -1,2 +0,0 @@
-'use strict';window.addEventListener("load",function(){var c=document.querySelectorAll("fieldset.collapse");for(const [a,b]of c.entries())if(0===b.querySelectorAll("div.errors, ul.errorlist").length){b.classList.add("collapsed");c=b.querySelector("h2");const d=document.createElement("a");d.id="fieldsetcollapser"+a;d.className="collapse-toggle";d.href="#";d.textContent=gettext("Show");c.appendChild(document.createTextNode(" ("));c.appendChild(d);c.appendChild(document.createTextNode(")"))}const e=
-function(a){if(a.target.matches(".collapse-toggle")){a.preventDefault();a.stopPropagation();const b=a.target.closest("fieldset");b.classList.contains("collapsed")?(a.target.textContent=gettext("Hide"),b.classList.remove("collapsed")):(a.target.textContent=gettext("Show"),b.classList.add("collapsed"))}};document.querySelectorAll("fieldset.module").forEach(function(a){a.addEventListener("click",e)})});

+ 24 - 3
static/admin/js/core.js

@@ -1,4 +1,4 @@
-// Core javascript helper functions
+// Core JavaScript helper functions
 'use strict';
 'use strict';
 
 
 // quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]);
 // quickElement(tagType, parentReference [, textInChildNode, attribute, attributeValue ...]);
@@ -85,6 +85,24 @@ function findPosY(obj) {
         return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
         return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
     };
     };
 
 
+    Date.prototype.getAbbrevDayName = function() {
+        return typeof window.CalendarNamespace === "undefined"
+            ? '0' + this.getDay()
+            : window.CalendarNamespace.daysOfWeekAbbrev[this.getDay()];
+    };
+
+    Date.prototype.getFullDayName = function() {
+        return typeof window.CalendarNamespace === "undefined"
+            ? '0' + this.getDay()
+            : window.CalendarNamespace.daysOfWeek[this.getDay()];
+    };
+
+    Date.prototype.getAbbrevMonthName = function() {
+        return typeof window.CalendarNamespace === "undefined"
+            ? this.getTwoDigitMonth()
+            : window.CalendarNamespace.monthsOfYearAbbrev[this.getMonth()];
+    };
+
     Date.prototype.getFullMonthName = function() {
     Date.prototype.getFullMonthName = function() {
         return typeof window.CalendarNamespace === "undefined"
         return typeof window.CalendarNamespace === "undefined"
             ? this.getTwoDigitMonth()
             ? this.getTwoDigitMonth()
@@ -93,6 +111,9 @@ function findPosY(obj) {
 
 
     Date.prototype.strftime = function(format) {
     Date.prototype.strftime = function(format) {
         const fields = {
         const fields = {
+            a: this.getAbbrevDayName(),
+            A: this.getFullDayName(),
+            b: this.getAbbrevMonthName(),
             B: this.getFullMonthName(),
             B: this.getFullMonthName(),
             c: this.toString(),
             c: this.toString(),
             d: this.getTwoDigitDate(),
             d: this.getTwoDigitDate(),
@@ -112,11 +133,11 @@ function findPosY(obj) {
         let result = '', i = 0;
         let result = '', i = 0;
         while (i < format.length) {
         while (i < format.length) {
             if (format.charAt(i) === '%') {
             if (format.charAt(i) === '%') {
-                result = result + fields[format.charAt(i + 1)];
+                result += fields[format.charAt(i + 1)];
                 ++i;
                 ++i;
             }
             }
             else {
             else {
-                result = result + format.charAt(i);
+                result += format.charAt(i);
             }
             }
             ++i;
             ++i;
         }
         }

+ 30 - 0
static/admin/js/filters.js

@@ -0,0 +1,30 @@
+/**
+ * Persist changelist filters state (collapsed/expanded).
+ */
+'use strict';
+{
+    // Init filters.
+    let filters = JSON.parse(sessionStorage.getItem('django.admin.filtersState'));
+
+    if (!filters) {
+        filters = {};
+    }
+
+    Object.entries(filters).forEach(([key, value]) => {
+        const detailElement = document.querySelector(`[data-filter-title='${CSS.escape(key)}']`);
+
+        // Check if the filter is present, it could be from other view.
+        if (detailElement) {
+            value ? detailElement.setAttribute('open', '') : detailElement.removeAttribute('open');
+        }
+    });
+
+    // Save filter state when clicks.
+    const details = document.querySelectorAll('details');
+    details.forEach(detail => {
+        detail.addEventListener('toggle', event => {
+            filters[`${event.target.dataset.filterTitle}`] = detail.open;
+            sessionStorage.setItem('django.admin.filtersState', JSON.stringify(filters));
+        });
+    });
+}

+ 22 - 11
static/admin/js/inlines.js

@@ -88,7 +88,12 @@
             if (options.added) {
             if (options.added) {
                 options.added(row);
                 options.added(row);
             }
             }
-            $(document).trigger('formset:added', [row, options.prefix]);
+            row.get(0).dispatchEvent(new CustomEvent("formset:added", {
+                bubbles: true,
+                detail: {
+                    formsetName: options.prefix
+                }
+            }));
         };
         };
 
 
         /**
         /**
@@ -130,7 +135,11 @@
             if (options.removed) {
             if (options.removed) {
                 options.removed(row);
                 options.removed(row);
             }
             }
-            $(document).trigger('formset:removed', [row, options.prefix]);
+            document.dispatchEvent(new CustomEvent("formset:removed", {
+                detail: {
+                    formsetName: options.prefix
+                }
+            }));
             // Update the TOTAL_FORMS form count.
             // Update the TOTAL_FORMS form count.
             const forms = $("." + options.formCssClass);
             const forms = $("." + options.formCssClass);
             $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
             $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
@@ -218,12 +227,10 @@
             // instantiate a new SelectFilter instance for it.
             // instantiate a new SelectFilter instance for it.
             if (typeof SelectFilter !== 'undefined') {
             if (typeof SelectFilter !== 'undefined') {
                 $('.selectfilter').each(function(index, value) {
                 $('.selectfilter').each(function(index, value) {
-                    const namearr = value.name.split('-');
-                    SelectFilter.init(value.id, namearr[namearr.length - 1], false);
+                    SelectFilter.init(value.id, this.dataset.fieldName, false);
                 });
                 });
                 $('.selectfilterstacked').each(function(index, value) {
                 $('.selectfilterstacked').each(function(index, value) {
-                    const namearr = value.name.split('-');
-                    SelectFilter.init(value.id, namearr[namearr.length - 1], true);
+                    SelectFilter.init(value.id, this.dataset.fieldName, true);
                 });
                 });
             }
             }
         };
         };
@@ -283,12 +290,10 @@
             // If any SelectFilter widgets were added, instantiate a new instance.
             // If any SelectFilter widgets were added, instantiate a new instance.
             if (typeof SelectFilter !== "undefined") {
             if (typeof SelectFilter !== "undefined") {
                 $(".selectfilter").each(function(index, value) {
                 $(".selectfilter").each(function(index, value) {
-                    const namearr = value.name.split('-');
-                    SelectFilter.init(value.id, namearr[namearr.length - 1], false);
+                    SelectFilter.init(value.id, this.dataset.fieldName, false);
                 });
                 });
                 $(".selectfilterstacked").each(function(index, value) {
                 $(".selectfilterstacked").each(function(index, value) {
-                    const namearr = value.name.split('-');
-                    SelectFilter.init(value.id, namearr[namearr.length - 1], true);
+                    SelectFilter.init(value.id, this.dataset.fieldName, true);
                 });
                 });
             }
             }
         };
         };
@@ -300,7 +305,13 @@
                     dependency_list = input.data('dependency_list') || [],
                     dependency_list = input.data('dependency_list') || [],
                     dependencies = [];
                     dependencies = [];
                 $.each(dependency_list, function(i, field_name) {
                 $.each(dependency_list, function(i, field_name) {
-                    dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
+                    // Dependency in a fieldset.
+                    let field_element = row.find('.form-row .field-' + field_name);
+                    // Dependency without a fieldset.
+                    if (!field_element.length) {
+                        field_element = row.find('.form-row.field-' + field_name);
+                    }
+                    dependencies.push('#' + field_element.find('input, select, textarea').attr('id'));
                 });
                 });
                 if (dependencies.length) {
                 if (dependencies.length) {
                     input.prepopulate(dependencies, input.attr('maxlength'));
                     input.prepopulate(dependencies, input.attr('maxlength'));

+ 0 - 11
static/admin/js/inlines.min.js

@@ -1,11 +0,0 @@
-'use strict';{const b=django.jQuery;b.fn.formset=function(c){const a=b.extend({},b.fn.formset.defaults,c),e=b(this),l=e.parent(),m=function(a,d,h){const g=new RegExp("("+d+"-(\\d+|__prefix__))");d=d+"-"+h;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(g,d));a.id&&(a.id=a.id.replace(g,d));a.name&&(a.name=a.name.replace(g,d))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off");let n=parseInt(f.val(),10);const h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),q=
-b("#id_"+a.prefix+"-MIN_NUM_FORMS").prop("autocomplete","off");let k;const t=function(g){g.preventDefault();g=b("#"+a.prefix+"-empty");const d=g.clone(!0);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+n);r(d);d.find("*").each(function(){m(this,a.prefix,f.val())});d.insertBefore(b(g));b(f).val(parseInt(f.val(),10)+1);n+=1;""!==h.val()&&0>=h.val()-f.val()&&k.parent().hide();p(d.closest(".inline-group"));a.added&&a.added(d);b(document).trigger("formset:added",[d,a.prefix])},
-r=function(b){b.is("tr")?b.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></div>"):b.is("ul")||b.is("ol")?b.append('<li><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></li>"):b.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="#">'+a.deleteText+"</a></span>");b.find("a."+a.deleteCssClass).on("click",u.bind(this))},u=function(g){g.preventDefault();var d=b(g.target).closest("."+a.formCssClass);g=d.closest(".inline-group");
-var f=d.prev();f.length&&f.hasClass("row-form-errors")&&f.remove();d.remove();--n;a.removed&&a.removed(d);b(document).trigger("formset:removed",[d,a.prefix]);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(""===h.val()||0<h.val()-d.length)&&k.parent().show();p(g);let c;f=function(){m(this,a.prefix,c)};c=0;for(g=d.length;c<g;c++)m(b(d).get(c),a.prefix,c),b(d.get(c)).find("*").each(f)},p=function(a){""!==q.val()&&0<=q.val()-f.val()?a.find(".inline-deletelink").hide():a.find(".inline-deletelink").show()};
-e.each(function(c){b(this).not("."+a.emptyCssClass).addClass(a.formCssClass)});e.filter("."+a.formCssClass+":not(.has_original):not(."+a.emptyCssClass+")").each(function(){r(b(this))});p(e);k=a.addButton;(function(){if(null===k)if("TR"===e.prop("tagName")){const b=e.eq(-1).children().length;l.append('<tr class="'+a.addCssClass+'"><td colspan="'+b+'"><a href="#">'+a.addText+"</a></tr>");k=l.find("tr:last a")}else e.filter(":last").after('<div class="'+a.addCssClass+'"><a href="#">'+a.addText+"</a></div>"),
-k=e.filter(":last").next().find("a");k.on("click",t)})();c=""===h.val()||0<h.val()-f.val();e.length&&c?k.parent().show():k.parent().hide();return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null,addButton:null};b.fn.tabularFormset=function(c,a){c=b(this);const e=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,
-b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!0)}))},l=function(a){a.find(".prepopulated_field").each(function(){const c=b(this).find("input, select, textarea"),n=c.data("dependency_list")||[],h=[];b.each(n,function(b,c){h.push("#"+a.find(".field-"+c).find("input, select, textarea").attr("id"))});h.length&&c.prepopulate(h,c.attr("maxlength"))})};c.formset({prefix:a.prefix,addText:a.addText,
-formCssClass:"dynamic-"+a.prefix,deleteCssClass:"inline-deletelink",deleteText:a.deleteText,emptyCssClass:"empty-form",added:function(a){l(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());e()},addButton:a.addButton});return c};b.fn.stackedFormset=function(c,a){const e=b(this),l=function(a){b(c).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})},m=function(){"undefined"!==typeof SelectFilter&&(b(".selectfilter").each(function(a,
-b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!1)}),b(".selectfilterstacked").each(function(a,b){a=b.name.split("-");SelectFilter.init(b.id,a[a.length-1],!0)}))},f=function(a){a.find(".prepopulated_field").each(function(){const c=b(this).find("input, select, textarea"),f=c.data("dependency_list")||[],e=[];b.each(f,function(b,c){e.push("#"+a.find(".form-row .field-"+c).find("input, select, textarea").attr("id"))});e.length&&c.prepopulate(e,c.attr("maxlength"))})};e.formset({prefix:a.prefix,
-addText:a.addText,formCssClass:"dynamic-"+a.prefix,deleteCssClass:"inline-deletelink",deleteText:a.deleteText,emptyCssClass:"empty-form",removed:l,added:function(a){f(a);"undefined"!==typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());m();l(a)},addButton:a.addButton});return e};b(document).ready(function(){b(".js-inline-admin-formset").each(function(){var c=b(this).data();const a=c.inlineFormset;switch(c.inlineType){case "stacked":c=a.name+"-group .inline-related";
-b(c).stackedFormset(c,a.options);break;case "tabular":c=a.name+"-group .tabular.inline-related tbody:first > tr.form-row",b(c).tabularFormset(c,a.options)}})})};

+ 57 - 17
static/admin/js/jquery.init.js

@@ -2,38 +2,78 @@
 {
 {
     const toggleNavSidebar = document.getElementById('toggle-nav-sidebar');
     const toggleNavSidebar = document.getElementById('toggle-nav-sidebar');
     if (toggleNavSidebar !== null) {
     if (toggleNavSidebar !== null) {
-        const navLinks = document.querySelectorAll('#nav-sidebar a');
-        function disableNavLinkTabbing() {
-            for (const navLink of navLinks) {
-                navLink.tabIndex = -1;
-            }
-        }
-        function enableNavLinkTabbing() {
-            for (const navLink of navLinks) {
-                navLink.tabIndex = 0;
-            }
-        }
-
+        const navSidebar = document.getElementById('nav-sidebar');
         const main = document.getElementById('main');
         const main = document.getElementById('main');
         let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen');
         let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen');
         if (navSidebarIsOpen === null) {
         if (navSidebarIsOpen === null) {
             navSidebarIsOpen = 'true';
             navSidebarIsOpen = 'true';
         }
         }
-        if (navSidebarIsOpen === 'false') {
-            disableNavLinkTabbing();
-        }
         main.classList.toggle('shifted', navSidebarIsOpen === 'true');
         main.classList.toggle('shifted', navSidebarIsOpen === 'true');
+        navSidebar.setAttribute('aria-expanded', navSidebarIsOpen);
 
 
         toggleNavSidebar.addEventListener('click', function() {
         toggleNavSidebar.addEventListener('click', function() {
             if (navSidebarIsOpen === 'true') {
             if (navSidebarIsOpen === 'true') {
                 navSidebarIsOpen = 'false';
                 navSidebarIsOpen = 'false';
-                disableNavLinkTabbing();
             } else {
             } else {
                 navSidebarIsOpen = 'true';
                 navSidebarIsOpen = 'true';
-                enableNavLinkTabbing();
             }
             }
             localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen);
             localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen);
             main.classList.toggle('shifted');
             main.classList.toggle('shifted');
+            navSidebar.setAttribute('aria-expanded', navSidebarIsOpen);
+        });
+    }
+
+    function initSidebarQuickFilter() {
+        const options = [];
+        const navSidebar = document.getElementById('nav-sidebar');
+        if (!navSidebar) {
+            return;
+        }
+        navSidebar.querySelectorAll('th[scope=row] a').forEach((container) => {
+            options.push({title: container.innerHTML, node: container});
         });
         });
+
+        function checkValue(event) {
+            let filterValue = event.target.value;
+            if (filterValue) {
+                filterValue = filterValue.toLowerCase();
+            }
+            if (event.key === 'Escape') {
+                filterValue = '';
+                event.target.value = ''; // clear input
+            }
+            let matches = false;
+            for (const o of options) {
+                let displayValue = '';
+                if (filterValue) {
+                    if (o.title.toLowerCase().indexOf(filterValue) === -1) {
+                        displayValue = 'none';
+                    } else {
+                        matches = true;
+                    }
+                }
+                // show/hide parent <TR>
+                o.node.parentNode.parentNode.style.display = displayValue;
+            }
+            if (!filterValue || matches) {
+                event.target.classList.remove('no-results');
+            } else {
+                event.target.classList.add('no-results');
+            }
+            sessionStorage.setItem('django.admin.navSidebarFilterValue', filterValue);
+        }
+
+        const nav = document.getElementById('nav-filter');
+        nav.addEventListener('change', checkValue, false);
+        nav.addEventListener('input', checkValue, false);
+        nav.addEventListener('keyup', checkValue, false);
+
+        const storedValue = sessionStorage.getItem('django.admin.navSidebarFilterValue');
+        if (storedValue) {
+            nav.value = storedValue;
+            checkValue({target: nav, key: ''});
+        }
     }
     }
+    window.initSidebarQuickFilter = initSidebarQuickFilter;
+    initSidebarQuickFilter();
 }
 }

+ 0 - 1
static/admin/js/popup_response.js

@@ -1,4 +1,3 @@
-/*global opener */
 'use strict';
 'use strict';
 {
 {
     const initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
     const initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);

+ 0 - 1
static/admin/js/prepopulate.js

@@ -1 +0,0 @@
-'use strict';{const b=django.jQuery;b.fn.prepopulate=function(d,f,g){return this.each(function(){const a=b(this),h=function(){if(!a.data("_changed")){var e=[];b.each(d,function(a,c){c=b(c);0<c.val().length&&e.push(c.val())});a.val(URLify(e.join(" "),f,g))}};a.data("_changed",!1);a.on("change",function(){a.data("_changed",!0)});if(!a.val())b(d.join(",")).on("keyup change focus",h)})}};

+ 5 - 1
static/admin/js/prepopulate_init.js

@@ -3,7 +3,11 @@
     const $ = django.jQuery;
     const $ = django.jQuery;
     const fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields');
     const fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields');
     $.each(fields, function(index, field) {
     $.each(fields, function(index, field) {
-        $('.empty-form .form-row .field-' + field.name + ', .empty-form.form-row .field-' + field.name).addClass('prepopulated_field');
+        $(
+            '.empty-form .form-row .field-' + field.name +
+            ', .empty-form.form-row .field-' + field.name +
+            ', .empty-form .form-row.field-' + field.name
+        ).addClass('prepopulated_field');
         $(field.id).data('dependency_list', field.dependency_list).prepopulate(
         $(field.id).data('dependency_list', field.dependency_list).prepopulate(
             field.dependency_ids, field.maxLength, field.allowUnicode
             field.dependency_ids, field.maxLength, field.allowUnicode
         );
         );

+ 51 - 0
static/admin/js/theme.js

@@ -0,0 +1,51 @@
+'use strict';
+{
+    function setTheme(mode) {
+        if (mode !== "light" && mode !== "dark" && mode !== "auto") {
+            console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);
+            mode = "auto";
+        }
+        document.documentElement.dataset.theme = mode;
+        localStorage.setItem("theme", mode);
+    }
+
+    function cycleTheme() {
+        const currentTheme = localStorage.getItem("theme") || "auto";
+        const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
+
+        if (prefersDark) {
+            // Auto (dark) -> Light -> Dark
+            if (currentTheme === "auto") {
+                setTheme("light");
+            } else if (currentTheme === "light") {
+                setTheme("dark");
+            } else {
+                setTheme("auto");
+            }
+        } else {
+            // Auto (light) -> Dark -> Light
+            if (currentTheme === "auto") {
+                setTheme("dark");
+            } else if (currentTheme === "dark") {
+                setTheme("light");
+            } else {
+                setTheme("auto");
+            }
+        }
+    }
+
+    function initTheme() {
+        // set theme defined in localStorage if there is one, or fallback to auto mode
+        const currentTheme = localStorage.getItem("theme");
+        currentTheme ? setTheme(currentTheme) : setTheme("auto");
+    }
+
+    window.addEventListener('load', function(_) {
+        const buttons = document.getElementsByClassName("theme-toggle");
+        Array.from(buttons).forEach((btn) => {
+            btn.addEventListener("click", cycleTheme);
+        });
+    });
+
+    initTheme();
+}

+ 29 - 0
static/admin/js/unusable_password_field.js

@@ -0,0 +1,29 @@
+"use strict";
+// Fallback JS for browsers which do not support :has selector used in
+// admin/css/unusable_password_fields.css
+// Remove file once all supported browsers support :has selector
+try {
+    // If browser does not support :has selector this will raise an error
+    document.querySelector("form:has(input)");
+} catch (error) {
+    console.log("Defaulting to javascript for usable password form management: " + error);
+    // JS replacement for unsupported :has selector
+    document.querySelectorAll('input[name="usable_password"]').forEach(option => {
+        option.addEventListener('change', function() {
+            const usablePassword = (this.value === "true" ? this.checked : !this.checked);
+            const submit1 = document.querySelector('input[type="submit"].set-password');
+            const submit2 = document.querySelector('input[type="submit"].unset-password');
+            const messages = document.querySelector('#id_unusable_warning');
+            document.getElementById('id_password1').closest('.form-row').hidden = !usablePassword;
+            document.getElementById('id_password2').closest('.form-row').hidden = !usablePassword;
+            if (messages) {
+                messages.hidden = usablePassword;
+            }
+            if (submit1 && submit2) {
+                submit1.hidden = !usablePassword;
+                submit2.hidden = usablePassword;
+            }
+        });
+        option.dispatchEvent(new Event('change'));
+    });
+}

+ 2 - 18
static/admin/js/urlify.js

@@ -134,8 +134,7 @@
             for (const lookup of ALL_DOWNCODE_MAPS) {
             for (const lookup of ALL_DOWNCODE_MAPS) {
                 Object.assign(Downcoder.map, lookup);
                 Object.assign(Downcoder.map, lookup);
             }
             }
-            Downcoder.chars = Object.keys(Downcoder.map);
-            Downcoder.regex = new RegExp(Downcoder.chars.join('|'), 'g');
+            Downcoder.regex = new RegExp(Object.keys(Downcoder.map).join('|'), 'g');
         }
         }
     };
     };
 
 
@@ -149,23 +148,9 @@
 
 
     function URLify(s, num_chars, allowUnicode) {
     function URLify(s, num_chars, allowUnicode) {
         // changes, e.g., "Petty theft" to "petty-theft"
         // changes, e.g., "Petty theft" to "petty-theft"
-        // remove all these words from the string before urlifying
         if (!allowUnicode) {
         if (!allowUnicode) {
             s = downcode(s);
             s = downcode(s);
         }
         }
-        const hasUnicodeChars = /[^\u0000-\u007f]/.test(s);
-        // Remove English words only if the string contains ASCII (English)
-        // characters.
-        if (!hasUnicodeChars) {
-            const removeList = [
-                "a", "an", "as", "at", "before", "but", "by", "for", "from",
-                "is", "in", "into", "like", "of", "off", "on", "onto", "per",
-                "since", "than", "the", "this", "that", "to", "up", "via",
-                "with"
-            ];
-            const r = new RegExp('\\b(' + removeList.join('|') + ')\\b', 'gi');
-            s = s.replace(r, '');
-        }
         s = s.toLowerCase(); // convert to lowercase
         s = s.toLowerCase(); // convert to lowercase
         // if downcode doesn't hit, the char will be stripped here
         // if downcode doesn't hit, the char will be stripped here
         if (allowUnicode) {
         if (allowUnicode) {
@@ -178,8 +163,7 @@
         s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
         s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
         s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
         s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
         s = s.substring(0, num_chars); // trim to first num_chars chars
         s = s.substring(0, num_chars); // trim to first num_chars chars
-        s = s.replace(/-+$/g, ''); // trim any trailing hyphens
-        return s;
+        return s.replace(/-+$/g, ''); // trim any trailing hyphens
     }
     }
     window.URLify = URLify;
     window.URLify = URLify;
 }
 }

+ 1 - 1
static/admin/js/vendor/jquery/LICENSE.txt

@@ -1,4 +1,4 @@
-Copyright JS Foundation and other contributors, https://js.foundation/
+Copyright OpenJS Foundation and other contributors, https://openjsf.org/
 
 
 Permission is hereby granted, free of charge, to any person obtaining
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 a copy of this software and associated documentation files (the

File diff suppressed because it is too large
+ 354 - 472
static/admin/js/vendor/jquery/jquery.js


File diff suppressed because it is too large
+ 0 - 1
static/admin/js/vendor/jquery/jquery.min.js


+ 1 - 1
static/admin/js/vendor/select2/LICENSE.md

@@ -1,6 +1,6 @@
 The MIT License
 The MIT License
 
 
-Copyright (c) 2007-2017 Steven Levithan <http://xregexp.com/>
+Copyright (c) 2007-present Steven Levithan <http://xregexp.com/>
 
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 of this software and associated documentation files (the "Software"), to deal

File diff suppressed because it is too large
+ 284 - 1754
static/admin/js/vendor/xregexp/xregexp.js


File diff suppressed because it is too large
+ 0 - 69
static/admin/js/vendor/xregexp/xregexp.min.js


Some files were not shown because too many files changed in this diff