tweet.nim 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import strutils
  2. import karax/[karaxdsl, vdom, vstyles]
  3. import ../types, ../utils, ../formatters
  4. import renderutils
  5. proc renderHeader(tweet: Tweet): VNode =
  6. buildHtml(tdiv):
  7. if tweet.retweet.isSome:
  8. tdiv(class="retweet"):
  9. span: text "🔄 " & get(tweet.retweet).by & " retweeted"
  10. if tweet.pinned:
  11. tdiv(class="pinned"):
  12. span: text "📌 Pinned Tweet"
  13. tdiv(class="tweet-header"):
  14. tdiv(class="tweet-name-row"):
  15. a(class="tweet-avatar", href=("/" & tweet.profile.username)):
  16. genImg(tweet.profile.getUserpic("_bigger"), class="avatar")
  17. tdiv(class="fullname-and-username"):
  18. linkUser(tweet.profile, class="fullname")
  19. linkUser(tweet.profile, class="username")
  20. span(class="tweet-date"):
  21. a(href=getLink(tweet), title=tweet.getTime()):
  22. text tweet.shortTime
  23. proc renderAlbum(tweet: Tweet): VNode =
  24. let
  25. groups = if tweet.photos.len < 3: @[tweet.photos]
  26. else: tweet.photos.distribute(2)
  27. class = if groups.len == 1 and groups[0].len == 1: "single-image"
  28. else: ""
  29. buildHtml(tdiv(class=("attachments " & class))):
  30. for i, photos in groups:
  31. let margin = if i > 0: ".25em" else: ""
  32. let flex = if photos.len > 1 or groups.len > 1: "flex" else: "block"
  33. tdiv(class="gallery-row", style={marginTop: margin}):
  34. for photo in photos:
  35. tdiv(class="attachment image"):
  36. a(href=getSigUrl(photo & "?name=orig", "pic"), class="still-image",
  37. target="_blank", style={display: flex}):
  38. genImg(photo)
  39. proc renderVideo(video: Video): VNode =
  40. buildHtml(tdiv(class="attachments")):
  41. tdiv(class="gallery-video"):
  42. tdiv(class="attachment video-container"):
  43. case video.playbackType
  44. of mp4:
  45. video(poster=video.thumb.getSigUrl("pic"), controls=""):
  46. source(src=video.url.getSigUrl("video"), `type`="video/mp4")
  47. of m3u8, vmap:
  48. video(poster=video.thumb.getSigUrl("pic"))
  49. tdiv(class="video-overlay"):
  50. p: text "Video playback not supported"
  51. proc renderGif(gif: Gif): VNode =
  52. buildHtml(tdiv(class="attachments media-gif")):
  53. tdiv(class="gallery-gif", style=style(maxHeight, "unset")):
  54. tdiv(class="attachment"):
  55. video(class="gif", poster=gif.thumb.getSigUrl("pic"),
  56. autoplay="", muted="", loop=""):
  57. source(src=gif.url.getSigUrl("video"), `type`="video/mp4")
  58. proc renderPoll(poll: Poll): VNode =
  59. buildHtml(tdiv(class="poll")):
  60. for i in 0 ..< poll.options.len:
  61. let leader = if poll.leader == i: " leader" else: ""
  62. let perc = $poll.values[i] & "%"
  63. tdiv(class=("poll-meter" & leader)):
  64. span(class="poll-choice-bar", style=style(width, perc))
  65. span(class="poll-choice-value"): text perc
  66. span(class="poll-choice-option"): text poll.options[i]
  67. span(class="poll-info"):
  68. text $poll.votes & " votes • " & poll.status
  69. proc renderCardImage(card: Card): VNode =
  70. buildHtml(tdiv(class="card-image-container")):
  71. tdiv(class="card-image"):
  72. img(src=get(card.image).getSigUrl("pic"))
  73. if card.kind == player:
  74. tdiv(class="card-overlay"):
  75. tdiv(class="card-overlay-circle"):
  76. span(class="card-overlay-triangle")
  77. proc renderCard(card: Card): VNode =
  78. const largeCards = {summaryLarge, liveEvent, promoWebsite, promoVideo}
  79. let large = if card.kind in largeCards: " large" else: ""
  80. buildHtml(tdiv(class=("card" & large))):
  81. a(class="card-container", href=card.url):
  82. if card.image.isSome:
  83. renderCardImage(card)
  84. elif card.video.isSome:
  85. renderVideo(get(card.video))
  86. tdiv(class="card-content-container"):
  87. tdiv(class="card-content"):
  88. h2(class="card-title"): text card.title
  89. p(class="card-description"): text card.text
  90. span(class="card-destination"): text card.dest
  91. proc renderStats(stats: TweetStats): VNode =
  92. buildHtml(tdiv(class="tweet-stats")):
  93. span(class="tweet-stat"): text "💬 " & $stats.replies
  94. span(class="tweet-stat"): text "🔄 " & $stats.retweets
  95. span(class="tweet-stat"): text "👍 " & $stats.likes
  96. proc renderReply(tweet: Tweet): VNode =
  97. buildHtml(tdiv(class="replying-to")):
  98. text "Replying to "
  99. for i, u in tweet.reply:
  100. if i > 0: text " "
  101. a(href=("/" & u)): text "@" & u
  102. proc renderReply(quote: Quote): VNode =
  103. buildHtml(tdiv(class="replying-to")):
  104. text "Replying to "
  105. for i, u in quote.reply:
  106. if i > 0: text " "
  107. a(href=("/" & u)): text "@" & u
  108. proc renderQuoteMedia(quote: Quote): VNode =
  109. buildHtml(tdiv(class="quote-media-container")):
  110. if quote.thumb.len > 0:
  111. tdiv(class="quote-media"):
  112. genImg(quote.thumb)
  113. if quote.badge.len > 0:
  114. tdiv(class="quote-badge"):
  115. tdiv(class="quote-badge-text"): text quote.badge
  116. elif quote.sensitive:
  117. tdiv(class="quote-sensitive"):
  118. span(class="icon quote-sensitive-icon"): text "❗"
  119. proc renderQuote(quote: Quote): VNode =
  120. if not quote.available:
  121. return buildHtml(tdiv(class="quote unavailable")):
  122. tdiv(class="unavailable-quote"):
  123. text "This tweet is unavailable"
  124. buildHtml(tdiv(class="quote")):
  125. a(class="quote-link", href=getLink(quote))
  126. if quote.thumb.len > 0 or quote.sensitive:
  127. renderQuoteMedia(quote)
  128. tdiv(class="fullname-and-username"):
  129. linkUser(quote.profile, class="fullname")
  130. linkUser(quote.profile, class="username")
  131. if quote.reply.len > 0:
  132. renderReply(quote)
  133. tdiv(class="quote-text"):
  134. verbatim linkifyText(quote.text)
  135. if quote.hasThread:
  136. a(href=getLink(quote)):
  137. text "Show this thread"
  138. proc renderTweet*(tweet: Tweet; class=""; index=0; total=(-1); last=false): VNode =
  139. var divClass = class
  140. if index == total or last:
  141. divClass = "thread-last " & class
  142. if not tweet.available:
  143. return buildHtml(tdiv(class=divClass)):
  144. tdiv(class="status-el unavailable"):
  145. tdiv(class="unavailable-box"):
  146. text "This tweet is unavailable"
  147. buildHtml(tdiv(class=divClass)):
  148. tdiv(class="status-el"):
  149. tdiv(class="status-body"):
  150. renderHeader(tweet)
  151. if index == 0 and tweet.reply.len > 0:
  152. renderReply(tweet)
  153. tdiv(class="status-content media-body"):
  154. verbatim linkifyText(tweet.text)
  155. if tweet.quote.isSome:
  156. renderQuote(tweet.quote.get())
  157. if tweet.card.isSome:
  158. renderCard(tweet.card.get())
  159. elif tweet.photos.len > 0:
  160. renderAlbum(tweet)
  161. elif tweet.video.isSome:
  162. renderVideo(tweet.video.get())
  163. elif tweet.gif.isSome:
  164. renderGif(tweet.gif.get())
  165. elif tweet.poll.isSome:
  166. renderPoll(tweet.poll.get())
  167. renderStats(tweet.stats)
  168. if tweet.hasThread and "timeline" in class:
  169. a(href=getLink(tweet)):
  170. text "Show this thread"