mirror of
https://git.maid.zone/stuff/soundcloak.git
synced 2025-12-10 21:59:38 +05:00
Highlight @username, links and emails in descriptions; and some fixes
This commit is contained in:
@@ -5,11 +5,12 @@ wip alternative frontend for soundcloud
|
|||||||
|
|
||||||
# Already implemented
|
# Already implemented
|
||||||
- Searching for songs, users, playlists
|
- Searching for songs, users, playlists
|
||||||
- Basic user overview (songs, playlists, albums, metadata)
|
- Basic user overview (songs, playlists, albums, reposts, metadata)
|
||||||
- Basic song overview (author, metadata) & streaming (requires some JS if instance has `Restream` disabled)
|
- Basic song overview (author, metadata) & streaming (requires some JS if instance has `Restream` disabled)
|
||||||
- Basic playlist/set/album overview (songs list, author, metadata)
|
- Basic playlist/set/album overview (songs list, author, metadata)
|
||||||
- Resolving shortened links (`https://on.soundcloud.com/boiKDP46fayYDoVK9` -> `https://sc.maid.zone/on/boiKDP46fayYDoVK9`)
|
- Resolving shortened links (`https://on.soundcloud.com/boiKDP46fayYDoVK9` -> `https://sc.maid.zone/on/boiKDP46fayYDoVK9`)
|
||||||
- Content proxying (images, audio)
|
- Content proxying (images, audio)
|
||||||
|
- View featured tracks, playlists
|
||||||
- Users can change their preferences (should proxying be enabled, what method of playing the song should be used etc)
|
- Users can change their preferences (should proxying be enabled, what method of playing the song should be used etc)
|
||||||
|
|
||||||
## In the works
|
## In the works
|
||||||
@@ -25,6 +26,7 @@ An easier way is to navigate to `<instance>/_/preferences`.
|
|||||||
If some features are disabled by the instance, they won't show up there.
|
If some features are disabled by the instance, they won't show up there.
|
||||||
|
|
||||||
Available features:
|
Available features:
|
||||||
|
- ParseDescriptions: Highlight `@username`, `https://example.com` and `email@example.com` in text as clickable links
|
||||||
- Proxy images: Retrieve images through the instance, instead of going to soundcloud's servers for them
|
- Proxy images: Retrieve images through the instance, instead of going to soundcloud's servers for them
|
||||||
- Player: In what way should the track be streamed. Can be Restream (does not require JS, better compatibility, can be a bit buggy client-side) or HLS (requires JS, more stable, less good compatibility (you'll be ok unless you are using a very outdated browser))
|
- Player: In what way should the track be streamed. Can be Restream (does not require JS, better compatibility, can be a bit buggy client-side) or HLS (requires JS, more stable, less good compatibility (you'll be ok unless you are using a very outdated browser))
|
||||||
- Player-specific settings: They will only show up if you have selected HLS player currently.
|
- Player-specific settings: They will only show up if you have selected HLS player currently.
|
||||||
|
|||||||
@@ -157,3 +157,7 @@ select:focus {
|
|||||||
grid-template: auto / auto auto auto;
|
grid-template: auto / auto auto auto;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
@@ -32,6 +32,9 @@ type Preferences struct {
|
|||||||
FullyPreloadTrack *bool
|
FullyPreloadTrack *bool
|
||||||
|
|
||||||
ProxyImages *bool
|
ProxyImages *bool
|
||||||
|
|
||||||
|
// Highlight @username, https://example.com and email@example.com in text as clickable links
|
||||||
|
ParseDescriptions *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// // config // //
|
// // config // //
|
||||||
@@ -218,8 +221,10 @@ func init() {
|
|||||||
// ProxyStreams: same as ProxyStreams in your config (false by default)
|
// ProxyStreams: same as ProxyStreams in your config (false by default)
|
||||||
// FullyPreloadTrack: false
|
// FullyPreloadTrack: false
|
||||||
// ProxyImages: same as ProxyImages in your config (false by default)
|
// ProxyImages: same as ProxyImages in your config (false by default)
|
||||||
|
// ParseDescriptions: true
|
||||||
if config.DefaultPreferences != nil {
|
if config.DefaultPreferences != nil {
|
||||||
var f bool
|
var f bool
|
||||||
|
var t = true
|
||||||
if config.DefaultPreferences.Player != nil {
|
if config.DefaultPreferences.Player != nil {
|
||||||
DefaultPreferences.Player = config.DefaultPreferences.Player
|
DefaultPreferences.Player = config.DefaultPreferences.Player
|
||||||
} else {
|
} else {
|
||||||
@@ -249,6 +254,12 @@ func init() {
|
|||||||
} else {
|
} else {
|
||||||
DefaultPreferences.ProxyImages = &ProxyImages
|
DefaultPreferences.ProxyImages = &ProxyImages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.DefaultPreferences.ParseDescriptions != nil {
|
||||||
|
DefaultPreferences.ParseDescriptions = config.DefaultPreferences.ParseDescriptions
|
||||||
|
} else {
|
||||||
|
DefaultPreferences.ParseDescriptions = &t
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var p string
|
var p string
|
||||||
if Restream {
|
if Restream {
|
||||||
@@ -261,8 +272,11 @@ func init() {
|
|||||||
DefaultPreferences.ProxyStreams = &ProxyStreams
|
DefaultPreferences.ProxyStreams = &ProxyStreams
|
||||||
|
|
||||||
var f bool
|
var f bool
|
||||||
|
var t = true
|
||||||
DefaultPreferences.FullyPreloadTrack = &f
|
DefaultPreferences.FullyPreloadTrack = &f
|
||||||
|
|
||||||
DefaultPreferences.ProxyImages = &ProxyImages
|
DefaultPreferences.ProxyImages = &ProxyImages
|
||||||
|
|
||||||
|
DefaultPreferences.ParseDescriptions = &t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ func Defaults(dst *cfg.Preferences) {
|
|||||||
if dst.ProxyImages == nil {
|
if dst.ProxyImages == nil {
|
||||||
dst.ProxyImages = cfg.DefaultPreferences.ProxyImages
|
dst.ProxyImages = cfg.DefaultPreferences.ProxyImages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dst.ParseDescriptions == nil {
|
||||||
|
dst.ParseDescriptions = cfg.DefaultPreferences.ParseDescriptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(c *fiber.Ctx) (cfg.Preferences, error) {
|
func Get(c *fiber.Ctx) (cfg.Preferences, error) {
|
||||||
@@ -43,6 +47,7 @@ func Get(c *fiber.Ctx) (cfg.Preferences, error) {
|
|||||||
|
|
||||||
type PrefsForm struct {
|
type PrefsForm struct {
|
||||||
ProxyImages string
|
ProxyImages string
|
||||||
|
ParseDescriptions string
|
||||||
Player string
|
Player string
|
||||||
ProxyStreams string
|
ProxyStreams string
|
||||||
FullyPreloadTrack string
|
FullyPreloadTrack string
|
||||||
@@ -96,6 +101,12 @@ func Load(r fiber.Router) {
|
|||||||
old.ProxyImages = &f
|
old.ProxyImages = &f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.ParseDescriptions == "on" {
|
||||||
|
old.ParseDescriptions = &t
|
||||||
|
} else {
|
||||||
|
old.ParseDescriptions = &f
|
||||||
|
}
|
||||||
old.Player = &p.Player
|
old.Player = &p.Player
|
||||||
|
|
||||||
data, err := json.Marshal(old)
|
data, err := json.Marshal(old)
|
||||||
|
|||||||
@@ -119,13 +119,19 @@ func GetClientID() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DoWithRetry(httpc *fasthttp.HostClient, req *fasthttp.Request, resp *fasthttp.Response) (err error) {
|
func DoWithRetry(httpc *fasthttp.HostClient, req *fasthttp.Request, resp *fasthttp.Response) (err error) {
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
err = httpc.Do(req, resp)
|
err = httpc.Do(req, resp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != fasthttp.ErrTimeout && err != fasthttp.ErrConnectionClosed && !os.IsTimeout(err) && !errors.Is(err, syscall.EPIPE) { // EPIPE is "broken pipe" error
|
if err != fasthttp.ErrTimeout &&
|
||||||
|
err != fasthttp.ErrDialTimeout &&
|
||||||
|
err != fasthttp.ErrTLSHandshakeTimeout &&
|
||||||
|
err != fasthttp.ErrConnectionClosed &&
|
||||||
|
!os.IsTimeout(err) &&
|
||||||
|
!errors.Is(err, syscall.EPIPE) && // EPIPE is "broken pipe" error
|
||||||
|
err.Error() != "timeout" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
lib/textparsing/init.go
Normal file
42
lib/textparsing/init.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package textparsing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//var wordre = regexp.MustCompile(`\S+`)
|
||||||
|
|
||||||
|
// var urlre = regexp.MustCompile(`https?:\/\/[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{1,6}[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*`)
|
||||||
|
// var emailre = regexp.MustCompile(`[-a-zA-Z0-9%._\+~#=]+@[-a-zA-Z0-9%._\+~=&]{2,256}\.[a-z]{1,6}`)
|
||||||
|
// var usernamere = regexp.MustCompile(`@[a-z0-9\-]+`)
|
||||||
|
var theregex = regexp.MustCompile(`@[a-z0-9\-]+|(?:https?:\/\/[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{1,6}[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)|(?:[-a-zA-Z0-9%._\+~#=]+@[-a-zA-Z0-9%._\+~=&]{2,256}\.[a-z]{1,6})`)
|
||||||
|
|
||||||
|
func replacer(ent string) string {
|
||||||
|
if strings.HasPrefix(ent, "@") {
|
||||||
|
return fmt.Sprintf(`<a class="link" href="/%s">%s</a>`, ent[1:], ent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(ent, "https://") || strings.HasPrefix(ent, "http://") {
|
||||||
|
ent = html.UnescapeString(ent)
|
||||||
|
parsed, err := url.Parse(ent)
|
||||||
|
if err == nil {
|
||||||
|
href := ent
|
||||||
|
if parsed.Host == "soundcloud.com" || strings.HasSuffix(parsed.Host, ".soundcloud.com") {
|
||||||
|
href = "/" + strings.Join(strings.Split(ent, "/")[3:], "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<a class="link" href="%s" referrerpolicy="no-referrer" rel="external nofollow noopener noreferrer ugc" target="_blank">%s</a>`, href, ent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, it can only be an email
|
||||||
|
return fmt.Sprintf(`<a class="link" href="mailto:%s">%s</a>`, ent, ent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Format(text string) string {
|
||||||
|
return theregex.ReplaceAllStringFunc(html.EscapeString(text), replacer)
|
||||||
|
}
|
||||||
10
main.go
10
main.go
@@ -243,7 +243,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "text/html")
|
c.Set("Content-Type", "text/html")
|
||||||
return templates.Base(user.Username, templates.UserPlaylists(user, pl), templates.UserHeader(user)).Render(context.Background(), c)
|
return templates.Base(user.Username, templates.UserPlaylists(prefs, user, pl), templates.UserHeader(user)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/:user/albums", func(c *fiber.Ctx) error {
|
app.Get("/:user/albums", func(c *fiber.Ctx) error {
|
||||||
@@ -265,7 +265,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "text/html")
|
c.Set("Content-Type", "text/html")
|
||||||
return templates.Base(user.Username, templates.UserAlbums(user, pl), templates.UserHeader(user)).Render(context.Background(), c)
|
return templates.Base(user.Username, templates.UserAlbums(prefs, user, pl), templates.UserHeader(user)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/:user/reposts", func(c *fiber.Ctx) error {
|
app.Get("/:user/reposts", func(c *fiber.Ctx) error {
|
||||||
@@ -287,7 +287,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "text/html")
|
c.Set("Content-Type", "text/html")
|
||||||
return templates.Base(user.Username, templates.UserReposts(user, p), templates.UserHeader(user)).Render(context.Background(), c)
|
return templates.Base(user.Username, templates.UserReposts(prefs, user, p), templates.UserHeader(user)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/:user/:track", func(c *fiber.Ctx) error {
|
app.Get("/:user/:track", func(c *fiber.Ctx) error {
|
||||||
@@ -347,7 +347,7 @@ func main() {
|
|||||||
//fmt.Println("gettracks", time.Since(h))
|
//fmt.Println("gettracks", time.Since(h))
|
||||||
|
|
||||||
c.Set("Content-Type", "text/html")
|
c.Set("Content-Type", "text/html")
|
||||||
return templates.Base(usr.Username, templates.User(usr, p), templates.UserHeader(usr)).Render(context.Background(), c)
|
return templates.Base(usr.Username, templates.User(prefs, usr, p), templates.UserHeader(usr)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/:user/sets/:playlist", func(c *fiber.Ctx) error {
|
app.Get("/:user/sets/:playlist", func(c *fiber.Ctx) error {
|
||||||
@@ -375,7 +375,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Set("Content-Type", "text/html")
|
c.Set("Content-Type", "text/html")
|
||||||
return templates.Base(playlist.Title+" by "+playlist.Author.Username, templates.Playlist(playlist), templates.PlaylistHeader(playlist)).Render(context.Background(), c)
|
return templates.Base(playlist.Title+" by "+playlist.Author.Username, templates.Playlist(prefs, playlist), templates.PlaylistHeader(playlist)).Render(context.Background(), c)
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Fatal(app.Listen(cfg.Addr))
|
log.Fatal(app.Listen(cfg.Addr))
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package templates
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/maid-zone/soundcloak/lib/cfg"
|
||||||
|
"github.com/maid-zone/soundcloak/lib/textparsing"
|
||||||
|
)
|
||||||
|
|
||||||
templ Base(title string, content templ.Component, head templ.Component) {
|
templ Base(title string, content templ.Component, head templ.Component) {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -22,3 +27,16 @@ templ Base(title string, content templ.Component, head templ.Component) {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ Description(prefs cfg.Preferences, text string) {
|
||||||
|
<details>
|
||||||
|
<summary>Toggle description</summary>
|
||||||
|
<p style="white-space: pre-wrap;">
|
||||||
|
if *prefs.ParseDescriptions {
|
||||||
|
@templ.Raw(textparsing.Format(text))
|
||||||
|
} else {
|
||||||
|
{ text }
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"github.com/maid-zone/soundcloak/lib/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ PlaylistHeader(p sc.Playlist) {
|
templ PlaylistHeader(p sc.Playlist) {
|
||||||
@@ -32,7 +33,7 @@ templ PlaylistItem(playlist *sc.Playlist, showUsername bool) {
|
|||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ Playlist(p sc.Playlist) {
|
templ Playlist(prefs cfg.Preferences, p sc.Playlist) {
|
||||||
if p.Artwork != "" {
|
if p.Artwork != "" {
|
||||||
<img src={ p.Artwork } width="300px"/>
|
<img src={ p.Artwork } width="300px"/>
|
||||||
}
|
}
|
||||||
@@ -43,10 +44,7 @@ templ Playlist(p sc.Playlist) {
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
if p.Description != "" {
|
if p.Description != "" {
|
||||||
<details>
|
@Description(prefs, p.Description)
|
||||||
<summary>Toggle description</summary>
|
|
||||||
<p style="white-space: pre-wrap">{ p.Description }</p>
|
|
||||||
</details>
|
|
||||||
}
|
}
|
||||||
<p>{ strconv.FormatInt(p.TrackCount, 10) } tracks</p>
|
<p>{ strconv.FormatInt(p.TrackCount, 10) } tracks</p>
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ templ checkbox(name string, checked bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type option struct {
|
type option struct {
|
||||||
value string
|
value string
|
||||||
desc string
|
desc string
|
||||||
disabled bool
|
disabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +36,11 @@ templ sel(name string, options []option, selected string) {
|
|||||||
templ Preferences(prefs cfg.Preferences) {
|
templ Preferences(prefs cfg.Preferences) {
|
||||||
<h1>Preferences</h1>
|
<h1>Preferences</h1>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
<label>
|
||||||
|
Parse descriptions:
|
||||||
|
@checkbox("ParseDescriptions", *prefs.ParseDescriptions)
|
||||||
|
</label>
|
||||||
|
<br/>
|
||||||
if cfg.ProxyImages {
|
if cfg.ProxyImages {
|
||||||
<label>
|
<label>
|
||||||
Proxy images:
|
Proxy images:
|
||||||
@@ -68,9 +73,8 @@ templ Preferences(prefs cfg.Preferences) {
|
|||||||
<br/>
|
<br/>
|
||||||
}
|
}
|
||||||
<input type="submit" value="Update" class="btn" style="margin-top: 1rem;"/>
|
<input type="submit" value="Update" class="btn" style="margin-top: 1rem;"/>
|
||||||
|
<br/>
|
||||||
<br>
|
<br/>
|
||||||
<br>
|
|
||||||
<p>These preferences get saved in a cookie.</p>
|
<p>These preferences get saved in a cookie.</p>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,10 +87,7 @@ templ Track(prefs cfg.Preferences, t sc.Track, stream string, displayErr string)
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
if t.Description != "" {
|
if t.Description != "" {
|
||||||
<details>
|
@Description(prefs, t.Description)
|
||||||
<summary>Toggle description</summary>
|
|
||||||
<p style="white-space: pre-wrap">{ t.Description }</p>
|
|
||||||
</details>
|
|
||||||
}
|
}
|
||||||
<p>{ strconv.FormatInt(t.Likes, 10) } likes</p>
|
<p>{ strconv.FormatInt(t.Likes, 10) } likes</p>
|
||||||
<p>{ strconv.FormatInt(t.Played, 10) } plays</p>
|
<p>{ strconv.FormatInt(t.Played, 10) } plays</p>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"github.com/maid-zone/soundcloak/lib/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ UserHeader(u sc.User) {
|
templ UserHeader(u sc.User) {
|
||||||
@@ -31,7 +32,7 @@ templ UserItem(user *sc.User) {
|
|||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ UserBase(u sc.User) {
|
templ UserBase(prefs cfg.Preferences, u sc.User) {
|
||||||
<div>
|
<div>
|
||||||
if u.Avatar != "" {
|
if u.Avatar != "" {
|
||||||
<img src={ u.Avatar } width="300px"/>
|
<img src={ u.Avatar } width="300px"/>
|
||||||
@@ -45,10 +46,7 @@ templ UserBase(u sc.User) {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
if u.Description != "" {
|
if u.Description != "" {
|
||||||
<details>
|
@Description(prefs, u.Description)
|
||||||
<summary>Toggle description</summary>
|
|
||||||
<p style="white-space: pre-wrap">{ u.Description }</p>
|
|
||||||
</details>
|
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
<p>{ strconv.FormatInt(u.Followers, 10) } followers</p>
|
<p>{ strconv.FormatInt(u.Followers, 10) } followers</p>
|
||||||
@@ -61,15 +59,15 @@ templ UserBase(u sc.User) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ User(u sc.User, p *sc.Paginated[*sc.Track]) {
|
templ User(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Track]) {
|
||||||
@UserBase(u)
|
@UserBase(prefs, u)
|
||||||
// kinda tedious but whatever, might make it more flexible in the future
|
// kinda tedious but whatever, might make it more flexible in the future
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a class="btn active">tracks</a>
|
<a class="btn active">tracks</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/sets") }>playlists</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/sets") }>playlists</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/albums") }>albums</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/albums") }>albums</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/reposts") }>reposts</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/reposts") }>reposts</a>
|
||||||
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) }>view on soundcloud</a>
|
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) } referrerpolicy="no-referrer" rel="external nofollow noopener noreferrer" target="_blank">view on soundcloud</a>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
if len(p.Collection) != 0 {
|
if len(p.Collection) != 0 {
|
||||||
@@ -86,14 +84,14 @@ templ User(u sc.User, p *sc.Paginated[*sc.Track]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ UserPlaylists(u sc.User, p *sc.Paginated[*sc.Playlist]) {
|
templ UserPlaylists(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Playlist]) {
|
||||||
@UserBase(u)
|
@UserBase(prefs, u)
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink) }>tracks</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink) }>tracks</a>
|
||||||
<a class="btn active">playlists</a>
|
<a class="btn active">playlists</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/albums") }>albums</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/albums") }>albums</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/reposts") }>reposts</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/reposts") }>reposts</a>
|
||||||
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) }>view on soundcloud</a>
|
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) } referrerpolicy="no-referrer" rel="external nofollow noopener noreferrer" target="_blank">view on soundcloud</a>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
if len(p.Collection) != 0 {
|
if len(p.Collection) != 0 {
|
||||||
@@ -110,14 +108,14 @@ templ UserPlaylists(u sc.User, p *sc.Paginated[*sc.Playlist]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ UserAlbums(u sc.User, p *sc.Paginated[*sc.Playlist]) {
|
templ UserAlbums(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Playlist]) {
|
||||||
@UserBase(u)
|
@UserBase(prefs, u)
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink) }>tracks</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink) }>tracks</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/sets") }>playlists</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/sets") }>playlists</a>
|
||||||
<a class="btn active">albums</a>
|
<a class="btn active">albums</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/reposts") }>reposts</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/reposts") }>reposts</a>
|
||||||
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) }>view on soundcloud</a>
|
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) } referrerpolicy="no-referrer" rel="external nofollow noopener noreferrer" target="_blank">view on soundcloud</a>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
if len(p.Collection) != 0 {
|
if len(p.Collection) != 0 {
|
||||||
@@ -134,14 +132,14 @@ templ UserAlbums(u sc.User, p *sc.Paginated[*sc.Playlist]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ UserReposts(u sc.User, p *sc.Paginated[*sc.Repost]) {
|
templ UserReposts(prefs cfg.Preferences, u sc.User, p *sc.Paginated[*sc.Repost]) {
|
||||||
@UserBase(u)
|
@UserBase(prefs, u)
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink) }>tracks</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink) }>tracks</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/sets") }>playlists</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/sets") }>playlists</a>
|
||||||
<a class="btn" href={ templ.URL("/" + u.Permalink + "/albums") }>albums</a>
|
<a class="btn" href={ templ.URL("/" + u.Permalink + "/albums") }>albums</a>
|
||||||
<a class="btn active">reposts</a>
|
<a class="btn active">reposts</a>
|
||||||
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) }>view on soundcloud</a>
|
<a class="btn" href={ templ.URL("https://soundcloud.com/" + u.Permalink) } referrerpolicy="no-referrer" rel="external nofollow noopener noreferrer" target="_blank">view on soundcloud</a>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
if len(p.Collection) != 0 {
|
if len(p.Collection) != 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user