linkify game, edition, purchase, session references
also add link styles for links in a table row
This commit is contained in:
parent
b8258e2937
commit
e067e65bce
|
@ -27,6 +27,20 @@ def Popover(
|
|||
return result
|
||||
|
||||
|
||||
HTMLAttribute = tuple[str, str]
|
||||
HTMLTag = str
|
||||
|
||||
|
||||
def A(attributes: list[HTMLAttribute], children: list[HTMLTag] | HTMLTag) -> HTMLTag:
|
||||
if isinstance(children, str):
|
||||
children = [children]
|
||||
childrenBlob = "\n".join(children)
|
||||
attributesList = [f'{name} = "{value}"' for name, value in attributes]
|
||||
attributesBlob = " ".join(attributesList)
|
||||
tag: str = f"<a {attributesBlob}>{childrenBlob}</a>"
|
||||
return mark_safe(tag)
|
||||
|
||||
|
||||
def safe_division(numerator: int | float, denominator: int | float) -> int | float:
|
||||
"""
|
||||
Divides without triggering division by zero exception.
|
||||
|
|
|
@ -1386,6 +1386,10 @@ input:checked + .toggle-bg {
|
|||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
@ -1394,10 +1398,6 @@ input:checked + .toggle-bg {
|
|||
margin-inline-end: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.mr-4 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
@ -1422,14 +1422,6 @@ input:checked + .toggle-bg {
|
|||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
@ -1479,10 +1471,6 @@ input:checked + .toggle-bg {
|
|||
height: 0.625rem;
|
||||
}
|
||||
|
||||
.h-3 {
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
|
@ -1543,10 +1531,6 @@ input:checked + .toggle-bg {
|
|||
width: 16rem;
|
||||
}
|
||||
|
||||
.w-7 {
|
||||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -1705,12 +1689,6 @@ input:checked + .toggle-bg {
|
|||
margin-left: calc(-1px * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.space-x-1 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.25rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||
|
@ -1764,10 +1742,6 @@ input:checked + .toggle-bg {
|
|||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
@ -1886,11 +1860,6 @@ input:checked + .toggle-bg {
|
|||
background-color: rgb(5 122 85 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-violet-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(124 58 237 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
|
@ -2000,10 +1969,6 @@ input:checked + .toggle-bg {
|
|||
vertical-align: top;
|
||||
}
|
||||
|
||||
.font-condensed {
|
||||
font-family: IBM Plex Sans Condensed, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: IBM Plex Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
@ -2174,6 +2139,18 @@ input:checked + .toggle-bg {
|
|||
text-decoration-color: #64748b;
|
||||
}
|
||||
|
||||
.decoration-wavy {
|
||||
text-decoration-style: wavy;
|
||||
}
|
||||
|
||||
.underline-offset-2 {
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.underline-offset-4 {
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -2239,6 +2216,12 @@ input:checked + .toggle-bg {
|
|||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.duration-200 {
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
|
@ -2247,6 +2230,14 @@ input:checked + .toggle-bg {
|
|||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.duration-1000 {
|
||||
transition-duration: 1000ms;
|
||||
}
|
||||
|
||||
.duration-500 {
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
.ease-in {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
||||
}
|
||||
|
@ -2573,11 +2564,6 @@ textarea:disabled:is(.dark *) {
|
|||
background-color: rgb(240 82 82 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-violet-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(109 40 217 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-white:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
|
@ -2613,6 +2599,10 @@ textarea:disabled:is(.dark *) {
|
|||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:decoration-red-600:hover {
|
||||
text-decoration-color: #E02424;
|
||||
}
|
||||
|
||||
.focus\:z-10:focus {
|
||||
z-index: 10;
|
||||
}
|
||||
|
@ -2664,11 +2654,6 @@ textarea:disabled:is(.dark *) {
|
|||
--tw-ring-color: rgb(4 108 78 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-violet-500:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-offset-2:focus {
|
||||
--tw-ring-offset-width: 2px;
|
||||
}
|
||||
|
@ -2677,10 +2662,6 @@ textarea:disabled:is(.dark *) {
|
|||
--tw-ring-offset-color: #C3DDFD;
|
||||
}
|
||||
|
||||
.focus\:ring-offset-violet-200:focus {
|
||||
--tw-ring-offset-color: #ddd6fe;
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
@ -2848,11 +2829,6 @@ textarea:disabled:is(.dark *) {
|
|||
color: rgb(148 163 184 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-slate-500:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(100 116 139 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-slate-600:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(71 85 105 / var(--tw-text-opacity));
|
||||
|
@ -2979,14 +2955,6 @@ textarea:disabled:is(.dark *) {
|
|||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.sm\:pl-2 {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.sm\:pl-4 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.sm\:decoration-2 {
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
@ -3058,11 +3026,6 @@ textarea:disabled:is(.dark *) {
|
|||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.md\:text-blue-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(26 86 219 / var(--tw-text-opacity));
|
||||
|
@ -3113,11 +3076,6 @@ textarea:disabled:is(.dark *) {
|
|||
.lg\:max-w-lg {
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.lg\:text-6xl {
|
||||
font-size: 3.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
|
@ -3152,3 +3110,76 @@ textarea:disabled:is(.dark *) {
|
|||
border-start-end-radius: 0.5rem;
|
||||
border-end-end-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.\[\&\>a\]\:underline>a {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:underline a {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:decoration-double a {
|
||||
text-decoration-style: double;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:decoration-dashed a {
|
||||
text-decoration-style: dashed;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:decoration-wavy a {
|
||||
text-decoration-style: wavy;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:decoration-2 a {
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:underline-offset-4 a {
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:bg-purple-800:hover a {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(85 33 181 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:p-5:hover a {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:p-1:hover a {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-red-600:hover a {
|
||||
text-decoration-color: #E02424;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-purple-600:hover a {
|
||||
text-decoration-color: #7E3AF2;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-purple-300:hover a {
|
||||
text-decoration-color: #CABFFD;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-purple-400:hover a {
|
||||
text-decoration-color: #AC94FA;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-purple-500:hover a {
|
||||
text-decoration-color: #9061F9;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-purple-900:hover a {
|
||||
text-decoration-color: #4A1D96;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:decoration-gray-800:hover a {
|
||||
text-decoration-color: #1F2937;
|
||||
}
|
||||
|
||||
.\[\&_a\]\:hover\:underline-offset-8:hover a {
|
||||
text-underline-offset: 8px;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 border-b">
|
||||
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 border-b [&_a]:underline [&_a]:underline-offset-4 [&_a]:decoration-2">
|
||||
{% if slot %}
|
||||
{{ slot }}
|
||||
{% else %}
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
|||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
|
||||
from common.utils import truncate_with_popover
|
||||
from common.utils import A, truncate_with_popover
|
||||
from games.forms import EditionForm
|
||||
from games.models import Edition, Game
|
||||
from games.views.general import dateformat
|
||||
|
@ -48,7 +48,18 @@ def list_editions(request: HttpRequest) -> HttpResponse:
|
|||
],
|
||||
"rows": [
|
||||
[
|
||||
A(
|
||||
[
|
||||
(
|
||||
"href",
|
||||
reverse(
|
||||
"view_game",
|
||||
args=[edition.game.pk],
|
||||
),
|
||||
)
|
||||
],
|
||||
truncate_with_popover(edition.game.name),
|
||||
),
|
||||
truncate_with_popover(
|
||||
edition.name
|
||||
if edition.game.name != edition.name
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.template.loader import render_to_string
|
|||
from django.urls import reverse
|
||||
|
||||
from common.time import format_duration
|
||||
from common.utils import safe_division, truncate_with_popover
|
||||
from common.utils import A, safe_division, truncate_with_popover
|
||||
from games.forms import GameForm
|
||||
from games.models import Edition, Game, Purchase, Session
|
||||
from games.views.general import (
|
||||
|
@ -55,7 +55,18 @@ def list_games(request: HttpRequest) -> HttpResponse:
|
|||
],
|
||||
"rows": [
|
||||
[
|
||||
A(
|
||||
[
|
||||
(
|
||||
"href",
|
||||
reverse(
|
||||
"view_game",
|
||||
args=[game.pk],
|
||||
),
|
||||
)
|
||||
],
|
||||
truncate_with_popover(game.name),
|
||||
),
|
||||
truncate_with_popover(
|
||||
game.sort_name
|
||||
if game.sort_name is not None and game.name != game.sort_name
|
||||
|
|
|
@ -13,7 +13,7 @@ from django.template.loader import render_to_string
|
|||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from common.utils import truncate_with_popover
|
||||
from common.utils import A, truncate_with_popover
|
||||
from games.forms import PurchaseForm
|
||||
from games.models import Edition, Purchase
|
||||
from games.views.general import dateformat, use_custom_redirect
|
||||
|
@ -57,7 +57,18 @@ def list_purchases(request: HttpRequest) -> HttpResponse:
|
|||
],
|
||||
"rows": [
|
||||
[
|
||||
truncate_with_popover(purchase.edition.name),
|
||||
A(
|
||||
[
|
||||
(
|
||||
"href",
|
||||
reverse(
|
||||
"view_game",
|
||||
args=[purchase.edition.game.pk],
|
||||
),
|
||||
),
|
||||
],
|
||||
truncate_with_popover(purchase.edition.game.name),
|
||||
),
|
||||
purchase.platform,
|
||||
purchase.price,
|
||||
purchase.price_currency,
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.urls import reverse
|
|||
from django.utils import timezone
|
||||
|
||||
from common.time import format_duration
|
||||
from common.utils import truncate_with_popover
|
||||
from common.utils import A, truncate_with_popover
|
||||
from games.forms import SessionForm
|
||||
from games.models import Purchase, Session
|
||||
from games.views.general import (
|
||||
|
@ -56,7 +56,18 @@ def list_sessions(request: HttpRequest) -> HttpResponse:
|
|||
],
|
||||
"rows": [
|
||||
[
|
||||
A(
|
||||
[
|
||||
(
|
||||
"href",
|
||||
reverse(
|
||||
"view_game",
|
||||
args=[session.purchase.edition.game.pk],
|
||||
),
|
||||
)
|
||||
],
|
||||
truncate_with_popover(session.purchase.edition.name),
|
||||
),
|
||||
f"{session.timestamp_start.strftime(datetimeformat)}{f" — {session.timestamp_end.strftime(timeformat)}" if session.timestamp_end else ""}",
|
||||
(
|
||||
format_duration(session.duration_calculated, durationformat)
|
||||
|
|
Loading…
Reference in New Issue