Rearchitect profile, support pins, Profile -> User

This commit is contained in:
Zed
2022-01-23 07:04:50 +01:00
parent 79b98a8081
commit 51ae076ea0
23 changed files with 374 additions and 285 deletions

View File

@@ -12,32 +12,32 @@ proc renderStat(num: int; class: string; text=""): VNode =
span(class="profile-stat-num"):
text insertSep($num, ',')
proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode =
proc renderUserCard*(user: User; prefs: Prefs): VNode =
buildHtml(tdiv(class="profile-card")):
tdiv(class="profile-card-info"):
let
url = getPicUrl(profile.getUserPic())
url = getPicUrl(user.getUserPic())
size =
if prefs.autoplayGifs and profile.userPic.endsWith("gif"): ""
if prefs.autoplayGifs and user.userPic.endsWith("gif"): ""
else: "_400x400"
a(class="profile-card-avatar", href=url, target="_blank"):
genImg(profile.getUserPic(size))
genImg(user.getUserPic(size))
tdiv(class="profile-card-tabs-name"):
linkUser(profile, class="profile-card-fullname")
linkUser(profile, class="profile-card-username")
linkUser(user, class="profile-card-fullname")
linkUser(user, class="profile-card-username")
tdiv(class="profile-card-extra"):
if profile.bio.len > 0:
if user.bio.len > 0:
tdiv(class="profile-bio"):
p(dir="auto"):
verbatim replaceUrls(profile.bio, prefs)
verbatim replaceUrls(user.bio, prefs)
if profile.location.len > 0:
if user.location.len > 0:
tdiv(class="profile-location"):
span: icon "location"
let (place, url) = getLocation(profile)
let (place, url) = getLocation(user)
if url.len > 1:
a(href=url): text place
elif "://" in place:
@@ -45,29 +45,29 @@ proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode =
else:
span: text place
if profile.website.len > 0:
if user.website.len > 0:
tdiv(class="profile-website"):
span:
let url = replaceUrls(profile.website, prefs)
let url = replaceUrls(user.website, prefs)
icon "link"
a(href=url): text shortLink(url)
tdiv(class="profile-joindate"):
span(title=getJoinDateFull(profile)):
icon "calendar", getJoinDate(profile)
span(title=getJoinDateFull(user)):
icon "calendar", getJoinDate(user)
tdiv(class="profile-card-extra-links"):
ul(class="profile-statlist"):
renderStat(profile.tweets, "posts", text="Tweets")
renderStat(profile.following, "following")
renderStat(profile.followers, "followers")
renderStat(profile.likes, "likes")
renderStat(user.tweets, "posts", text="Tweets")
renderStat(user.following, "following")
renderStat(user.followers, "followers")
renderStat(user.likes, "likes")
proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode =
let count = insertSep($profile.media, ',')
proc renderPhotoRail(profile: Profile): VNode =
let count = insertSep($profile.user.media, ',')
buildHtml(tdiv(class="photo-rail-card")):
tdiv(class="photo-rail-header"):
a(href=(&"/{profile.username}/media")):
a(href=(&"/{profile.user.username}/media")):
icon "picture", count & " Photos and videos"
input(id="photo-rail-grid-toggle", `type`="checkbox")
@@ -76,18 +76,19 @@ proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode =
icon "down"
tdiv(class="photo-rail-grid"):
for i, photo in photoRail:
for i, photo in profile.photoRail:
if i == 16: break
a(href=(&"/{profile.username}/status/{photo.tweetId}#m")):
a(href=(&"/{profile.user.username}/status/{photo.tweetId}#m")):
genImg(photo.url & (if "format" in photo.url: "" else: ":thumb"))
proc renderBanner(banner: string): VNode =
buildHtml():
if banner.startsWith('#'):
if banner.len == 0:
a()
elif banner.startsWith('#'):
a(style={backgroundColor: banner})
else:
a(href=getPicUrl(banner), target="_blank"):
genImg(banner)
a(href=getPicUrl(banner), target="_blank"): genImg(banner)
proc renderProtected(username: string): VNode =
buildHtml(tdiv(class="timeline-container")):
@@ -95,22 +96,21 @@ proc renderProtected(username: string): VNode =
h2: text "This account's tweets are protected."
p: text &"Only confirmed followers have access to @{username}'s tweets."
proc renderProfile*(profile: Profile; timeline: var Timeline;
photoRail: PhotoRail; prefs: Prefs; path: string): VNode =
timeline.query.fromUser = @[profile.username]
proc renderProfile*(profile: var Profile; prefs: Prefs; path: string): VNode =
profile.tweets.query.fromUser = @[profile.user.username]
buildHtml(tdiv(class="profile-tabs")):
if not prefs.hideBanner:
tdiv(class="profile-banner"):
if profile.banner.len > 0:
renderBanner(profile.banner)
renderBanner(profile.user.banner)
let sticky = if prefs.stickyProfile: " sticky" else: ""
tdiv(class=(&"profile-tab{sticky}")):
renderProfileCard(profile, prefs)
if photoRail.len > 0:
renderPhotoRail(profile, photoRail)
renderUserCard(profile.user, prefs)
if profile.photoRail.len > 0:
renderPhotoRail(profile)
if profile.protected:
renderProtected(profile.username)
if profile.user.protected:
renderProtected(profile.user.username)
else:
renderTweetSearch(timeline, prefs, path)
renderTweetSearch(profile.tweets, prefs, path, profile.pinned)

