diff --git a/internal/webserver/response_helpers.go b/internal/webserver/response_helpers.go index ce966e9..c94f37f 100644 --- a/internal/webserver/response_helpers.go +++ b/internal/webserver/response_helpers.go @@ -45,6 +45,7 @@ type TweetCollection interface { Tweet(id scraper.TweetID) scraper.Tweet User(id scraper.UserID) scraper.User Retweet(id scraper.TweetID) scraper.Retweet + Space(id scraper.SpaceID) scraper.Space FocusedTweetID() scraper.TweetID } @@ -62,6 +63,7 @@ func (app *Application) buffered_render_tweet_page(w http.ResponseWriter, tpl_fi "tweet": data.Tweet, "user": data.User, "retweet": data.Retweet, + "space": data.Space, "active_user": app.get_active_user, "focused_tweet_id": data.FocusedTweetID, }), @@ -99,6 +101,7 @@ func (app *Application) buffered_render_tweet_htmx(w http.ResponseWriter, tpl_na "tweet": data.Tweet, "user": data.User, "retweet": data.Retweet, + "space": data.Space, "active_user": app.get_active_user, "focused_tweet_id": data.FocusedTweetID, }), diff --git a/internal/webserver/server.go b/internal/webserver/server.go index bbeb5f4..5312f22 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -172,6 +172,9 @@ func (t TweetDetailData) User(id scraper.UserID) scraper.User { func (t TweetDetailData) Retweet(id scraper.TweetID) scraper.Retweet { return t.Retweets[id] } +func (t TweetDetailData) Space(id scraper.SpaceID) scraper.Space { + return t.Spaces[id] +} func (t TweetDetailData) FocusedTweetID() scraper.TweetID { return t.MainTweetID } @@ -254,7 +257,9 @@ func (t UserProfileData) User(id scraper.UserID) scraper.User { func (t UserProfileData) Retweet(id scraper.TweetID) scraper.Retweet { return t.Retweets[id] } - +func (t UserProfileData) Space(id scraper.SpaceID) scraper.Space { + return t.Spaces[id] +} func (t UserProfileData) FocusedTweetID() scraper.TweetID { return scraper.TweetID(0) } diff --git a/internal/webserver/server_test.go b/internal/webserver/server_test.go index 5946c27..5d632ee 100644 --- a/internal/webserver/server_test.go +++ b/internal/webserver/server_test.go @@ -217,6 +217,14 @@ func TestTweetsWithContent(t *testing.T) { root, err = html.Parse(resp.Body) require.NoError(err) assert.Len(cascadia.QueryAll(root, selector(".embedded-link")), 3) + + // Space + resp = do_request(httptest.NewRequest("GET", "/tweet/1624833173514293249", nil)) + require.Equal(resp.StatusCode, 200) + root, err = html.Parse(resp.Body) + require.NoError(err) + assert.Len(cascadia.QueryAll(root, selector(".space")), 1) + assert.Len(cascadia.QueryAll(root, selector("ul.space-participants-list li")), 9) } // Follow and unfollow diff --git a/internal/webserver/static/styles.css b/internal/webserver/static/styles.css index 253784c..b481be9 100644 --- a/internal/webserver/static/styles.css +++ b/internal/webserver/static/styles.css @@ -6,6 +6,9 @@ --color-twitter-off-white-dark: #dae5e5; /* hsv(180, 4.8, 89.8) */ --color-outline-gray: #dcdcdc; + --color-space-purple: #a49bfd; + --color-space-purple-outline: #6452fc; + /* const QColor COLOR_OUTLINE_GRAY = QColor(220, 220, 220); const QColor COLOR_TWITTER_BLUE = QColor(27, 149, 224); @@ -48,6 +51,20 @@ input, select { border-radius: 0.5em; } +ul.inline-dotted-list { + list-style: none; + margin: 0; +} +ul.inline-dotted-list li { + display: inline; +} +ul.inline-dotted-list li:after { + content: " ⋅"; +} +ul.inline-dotted-list li:last-child:after { + content: ""; +} + .tweet { padding: 0 1.5em; } @@ -118,20 +135,10 @@ input, select { margin: 0 5em; cursor: default; } -ul.reply-mentions { - list-style: none; +.reply-mentions { padding: 0 0.5em; - margin: 0; -} -ul.reply-mentions li { - display: inline; -} -ul.reply-mentions li:after { - content: " ⋅"; -} -ul.reply-mentions li:last-child:after { - content: ""; } + .replying-to-label { color: var(--color-twitter-text-gray); } @@ -170,7 +177,7 @@ img.embedded-link-preview { font-size: 0.8em; margin: 0; } -.embedded-link-title { +h3 { margin: 0.5em 0; } .embedded-link-domain-container { @@ -474,15 +481,52 @@ input[type="submit"] { margin: 0 0.5em; } .poll-choice-label { -/* flex-grow: 1;*/ width: 50%; } .poll-choice-votes { width: 50%; -/* flex-grow: 1;*/ } .poll-metadata { color: var(--color-twitter-text-gray); margin: 0; font-size: 0.9em; } + +.space { + outline: 1px solid var(--color-space-purple-outline); + background-color: var(--color-space-purple); + border-radius: 1.5em; + padding: 1.5em; +} +.space-title { + padding-top: 0.5em; +} +.space .host-label { + color: var(--color-space-purple-outline); +} +.space-date { + color: var(--color-space-purple-outline); + font-size: 0.8em; +} +.space-info-list { + padding: 0; +} +.space .layout-spacer { + flex-grow: 1; +} +ul.space-participants-list { + list-style: none; + padding: 0; +} +ul.space-participants-list li { + padding: 0.5em 0; + display: inline-block; + width: 24%; +} +.space-participants-list .author-info { + font-size: 0.9em; + line-height: 1.2em; +} +.space-participants-list .author-info .profile-image { + font-size: 0.8em; +} diff --git a/internal/webserver/tpl/tweet_page_includes/single_tweet.tpl b/internal/webserver/tpl/tweet_page_includes/single_tweet.tpl index 393ddbf..0be4528 100644 --- a/internal/webserver/tpl/tweet_page_includes/single_tweet.tpl +++ b/internal/webserver/tpl/tweet_page_includes/single_tweet.tpl @@ -28,7 +28,7 @@ {{if $main_tweet.ReplyMentions}}
Replying to -
{{end}} + {{if $main_tweet.SpaceID}} + {{template "space" (space $main_tweet.SpaceID)}} + {{end}}
diff --git a/internal/webserver/tpl/tweet_page_includes/space.tpl b/internal/webserver/tpl/tweet_page_includes/space.tpl new file mode 100644 index 0000000..f36e19a --- /dev/null +++ b/internal/webserver/tpl/tweet_page_includes/space.tpl @@ -0,0 +1,34 @@ +{{define "space"}} +
+
+ {{template "author-info" (user .CreatedById)}} + (Host) +
+
+ {{.StartedAt.Format "Jan 2, 2006"}}
{{.StartedAt.Format "3:04pm"}} +
+
+

{{.Title}}

+
+ + {{if (eq .State "Ended")}} +
    +
  • {{.State}}
  • +
  • {{(len .ParticipantIds)}} participants
  • +
  • {{.LiveListenersCount}} tuned in
  • +
  • Lasted {{.FormatDuration}}
  • +
+ {{else}} + {{.State}} + {{end}} +
+
+ +
+{{end}} diff --git a/pkg/persistence/compound_queries.go b/pkg/persistence/compound_queries.go index 8d8f2e3..9c4a5b2 100644 --- a/pkg/persistence/compound_queries.go +++ b/pkg/persistence/compound_queries.go @@ -45,6 +45,33 @@ func (p Profile) fill_content(trove *TweetTrove) { } } + space_ids := []interface{}{} + for _, t := range trove.Tweets { + if t.SpaceID != "" { + space_ids = append(space_ids, t.SpaceID) + } + } + if len(space_ids) > 0 { + var spaces []Space + err := p.DB.Select(&spaces, ` + select id, created_by_id, short_url, state, title, created_at, started_at, ended_at, updated_at, is_available_for_replay, + replay_watch_count, live_listeners_count, is_details_fetched + from spaces + where id in (`+strings.Repeat("?,", len(space_ids)-1)+`?)`, + space_ids..., + ) + if err != nil { + panic(err) + } + for _, s := range spaces { + err := p.DB.Select(&s.ParticipantIds, "select user_id from space_participants where space_id = ?", s.ID) + if err != nil { + panic(err) + } + trove.Spaces[s.ID] = s + } + } + in_clause := "" user_ids := []interface{}{} tweet_ids := []interface{}{} @@ -58,6 +85,12 @@ func (p Profile) fill_content(trove *TweetTrove) { for _, r := range trove.Retweets { user_ids = append(user_ids, int(r.RetweetedByID)) } + for _, s := range trove.Spaces { + user_ids = append(user_ids, s.CreatedById) + for _, p := range s.ParticipantIds { + user_ids = append(user_ids, p) + } + } // Get all the users if len(user_ids) > 0 { // It could be a search with no results, end of feed, etc-- strings.Repeat will fail!