Compare commits
No commits in common. "b8660d3200de934c780fce2793d90edf8c1003e6" and "0373572eb7f049a5ee36fe2b31eef70c8c2b2bf8" have entirely different histories.
b8660d3200
...
0373572eb7
@ -1,27 +0,0 @@
|
|||||||
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
|
|
||||||
I should do this at some point in the future and change things I don't like. */
|
|
||||||
import search from "../../model/search.js"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
view: function (vnode) {
|
|
||||||
return ["Möchtest du ", m(
|
|
||||||
"a.clear-search-link",
|
|
||||||
{
|
|
||||||
onclick: e => {
|
|
||||||
e.preventDefault()
|
|
||||||
search.query = null
|
|
||||||
},
|
|
||||||
href: "#",
|
|
||||||
role: "button",
|
|
||||||
tabindex: 0,
|
|
||||||
onkeypress: e => {
|
|
||||||
if ((e.key === "Enter" || e.key === " ")) {
|
|
||||||
e.preventDefault()
|
|
||||||
search.query = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`alle ${vnode.attrs.totalFavoriteCount} Favoriten anzeigen`,
|
|
||||||
), "?"]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
|
|
||||||
I should do this at some point in the future and change things I don't like. */
|
|
||||||
const PaginationControls = {
|
|
||||||
view: function (vnode) {
|
|
||||||
const { currentPage, totalPages, onPageChange } = vnode.attrs
|
|
||||||
|
|
||||||
if (totalPages <= 1) {
|
|
||||||
return null // Keine Steuerelemente anzeigen, wenn nicht benötigt
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = []
|
|
||||||
const delta = 2 // Anzahl der Seiten, die um die aktuelle Seite herum angezeigt werden
|
|
||||||
|
|
||||||
// Hilfsfunktion zum Hinzufügen einer Seitenzahl
|
|
||||||
const addPage = pageNumber => {
|
|
||||||
pages.push(
|
|
||||||
m(
|
|
||||||
"span.page-number",
|
|
||||||
{
|
|
||||||
class:
|
|
||||||
pageNumber === currentPage ? "active" : "clickable",
|
|
||||||
onclick: () =>
|
|
||||||
pageNumber !== currentPage
|
|
||||||
? onPageChange(pageNumber)
|
|
||||||
: null,
|
|
||||||
role: "button",
|
|
||||||
tabindex: 0, // Für Tastaturnavigation
|
|
||||||
onkeypress: e => {
|
|
||||||
// Für Tastaturnavigation (Enter/Space)
|
|
||||||
if (
|
|
||||||
(e.key === "Enter" || e.key === " ") &&
|
|
||||||
pageNumber !== currentPage
|
|
||||||
) {
|
|
||||||
e.preventDefault()
|
|
||||||
onPageChange(pageNumber)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aria-label": `Gehe zu Seite ${pageNumber}`,
|
|
||||||
"aria-current":
|
|
||||||
pageNumber === currentPage ? "page" : undefined,
|
|
||||||
},
|
|
||||||
pageNumber,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Zurück"-Button
|
|
||||||
const prevButton = m(
|
|
||||||
"button.pagination-button",
|
|
||||||
{
|
|
||||||
onclick: () => onPageChange(currentPage - 1),
|
|
||||||
disabled: currentPage === 1,
|
|
||||||
"aria-label": "Vorherige Seite",
|
|
||||||
},
|
|
||||||
"Zurück",
|
|
||||||
)
|
|
||||||
|
|
||||||
// "Weiter"-Button
|
|
||||||
const nextButton = m(
|
|
||||||
"button.pagination-button",
|
|
||||||
{
|
|
||||||
onclick: () => onPageChange(currentPage + 1),
|
|
||||||
disabled: currentPage === totalPages,
|
|
||||||
"aria-label": "Nächste Seite",
|
|
||||||
},
|
|
||||||
"Weiter",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logik zur Seitenzahlanzeige
|
|
||||||
if (totalPages <= 7) {
|
|
||||||
// Alle Seiten anzeigen, wenn es 7 oder weniger sind
|
|
||||||
for (let i = 1; i <= totalPages; i++) {
|
|
||||||
addPage(i)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addPage(1) // Immer die erste Seite anzeigen
|
|
||||||
|
|
||||||
if (currentPage > delta + 2) {
|
|
||||||
// Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Anfang entfernt ist
|
|
||||||
pages.push(m("span.dots", "..."))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bereich der Seiten um die aktuelle Seite bestimmen
|
|
||||||
let start = Math.max(2, currentPage - delta)
|
|
||||||
let end = Math.min(totalPages - 1, currentPage + delta)
|
|
||||||
|
|
||||||
if (currentPage <= delta + 1) {
|
|
||||||
// Aktuelle Seite ist nahe am Anfang
|
|
||||||
start = 2
|
|
||||||
end = Math.min(totalPages - 1, 2 * delta + 1)
|
|
||||||
} else if (currentPage >= totalPages - delta) {
|
|
||||||
// Aktuelle Seite ist nahe am Ende
|
|
||||||
start = Math.max(2, totalPages - 2 * delta)
|
|
||||||
end = totalPages - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Korrektur für den Fall, dass start und end sich überschneiden oder ungültig werden
|
|
||||||
if (start > end && currentPage < totalPages / 2) {
|
|
||||||
// Wenn start > end und wir sind eher am Anfang
|
|
||||||
end = start // Zeige zumindest die Startseite
|
|
||||||
} else if (start > end && currentPage > totalPages / 2) {
|
|
||||||
// Wenn start > end und wir sind eher am Ende
|
|
||||||
start = end // Zeige zumindest die Endseite
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = start; i <= end; i++) {
|
|
||||||
if (i > 0) addPage(i) // Sicherstellen, dass i positiv ist
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPage < totalPages - delta - 1 && end < totalPages - 1) {
|
|
||||||
// Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Ende entfernt ist
|
|
||||||
pages.push(m("span.dots", "..."))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalPages > 1) addPage(totalPages) // Immer die letzte Seite anzeigen (wenn nicht 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m("div.pagination-controls", [prevButton, ...pages, nextButton])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PaginationControls
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
export default {
|
|
||||||
view: function (vnode) {
|
|
||||||
const { song } = vnode.attrs
|
|
||||||
|
|
||||||
return m("article.song", [
|
|
||||||
m(".info", [
|
|
||||||
m(".title", song.title),
|
|
||||||
m(".artist", song.artist),
|
|
||||||
]),
|
|
||||||
m(".actions", [
|
|
||||||
m(
|
|
||||||
"span.material-symbols-outlined.heart-icon",
|
|
||||||
{
|
|
||||||
class: song.favorite ? "is-favorite" : "",
|
|
||||||
onclick: () => song.toggleFavorite(),
|
|
||||||
title: song.favorite
|
|
||||||
? "Als Favorit entfernen"
|
|
||||||
: "Als Favorit markieren",
|
|
||||||
},
|
|
||||||
"favorite",
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
|
|
||||||
I should do this at some point in the future and change things I don't like. */
|
|
||||||
export const Tab = {
|
|
||||||
view(vnode) {
|
|
||||||
return m(
|
|
||||||
m.route.Link,
|
|
||||||
{
|
|
||||||
class: m.route.get() === vnode.attrs.href ? "active" : "",
|
|
||||||
href: vnode.attrs.href,
|
|
||||||
role: "button", // Für Barrierefreiheit
|
|
||||||
tabindex: 0, // Für Tastaturnavigation
|
|
||||||
},
|
|
||||||
vnode.attrs.label,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,275 +0,0 @@
|
|||||||
/* TODO: TAINT: AI-generated. This file was AI-generated (Gemini and/or Cursor), and I haven't manually reviewed it yet.
|
|
||||||
I should do this at some point in the future and change things I don't like. */
|
|
||||||
|
|
||||||
/* Globale Resets und Basis-Styling (wie vorher) */
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
background-color: #f4f4f9;
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
main > p {
|
|
||||||
text-align: center;
|
|
||||||
padding: 1em;
|
|
||||||
font-size: 1.1em;
|
|
||||||
color: #757575;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Suchleiste (Höhe ca. 60px) */
|
|
||||||
search {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: #3f51b5; /* Indigo - Material Primary */
|
|
||||||
padding: 12px 16px;
|
|
||||||
z-index: 1000;
|
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
||||||
box-sizing: border-box; /* Stellt sicher, dass Padding die Höhe nicht sprengt */
|
|
||||||
height: 60px; /* Feste Höhe für genaue Positionierung */
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="search"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
font-size: 16px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="search"]::placeholder {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tab Bar (Höhe ca. 48px) */
|
|
||||||
nav {
|
|
||||||
display: flex;
|
|
||||||
position: fixed;
|
|
||||||
top: 60px; /* Direkt unter der Suchleiste */
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: #3f51b5; /* Gleiche Farbe wie Suchleiste für einen einheitlichen Header-Block */
|
|
||||||
z-index: 999; /* Unter der Suchleiste, falls sie überlappen könnten */
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); /* Dezenter Schatten */
|
|
||||||
height: 48px; /* Feste Höhe */
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex; /* Für vertikales Zentrieren des Texts */
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 8px; /* Horizontal Padding */
|
|
||||||
text-align: center;
|
|
||||||
color: rgba(
|
|
||||||
255,
|
|
||||||
255,
|
|
||||||
255,
|
|
||||||
0.75
|
|
||||||
); /* Etwas helleres, aber gedämpftes Weiß für inaktive Tabs */
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.9em; /* Leicht kleiner als Haupttext */
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-bottom: 3px solid transparent; /* Platzhalter für den aktiven Indikator */
|
|
||||||
transition:
|
|
||||||
color 0.2s ease-in-out,
|
|
||||||
border-bottom-color 0.2s ease-in-out;
|
|
||||||
user-select: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1); /* Leichter Hover-Effekt */
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a.active {
|
|
||||||
color: #ffffff; /* Reines Weiß für aktiven Tab */
|
|
||||||
border-bottom-color: #ff4081; /* Material Accent Pink für den Indikator */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* song .Liste Container */
|
|
||||||
main {
|
|
||||||
padding-top: calc(60px + 48px); /* Höhe Suchleiste + Höhe TabBar */
|
|
||||||
padding-bottom: 20px; /* Platz für Paginierung, falls am Ende */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Link zum Zurücksetzen der Suche in der main > p */
|
|
||||||
main > p a.clear-search-link,
|
|
||||||
main > p .clear-search-link {
|
|
||||||
/* Zweite Regel für den Fall, dass Mithril das 'a' nicht direkt rendert */
|
|
||||||
color: #3f51b5; /* Material Indigo, passend zum Header */
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500; /* Etwas hervorheben */
|
|
||||||
background: none; /* Sicherstellen, dass keine Button-Styles übernommen werden */
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
display: inline; /* Damit es sich wie ein normaler Link im Text verhält */
|
|
||||||
}
|
|
||||||
|
|
||||||
main > p a.clear-search-link:hover,
|
|
||||||
main > p a.clear-search-link:focus,
|
|
||||||
main > p .clear-search-link:hover,
|
|
||||||
main > p .clear-search-link:focus {
|
|
||||||
color: #303f9f; /* Dunkleres Indigo für Hover/Focus */
|
|
||||||
text-decoration: none; /* Optional: Unterstreichung bei Hover entfernen */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Restliche Styles für Artist-Group, song .Item etc. bleiben wie zuvor */
|
|
||||||
section.artist {
|
|
||||||
margin: 12px 8px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.artist > h2 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background-color: #e8eaf6;
|
|
||||||
color: #303f9f;
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: 500;
|
|
||||||
border-bottom: 1px solid #c5cae9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
transition: background-color 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song:hover {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song .info {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin-right: 8px; /* Abstand zum Icon */
|
|
||||||
overflow: hidden; /* Verhindert Textüberlauf */
|
|
||||||
}
|
|
||||||
|
|
||||||
.song .title {
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 400;
|
|
||||||
color: #212121;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis; /* Text abschneiden bei Überlauf */
|
|
||||||
}
|
|
||||||
|
|
||||||
.song .artist {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: #757575;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song .actions {
|
|
||||||
margin-left: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heart-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #bdbdbd;
|
|
||||||
font-size: 24px;
|
|
||||||
user-select: none;
|
|
||||||
transition:
|
|
||||||
color 0.2s ease-in-out,
|
|
||||||
transform 0.1s ease-out;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heart-icon:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heart-icon.is-favorite {
|
|
||||||
color: #ff4081;
|
|
||||||
font-variation-settings: "FILL" 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-symbols-outlined {
|
|
||||||
font-variation-settings:
|
|
||||||
"FILL" 0,
|
|
||||||
"wght" 400,
|
|
||||||
"GRAD" 0,
|
|
||||||
"opsz" 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NEU: Pagination Controls Styles */
|
|
||||||
.pagination-controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px 0; /* Mehr Padding für bessere Sichtbarkeit */
|
|
||||||
margin-top: 10px;
|
|
||||||
user-select: none;
|
|
||||||
flex-wrap: wrap; /* Erlaubt Umbruch bei vielen Seitenzahlen auf kleinen Bildschirmen */
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-controls button.pagination-button, /* Klasse für Buttons hinzugefügt */
|
|
||||||
.pagination-controls span.page-number {
|
|
||||||
margin: 4px; /* Etwas mehr Margin für Umbruch */
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #3f51b5; /* Material Indigo */
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition:
|
|
||||||
background-color 0.2s,
|
|
||||||
color 0.2s,
|
|
||||||
border-color 0.2s;
|
|
||||||
font-size: 0.9em;
|
|
||||||
min-width: 36px; /* Mindestbreite für bessere Klickbarkeit */
|
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-controls button.pagination-button:hover:not(:disabled),
|
|
||||||
.pagination-controls span.page-number.clickable:hover {
|
|
||||||
background-color: #e8eaf6; /* Heller Indigo-Ton für Hover */
|
|
||||||
border-color: #c5cae9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-controls button.pagination-button:disabled {
|
|
||||||
color: #aaa;
|
|
||||||
cursor: not-allowed;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
border-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-controls span.page-number.active {
|
|
||||||
background-color: #3f51b5;
|
|
||||||
color: #fff;
|
|
||||||
border-color: #3f51b5;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-controls span.dots {
|
|
||||||
padding: 8px 6px;
|
|
||||||
color: #777;
|
|
||||||
margin: 4px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user