tweet.nimf 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #? stdtmpl(subsChar = '$', metaChar = '#')
  2. #import xmltree, strutils, strformat, sequtils, times, uri
  3. #import ../types, ../formatters, ../utils
  4. #
  5. #proc renderHeading(tweet: Tweet): string =
  6. #if tweet.retweet.isSome:
  7. <div class="retweet">
  8. <span>🔄 ${get(tweet.retweet).by} retweeted</span>
  9. </div>
  10. #end if
  11. #if tweet.pinned:
  12. <div class="pinned">
  13. <span>📌 Pinned Tweet</span>
  14. </div>
  15. #end if
  16. <div class="media-heading">
  17. <div class="heading-name-row">
  18. <a class="tweet-avatar" href="/${tweet.profile.username}">
  19. ${genImg(tweet.profile.getUserpic("_bigger"), "avatar")}
  20. </a>
  21. <div class="fullname-and-username">
  22. ${linkUser(tweet.profile, class="fullname")}
  23. ${linkUser(tweet.profile, class="username")}
  24. </div>
  25. <span class="heading-right">
  26. <a href="${getLink(tweet)}" title="${tweet.getTime()}">${tweet.shortTime}</a>
  27. </span>
  28. </div>
  29. </div>
  30. #end proc
  31. #
  32. #proc renderMediaGroup(tweet: Tweet): string =
  33. #let groups = if tweet.photos.len > 2: tweet.photos.distribute(2) else: @[tweet.photos]
  34. #let class = if groups.len == 1 and groups[0].len == 1: "single-image" else: ""
  35. #var first = true
  36. <div class="attachments ${class}">
  37. #for photos in groups:
  38. #let margin = if not first: "margin-top: .25em;" else: ""
  39. #let flex = if photos.len > 1 or groups.len > 1: "display: flex;" else: ""
  40. <div class="gallery-row cover-fit" style="${margin}">
  41. #for photo in photos:
  42. <div class="attachment image">
  43. ##TODO: why doesn't this work?
  44. <a href=${getSigUrl(photo & "?name=orig", "pic")} target="_blank" class="image-attachment">
  45. <div class="still-image" style="${flex}">
  46. ${genImg(photo)}
  47. </div>
  48. </a>
  49. </div>
  50. #end for
  51. </div>
  52. #first = false
  53. #end for
  54. </div>
  55. #end proc
  56. #
  57. #proc renderVideo(video: Video): string =
  58. <div class="attachments">
  59. <div class="gallery-video">
  60. <div class="attachment video-container">
  61. #case video.playbackType
  62. #of mp4:
  63. <video poster=${video.thumb.getSigUrl("pic")} controls>
  64. <source src=${video.url.getSigUrl("video")} type="video/mp4">
  65. </video>
  66. #of m3u8, vmap:
  67. <video poster=${video.thumb.getSigUrl("pic")} autoplay muted loop></video>
  68. <div class="video-overlay">
  69. <p>Video playback not supported</p>
  70. </div>
  71. #end case
  72. </div>
  73. </div>
  74. </div>
  75. #end proc
  76. #
  77. #proc renderGif(gif: Gif): string =
  78. <div class="attachments media-gif">
  79. <div class="gallery-gif" style="max-height: unset;">
  80. <div class="attachment">
  81. <video class="gif" poster=${gif.thumb.getSigUrl("pic")} autoplay muted loop>
  82. <source src=${gif.url.getSigUrl("video")} type="video/mp4">
  83. </video>
  84. </div>
  85. </div>
  86. </div>
  87. #end proc
  88. #
  89. #proc renderPoll(poll: Poll): string =
  90. <div class="poll">
  91. #for i in 0 ..< poll.options.len:
  92. #let leader = if poll.leader == i: " leader" else: ""
  93. <div class="poll-meter${leader}">
  94. <span class="poll-choice-bar" style="width: ${poll.values[i]}%"></span>
  95. <span class="poll-choice-value">${poll.values[i]}%</span>
  96. <span class="poll-choice-option">${poll.options[i]}</span>
  97. </div>
  98. #end for
  99. <span class="poll-info">${poll.votes} votes • ${poll.status}</span>
  100. </div>
  101. #end proc
  102. #
  103. #proc renderStats(stats: TweetStats): string =
  104. <div class="tweet-stats">
  105. <span class="tweet-stat">💬 ${$stats.replies}</span>
  106. <span class="tweet-stat">🔄 ${$stats.retweets}</span>
  107. <span class="tweet-stat">👍 ${$stats.likes}</span>
  108. </div>
  109. #end proc
  110. #
  111. #proc renderShowThread(tweet: Tweet | Quote): string =
  112. <a href="${getLink(tweet)}">Show this thread</a>
  113. #end proc
  114. #
  115. #proc renderReply(tweet: Tweet | Quote): string =
  116. #let usernames = tweet.reply.mapIt(&"""<a href="/{it}">@{it}</a>""")
  117. <div class="replying-to">Replying to ${usernames.join(" ")}</div>
  118. #end proc
  119. #
  120. #proc renderQuote(quote: Quote): string =
  121. #let hasMedia = quote.thumb.len > 0 or quote.sensitive
  122. <div class="quote">
  123. <div class="quote-container">
  124. <a class="quote-link" href="${getLink(quote)}"></a>
  125. #if hasMedia:
  126. <div class="quote-media-container">
  127. <div class="quote-media">
  128. #if quote.thumb.len > 0:
  129. ${genImg(quote.thumb)}
  130. #if quote.badge.len > 0:
  131. <div class="quote-badge">
  132. <div class="quote-badge-text">${quote.badge}</div>
  133. </div>
  134. #end if
  135. #elif quote.sensitive:
  136. <div class="quote-sensitive">
  137. <span class="icon quote-sensitive-icon">❗</span>
  138. </div>
  139. #end if
  140. </div>
  141. </div>
  142. #end if
  143. <div class="fullname-and-username">
  144. ${linkUser(quote.profile, class="fullname")}
  145. ${linkUser(quote.profile, class="username")}
  146. </div>
  147. #if quote.reply.len > 0:
  148. ${renderReply(quote)}
  149. #end if
  150. <div class="quote-text">${linkifyText(quote.text)}</div>
  151. #if quote.hasThread:
  152. ${renderShowThread(quote)}
  153. #end if
  154. </div>
  155. </div>
  156. #end proc
  157. #
  158. #proc renderTweet*(tweet: Tweet; class=""; first=true; last=false): string =
  159. #var divClass = if last: "thread-last " & class else: class
  160. #if divClass.len > 0:
  161. <div class="${divClass}">
  162. #end if
  163. #if tweet.available:
  164. <div class="status-el">
  165. <div class="status-body">
  166. ${renderHeading(tweet)}
  167. #if first and tweet.reply.len > 0:
  168. ${renderReply(tweet)}
  169. #end if
  170. <div class="status-content-wrapper">
  171. <div class="status-content media-body">${linkifyText(tweet.text)}</div>
  172. </div>
  173. #if tweet.photos.len > 0:
  174. ${renderMediaGroup(tweet)}
  175. #elif tweet.video.isSome:
  176. ${renderVideo(tweet.video.get())}
  177. #elif tweet.gif.isSome:
  178. ${renderGif(tweet.gif.get())}
  179. #elif tweet.quote.isSome:
  180. ${renderQuote(tweet.quote.get())}
  181. #elif tweet.poll.isSome:
  182. ${renderPoll(tweet.poll.get())}
  183. #end if
  184. ${renderStats(tweet.stats)}
  185. #if tweet.hasThread and "timeline" in class:
  186. ${renderShowThread(tweet)}
  187. #end if
  188. </div>
  189. </div>
  190. #else:
  191. <div class="status-el unavailable">
  192. <div class="unavailable-box">This tweet is unavailable</div>
  193. </div>
  194. #end if
  195. #if divClass.len > 0:
  196. </div>
  197. #end if
  198. #end proc