View File

@@ -15,18 +15,18 @@ proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode =
if text.len > 0:
text " " & text
proc linkUser*(profile: Profile, class=""): VNode =
proc linkUser*(user: User, class=""): VNode =
let
isName = "username" notin class
href = "/" & profile.username
nameText = if isName: profile.fullname
else: "@" & profile.username
href = "/" & user.username
nameText = if isName: user.fullname
else: "@" & user.username
buildHtml(a(href=href, class=class, title=nameText)):
text nameText
if isName and profile.verified:
if isName and user.verified:
icon "ok", class="verified-icon", title="Verified account"
if isName and profile.protected:
if isName and user.protected:
text " "
icon "lock", title="Protected account"

View File

@@ -60,7 +60,7 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
#let urlPrefix = getUrlPrefix(cfg)
#var links: seq[string]
#for t in tweets:
# let retweet = if t.retweet.isSome: t.profile.username else: ""
# let retweet = if t.retweet.isSome: t.user.username else: ""
# let tweet = if retweet.len > 0: t.retweet.get else: t
# let link = getLink(tweet)
# if link in links: continue
@@ -68,7 +68,7 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
# links.add link
<item>
<title>${getTitle(tweet, retweet)}</title>
<dc:creator>@${tweet.profile.username}</dc:creator>
<dc:creator>@${tweet.user.username}</dc:creator>
<description><![CDATA[${renderRssTweet(tweet, cfg).strip(chars={'\n'})}]]></description>
<pubDate>${getRfc822Time(tweet)}</pubDate>
<guid>${urlPrefix & link}</guid>
@@ -77,32 +77,32 @@ Twitter feed for: ${desc}. Generated by ${cfg.hostname}
#end for
#end proc
#
#proc renderTimelineRss*(timeline: Timeline; profile: Profile; cfg: Config; multi=false): string =
#proc renderTimelineRss*(profile: Profile; cfg: Config; multi=false): string =
#let urlPrefix = getUrlPrefix(cfg)
#result = ""
#let user = (if multi: "" else: "@") & profile.username
#var title = profile.fullname
#if not multi: title &= " / " & user
#let handle = (if multi: "" else: "@") & profile.user.username
#var title = profile.user.fullname
#if not multi: title &= " / " & handle
#end if
#title = xmltree.escape(title).sanitizeXml
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<atom:link href="${urlPrefix}/${profile.username}/rss" rel="self" type="application/rss+xml" />
<atom:link href="${urlPrefix}/${profile.user.username}/rss" rel="self" type="application/rss+xml" />
<title>${title}</title>
<link>${urlPrefix}/${profile.username}</link>
<description>${getDescription(user, cfg)}</description>
<link>${urlPrefix}/${profile.user.username}</link>
<description>${getDescription(handle, cfg)}</description>
<language>en-us</language>
<ttl>40</ttl>
<image>
<title>${title}</title>
<link>${urlPrefix}/${profile.username}</link>
<url>${urlPrefix}${getPicUrl(profile.getUserPic(style="_400x400"))}</url>
<link>${urlPrefix}/${profile.user.username}</link>
<url>${urlPrefix}${getPicUrl(profile.user.getUserPic(style="_400x400"))}</url>
<width>128</width>
<height>128</height>
</image>
#if timeline.content.len > 0:
${renderRssTweets(timeline.content, cfg)}
#if profile.tweets.content.len > 0:
${renderRssTweets(profile.tweets.content, cfg)}
#end if
</channel>
</rss>

