Support "Replying to" and "Show thread"

This commit is contained in:
Zed 2019-07-02 00:52:50 +02:00
parent 97166feec9
commit a67d27e0c4
5 changed files with 83 additions and 43 deletions

View File

@ -138,6 +138,11 @@ a:hover {
margin-left: 4px; margin-left: 4px;
} }
.replying-to {
color: hsla(240,1%,73%,.7);
margin: 4px 0;
}
.status-el .status-content { .status-el .status-content {
font-family: sans-serif; font-family: sans-serif;
line-height: 1.4em; line-height: 1.4em;

View File

@ -42,7 +42,9 @@ proc parseTweetProfile*(profile: XmlNode): Profile =
proc parseQuote*(quote: XmlNode): Quote = proc parseQuote*(quote: XmlNode): Quote =
result = Quote( result = Quote(
id: quote.attr("data-item-id"), id: quote.attr("data-item-id"),
text: getQuoteText(quote) text: getQuoteText(quote),
reply: parseTweetReply(quote),
hasThread: quote.select(".self-thread-context") != nil,
) )
result.profile = Profile( result.profile = Profile(
@ -64,6 +66,8 @@ proc parseTweet*(node: XmlNode): Tweet =
shortTime: getShortTime(tweet), shortTime: getShortTime(tweet),
profile: parseTweetProfile(tweet), profile: parseTweetProfile(tweet),
stats: parseTweetStats(tweet), stats: parseTweetStats(tweet),
reply: parseTweetReply(tweet),
hasThread: tweet.select(".self-thread-context") != nil,
pinned: "pinned" in tweet.attr("class"), pinned: "pinned" in tweet.attr("class"),
available: true available: true
) )

View File

@ -120,11 +120,17 @@ proc parseTweetStats*(node: XmlNode): TweetStats =
of "rep": result.replies = text[0] of "rep": result.replies = text[0]
of "lik": result.likes = text[0] of "lik": result.likes = text[0]
proc parseTweetReply*(node: XmlNode): seq[string] =
let reply = node.select(".ReplyingToContextBelowAuthor")
if reply == nil: return
for username in reply.selectAll("a"):
result.add username.selectText("b")
proc getGif(player: XmlNode): Gif = proc getGif(player: XmlNode): Gif =
let let
thumb = player.attr("style").replace(thumbRegex, "$1") thumb = player.attr("style").replace(thumbRegex, "$1")
id = thumb.replace(gifRegex, "$1") id = thumb.replace(gifRegex, "$1")
url = fmt"https://video.twimg.com/tweet_video/{id}.mp4" url = &"https://video.twimg.com/tweet_video/{id}.mp4"
Gif(url: url, thumb: thumb) Gif(url: url, thumb: thumb)
proc getTweetMedia*(tweet: Tweet; node: XmlNode) = proc getTweetMedia*(tweet: Tweet; node: XmlNode) =
@ -146,15 +152,15 @@ proc getQuoteMedia*(quote: var Quote; node: XmlNode) =
let media = node.select(".QuoteMedia") let media = node.select(".QuoteMedia")
if media != nil: if media != nil:
quote.thumb = some(media.selectAttr("img", "src")) quote.thumb = media.selectAttr("img", "src")
let badge = node.select(".AdaptiveMedia-badgeText") let badge = node.select(".AdaptiveMedia-badgeText")
let gifBadge = node.select(".Icon--gifBadge") let gifBadge = node.select(".Icon--gifBadge")
if badge != nil: if badge != nil:
quote.badge = some(badge.innerText()) quote.badge = badge.innerText()
elif gifBadge != nil: elif gifBadge != nil:
quote.badge = some("GIF") quote.badge = "GIF"
proc getTweetCards*(tweet: Tweet; node: XmlNode) = proc getTweetCards*(tweet: Tweet; node: XmlNode) =
if node.attr("data-has-cards") == "false": return if node.attr("data-has-cards") == "false": return

View File

@ -58,9 +58,11 @@ type
id*: string id*: string
profile*: Profile profile*: Profile
text*: string text*: string
reply*: seq[string]
hasThread*: bool
sensitive*: bool sensitive*: bool
thumb*: Option[string] thumb*: string
badge*: Option[string] badge*: string
Retweet* = object Retweet* = object
by*: string by*: string
@ -77,8 +79,10 @@ type
text*: string text*: string
time*: Time time*: Time
shortTime*: string shortTime*: string
available*: bool reply*: seq[string]
pinned*: bool pinned*: bool
available*: bool
hasThread*: bool
stats*: TweetStats stats*: TweetStats
retweet*: Option[Retweet] retweet*: Option[Retweet]
quote*: Option[Quote] quote*: Option[Quote]

View File

@ -1,5 +1,5 @@
#? stdtmpl(subsChar = '$', metaChar = '#') #? stdtmpl(subsChar = '$', metaChar = '#')
#import xmltree, strutils, times, sequtils, uri #import xmltree, strutils, strformat, sequtils, times, uri
#import ../types, ../formatters, ../utils #import ../types, ../formatters, ../utils
# #
#proc renderHeading(tweet: Tweet): string = #proc renderHeading(tweet: Tweet): string =
@ -29,38 +29,6 @@
</div> </div>
#end proc #end proc
# #
#proc renderQuote(quote: Quote): string =
#let hasMedia = quote.thumb.isSome() or quote.sensitive
<div class="quote">
<div class="quote-container">
<a class="quote-link" href="${getLink(quote)}"></a>
#if hasMedia:
<div class="quote-media-container">
<div class="quote-media">
#if quote.thumb.isSome():
${genImg(quote.thumb.get())}
#if quote.badge.isSome():
<div class="quote-badge">
<div class="quote-badge-text">${quote.badge.get()}</div>
</div>
#end if
#elif quote.sensitive:
<div class="quote-sensitive">
<span class="icon quote-sensitive-icon">❗</span>
</div>
#end if
</div>
</div>
#end if
<div class="fullname-and-username">
${linkUser(quote.profile, class="fullname")}
${linkUser(quote.profile, class="username")}
</div>
<div class="quote-text">${linkifyText(quote.text)}</div>
</div>
</div>
#end proc
#
#proc renderMediaGroup(tweet: Tweet): string = #proc renderMediaGroup(tweet: Tweet): string =
#let groups = if tweet.photos.len > 2: tweet.photos.distribute(2) else: @[tweet.photos] #let groups = if tweet.photos.len > 2: tweet.photos.distribute(2) else: @[tweet.photos]
#let class = if groups.len == 1 and groups[0].len == 1: "single-image" else: "" #let class = if groups.len == 1 and groups[0].len == 1: "single-image" else: ""
@ -140,6 +108,53 @@
</div> </div>
#end proc #end proc
# #
#proc renderShowThread(tweet: Tweet | Quote): string =
<a href="${getLink(tweet)}">Show this thread</a>
#end proc
#
#proc renderReply(tweet: Tweet | Quote): string =
#let usernames = tweet.reply.mapIt(&"""<a href="{it}">@{it}</a>""")
<div class="replying-to">Replying to ${usernames.join(" ")}</div>
#end proc
#
#proc renderQuote(quote: Quote): string =
#let hasMedia = quote.thumb.len > 0 or quote.sensitive
<div class="quote">
<div class="quote-container">
<a class="quote-link" href="${getLink(quote)}"></a>
#if hasMedia:
<div class="quote-media-container">
<div class="quote-media">
#if quote.thumb.len > 0:
${genImg(quote.thumb)}
#if quote.badge.len > 0:
<div class="quote-badge">
<div class="quote-badge-text">${quote.badge}</div>
</div>
#end if
#elif quote.sensitive:
<div class="quote-sensitive">
<span class="icon quote-sensitive-icon">❗</span>
</div>
#end if
</div>
</div>
#end if
<div class="fullname-and-username">
${linkUser(quote.profile, class="fullname")}
${linkUser(quote.profile, class="username")}
</div>
#if quote.reply.len > 0:
${renderReply(quote)}
#end if
<div class="quote-text">${linkifyText(quote.text)}</div>
#if quote.hasThread:
${renderShowThread(quote)}
#end if
</div>
</div>
#end proc
#
#proc renderTweet*(tweet: Tweet; class=""; last=false): string = #proc renderTweet*(tweet: Tweet; class=""; last=false): string =
#var divClass = if last: "thread-last " & class else: class #var divClass = if last: "thread-last " & class else: class
#if divClass.len > 0: #if divClass.len > 0:
@ -149,6 +164,9 @@
<div class="status-el"> <div class="status-el">
<div class="status-body"> <div class="status-body">
${renderHeading(tweet)} ${renderHeading(tweet)}
#if tweet.reply.len > 0:
${renderReply(tweet)}
#end if
<div class="status-content-wrapper"> <div class="status-content-wrapper">
<div class="status-content media-body">${linkifyText(tweet.text)}</div> <div class="status-content media-body">${linkifyText(tweet.text)}</div>
</div> </div>
@ -164,6 +182,9 @@
${renderPoll(tweet.poll.get())} ${renderPoll(tweet.poll.get())}
#end if #end if
${renderStats(tweet.stats)} ${renderStats(tweet.stats)}
#if tweet.hasThread and "timeline" in class:
${renderShowThread(tweet)}
#end if
</div> </div>
</div> </div>
#else: #else: