Add rendering of Spaces

This commit is contained in:
Alessio 2023-08-18 19:01:58 -03:00
parent a12dcae4e4
commit 2078eb8026
7 changed files with 147 additions and 17 deletions

View File

@ -45,6 +45,7 @@ type TweetCollection interface {
Tweet(id scraper.TweetID) scraper.Tweet Tweet(id scraper.TweetID) scraper.Tweet
User(id scraper.UserID) scraper.User User(id scraper.UserID) scraper.User
Retweet(id scraper.TweetID) scraper.Retweet Retweet(id scraper.TweetID) scraper.Retweet
Space(id scraper.SpaceID) scraper.Space
FocusedTweetID() scraper.TweetID FocusedTweetID() scraper.TweetID
} }
@ -62,6 +63,7 @@ func (app *Application) buffered_render_tweet_page(w http.ResponseWriter, tpl_fi
"tweet": data.Tweet, "tweet": data.Tweet,
"user": data.User, "user": data.User,
"retweet": data.Retweet, "retweet": data.Retweet,
"space": data.Space,
"active_user": app.get_active_user, "active_user": app.get_active_user,
"focused_tweet_id": data.FocusedTweetID, "focused_tweet_id": data.FocusedTweetID,
}), }),
@ -99,6 +101,7 @@ func (app *Application) buffered_render_tweet_htmx(w http.ResponseWriter, tpl_na
"tweet": data.Tweet, "tweet": data.Tweet,
"user": data.User, "user": data.User,
"retweet": data.Retweet, "retweet": data.Retweet,
"space": data.Space,
"active_user": app.get_active_user, "active_user": app.get_active_user,
"focused_tweet_id": data.FocusedTweetID, "focused_tweet_id": data.FocusedTweetID,
}), }),

View File