View File

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-only
import strutils, strformat, sequtils, unicode, tables
import strutils, strformat, sequtils, unicode, tables, options
import karax/[karaxdsl, vdom]
import renderutils, timeline
@@ -88,7 +88,8 @@ proc renderSearchPanel*(query: Query): VNode =
span(class="search-title"): text "Near"
genInput("near", "", query.near, placeholder="Location...")
proc renderTweetSearch*(results: Result[Tweet]; prefs: Prefs; path: string): VNode =
proc renderTweetSearch*(results: Result[Tweet]; prefs: Prefs; path: string;
pinned=none(Tweet)): VNode =
let query = results.query
buildHtml(tdiv(class="timeline-container")):
if query.fromUser.len > 1:
@@ -105,9 +106,9 @@ proc renderTweetSearch*(results: Result[Tweet]; prefs: Prefs; path: string): VNo
if query.fromUser.len == 0:
renderSearchTabs(query)
renderTimelineTweets(results, prefs, path)
renderTimelineTweets(results, prefs, path, pinned)
proc renderUserSearch*(results: Result[Profile]; prefs: Prefs): VNode =
proc renderUserSearch*(results: Result[User]; prefs: Prefs): VNode =
buildHtml(tdiv(class="timeline-container")):
tdiv(class="timeline-header"):
form(`method`="get", action="/search", class="search-field"):

View File

@@ -10,24 +10,22 @@ proc renderEarlier(thread: Chain): VNode =
text "earlier replies"
proc renderMoreReplies(thread: Chain): VNode =
let num = if thread.more != -1: $thread.more & " " else: ""
let reply = if thread.more == 1: "reply" else: "replies"
let link = getLink(thread.content[^1])
buildHtml(tdiv(class="timeline-item more-replies")):
if thread.content[^1].available:
a(class="more-replies-text", href=link):
text $num & "more " & reply
text "more replies"
else:
a(class="more-replies-text"):
text $num & "more " & reply
text "more replies"
proc renderReplyThread(thread: Chain; prefs: Prefs; path: string): VNode =
buildHtml(tdiv(class="reply thread thread-line")):
for i, tweet in thread.content:
let last = (i == thread.content.high and thread.more == 0)
let last = (i == thread.content.high and not thread.hasMore)
renderTweet(tweet, prefs, path, index=i, last=last)
if thread.more != 0:
if thread.hasMore:
renderMoreReplies(thread)
proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string): VNode =
@@ -60,12 +58,12 @@ proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode
tdiv(class="after-tweet thread-line"):
let
total = conv.after.content.high
more = conv.after.more
hasMore = conv.after.hasMore
for i, tweet in conv.after.content:
renderTweet(tweet, prefs, path, index=i,
last=(i == total and more == 0), afterTweet=true)
last=(i == total and not hasMore), afterTweet=true)
if more != 0:
if hasMore:
renderMoreReplies(conv.after)
if not prefs.hideReplies:

View File

@@ -57,7 +57,7 @@ proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet
elif t.replyId == result[0].id:
result.add t
proc renderUser(user: Profile; prefs: Prefs): VNode =
proc renderUser(user: User; prefs: Prefs): VNode =
buildHtml(tdiv(class="timeline-item")):
a(class="tweet-link", href=("/" & user.username))
tdiv(class="tweet-body profile-result"):
@@ -73,7 +73,7 @@ proc renderUser(user: Profile; prefs: Prefs): VNode =
tdiv(class="tweet-content media-body", dir="auto"):
verbatim replaceUrls(user.bio, prefs)
proc renderTimelineUsers*(results: Result[Profile]; prefs: Prefs; path=""): VNode =
proc renderTimelineUsers*(results: Result[User]; prefs: Prefs; path=""): VNode =
buildHtml(tdiv(class="timeline")):
if not results.beginning:
renderNewer(results.query, path)
@@ -89,11 +89,16 @@ proc renderTimelineUsers*(results: Result[Profile]; prefs: Prefs; path=""): VNod
else:
renderNoMore()
proc renderTimelineTweets*(results: Result[Tweet]; prefs: Prefs; path: string): VNode =
proc renderTimelineTweets*(results: Result[Tweet]; prefs: Prefs; path: string;
pinned=none(Tweet)): VNode =
buildHtml(tdiv(class="timeline")):
if not results.beginning:
renderNewer(results.query, parseUri(path).path)
if pinned.isSome:
let tweet = get pinned
renderTweet(tweet, prefs, path, showThread=tweet.hasThread)
if results.content.len == 0:
if not results.beginning:
renderNoMore()

