@rmdes/indiekit-endpoint-funkwhale 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -34
- package/assets/styles.css +181 -0
- package/package.json +2 -1
- package/views/funkwhale.njk +46 -206
- package/views/layouts/funkwhale.njk +6 -0
package/README.md
CHANGED
|
@@ -1,50 +1,54 @@
|
|
|
1
1
|
# @rmdes/indiekit-endpoint-funkwhale
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@rmdes/indiekit-endpoint-funkwhale)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
3
6
|
Funkwhale listening activity endpoint for [Indiekit](https://getindiekit.com/).
|
|
4
7
|
|
|
5
8
|
Display your Funkwhale listening history, favorite tracks, and listening statistics on your IndieWeb site.
|
|
6
9
|
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Install from npm:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @rmdes/indiekit-endpoint-funkwhale
|
|
16
|
+
```
|
|
17
|
+
|
|
7
18
|
## Features
|
|
8
19
|
|
|
20
|
+
- **Admin Dashboard** - Overview of your listening activity in Indiekit's admin UI
|
|
9
21
|
- **Now Playing Widget** - Shows currently playing or recently played tracks
|
|
10
22
|
- **Listening History** - Browse your listening history with album art
|
|
11
23
|
- **Favorites** - Display your favorite tracks
|
|
12
|
-
- **Statistics** - View listening stats
|
|
13
|
-
|
|
14
|
-
- Top Artists and Albums
|
|
15
|
-
- Listening trend charts
|
|
24
|
+
- **Statistics** - View listening stats (plays, unique tracks, unique artists)
|
|
25
|
+
- **Background Sync** - Automatically syncs listening data to MongoDB
|
|
16
26
|
- **Public JSON API** - For integration with static site generators like Eleventy
|
|
17
27
|
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install @rmdes/indiekit-endpoint-funkwhale
|
|
22
|
-
```
|
|
23
|
-
|
|
24
28
|
## Configuration
|
|
25
29
|
|
|
26
30
|
Add to your `indiekit.config.js`:
|
|
27
31
|
|
|
28
32
|
```javascript
|
|
33
|
+
import FunkwhaleEndpoint from "@rmdes/indiekit-endpoint-funkwhale";
|
|
34
|
+
|
|
29
35
|
export default {
|
|
30
36
|
plugins: [
|
|
31
|
-
|
|
37
|
+
new FunkwhaleEndpoint({
|
|
38
|
+
mountPath: "/funkwhale",
|
|
39
|
+
instanceUrl: process.env.FUNKWHALE_INSTANCE,
|
|
40
|
+
username: process.env.FUNKWHALE_USERNAME,
|
|
41
|
+
token: process.env.FUNKWHALE_TOKEN,
|
|
42
|
+
cacheTtl: 900_000, // 15 minutes
|
|
43
|
+
syncInterval: 300_000, // 5 minutes
|
|
44
|
+
limits: {
|
|
45
|
+
listenings: 20,
|
|
46
|
+
favorites: 20,
|
|
47
|
+
topArtists: 10,
|
|
48
|
+
topAlbums: 10
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
32
51
|
],
|
|
33
|
-
|
|
34
|
-
"@rmdes/indiekit-endpoint-funkwhale": {
|
|
35
|
-
mountPath: "/funkwhale",
|
|
36
|
-
instanceUrl: process.env.FUNKWHALE_INSTANCE,
|
|
37
|
-
username: process.env.FUNKWHALE_USERNAME,
|
|
38
|
-
token: process.env.FUNKWHALE_TOKEN,
|
|
39
|
-
cacheTtl: 900_000, // 15 minutes
|
|
40
|
-
syncInterval: 300_000, // 5 minutes
|
|
41
|
-
limits: {
|
|
42
|
-
listenings: 20,
|
|
43
|
-
favorites: 20,
|
|
44
|
-
topArtists: 10,
|
|
45
|
-
topAlbums: 10
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
52
|
};
|
|
49
53
|
```
|
|
50
54
|
|
|
@@ -65,24 +69,45 @@ export default {
|
|
|
65
69
|
|
|
66
70
|
## Routes
|
|
67
71
|
|
|
68
|
-
###
|
|
72
|
+
### Admin Routes (require authentication)
|
|
69
73
|
|
|
70
74
|
| Route | Description |
|
|
71
75
|
|-------|-------------|
|
|
72
|
-
| `GET /funkwhale/` | Dashboard with
|
|
73
|
-
| `
|
|
74
|
-
| `GET /funkwhale/favorites` | Favorite tracks |
|
|
75
|
-
| `GET /funkwhale/stats` | Statistics with tabs |
|
|
76
|
+
| `GET /funkwhale/` | Dashboard overview with stats, recent plays, favorites |
|
|
77
|
+
| `POST /funkwhale/sync` | Trigger manual sync |
|
|
76
78
|
|
|
77
79
|
### Public API Routes (JSON)
|
|
78
80
|
|
|
81
|
+
These endpoints are publicly accessible and can be used by static site generators like Eleventy to display listening activity on your site.
|
|
82
|
+
|
|
79
83
|
| Route | Description |
|
|
80
84
|
|-------|-------------|
|
|
81
85
|
| `GET /funkwhale/api/now-playing` | Current/recent track |
|
|
82
86
|
| `GET /funkwhale/api/listenings` | Recent listenings |
|
|
83
87
|
| `GET /funkwhale/api/favorites` | Favorites list |
|
|
84
|
-
| `GET /funkwhale/api/stats` | All statistics |
|
|
85
|
-
| `GET /funkwhale/api/stats/trends` | Trend data for charts |
|
|
88
|
+
| `GET /funkwhale/api/stats` | All statistics (summary, top artists, top albums) |
|
|
89
|
+
| `GET /funkwhale/api/stats/trends` | Trend data for charts (30 days) |
|
|
90
|
+
|
|
91
|
+
### Example: Eleventy Integration
|
|
92
|
+
|
|
93
|
+
Fetch data from the public API in your Eleventy `_data` file:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
// _data/funkwhale.js
|
|
97
|
+
import EleventyFetch from "@11ty/eleventy-fetch";
|
|
98
|
+
|
|
99
|
+
export default async function() {
|
|
100
|
+
const baseUrl = process.env.SITE_URL || "https://example.com";
|
|
101
|
+
|
|
102
|
+
const [nowPlaying, listenings, stats] = await Promise.all([
|
|
103
|
+
EleventyFetch(`${baseUrl}/funkwhale/api/now-playing`, { duration: "15m", type: "json" }),
|
|
104
|
+
EleventyFetch(`${baseUrl}/funkwhale/api/listenings`, { duration: "15m", type: "json" }),
|
|
105
|
+
EleventyFetch(`${baseUrl}/funkwhale/api/stats`, { duration: "15m", type: "json" }),
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
return { nowPlaying, listenings, stats };
|
|
109
|
+
}
|
|
110
|
+
```
|
|
86
111
|
|
|
87
112
|
## Options
|
|
88
113
|
|
|
@@ -108,7 +133,7 @@ export default {
|
|
|
108
133
|
## Requirements
|
|
109
134
|
|
|
110
135
|
- Indiekit >= 1.0.0-beta.25
|
|
111
|
-
- MongoDB (for statistics aggregation)
|
|
136
|
+
- MongoDB (for statistics aggregation and sync)
|
|
112
137
|
- Funkwhale instance with API v2
|
|
113
138
|
|
|
114
139
|
## License
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/* Funkwhale endpoint styles */
|
|
2
|
+
|
|
3
|
+
/* Stats grid */
|
|
4
|
+
.funkwhale-stats {
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: repeat(3, 1fr);
|
|
7
|
+
gap: var(--space-s);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.funkwhale-stat {
|
|
11
|
+
align-items: center;
|
|
12
|
+
background: var(--color-offset);
|
|
13
|
+
border-radius: var(--radius-m);
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
padding: var(--space-s);
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.funkwhale-stat__value {
|
|
21
|
+
color: var(--color-accent);
|
|
22
|
+
font-size: var(--step-2);
|
|
23
|
+
font-weight: var(--font-weight-bold);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.funkwhale-stat__label {
|
|
27
|
+
color: var(--color-text-secondary);
|
|
28
|
+
font-size: var(--step--1);
|
|
29
|
+
letter-spacing: 0.05em;
|
|
30
|
+
text-transform: uppercase;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Featured track */
|
|
34
|
+
.funkwhale-featured {
|
|
35
|
+
align-items: flex-start;
|
|
36
|
+
background: var(--color-offset);
|
|
37
|
+
border-radius: var(--radius-m);
|
|
38
|
+
display: flex;
|
|
39
|
+
gap: var(--space-m);
|
|
40
|
+
padding: var(--space-m);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.funkwhale-featured img {
|
|
44
|
+
border-radius: var(--radius-s);
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
height: 80px;
|
|
47
|
+
object-fit: cover;
|
|
48
|
+
width: 80px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.funkwhale-featured__info {
|
|
52
|
+
flex: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.funkwhale-featured__title {
|
|
56
|
+
color: inherit;
|
|
57
|
+
display: block;
|
|
58
|
+
font-weight: var(--font-weight-semibold);
|
|
59
|
+
text-decoration: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.funkwhale-featured__title:hover {
|
|
63
|
+
text-decoration: underline;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.funkwhale-featured__album {
|
|
67
|
+
color: var(--color-text-secondary);
|
|
68
|
+
font-size: var(--step--1);
|
|
69
|
+
margin: var(--space-3xs) 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.funkwhale-meta {
|
|
73
|
+
color: var(--color-text-secondary);
|
|
74
|
+
display: flex;
|
|
75
|
+
font-size: var(--step--2);
|
|
76
|
+
gap: var(--space-xs);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Now playing animation */
|
|
80
|
+
.funkwhale-playing {
|
|
81
|
+
align-items: center;
|
|
82
|
+
color: #22c55e;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
gap: var(--space-xs);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.funkwhale-bars {
|
|
88
|
+
align-items: flex-end;
|
|
89
|
+
display: flex;
|
|
90
|
+
gap: 2px;
|
|
91
|
+
height: 16px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.funkwhale-bars span {
|
|
95
|
+
animation: funkwhale-bar 0.5s infinite ease-in-out alternate;
|
|
96
|
+
background: currentColor;
|
|
97
|
+
width: 3px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.funkwhale-bars span:nth-child(2) {
|
|
101
|
+
animation-delay: 0.2s;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.funkwhale-bars span:nth-child(3) {
|
|
105
|
+
animation-delay: 0.4s;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@keyframes funkwhale-bar {
|
|
109
|
+
from { height: 4px; }
|
|
110
|
+
to { height: 16px; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* List */
|
|
114
|
+
.funkwhale-list {
|
|
115
|
+
list-style: none;
|
|
116
|
+
margin: 0;
|
|
117
|
+
padding: 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.funkwhale-list__item {
|
|
121
|
+
align-items: center;
|
|
122
|
+
border-block-end: 1px solid var(--color-border);
|
|
123
|
+
display: flex;
|
|
124
|
+
gap: var(--space-s);
|
|
125
|
+
padding: var(--space-s) 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.funkwhale-list__item:last-child {
|
|
129
|
+
border-block-end: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.funkwhale-list__item img {
|
|
133
|
+
border-radius: var(--radius-s);
|
|
134
|
+
flex-shrink: 0;
|
|
135
|
+
height: 48px;
|
|
136
|
+
object-fit: cover;
|
|
137
|
+
width: 48px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.funkwhale-list__placeholder {
|
|
141
|
+
background: var(--color-border);
|
|
142
|
+
border-radius: var(--radius-s);
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
height: 48px;
|
|
145
|
+
width: 48px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.funkwhale-list__info {
|
|
149
|
+
flex: 1;
|
|
150
|
+
min-width: 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.funkwhale-list__title {
|
|
154
|
+
color: inherit;
|
|
155
|
+
display: block;
|
|
156
|
+
font-weight: var(--font-weight-medium);
|
|
157
|
+
overflow: hidden;
|
|
158
|
+
text-decoration: none;
|
|
159
|
+
text-overflow: ellipsis;
|
|
160
|
+
white-space: nowrap;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.funkwhale-list__title:hover {
|
|
164
|
+
text-decoration: underline;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Public link banner */
|
|
168
|
+
.funkwhale-public-link {
|
|
169
|
+
align-items: center;
|
|
170
|
+
background: var(--color-offset);
|
|
171
|
+
border-radius: var(--radius-m);
|
|
172
|
+
display: flex;
|
|
173
|
+
justify-content: space-between;
|
|
174
|
+
margin-block-start: var(--space-xl);
|
|
175
|
+
padding: var(--space-m);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.funkwhale-public-link p {
|
|
179
|
+
color: var(--color-text-secondary);
|
|
180
|
+
margin: 0;
|
|
181
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-funkwhale",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Funkwhale listening activity endpoint for Indiekit. Display listening history, favorites, and statistics.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
".": "./index.js"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
|
+
"assets",
|
|
37
38
|
"includes",
|
|
38
39
|
"lib",
|
|
39
40
|
"locales",
|
package/views/funkwhale.njk
CHANGED
|
@@ -1,235 +1,76 @@
|
|
|
1
|
-
{% extends "
|
|
2
|
-
|
|
3
|
-
{% block content %}
|
|
4
|
-
<style>
|
|
5
|
-
/* Inline styles to ensure proper rendering */
|
|
6
|
-
.fw-section { margin-bottom: 2rem; }
|
|
7
|
-
.fw-section h2 { margin-bottom: 1rem; }
|
|
8
|
-
|
|
9
|
-
/* Stats grid */
|
|
10
|
-
.fw-stats-grid {
|
|
11
|
-
display: grid;
|
|
12
|
-
grid-template-columns: repeat(3, 1fr);
|
|
13
|
-
gap: 1rem;
|
|
14
|
-
margin-bottom: 1rem;
|
|
15
|
-
}
|
|
16
|
-
.fw-stat {
|
|
17
|
-
display: flex;
|
|
18
|
-
flex-direction: column;
|
|
19
|
-
align-items: center;
|
|
20
|
-
padding: 1rem;
|
|
21
|
-
background: var(--color-offset, #f5f5f5);
|
|
22
|
-
border-radius: 0.5rem;
|
|
23
|
-
text-align: center;
|
|
24
|
-
}
|
|
25
|
-
.fw-stat-value {
|
|
26
|
-
font-size: 1.5rem;
|
|
27
|
-
font-weight: 700;
|
|
28
|
-
color: var(--color-accent, #3b82f6);
|
|
29
|
-
}
|
|
30
|
-
.fw-stat-label {
|
|
31
|
-
font-size: 0.75rem;
|
|
32
|
-
color: var(--color-text-secondary, #666);
|
|
33
|
-
text-transform: uppercase;
|
|
34
|
-
letter-spacing: 0.05em;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/* Featured track */
|
|
38
|
-
.fw-featured {
|
|
39
|
-
display: flex;
|
|
40
|
-
gap: 1rem;
|
|
41
|
-
padding: 1rem;
|
|
42
|
-
background: var(--color-offset, #f5f5f5);
|
|
43
|
-
border-radius: 0.5rem;
|
|
44
|
-
align-items: flex-start;
|
|
45
|
-
}
|
|
46
|
-
.fw-featured img {
|
|
47
|
-
width: 80px;
|
|
48
|
-
height: 80px;
|
|
49
|
-
object-fit: cover;
|
|
50
|
-
border-radius: 0.25rem;
|
|
51
|
-
flex-shrink: 0;
|
|
52
|
-
}
|
|
53
|
-
.fw-featured-info { flex: 1; }
|
|
54
|
-
.fw-featured-title {
|
|
55
|
-
font-weight: 600;
|
|
56
|
-
text-decoration: none;
|
|
57
|
-
color: inherit;
|
|
58
|
-
display: block;
|
|
59
|
-
}
|
|
60
|
-
.fw-featured-title:hover { text-decoration: underline; }
|
|
61
|
-
.fw-featured-album {
|
|
62
|
-
margin: 0.25rem 0;
|
|
63
|
-
color: var(--color-text-secondary, #666);
|
|
64
|
-
font-size: 0.875rem;
|
|
65
|
-
}
|
|
66
|
-
.fw-meta {
|
|
67
|
-
display: flex;
|
|
68
|
-
gap: 0.5rem;
|
|
69
|
-
color: var(--color-text-secondary, #666);
|
|
70
|
-
font-size: 0.75rem;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/* Now playing animation */
|
|
74
|
-
.fw-playing-status {
|
|
75
|
-
display: inline-flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
gap: 0.5rem;
|
|
78
|
-
color: #22c55e;
|
|
79
|
-
}
|
|
80
|
-
.fw-bars {
|
|
81
|
-
display: flex;
|
|
82
|
-
gap: 2px;
|
|
83
|
-
align-items: flex-end;
|
|
84
|
-
height: 16px;
|
|
85
|
-
}
|
|
86
|
-
.fw-bars span {
|
|
87
|
-
width: 3px;
|
|
88
|
-
background: currentColor;
|
|
89
|
-
animation: fw-bar 0.5s infinite ease-in-out alternate;
|
|
90
|
-
}
|
|
91
|
-
.fw-bars span:nth-child(2) { animation-delay: 0.2s; }
|
|
92
|
-
.fw-bars span:nth-child(3) { animation-delay: 0.4s; }
|
|
93
|
-
@keyframes fw-bar {
|
|
94
|
-
from { height: 4px; }
|
|
95
|
-
to { height: 16px; }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/* List */
|
|
99
|
-
.fw-list {
|
|
100
|
-
list-style: none;
|
|
101
|
-
padding: 0;
|
|
102
|
-
margin: 0 0 1rem 0;
|
|
103
|
-
}
|
|
104
|
-
.fw-list-item {
|
|
105
|
-
display: flex;
|
|
106
|
-
align-items: center;
|
|
107
|
-
gap: 0.75rem;
|
|
108
|
-
padding: 0.75rem 0;
|
|
109
|
-
border-bottom: 1px solid var(--color-border, #eee);
|
|
110
|
-
}
|
|
111
|
-
.fw-list-item:last-child { border-bottom: none; }
|
|
112
|
-
.fw-list-item img {
|
|
113
|
-
width: 48px;
|
|
114
|
-
height: 48px;
|
|
115
|
-
object-fit: cover;
|
|
116
|
-
border-radius: 0.25rem;
|
|
117
|
-
flex-shrink: 0;
|
|
118
|
-
}
|
|
119
|
-
.fw-list-item-placeholder {
|
|
120
|
-
width: 48px;
|
|
121
|
-
height: 48px;
|
|
122
|
-
background: var(--color-border, #ddd);
|
|
123
|
-
border-radius: 0.25rem;
|
|
124
|
-
flex-shrink: 0;
|
|
125
|
-
}
|
|
126
|
-
.fw-list-info {
|
|
127
|
-
flex: 1;
|
|
128
|
-
min-width: 0;
|
|
129
|
-
}
|
|
130
|
-
.fw-list-title {
|
|
131
|
-
display: block;
|
|
132
|
-
font-weight: 500;
|
|
133
|
-
text-decoration: none;
|
|
134
|
-
color: inherit;
|
|
135
|
-
white-space: nowrap;
|
|
136
|
-
overflow: hidden;
|
|
137
|
-
text-overflow: ellipsis;
|
|
138
|
-
}
|
|
139
|
-
.fw-list-title:hover { text-decoration: underline; }
|
|
140
|
-
|
|
141
|
-
/* Public link banner */
|
|
142
|
-
.fw-public-link {
|
|
143
|
-
display: flex;
|
|
144
|
-
align-items: center;
|
|
145
|
-
justify-content: space-between;
|
|
146
|
-
padding: 1rem;
|
|
147
|
-
background: var(--color-offset, #f5f5f5);
|
|
148
|
-
border-radius: 0.5rem;
|
|
149
|
-
margin-top: 2rem;
|
|
150
|
-
}
|
|
151
|
-
.fw-public-link p {
|
|
152
|
-
margin: 0;
|
|
153
|
-
color: var(--color-text-secondary, #666);
|
|
154
|
-
}
|
|
155
|
-
</style>
|
|
1
|
+
{% extends "layouts/funkwhale.njk" %}
|
|
156
2
|
|
|
3
|
+
{% block funkwhale %}
|
|
157
4
|
{% if error %}
|
|
158
5
|
{{ prose({ text: error.message }) }}
|
|
159
6
|
{% else %}
|
|
160
7
|
{# Now Playing / Recently Played #}
|
|
161
8
|
{% if nowPlaying %}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{{ __("funkwhale.recentlyPlayed") }}
|
|
173
|
-
{% endif %}
|
|
174
|
-
</h2>
|
|
175
|
-
<article class="fw-featured">
|
|
9
|
+
{% call section({ title: nowPlaying.status == "now-playing" and __("funkwhale.nowPlaying") or __("funkwhale.recentlyPlayed") }) %}
|
|
10
|
+
{% if nowPlaying.status == "now-playing" %}
|
|
11
|
+
<p class="funkwhale-playing">
|
|
12
|
+
<span class="funkwhale-bars">
|
|
13
|
+
<span></span><span></span><span></span>
|
|
14
|
+
</span>
|
|
15
|
+
{{ __("funkwhale.nowPlaying") }}
|
|
16
|
+
</p>
|
|
17
|
+
{% endif %}
|
|
18
|
+
<article class="funkwhale-featured">
|
|
176
19
|
{% if nowPlaying.coverUrl %}
|
|
177
20
|
<img src="{{ nowPlaying.coverUrl }}" alt="" loading="lazy">
|
|
178
21
|
{% endif %}
|
|
179
|
-
<div class="
|
|
180
|
-
<a href="{{ nowPlaying.trackUrl }}" class="
|
|
22
|
+
<div class="funkwhale-featured__info">
|
|
23
|
+
<a href="{{ nowPlaying.trackUrl }}" class="funkwhale-featured__title" target="_blank" rel="noopener">
|
|
181
24
|
{{ nowPlaying.artist }} - {{ nowPlaying.track }}
|
|
182
25
|
</a>
|
|
183
26
|
{% if nowPlaying.album %}
|
|
184
|
-
<p class="
|
|
27
|
+
<p class="funkwhale-featured__album">{{ nowPlaying.album }}</p>
|
|
185
28
|
{% endif %}
|
|
186
|
-
<small class="
|
|
29
|
+
<small class="funkwhale-meta">
|
|
187
30
|
<span>{{ nowPlaying.duration }}</span>
|
|
188
31
|
<span>{{ nowPlaying.relativeTime }}</span>
|
|
189
32
|
</small>
|
|
190
33
|
</div>
|
|
191
34
|
</article>
|
|
192
|
-
|
|
35
|
+
{% endcall %}
|
|
193
36
|
{% endif %}
|
|
194
37
|
|
|
195
38
|
{# Quick Stats Summary #}
|
|
196
39
|
{% if hasStats %}
|
|
197
|
-
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<span class="
|
|
202
|
-
<span class="fw-stat-label">{{ __("funkwhale.plays") }}</span>
|
|
40
|
+
{% call section({ title: __("funkwhale.stats") }) %}
|
|
41
|
+
<div class="funkwhale-stats">
|
|
42
|
+
<div class="funkwhale-stat">
|
|
43
|
+
<span class="funkwhale-stat__value">{{ totalPlays }}</span>
|
|
44
|
+
<span class="funkwhale-stat__label">{{ __("funkwhale.plays") }}</span>
|
|
203
45
|
</div>
|
|
204
|
-
<div class="
|
|
205
|
-
<span class="
|
|
206
|
-
<span class="
|
|
46
|
+
<div class="funkwhale-stat">
|
|
47
|
+
<span class="funkwhale-stat__value">{{ uniqueTracks }}</span>
|
|
48
|
+
<span class="funkwhale-stat__label">{{ __("funkwhale.tracks") }}</span>
|
|
207
49
|
</div>
|
|
208
|
-
<div class="
|
|
209
|
-
<span class="
|
|
210
|
-
<span class="
|
|
50
|
+
<div class="funkwhale-stat">
|
|
51
|
+
<span class="funkwhale-stat__value">{{ uniqueArtists }}</span>
|
|
52
|
+
<span class="funkwhale-stat__label">{{ __("funkwhale.artists") }}</span>
|
|
211
53
|
</div>
|
|
212
54
|
</div>
|
|
213
|
-
|
|
55
|
+
{% endcall %}
|
|
214
56
|
{% endif %}
|
|
215
57
|
|
|
216
58
|
{# Recent Listenings #}
|
|
217
|
-
|
|
218
|
-
<h2>{{ __("funkwhale.listenings") }}</h2>
|
|
59
|
+
{% call section({ title: __("funkwhale.listenings") }) %}
|
|
219
60
|
{% if listenings and listenings.length > 0 %}
|
|
220
|
-
<ul class="
|
|
61
|
+
<ul class="funkwhale-list">
|
|
221
62
|
{% for listening in listenings %}
|
|
222
|
-
<li class="
|
|
63
|
+
<li class="funkwhale-list__item">
|
|
223
64
|
{% if listening.coverUrl %}
|
|
224
65
|
<img src="{{ listening.coverUrl }}" alt="" loading="lazy">
|
|
225
66
|
{% else %}
|
|
226
|
-
<div class="
|
|
67
|
+
<div class="funkwhale-list__placeholder"></div>
|
|
227
68
|
{% endif %}
|
|
228
|
-
<div class="
|
|
229
|
-
<a href="{{ listening.trackUrl }}" class="
|
|
69
|
+
<div class="funkwhale-list__info">
|
|
70
|
+
<a href="{{ listening.trackUrl }}" class="funkwhale-list__title" target="_blank" rel="noopener">
|
|
230
71
|
{{ listening.artist }} - {{ listening.track }}
|
|
231
72
|
</a>
|
|
232
|
-
<small class="
|
|
73
|
+
<small class="funkwhale-meta">{{ listening.relativeTime }}</small>
|
|
233
74
|
</div>
|
|
234
75
|
{% if listening.status == "now-playing" %}
|
|
235
76
|
{{ badge({ color: "green", text: __("funkwhale.nowPlaying") }) }}
|
|
@@ -242,26 +83,25 @@
|
|
|
242
83
|
{% else %}
|
|
243
84
|
{{ prose({ text: __("funkwhale.noRecentPlays") }) }}
|
|
244
85
|
{% endif %}
|
|
245
|
-
|
|
86
|
+
{% endcall %}
|
|
246
87
|
|
|
247
88
|
{# Favorites #}
|
|
248
|
-
|
|
249
|
-
<h2>{{ __("funkwhale.favorites") }}</h2>
|
|
89
|
+
{% call section({ title: __("funkwhale.favorites") }) %}
|
|
250
90
|
{% if favorites and favorites.length > 0 %}
|
|
251
|
-
<ul class="
|
|
91
|
+
<ul class="funkwhale-list">
|
|
252
92
|
{% for favorite in favorites %}
|
|
253
|
-
<li class="
|
|
93
|
+
<li class="funkwhale-list__item">
|
|
254
94
|
{% if favorite.coverUrl %}
|
|
255
95
|
<img src="{{ favorite.coverUrl }}" alt="" loading="lazy">
|
|
256
96
|
{% else %}
|
|
257
|
-
<div class="
|
|
97
|
+
<div class="funkwhale-list__placeholder"></div>
|
|
258
98
|
{% endif %}
|
|
259
|
-
<div class="
|
|
260
|
-
<a href="{{ favorite.trackUrl }}" class="
|
|
99
|
+
<div class="funkwhale-list__info">
|
|
100
|
+
<a href="{{ favorite.trackUrl }}" class="funkwhale-list__title" target="_blank" rel="noopener">
|
|
261
101
|
{{ favorite.artist }} - {{ favorite.track }}
|
|
262
102
|
</a>
|
|
263
103
|
{% if favorite.album %}
|
|
264
|
-
<small class="
|
|
104
|
+
<small class="funkwhale-meta">{{ favorite.album }}</small>
|
|
265
105
|
{% endif %}
|
|
266
106
|
</div>
|
|
267
107
|
</li>
|
|
@@ -270,10 +110,10 @@
|
|
|
270
110
|
{% else %}
|
|
271
111
|
{{ prose({ text: __("funkwhale.noFavorites") }) }}
|
|
272
112
|
{% endif %}
|
|
273
|
-
|
|
113
|
+
{% endcall %}
|
|
274
114
|
|
|
275
115
|
{# Link to public page #}
|
|
276
|
-
<div class="
|
|
116
|
+
<div class="funkwhale-public-link">
|
|
277
117
|
<p>{{ __("funkwhale.widget.description") }}</p>
|
|
278
118
|
{{ button({
|
|
279
119
|
href: publicUrl,
|