2021-12-27 01:37:38 +00:00
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
2022-05-18 17:47:03 +00:00
|
|
|
|
import strutils, sequtils, strformat, options, algorithm
|
2019-07-10 22:42:31 +00:00
|
|
|
|
import karax/[karaxdsl, vdom, vstyles]
|
2022-01-14 17:34:10 +00:00
|
|
|
|
from jester import Request
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
import renderutils
|
2019-09-06 00:42:35 +00:00
|
|
|
|
import ".."/[types, utils, formatters]
|
2022-01-14 17:01:47 +00:00
|
|
|
|
import general
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2020-06-07 06:26:39 +00:00
|
|
|
|
proc getSmallPic(url: string): string =
|
|
|
|
|
result = url
|
2022-01-03 01:55:25 +00:00
|
|
|
|
if "?" notin url and not url.endsWith("placeholder.png"):
|
2022-02-08 19:50:56 +00:00
|
|
|
|
result &= "?name=small"
|
2020-06-07 06:26:39 +00:00
|
|
|
|
result = getPicUrl(result)
|
|
|
|
|
|
2022-01-23 06:04:50 +00:00
|
|
|
|
proc renderMiniAvatar(user: User; prefs: Prefs): VNode =
|
|
|
|
|
let url = getPicUrl(user.getUserPic("_mini"))
|
2020-06-10 15:04:48 +00:00
|
|
|
|
buildHtml():
|
2022-01-14 02:16:09 +00:00
|
|
|
|
img(class=(prefs.getAvatarClass & " mini"), src=url)
|
2020-06-10 15:04:48 +00:00
|
|
|
|
|
2020-06-10 16:34:56 +00:00
|
|
|
|
proc renderHeader(tweet: Tweet; retweet: string; prefs: Prefs): VNode =
|
2019-07-10 22:42:31 +00:00
|
|
|
|
buildHtml(tdiv):
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if retweet.len > 0:
|
2019-09-23 22:59:13 +00:00
|
|
|
|
tdiv(class="retweet-header"):
|
2020-06-01 00:22:22 +00:00
|
|
|
|
span: icon "retweet", retweet & " retweeted"
|
2019-08-15 02:00:40 +00:00
|
|
|
|
|
2019-07-10 22:42:31 +00:00
|
|
|
|
if tweet.pinned:
|
|
|
|
|
tdiv(class="pinned"):
|
2019-08-15 02:00:40 +00:00
|
|
|
|
span: icon "pin", "Pinned Tweet"
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
tdiv(class="tweet-header"):
|
2022-01-23 06:04:50 +00:00
|
|
|
|
a(class="tweet-avatar", href=("/" & tweet.user.username)):
|
2020-11-08 01:56:06 +00:00
|
|
|
|
var size = "_bigger"
|
2022-01-23 06:04:50 +00:00
|
|
|
|
if not prefs.autoplayGifs and tweet.user.userPic.endsWith("gif"):
|
2020-06-10 16:34:56 +00:00
|
|
|
|
size = "_400x400"
|
2022-01-23 06:04:50 +00:00
|
|
|
|
genImg(tweet.user.getUserPic(size), class=prefs.getAvatarClass)
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2019-08-12 15:02:07 +00:00
|
|
|
|
tdiv(class="tweet-name-row"):
|
2019-07-10 22:42:31 +00:00
|
|
|
|
tdiv(class="fullname-and-username"):
|
2022-01-23 06:04:50 +00:00
|
|
|
|
linkUser(tweet.user, class="fullname")
|
|
|
|
|
linkUser(tweet.user, class="username")
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
span(class="tweet-date"):
|
2020-06-01 00:22:22 +00:00
|
|
|
|
a(href=getLink(tweet), title=tweet.getTime):
|
|
|
|
|
text tweet.getShortTime
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
proc renderAlbum(tweet: Tweet): VNode =
|
|
|
|
|
let
|
|
|
|
|
groups = if tweet.photos.len < 3: @[tweet.photos]
|
|
|
|
|
else: tweet.photos.distribute(2)
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
buildHtml(tdiv(class="attachments")):
|
|
|
|
|
for i, photos in groups:
|
|
|
|
|
let margin = if i > 0: ".25em" else: ""
|
|
|
|
|
tdiv(class="gallery-row", style={marginTop: margin}):
|
|
|
|
|
for photo in photos:
|
|
|
|
|
tdiv(class="attachment image"):
|
2020-06-07 05:57:32 +00:00
|
|
|
|
let
|
|
|
|
|
named = "name=" in photo
|
2022-02-07 19:01:05 +00:00
|
|
|
|
orig = photo
|
2020-06-07 05:57:32 +00:00
|
|
|
|
small = if named: photo else: photo & "?name=small"
|
2022-02-07 19:01:05 +00:00
|
|
|
|
a(href=getOrigPicUrl(orig), class="still-image", target="_blank"):
|
2020-06-07 05:57:32 +00:00
|
|
|
|
genImg(small)
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2022-02-25 18:26:24 +00:00
|
|
|
|
proc isPlaybackEnabled(prefs: Prefs; playbackType: VideoType): bool =
|
|
|
|
|
case playbackType
|
2019-08-19 01:28:04 +00:00
|
|
|
|
of mp4: prefs.mp4Playback
|
|
|
|
|
of m3u8, vmap: prefs.hlsPlayback
|
|
|
|
|
|
2022-02-25 18:26:24 +00:00
|
|
|
|
proc hasMp4Url(video: Video): bool =
|
|
|
|
|
video.variants.anyIt(it.contentType == mp4)
|
|
|
|
|
|
2019-09-05 20:40:36 +00:00
|
|
|
|
proc renderVideoDisabled(video: Video; path: string): VNode =
|
2022-01-03 02:27:04 +00:00
|
|
|
|
buildHtml(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
|
2019-08-19 01:28:04 +00:00
|
|
|
|
|
2019-08-19 20:03:00 +00:00
|
|
|
|
proc renderVideoUnavailable(video: Video): VNode =
|
2022-01-03 02:27:04 +00:00
|
|
|
|
buildHtml(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"
|
2019-08-19 20:03:00 +00:00
|
|
|
|
|
2019-12-06 14:15:56 +00:00
|
|
|
|
proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode =
|
2022-06-03 23:16:37 +00:00
|
|
|
|
let
|
|
|
|
|
container = if video.description.len == 0 and video.title.len == 0: ""
|
|
|
|
|
else: " card-container"
|
|
|
|
|
playbackType = if not prefs.proxyVideos and video.hasMp4Url: mp4
|
|
|
|
|
else: video.playbackType
|
2022-01-03 02:27:04 +00:00
|
|
|
|
|
2019-11-04 22:13:58 +00:00
|
|
|
|
buildHtml(tdiv(class="attachments card")):
|
2019-10-26 13:50:42 +00:00
|
|
|
|
tdiv(class="gallery-video" & container):
|
2019-07-10 22:42:31 +00:00
|
|
|
|
tdiv(class="attachment video-container"):
|
2020-06-07 06:26:39 +00:00
|
|
|
|
let thumb = getSmallPic(video.thumb)
|
2019-08-19 20:03:00 +00:00
|
|
|
|
if not video.available:
|
2022-01-03 02:27:04 +00:00
|
|
|
|
img(src=thumb)
|
2019-08-19 20:03:00 +00:00
|
|
|
|
renderVideoUnavailable(video)
|
2022-02-25 18:26:24 +00:00
|
|
|
|
elif not prefs.isPlaybackEnabled(playbackType):
|
2022-01-03 02:27:04 +00:00
|
|
|
|
img(src=thumb)
|
2019-09-05 20:40:36 +00:00
|
|
|
|
renderVideoDisabled(video, path)
|
2019-08-19 20:03:00 +00:00
|
|
|
|
else:
|
2022-05-18 17:47:03 +00:00
|
|
|
|
let
|
|
|
|
|
vars = video.variants.filterIt(it.contentType == playbackType)
|
|
|
|
|
vidUrl = vars.sortedByIt(it.resolution)[^1].url
|
|
|
|
|
source = if prefs.proxyVideos: getVidUrl(vidUrl)
|
|
|
|
|
else: vidUrl
|
2022-02-25 18:26:24 +00:00
|
|
|
|
case playbackType
|
2019-08-19 01:28:04 +00:00
|
|
|
|
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:
|
2019-08-19 18:25:00 +00:00
|
|
|
|
video(poster=thumb, data-url=source, data-autoload="false")
|
|
|
|
|
verbatim "<div class=\"video-overlay\" onclick=\"playVideo(this)\">"
|
2019-11-12 09:57:28 +00:00
|
|
|
|
tdiv(class="overlay-circle"): span(class="overlay-triangle")
|
|
|
|
|
verbatim "</div>"
|
2019-10-26 13:50:42 +00:00
|
|
|
|
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
|
2019-08-13 17:44:29 +00:00
|
|
|
|
|
|
|
|
|
proc renderGif(gif: Gif; prefs: Prefs): VNode =
|
2019-07-10 22:42:31 +00:00
|
|
|
|
buildHtml(tdiv(class="attachments media-gif")):
|
2020-05-26 12:24:41 +00:00
|
|
|
|
tdiv(class="gallery-gif", style={maxHeight: "unset"}):
|
2019-07-10 22:42:31 +00:00
|
|
|
|
tdiv(class="attachment"):
|
2020-06-07 06:26:39 +00:00
|
|
|
|
let thumb = getSmallPic(gif.thumb)
|
2020-06-06 02:39:22 +00:00
|
|
|
|
let url = getPicUrl(gif.url)
|
2019-08-13 17:44:29 +00:00
|
|
|
|
if prefs.autoplayGifs:
|
2020-03-29 06:05:09 +00:00
|
|
|
|
video(class="gif", poster=thumb, controls="", autoplay="", muted="", loop=""):
|
2019-08-13 17:44:29 +00:00
|
|
|
|
source(src=url, `type`="video/mp4")
|
|
|
|
|
else:
|
|
|
|
|
video(class="gif", poster=thumb, controls="", muted="", loop=""):
|
|
|
|
|
source(src=url, `type`="video/mp4")
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
proc renderPoll(poll: Poll): VNode =
|
|
|
|
|
buildHtml(tdiv(class="poll")):
|
|
|
|
|
for i in 0 ..< poll.options.len:
|
2020-05-26 12:24:41 +00:00
|
|
|
|
let
|
|
|
|
|
leader = if poll.leader == i: " leader" else: ""
|
2020-06-04 20:56:14 +00:00
|
|
|
|
val = poll.values[i]
|
|
|
|
|
perc = if val > 0: val / poll.votes * 100 else: 0
|
2020-05-26 12:24:41 +00:00
|
|
|
|
percStr = (&"{perc:>3.0f}").strip(chars={'.'}) & '%'
|
2019-07-10 22:42:31 +00:00
|
|
|
|
tdiv(class=("poll-meter" & leader)):
|
2020-05-26 12:24:41 +00:00
|
|
|
|
span(class="poll-choice-bar", style={width: percStr})
|
|
|
|
|
span(class="poll-choice-value"): text percStr
|
2019-07-10 22:42:31 +00:00
|
|
|
|
span(class="poll-choice-option"): text poll.options[i]
|
|
|
|
|
span(class="poll-info"):
|
2020-05-26 12:24:41 +00:00
|
|
|
|
text insertSep($poll.votes, ',') & " votes • " & poll.status
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2019-07-15 14:03:01 +00:00
|
|
|
|
proc renderCardImage(card: Card): VNode =
|
|
|
|
|
buildHtml(tdiv(class="card-image-container")):
|
|
|
|
|
tdiv(class="card-image"):
|
2020-06-01 00:22:22 +00:00
|
|
|
|
img(src=getPicUrl(card.image), alt="")
|
2019-07-15 14:03:01 +00:00
|
|
|
|
if card.kind == player:
|
|
|
|
|
tdiv(class="card-overlay"):
|
2019-08-19 19:27:28 +00:00
|
|
|
|
tdiv(class="overlay-circle"):
|
|
|
|
|
span(class="overlay-triangle")
|
2019-07-15 14:03:01 +00:00
|
|
|
|
|
2019-08-19 18:53:57 +00:00
|
|
|
|
proc renderCardContent(card: Card): VNode =
|
|
|
|
|
buildHtml(tdiv(class="card-content")):
|
|
|
|
|
h2(class="card-title"): text card.title
|
2020-06-10 14:13:40 +00:00
|
|
|
|
if card.text.len > 0:
|
|
|
|
|
p(class="card-description"): text card.text
|
|
|
|
|
if card.dest.len > 0:
|
|
|
|
|
span(class="card-destination"): text card.dest
|
2019-08-19 18:53:57 +00:00
|
|
|
|
|
2019-09-05 20:40:36 +00:00
|
|
|
|
proc renderCard(card: Card; prefs: Prefs; path: string): VNode =
|
2020-06-03 00:33:34 +00:00
|
|
|
|
const smallCards = {app, player, summary, storeLink}
|
2020-06-01 00:22:22 +00:00
|
|
|
|
let large = if card.kind notin smallCards: " large" else: ""
|
2021-12-27 01:27:49 +00:00
|
|
|
|
let url = replaceUrls(card.url, prefs)
|
2019-07-15 11:41:27 +00:00
|
|
|
|
|
|
|
|
|
buildHtml(tdiv(class=("card" & large))):
|
2019-08-19 18:53:57 +00:00
|
|
|
|
if card.video.isSome:
|
|
|
|
|
tdiv(class="card-container"):
|
2019-09-05 20:40:36 +00:00
|
|
|
|
renderVideo(get(card.video), prefs, path)
|
2019-08-19 18:53:57 +00:00
|
|
|
|
a(class="card-content-container", href=url):
|
|
|
|
|
renderCardContent(card)
|
|
|
|
|
else:
|
|
|
|
|
a(class="card-container", href=url):
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if card.image.len > 0:
|
2019-08-19 18:53:57 +00:00
|
|
|
|
renderCardImage(card)
|
|
|
|
|
tdiv(class="card-content-container"):
|
|
|
|
|
renderCardContent(card)
|
2019-07-15 11:41:27 +00:00
|
|
|
|
|
2022-01-05 12:27:52 +00:00
|
|
|
|
func formatStat(stat: int): string =
|
2022-01-05 12:10:02 +00:00
|
|
|
|
if stat > 0: insertSep($stat, ',')
|
|
|
|
|
else: ""
|
|
|
|
|
|
2019-08-19 19:18:18 +00:00
|
|
|
|
proc renderStats(stats: TweetStats; views: string): VNode =
|
2019-07-10 22:42:31 +00:00
|
|
|
|
buildHtml(tdiv(class="tweet-stats")):
|
2022-01-05 12:27:52 +00:00
|
|
|
|
span(class="tweet-stat"): icon "comment", formatStat(stats.replies)
|
|
|
|
|
span(class="tweet-stat"): icon "retweet", formatStat(stats.retweets)
|
|
|
|
|
span(class="tweet-stat"): icon "quote", formatStat(stats.quotes)
|
|
|
|
|
span(class="tweet-stat"): icon "heart", formatStat(stats.likes)
|
2019-08-19 19:18:18 +00:00
|
|
|
|
if views.len > 0:
|
2020-05-26 12:24:41 +00:00
|
|
|
|
span(class="tweet-stat"): icon "play", insertSep(views, ',')
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2022-01-23 06:04:50 +00:00
|
|
|
|
proc renderAttribution(user: User; prefs: Prefs): VNode =
|
|
|
|
|
buildHtml(a(class="attribution", href=("/" & user.username))):
|
|
|
|
|
renderMiniAvatar(user, prefs)
|
|
|
|
|
strong: text user.fullname
|
|
|
|
|
if user.verified:
|
2021-12-30 22:24:10 +00:00
|
|
|
|
icon "ok", class="verified-icon", title="Verified account"
|
2019-10-26 14:37:58 +00:00
|
|
|
|
|
2022-01-23 06:04:50 +00:00
|
|
|
|
proc renderMediaTags(tags: seq[User]): VNode =
|
2019-12-21 04:07:50 +00:00
|
|
|
|
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 ", "
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
proc renderQuoteMedia(quote: Tweet; prefs: Prefs; path: string): VNode =
|
2019-07-10 22:42:31 +00:00
|
|
|
|
buildHtml(tdiv(class="quote-media-container")):
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if quote.photos.len > 0:
|
|
|
|
|
renderAlbum(quote)
|
|
|
|
|
elif quote.video.isSome:
|
|
|
|
|
renderVideo(quote.video.get(), prefs, path)
|
|
|
|
|
elif quote.gif.isSome:
|
|
|
|
|
renderGif(quote.gif.get(), prefs)
|
|
|
|
|
|
|
|
|
|
proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode =
|
2019-07-10 22:42:31 +00:00
|
|
|
|
if not quote.available:
|
|
|
|
|
return buildHtml(tdiv(class="quote unavailable")):
|
|
|
|
|
tdiv(class="unavailable-quote"):
|
2019-09-08 12:34:26 +00:00
|
|
|
|
if quote.tombstone.len > 0:
|
|
|
|
|
text quote.tombstone
|
2021-08-21 15:13:38 +00:00
|
|
|
|
elif quote.text.len > 0:
|
|
|
|
|
text quote.text
|
2019-09-08 12:34:26 +00:00
|
|
|
|
else:
|
|
|
|
|
text "This tweet is unavailable"
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
buildHtml(tdiv(class="quote quote-big")):
|
2019-07-10 22:42:31 +00:00
|
|
|
|
a(class="quote-link", href=getLink(quote))
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
tdiv(class="tweet-name-row"):
|
|
|
|
|
tdiv(class="fullname-and-username"):
|
2022-01-23 06:04:50 +00:00
|
|
|
|
renderMiniAvatar(quote.user, prefs)
|
|
|
|
|
linkUser(quote.user, class="fullname")
|
|
|
|
|
linkUser(quote.user, class="username")
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
span(class="tweet-date"):
|
|
|
|
|
a(href=getLink(quote), title=quote.getTime):
|
|
|
|
|
text quote.getShortTime
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
if quote.reply.len > 0:
|
|
|
|
|
renderReply(quote)
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if quote.text.len > 0:
|
2020-11-07 23:06:37 +00:00
|
|
|
|
tdiv(class="quote-text", dir="auto"):
|
2021-12-27 01:27:49 +00:00
|
|
|
|
verbatim replaceUrls(quote.text, prefs)
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
|
|
|
|
if quote.hasThread:
|
2019-08-13 19:21:54 +00:00
|
|
|
|
a(class="show-thread", href=getLink(quote)):
|
2019-07-10 22:42:31 +00:00
|
|
|
|
text "Show this thread"
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if quote.photos.len > 0 or quote.video.isSome or quote.gif.isSome:
|
|
|
|
|
renderQuoteMedia(quote, prefs, path)
|
|
|
|
|
|
2019-12-21 04:44:58 +00:00
|
|
|
|
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
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
|
|
|
|
last=false; showThread=false; mainTweet=false; afterTweet=false): VNode =
|
2019-07-10 22:42:31 +00:00
|
|
|
|
var divClass = class
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if index == -1 or last:
|
2019-07-10 22:42:31 +00:00
|
|
|
|
divClass = "thread-last " & class
|
|
|
|
|
|
|
|
|
|
if not tweet.available:
|
2019-09-19 01:19:06 +00:00
|
|
|
|
return buildHtml(tdiv(class=divClass & "unavailable timeline-item")):
|
|
|
|
|
tdiv(class="unavailable-box"):
|
|
|
|
|
if tweet.tombstone.len > 0:
|
|
|
|
|
text tweet.tombstone
|
2021-08-21 15:13:38 +00:00
|
|
|
|
elif tweet.text.len > 0:
|
|
|
|
|
text tweet.text
|
2019-09-19 01:19:06 +00:00
|
|
|
|
else:
|
|
|
|
|
text "This tweet is unavailable"
|
2019-07-10 22:42:31 +00:00
|
|
|
|
|
2021-08-21 15:13:38 +00:00
|
|
|
|
if tweet.quote.isSome:
|
|
|
|
|
renderQuote(tweet.quote.get(), prefs, path)
|
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
let fullTweet = tweet
|
|
|
|
|
var retweet: string
|
|
|
|
|
var tweet = fullTweet
|
|
|
|
|
if tweet.retweet.isSome:
|
|
|
|
|
tweet = tweet.retweet.get
|
2022-01-23 06:04:50 +00:00
|
|
|
|
retweet = fullTweet.user.fullname
|
2020-06-01 00:22:22 +00:00
|
|
|
|
|
2019-09-13 17:57:27 +00:00
|
|
|
|
buildHtml(tdiv(class=("timeline-item " & divClass))):
|
2019-10-08 18:54:16 +00:00
|
|
|
|
if not mainTweet:
|
|
|
|
|
a(class="tweet-link", href=getLink(tweet))
|
|
|
|
|
|
2019-09-13 17:57:27 +00:00
|
|
|
|
tdiv(class="tweet-body"):
|
|
|
|
|
var views = ""
|
2020-06-10 16:34:56 +00:00
|
|
|
|
renderHeader(tweet, retweet, prefs)
|
2019-09-13 17:57:27 +00:00
|
|
|
|
|
2020-06-01 00:22:22 +00:00
|
|
|
|
if not afterTweet and index == 0 and tweet.reply.len > 0 and
|
2022-01-23 06:04:50 +00:00
|
|
|
|
(tweet.reply.len > 1 or tweet.reply[0] != tweet.user.username):
|
2019-09-13 17:57:27 +00:00
|
|
|
|
renderReply(tweet)
|
|
|
|
|
|
2020-11-07 23:41:12 +00:00
|
|
|
|
var tweetClass = "tweet-content media-body"
|
|
|
|
|
if prefs.bidiSupport:
|
|
|
|
|
tweetClass &= " tweet-bidi"
|
|
|
|
|
|
|
|
|
|
tdiv(class=tweetClass, dir="auto"):
|
2021-12-27 01:27:49 +00:00
|
|
|
|
verbatim replaceUrls(tweet.text, prefs) & renderLocation(tweet)
|
2019-09-13 17:57:27 +00:00
|
|
|
|
|
2019-10-26 14:37:58 +00:00
|
|
|
|
if tweet.attribution.isSome:
|
2022-01-14 02:16:09 +00:00
|
|
|
|
renderAttribution(tweet.attribution.get(), prefs)
|
2019-10-26 14:37:58 +00:00
|
|
|
|
|
2019-09-13 17:57:27 +00:00
|
|
|
|
if tweet.card.isSome:
|
|
|
|
|
renderCard(tweet.card.get(), prefs, path)
|
2020-06-01 00:22:22 +00:00
|
|
|
|
|
|
|
|
|
if tweet.photos.len > 0:
|
2019-09-13 17:57:27 +00:00
|
|
|
|
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)
|
2020-03-29 06:05:09 +00:00
|
|
|
|
views = "GIF"
|
2020-06-01 00:22:22 +00:00
|
|
|
|
|
|
|
|
|
if tweet.poll.isSome:
|
2019-09-13 17:57:27 +00:00
|
|
|
|
renderPoll(tweet.poll.get())
|
|
|
|
|
|
2020-05-26 12:24:41 +00:00
|
|
|
|
if tweet.quote.isSome:
|
2020-06-01 00:22:22 +00:00
|
|
|
|
renderQuote(tweet.quote.get(), prefs, path)
|
2020-05-26 12:24:41 +00:00
|
|
|
|
|
2019-10-08 11:28:57 +00:00
|
|
|
|
if mainTweet:
|
2022-02-27 00:00:06 +00:00
|
|
|
|
p(class="tweet-published"): text &"{getTime(tweet)} · {tweet.source}"
|
2019-10-08 11:28:57 +00:00
|
|
|
|
|
2019-12-21 04:07:50 +00:00
|
|
|
|
if tweet.mediaTags.len > 0:
|
|
|
|
|
renderMediaTags(tweet.mediaTags)
|
|
|
|
|
|
2019-09-13 17:57:27 +00:00
|
|
|
|
if not prefs.hideTweetStats:
|
|
|
|
|
renderStats(tweet.stats, views)
|
|
|
|
|
|
2019-09-19 01:51:15 +00:00
|
|
|
|
if showThread:
|
2019-10-23 07:47:15 +00:00
|
|
|
|
a(class="show-thread", href=("/i/status/" & $tweet.threadId)):
|
2019-09-13 17:57:27 +00:00
|
|
|
|
text "Show this thread"
|
2022-01-14 18:11:51 +00:00
|
|
|
|
|
2022-01-14 18:45:02 +00:00
|
|
|
|
proc renderTweetEmbed*(tweet: Tweet; path: string; prefs: Prefs; cfg: Config; req: Request): VNode =
|
2022-01-14 18:14:06 +00:00
|
|
|
|
buildHtml(tdiv(class="tweet-embed")):
|
2022-01-14 18:11:51 +00:00
|
|
|
|
renderHead(prefs, cfg, req)
|
2022-01-14 18:14:06 +00:00
|
|
|
|
renderTweet(tweet, prefs, path, mainTweet=true)
|