@ -172,6 +172,9 @@ func (t TweetDetailData) User(id scraper.UserID) scraper.User {
func (t TweetDetailData) Retweet(id scraper.TweetID) scraper.Retweet { func (t TweetDetailData) Retweet(id scraper.TweetID) scraper.Retweet {
return t.Retweets[id] return t.Retweets[id]
} }
func (t TweetDetailData) Space(id scraper.SpaceID) scraper.Space {
return t.Spaces[id]
}
func (t TweetDetailData) FocusedTweetID() scraper.TweetID { func (t TweetDetailData) FocusedTweetID() scraper.TweetID {
return t.MainTweetID 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 { func (t UserProfileData) Retweet(id scraper.TweetID) scraper.Retweet {
return t.Retweets[id] return t.Retweets[id]
} }
func (t UserProfileData) Space(id scraper.SpaceID) scraper.Space {
return t.Spaces[id]
}
func (t UserProfileData) FocusedTweetID() scraper.TweetID { func (t UserProfileData) FocusedTweetID() scraper.TweetID {
return scraper.TweetID(0) return scraper.TweetID(0)
} }

View File

@ -217,6 +217,14 @@ func TestTweetsWithContent(t *testing.T) {
root, err = html.Parse(resp.Body) root, err = html.Parse(resp.Body)
require.NoError(err) require.NoError(err)
assert.Len(cascadia.QueryAll(root, selector(".embedded-link")), 3) 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 // Follow and unfollow

View File

@ -6,6 +6,9 @@
--color-twitter-off-white-dark: #dae5e5; /* hsv(180, 4.8, 89.8) */ --color-twitter-off-white-dark: #dae5e5; /* hsv(180, 4.8, 89.8) */
--color-outline-gray: #dcdcdc; --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_OUTLINE_GRAY = QColor(220, 220, 220);
const QColor COLOR_TWITTER_BLUE = QColor(27, 149, 224); const QColor COLOR_TWITTER_BLUE = QColor(27, 149, 224);
@ -48,6 +51,20 @@ input, select {
border-radius: 0.5em; 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 { .tweet {
padding: 0 1.5em; padding: 0 1.5em;
} }
@ -118,20 +135,10 @@ input, select {
margin: 0 5em; margin: 0 5em;
cursor: default; cursor: default;
} }
ul.reply-mentions { .reply-mentions {
list-style: none;
padding: 0 0.5em; 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 { .replying-to-label {
color: var(--color-twitter-text-gray); color: var(--color-twitter-text-gray);
} }
@ -170,7 +177,7 @@ img.embedded-link-preview {
font-size: 0.8em; font-size: 0.8em;
margin: 0; margin: 0;
} }
.embedded-link-title { h3 {
margin: 0.5em 0; margin: 0.5em 0;
} }
.embedded-link-domain-container { .embedded-link-domain-container {
@ -474,15 +481,52 @@ input[type="submit"] {
margin: 0 0.5em; margin: 0 0.5em;
} }
.poll-choice-label { .poll-choice-label {
/* flex-grow: 1;*/
width: 50%; width: 50%;
} }
.poll-choice-votes { .poll-choice-votes {
width: 50%; width: 50%;
/* flex-grow: 1;*/
} }
.poll-metadata { .poll-metadata {
color: var(--color-twitter-text-gray); color: var(--color-twitter-text-gray);
margin: 0; margin: 0;
font-size: 0.9em; 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;
}

View File

@ -28,7 +28,7 @@
{{if $main_tweet.ReplyMentions}} {{if $main_tweet.ReplyMentions}}
<div class="reply-mentions-container" hx-trigger="click consume"> <div class="reply-mentions-container" hx-trigger="click consume">
<span class="replying-to-label">Replying&nbsp;to</span> <span class="replying-to-label">Replying&nbsp;to</span>
<ul class="reply-mentions"> <ul class="reply-mentions inline-dotted-list">
{{range $main_tweet.ReplyMentions}} {{range $main_tweet.ReplyMentions}}
<li><a class="entity" href="/{{.}}">@{{.}}</a></li> <li><a class="entity" href="/{{.}}">@{{.}}</a></li>
{{end}} {{end}}
@ -88,6 +88,9 @@
{{template "tweet" (dict "TweetID" $main_tweet.QuotedTweetID "RetweetID" 0 "QuoteNestingLevel" (add .QuoteNestingLevel 1))}} {{template "tweet" (dict "TweetID" $main_tweet.QuotedTweetID "RetweetID" 0 "QuoteNestingLevel" (add .QuoteNestingLevel 1))}}
</div> </div>
{{end}} {{end}}
{{if $main_tweet.SpaceID}}
{{template "space" (space $main_tweet.SpaceID)}}
{{end}}
</div> </div>
<div class="interactions-bar"> <div class="interactions-bar">

View File

@ -0,0 +1,34 @@
{{define "space"}}
<div class="space">
<div class="space-host row">
{{template "author-info" (user .CreatedById)}}
<span class="host-label">(Host)</span>
<div class="layout-spacer"></div>
<div class="space-date">
{{.StartedAt.Format "Jan 2, 2006"}}<br>{{.StartedAt.Format "3:04pm"}}
</div>
</div>
<h3 class="space-title">{{.Title}}</h3>
<div class="space-info row">
<span class="space-state">
{{if (eq .State "Ended")}}
<ul class="space-info-list inline-dotted-list">
<li>{{.State}}</li>
<li>{{(len .ParticipantIds)}} participants</li>
<li>{{.LiveListenersCount}} tuned in</li>
<li>Lasted {{.FormatDuration}}</li>
</ul>
{{else}}
{{.State}}
{{end}}
</span>
</div>
<ul class="space-participants-list">
{{range .ParticipantIds}}
{{if (ne . $.CreatedById)}}
<li>{{template "author-info" (user .)}}</li>
{{end}}
{{end}}
</ul>
</div>
{{end}}

View File

@ -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 := "" in_clause := ""
user_ids := []interface{}{} user_ids := []interface{}{}
tweet_ids := []interface{}{} tweet_ids := []interface{}{}
@ -58,6 +85,12 @@ func (p Profile) fill_content(trove *TweetTrove) {
for _, r := range trove.Retweets { for _, r := range trove.Retweets {
user_ids = append(user_ids, int(r.RetweetedByID)) 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 // 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! if len(user_ids) > 0 { // It could be a search with no results, end of feed, etc-- strings.Repeat will fail!