nitter/src/views/tweet.nim
2020-06-02 18:05:54 +02:00

322 lines
11 KiB
Nim
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import strutils, sequtils, strformat, options
import karax/[karaxdsl, vdom, vstyles]
import renderutils
import ".."/[types, utils, formatters]
proc renderHeader(tweet: Tweet): VNode =
buildHtml(tdiv):
if tweet.retweet.isSome:
tdiv(class="retweet-header"):
span: icon "retweet", get(tweet.retweet).by & " retweeted"
if tweet.pinned:
tdiv(class="pinned"):
span: icon "pin", "Pinned Tweet"
tdiv(class="tweet-header"):
a(class="tweet-avatar", href=("/" & tweet.profile.username)):
genImg(tweet.profile.getUserpic("_bigger"), class="avatar")
tdiv(class="tweet-name-row"):
tdiv(class="fullname-and-username"):
linkUser(tweet.profile, class="fullname")
linkUser(tweet.profile, class="username")
span(class="tweet-date"):
a(href=getLink(tweet), title=tweet.getTime()):
text tweet.shortTime
proc renderAlbum(tweet: Tweet): VNode =
let
groups = if tweet.photos.len < 3: @[tweet.photos]
else: tweet.photos.distribute(2)
if groups.len == 1 and groups[0].len == 1:
buildHtml(tdiv(class="single-image")):
tdiv(class="attachments gallery-row"):
a(href=getPicUrl(groups[0][0] & "?name=orig"), class="still-image",
target="_blank"):
genImg(groups[0][0])
else:
buildHtml(tdiv(class="attachments")):
for i, photos in groups:
let margin = if i > 0: ".25em" else: ""
let flex = if photos.len > 1 or groups.len > 1: "flex" else: "block"
tdiv(class="gallery-row", style={marginTop: margin}):
for photo in photos:
tdiv(class="attachment image"):
a(href=getPicUrl(photo & "?name=orig"), class="still-image",
target="_blank", style={display: flex}):
genImg(photo)
proc isPlaybackEnabled(prefs: Prefs; video: Video): bool =
case video.playbackType
of mp4: prefs.mp4Playback
of m3u8, vmap: prefs.hlsPlayback
proc renderVideoDisabled(video: Video; path: string): VNode =
buildHtml(tdiv):
img(src=getPicUrl(video.thumb))
tdiv(class="video-overlay"):
case video.playbackType
of mp4:
p: text "mp4 playback disabled in preferences"
of m3u8, vmap:
buttonReferer "/enablehls", "Enable hls playback", path
proc renderVideoUnavailable(video: Video): VNode =
buildHtml(tdiv):
img(src=getPicUrl(video.thumb))
tdiv(class="video-overlay"):
case video.reason
of "dmcaed":
p: text "This media has been disabled in response to a report by the copyright owner"
else:
p: text "This media is unavailable"
proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode =
let container =
if video.description.len > 0 or video.title.len > 0: " card-container"
else: ""
buildHtml(tdiv(class="attachments card")):
tdiv(class="gallery-video" & container):
tdiv(class="attachment video-container"):
let thumb = getPicUrl(video.thumb)
if not video.available:
renderVideoUnavailable(video)
elif not prefs.isPlaybackEnabled(video):
renderVideoDisabled(video, path)
else:
let source = getVidUrl(video.url)
case video.playbackType
of mp4:
if prefs.muteVideos:
video(poster=thumb, controls="", muted=""):
source(src=source, `type`="video/mp4")
else:
video(poster=thumb, controls=""):
source(src=source, `type`="video/mp4")
of m3u8, vmap:
video(poster=thumb, data-url=source, data-autoload="false")
verbatim "<div class=\"video-overlay\" onclick=\"playVideo(this)\">"
tdiv(class="overlay-circle"): span(class="overlay-triangle")
verbatim "</div>"
if container.len > 0:
tdiv(class="card-content"):
h2(class="card-title"): text video.title
if video.description.len > 0:
p(class="card-description"): text video.description
proc renderGif(gif: Gif; prefs: Prefs): VNode =
buildHtml(tdiv(class="attachments media-gif")):
tdiv(class="gallery-gif", style={maxHeight: "unset"}):
tdiv(class="attachment"):
let thumb = getPicUrl(gif.thumb)
let url = getGifUrl(gif.url)
if prefs.autoplayGifs:
video(class="gif", poster=thumb, controls="", autoplay="", muted="", loop=""):
source(src=url, `type`="video/mp4")
else:
video(class="gif", poster=thumb, controls="", muted="", loop=""):
source(src=url, `type`="video/mp4")
proc renderPoll(poll: Poll): VNode =
buildHtml(tdiv(class="poll")):
for i in 0 ..< poll.options.len:
let
leader = if poll.leader == i: " leader" else: ""
perc = poll.values[i] / poll.votes * 100
percStr = (&"{perc:>3.0f}").strip(chars={'.'}) & '%'
tdiv(class=("poll-meter" & leader)):
span(class="poll-choice-bar", style={width: percStr})
span(class="poll-choice-value"): text percStr
span(class="poll-choice-option"): text poll.options[i]
span(class="poll-info"):
text insertSep($poll.votes, ',') & " votes • " & poll.status
proc renderCardImage(card: Card): VNode =
buildHtml(tdiv(class="card-image-container")):
tdiv(class="card-image"):
img(src=getPicUrl(get(card.image)))
if card.kind == player:
tdiv(class="card-overlay"):
tdiv(class="overlay-circle"):
span(class="overlay-triangle")
proc renderCardContent(card: Card): VNode =
buildHtml(tdiv(class="card-content")):
h2(class="card-title"): text card.title
p(class="card-description"): text card.text
span(class="card-destination"): text card.dest
proc renderCard(card: Card; prefs: Prefs; path: string): VNode =
const largeCards = {summaryLarge, liveEvent, promoWebsite,
promoVideo, promoVideoConvo}
let large = if card.kind in largeCards: " large" else: ""
let url = replaceUrl(card.url, prefs)
buildHtml(tdiv(class=("card" & large))):
if card.video.isSome:
tdiv(class="card-container"):
renderVideo(get(card.video), prefs, path)
a(class="card-content-container", href=url):
renderCardContent(card)
else:
a(class="card-container", href=url):
if card.image.isSome:
renderCardImage(card)
tdiv(class="card-content-container"):
renderCardContent(card)
proc renderStats(stats: TweetStats; views: string): VNode =
buildHtml(tdiv(class="tweet-stats")):
span(class="tweet-stat"): icon "comment", insertSep($stats.replies, ',')
span(class="tweet-stat"): icon "retweet", insertSep($stats.retweets, ',')
span(class="tweet-stat"): icon "heart", insertSep($stats.likes, ',')
if views.len > 0:
span(class="tweet-stat"): icon "play", insertSep(views, ',')
proc renderReply(tweet: Tweet): VNode =
buildHtml(tdiv(class="replying-to")):
text "Replying to "
for i, u in tweet.reply:
if i > 0: text " "
a(href=("/" & u)): text "@" & u
proc renderReply(quote: Quote): VNode =
buildHtml(tdiv(class="replying-to")):
text "Replying to "
for i, u in quote.reply:
if i > 0: text " "
a(href=("/" & u)): text "@" & u
proc renderAttribution(profile: Profile): VNode =
let avatarUrl = getPicUrl(profile.getUserpic("_200x200"))
buildHtml(a(class="attribution", href=("/" & profile.username))):
img(class="avatar", width="20", height="20", src=avatarUrl)
strong: text profile.fullname
proc renderMediaTags(tags: seq[Profile]): VNode =
buildHtml(tdiv(class="media-tag-block")):
icon "user"
for i, p in tags:
a(class="media-tag", href=("/" & p.username), title=p.username):
text p.fullname
if i < tags.high:
text ", "
proc renderQuoteMedia(quote: Quote): VNode =
buildHtml(tdiv(class="quote-media-container")):
if quote.thumb.len > 0:
tdiv(class="quote-media"):
genImg(quote.thumb)
if quote.badge.len > 0:
tdiv(class="quote-badge"):
tdiv(class="quote-badge-text"): text quote.badge
elif quote.sensitive:
tdiv(class="quote-sensitive"):
icon "attention", class="quote-sensitive-icon"
proc renderQuote(quote: Quote; prefs: Prefs): VNode =
if not quote.available:
return buildHtml(tdiv(class="quote unavailable")):
tdiv(class="unavailable-quote"):
if quote.tombstone.len > 0:
text quote.tombstone
else:
text "This tweet is unavailable"
buildHtml(tdiv(class="quote")):
a(class="quote-link", href=getLink(quote))
if quote.thumb.len > 0 or quote.sensitive:
renderQuoteMedia(quote)
tdiv(class="fullname-and-username"):
linkUser(quote.profile, class="fullname")
linkUser(quote.profile, class="username")
if quote.reply.len > 0:
renderReply(quote)
tdiv(class="quote-text"):
verbatim replaceUrl(quote.text, prefs)
if quote.hasThread:
a(class="show-thread", href=getLink(quote)):
text "Show this thread"
proc renderLocation*(tweet: Tweet): string =
let (place, url) = tweet.getLocation()
if place.len == 0: return
let node = buildHtml(span(class="tweet-geo")):
text " at "
if url.len > 1:
a(href=url): text place
else:
text place
return $node
proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class="";
index=0; total=(-1); last=false; showThread=false;
mainTweet=false): VNode =
var divClass = class
if index == total or last:
divClass = "thread-last " & class
if not tweet.available:
return buildHtml(tdiv(class=divClass & "unavailable timeline-item")):
tdiv(class="unavailable-box"):
if tweet.tombstone.len > 0:
text tweet.tombstone
else:
text "This tweet is unavailable"
buildHtml(tdiv(class=("timeline-item " & divClass))):
if not mainTweet:
a(class="tweet-link", href=getLink(tweet))
tdiv(class="tweet-body"):
var views = ""
renderHeader(tweet)
if index == 0 and tweet.reply.len > 0 and
(tweet.reply.len > 1 or tweet.reply[0] != tweet.profile.username):
renderReply(tweet)
tdiv(class="tweet-content media-body", dir="auto"):
verbatim replaceUrl(tweet.text, prefs) & renderLocation(tweet)
if tweet.attribution.isSome:
renderAttribution(tweet.attribution.get())
if tweet.card.isSome:
renderCard(tweet.card.get(), prefs, path)
elif tweet.photos.len > 0:
renderAlbum(tweet)
elif tweet.video.isSome:
renderVideo(tweet.video.get(), prefs, path)
views = tweet.video.get().views
elif tweet.gif.isSome:
renderGif(tweet.gif.get(), prefs)
views = "GIF"
elif tweet.poll.isSome:
renderPoll(tweet.poll.get())
if tweet.quote.isSome:
renderQuote(tweet.quote.get(), prefs)
if mainTweet:
p(class="tweet-published"): text getTweetTime(tweet)
if tweet.mediaTags.len > 0:
renderMediaTags(tweet.mediaTags)
if not prefs.hideTweetStats:
renderStats(tweet.stats, views)
if showThread:
a(class="show-thread", href=("/i/status/" & $tweet.threadId)):
text "Show this thread"