View File

@@ -13,8 +13,8 @@ proc getSmallPic(url: string): string =
result &= ":small"
result = getPicUrl(result)
proc renderMiniAvatar(profile: Profile; prefs: Prefs): VNode =
let url = getPicUrl(profile.getUserPic("_mini"))
proc renderMiniAvatar(user: User; prefs: Prefs): VNode =
let url = getPicUrl(user.getUserPic("_mini"))
buildHtml():
img(class=(prefs.getAvatarClass & " mini"), src=url)
@@ -29,16 +29,16 @@ proc renderHeader(tweet: Tweet; retweet: string; prefs: Prefs): VNode =
span: icon "pin", "Pinned Tweet"
tdiv(class="tweet-header"):
a(class="tweet-avatar", href=("/" & tweet.profile.username)):
a(class="tweet-avatar", href=("/" & tweet.user.username)):
var size = "_bigger"
if not prefs.autoplayGifs and tweet.profile.userPic.endsWith("gif"):
if not prefs.autoplayGifs and tweet.user.userPic.endsWith("gif"):
size = "_400x400"
genImg(tweet.profile.getUserPic(size), class=prefs.getAvatarClass)
genImg(tweet.user.getUserPic(size), class=prefs.getAvatarClass)
tdiv(class="tweet-name-row"):
tdiv(class="fullname-and-username"):
linkUser(tweet.profile, class="fullname")
linkUser(tweet.profile, class="username")
linkUser(tweet.user, class="fullname")
linkUser(tweet.user, class="username")
span(class="tweet-date"):
a(href=getLink(tweet), title=tweet.getTime):
@@ -203,14 +203,14 @@ proc renderReply(tweet: Tweet): VNode =
if i > 0: text " "
a(href=("/" & u)): text "@" & u
proc renderAttribution(profile: Profile; prefs: Prefs): VNode =
buildHtml(a(class="attribution", href=("/" & profile.username))):
renderMiniAvatar(profile, prefs)
strong: text profile.fullname
if profile.verified:
proc renderAttribution(user: User; prefs: Prefs): VNode =
buildHtml(a(class="attribution", href=("/" & user.username))):
renderMiniAvatar(user, prefs)
strong: text user.fullname
if user.verified:
icon "ok", class="verified-icon", title="Verified account"
proc renderMediaTags(tags: seq[Profile]): VNode =
proc renderMediaTags(tags: seq[User]): VNode =
buildHtml(tdiv(class="media-tag-block")):
icon "user"
for i, p in tags:
@@ -244,9 +244,9 @@ proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode =
tdiv(class="tweet-name-row"):
tdiv(class="fullname-and-username"):
renderMiniAvatar(quote.profile, prefs)
linkUser(quote.profile, class="fullname")
linkUser(quote.profile, class="username")
renderMiniAvatar(quote.user, prefs)
linkUser(quote.user, class="fullname")
linkUser(quote.user, class="username")
span(class="tweet-date"):
a(href=getLink(quote), title=quote.getTime):
@@ -301,7 +301,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
var tweet = fullTweet
if tweet.retweet.isSome:
tweet = tweet.retweet.get
retweet = fullTweet.profile.fullname
retweet = fullTweet.user.fullname
buildHtml(tdiv(class=("timeline-item " & divClass))):
if not mainTweet:
@@ -312,7 +312,7 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
renderHeader(tweet, retweet, prefs)
if not afterTweet and index == 0 and tweet.reply.len > 0 and
(tweet.reply.len > 1 or tweet.reply[0] != tweet.profile.username):
(tweet.reply.len > 1 or tweet.reply[0] != tweet.user.username):
renderReply(tweet)
var tweetClass = "tweet-content media-body"