Handle empty user fields in navbar and improve UI responsivenessUnverified
eed7f2e parent: 8467321 modified
internal/db/user.go +3 -3 | @@ -19,9 +19,9 @@ func (db *DB) CreateUser(ctx context.Context, did, handle, displayName, avatarUR | ||
| 19 | 19 | INSERT INTO users (did, handle, display_name, avatar_url, updated_at) |
| 20 | 20 | VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) |
| 21 | 21 | ON CONFLICT(did) DO UPDATE SET |
| 22 | - handle = excluded.handle, | |
| 23 | - display_name = excluded.display_name, | |
| 24 | - avatar_url = excluded.avatar_url, | |
| 22 | + handle = COALESCE(NULLIF(excluded.handle, ''), users.handle), | |
| 23 | + display_name = COALESCE(NULLIF(excluded.display_name, ''), users.display_name), | |
| 24 | + avatar_url = COALESCE(NULLIF(excluded.avatar_url, ''), users.avatar_url), | |
| 25 | 25 | updated_at = CURRENT_TIMESTAMP |
| 26 | 26 | `, did, handle, displayName, avatarURL) |
| 27 | 27 | if err != nil { |
| @@ -19,9 +19,9 @@ func (db *DB) CreateUser(ctx context.Context, did, handle, displayName, avatarUR | |||
| 19 | INSERT INTO users (did, handle, display_name, avatar_url, updated_at) | 19 | INSERT INTO users (did, handle, display_name, avatar_url, updated_at) |
| 20 | VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) | 20 | VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) |
| 21 | ON CONFLICT(did) DO UPDATE SET | 21 | ON CONFLICT(did) DO UPDATE SET |
| 22 | - handle = excluded.handle, | 22 | + handle = COALESCE(NULLIF(excluded.handle, ''), users.handle), |
| 23 | - display_name = excluded.display_name, | 23 | + display_name = COALESCE(NULLIF(excluded.display_name, ''), users.display_name), |
| 24 | - avatar_url = excluded.avatar_url, | 24 | + avatar_url = COALESCE(NULLIF(excluded.avatar_url, ''), users.avatar_url), |
| 25 | updated_at = CURRENT_TIMESTAMP | 25 | updated_at = CURRENT_TIMESTAMP |
| 26 | `, did, handle, displayName, avatarURL) | 26 | `, did, handle, displayName, avatarURL) |
| 27 | if err != nil { | 27 | if err != nil { |
modified
internal/tmpl/feeds.html +6 -6 | @@ -1,5 +1,5 @@ | ||
| 1 | 1 | {{define "feeds.html"}} |
| 2 | -<div class="flex items-center justify-between mb-2"> | |
| 2 | +<div class="flex items-center justify-between gap-3 mb-2 flex-wrap"> | |
| 3 | 3 | <h1 class="text-2xl font-bold text-spot-text">Feeds <span class="text-base font-normal text-spot-secondary">({{.SubscriptionCount}})</span></h1> |
| 4 | 4 | <div class="flex items-center gap-3"> |
| 5 | 5 | <button hx-post="/feeds/refresh" hx-target="#feed-list" hx-swap="innerHTML" |
| @@ -17,16 +17,16 @@ | ||
| 17 | 17 | <h3 class="text-sm font-bold text-spot-red mb-2">Feeds with errors ({{len .DeadFeeds}})</h3> |
| 18 | 18 | <div class="space-y-2"> |
| 19 | 19 | {{range .DeadFeeds}} |
| 20 | - <div class="dead-feed-item flex items-center justify-between text-sm"> | |
| 21 | - <div class="min-w-0 flex-1 flex items-center gap-3"> | |
| 22 | - <span class="text-spot-text truncate">{{if .Title.Valid}}{{.Title.String}}{{else}}{{.FeedURL}}{{end}}</span> | |
| 23 | - <div class="flex items-center gap-2 shrink-0"> | |
| 20 | + <div class="dead-feed-item flex items-start justify-between gap-3 text-sm flex-wrap"> | |
| 21 | + <div class="min-w-0 flex-1"> | |
| 22 | + <span class="text-spot-text truncate block">{{if .Title.Valid}}{{.Title.String}}{{else}}{{.FeedURL}}{{end}}</span> | |
| 23 | + <div class="flex items-center gap-2 mt-1 flex-wrap"> | |
| 24 | 24 | <span class="text-spot-red text-xs">{{.ErrorCount}} errors</span> |
| 25 | 25 | {{if .LastError.Valid}}<span class="text-spot-secondary text-xs truncate max-w-48" title="{{.LastError.String}}">{{.LastError.String}}</span>{{end}} |
| 26 | 26 | </div> |
| 27 | 27 | </div> |
| 28 | 28 | <form hx-delete="/feeds/remove" hx-target="closest .dead-feed-item" hx-swap="outerHTML swap:0.3s" |
| 29 | - hx-confirm="Unsubscribe from this broken feed?" class="inline shrink-0 ml-3"> | |
| 29 | + hx-confirm="Unsubscribe from this broken feed?" class="inline shrink-0"> | |
| 30 | 30 | {{csrfInput $.CSRFToken}} |
| 31 | 31 | <input type="hidden" name="url" value="{{.FeedURL}}"> |
| 32 | 32 | <button type="submit" class="text-xs text-spot-secondary hover:text-spot-red transition font-bold uppercase">Unsubscribe</button> |
| @@ -1,5 +1,5 @@ | |||
| 1 | {{define "feeds.html"}} | 1 | {{define "feeds.html"}} |
| 2 | -<div class="flex items-center justify-between mb-2"> | 2 | +<div class="flex items-center justify-between gap-3 mb-2 flex-wrap"> |
| 3 | <h1 class="text-2xl font-bold text-spot-text">Feeds <span class="text-base font-normal text-spot-secondary">({{.SubscriptionCount}})</span></h1> | 3 | <h1 class="text-2xl font-bold text-spot-text">Feeds <span class="text-base font-normal text-spot-secondary">({{.SubscriptionCount}})</span></h1> |
| 4 | <div class="flex items-center gap-3"> | 4 | <div class="flex items-center gap-3"> |
| 5 | <button hx-post="/feeds/refresh" hx-target="#feed-list" hx-swap="innerHTML" | 5 | <button hx-post="/feeds/refresh" hx-target="#feed-list" hx-swap="innerHTML" |
| @@ -17,16 +17,16 @@ | |||
| 17 | <h3 class="text-sm font-bold text-spot-red mb-2">Feeds with errors ({{len .DeadFeeds}})</h3> | 17 | <h3 class="text-sm font-bold text-spot-red mb-2">Feeds with errors ({{len .DeadFeeds}})</h3> |
| 18 | <div class="space-y-2"> | 18 | <div class="space-y-2"> |
| 19 | {{range .DeadFeeds}} | 19 | {{range .DeadFeeds}} |
| 20 | - <div class="dead-feed-item flex items-center justify-between text-sm"> | 20 | + <div class="dead-feed-item flex items-start justify-between gap-3 text-sm flex-wrap"> |
| 21 | - <div class="min-w-0 flex-1 flex items-center gap-3"> | 21 | + <div class="min-w-0 flex-1"> |
| 22 | - <span class="text-spot-text truncate">{{if .Title.Valid}}{{.Title.String}}{{else}}{{.FeedURL}}{{end}}</span> | 22 | + <span class="text-spot-text truncate block">{{if .Title.Valid}}{{.Title.String}}{{else}}{{.FeedURL}}{{end}}</span> |
| 23 | - <div class="flex items-center gap-2 shrink-0"> | 23 | + <div class="flex items-center gap-2 mt-1 flex-wrap"> |
| 24 | <span class="text-spot-red text-xs">{{.ErrorCount}} errors</span> | 24 | <span class="text-spot-red text-xs">{{.ErrorCount}} errors</span> |
| 25 | {{if .LastError.Valid}}<span class="text-spot-secondary text-xs truncate max-w-48" title="{{.LastError.String}}">{{.LastError.String}}</span>{{end}} | 25 | {{if .LastError.Valid}}<span class="text-spot-secondary text-xs truncate max-w-48" title="{{.LastError.String}}">{{.LastError.String}}</span>{{end}} |
| 26 | </div> | 26 | </div> |
| 27 | </div> | 27 | </div> |
| 28 | <form hx-delete="/feeds/remove" hx-target="closest .dead-feed-item" hx-swap="outerHTML swap:0.3s" | 28 | <form hx-delete="/feeds/remove" hx-target="closest .dead-feed-item" hx-swap="outerHTML swap:0.3s" |
| 29 | - hx-confirm="Unsubscribe from this broken feed?" class="inline shrink-0 ml-3"> | 29 | + hx-confirm="Unsubscribe from this broken feed?" class="inline shrink-0"> |
| 30 | {{csrfInput $.CSRFToken}} | 30 | {{csrfInput $.CSRFToken}} |
| 31 | <input type="hidden" name="url" value="{{.FeedURL}}"> | 31 | <input type="hidden" name="url" value="{{.FeedURL}}"> |
| 32 | <button type="submit" class="text-xs text-spot-secondary hover:text-spot-red transition font-bold uppercase">Unsubscribe</button> | 32 | <button type="submit" class="text-xs text-spot-secondary hover:text-spot-red transition font-bold uppercase">Unsubscribe</button> |
modified
internal/tmpl/partials/feed-item.html +3 -3 | @@ -1,16 +1,16 @@ | ||
| 1 | 1 | {{define "feed-item.html"}} |
| 2 | -<div class="feed-item px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl"> | |
| 2 | +<div class="feed-item px-5 py-4 flex items-center justify-between gap-3 hover:bg-spot-hover-50 transition rounded-xl"> | |
| 3 | 3 | <a href="/articles?feed={{.FeedURL}}" class="min-w-0 flex-1 flex items-center gap-3"> |
| 4 | 4 | {{if .FaviconURL.Valid}}<img src="{{.FaviconURL.String}}" class="w-5 h-5 rounded shrink-0" loading="lazy">{{else}}<span class="shrink-0 w-5 h-5 flex items-center justify-center text-spot-muted"><svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">{{template "icon-globe"}}</svg></span>{{end}} |
| 5 | 5 | <div class="min-w-0 flex-1"> |
| 6 | - <div class="flex items-center gap-2"> | |
| 6 | + <div class="flex items-center gap-2 flex-wrap"> | |
| 7 | 7 | <span class="font-bold text-spot-text truncate">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span> |
| 8 | 8 | {{if .Category.Valid}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-full shrink-0">{{.Category.String}}</span>{{end}} |
| 9 | 9 | </div> |
| 10 | 10 | <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div> |
| 11 | 11 | </div> |
| 12 | 12 | </a> |
| 13 | - <div class="flex items-center gap-3"> | |
| 13 | + <div class="flex items-center gap-3 shrink-0"> | |
| 14 | 14 | {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}} |
| 15 | 15 | <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s" |
| 16 | 16 | hx-confirm="Unsubscribe from this feed?" class="inline"> |
| @@ -1,16 +1,16 @@ | |||
| 1 | {{define "feed-item.html"}} | 1 | {{define "feed-item.html"}} |
| 2 | -<div class="feed-item px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl"> | 2 | +<div class="feed-item px-5 py-4 flex items-center justify-between gap-3 hover:bg-spot-hover-50 transition rounded-xl"> |
| 3 | <a href="/articles?feed={{.FeedURL}}" class="min-w-0 flex-1 flex items-center gap-3"> | 3 | <a href="/articles?feed={{.FeedURL}}" class="min-w-0 flex-1 flex items-center gap-3"> |
| 4 | {{if .FaviconURL.Valid}}<img src="{{.FaviconURL.String}}" class="w-5 h-5 rounded shrink-0" loading="lazy">{{else}}<span class="shrink-0 w-5 h-5 flex items-center justify-center text-spot-muted"><svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">{{template "icon-globe"}}</svg></span>{{end}} | 4 | {{if .FaviconURL.Valid}}<img src="{{.FaviconURL.String}}" class="w-5 h-5 rounded shrink-0" loading="lazy">{{else}}<span class="shrink-0 w-5 h-5 flex items-center justify-center text-spot-muted"><svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">{{template "icon-globe"}}</svg></span>{{end}} |
| 5 | <div class="min-w-0 flex-1"> | 5 | <div class="min-w-0 flex-1"> |
| 6 | - <div class="flex items-center gap-2"> | 6 | + <div class="flex items-center gap-2 flex-wrap"> |
| 7 | <span class="font-bold text-spot-text truncate">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span> | 7 | <span class="font-bold text-spot-text truncate">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span> |
| 8 | {{if .Category.Valid}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-full shrink-0">{{.Category.String}}</span>{{end}} | 8 | {{if .Category.Valid}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-full shrink-0">{{.Category.String}}</span>{{end}} |
| 9 | </div> | 9 | </div> |
| 10 | <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div> | 10 | <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div> |
| 11 | </div> | 11 | </div> |
| 12 | </a> | 12 | </a> |
| 13 | - <div class="flex items-center gap-3"> | 13 | + <div class="flex items-center gap-3 shrink-0"> |
| 14 | {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}} | 14 | {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}} |
| 15 | <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s" | 15 | <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s" |
| 16 | hx-confirm="Unsubscribe from this feed?" class="inline"> | 16 | hx-confirm="Unsubscribe from this feed?" class="inline"> |