Rearchitect profile, support pins, Profile -> User
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user