diff --git a/src/sass/profile/card.scss b/src/sass/profile/card.scss index f775a0b..7c44781 100644 --- a/src/sass/profile/card.scss +++ b/src/sass/profile/card.scss @@ -42,7 +42,8 @@ img { display: block; - width: calc(100% - 8px); + box-sizing: border-box; + width: 100%; height: 100%; margin: 0; border: 4px solid var(--darker_grey); diff --git a/src/sass/profile/photo-rail.scss b/src/sass/profile/photo-rail.scss index 1cc78d7..ebe2321 100644 --- a/src/sass/profile/photo-rail.scss +++ b/src/sass/profile/photo-rail.scss @@ -14,9 +14,10 @@ } &-header-mobile { - padding: 5px 12px 0; display: none; - width: calc(100% - 24px); + box-sizing: border-box; + padding: 5px 12px 0; + width: 100%; float: unset; color: var(--accent); justify-content: space-between; diff --git a/src/sass/timeline.scss b/src/sass/timeline.scss index 02e4423..db19bed 100644 --- a/src/sass/timeline.scss +++ b/src/sass/timeline.scss @@ -13,18 +13,28 @@ } .timeline-header { + width: 100%; background-color: var(--bg_panel); text-align: center; padding: 8px; display: block; font-weight: bold; margin-bottom: 5px; + box-sizing: border-box; button { float: unset; } } +.timeline-banner img { + width: 100%; +} + +.timeline-description { + font-weight: normal; +} + .tab { align-items: center; display: flex; diff --git a/src/sass/tweet/card.scss b/src/sass/tweet/card.scss index 680b379..205fecb 100644 --- a/src/sass/tweet/card.scss +++ b/src/sass/tweet/card.scss @@ -76,7 +76,7 @@ left: 0; bottom: 0; right: 0; - background-color: var(--fg_color); + background-color: var(--bg_overlays); img { width: 100%; diff --git a/src/sass/tweet/media.scss b/src/sass/tweet/media.scss index f29c717..68a7dbf 100644 --- a/src/sass/tweet/media.scss +++ b/src/sass/tweet/media.scss @@ -13,7 +13,7 @@ .still-image { width: 100%; - display: block; + display: flex; } } @@ -67,17 +67,17 @@ display: inline-block; } -.single-image { - display: inline-block; - width: 100%; - max-height: 600px; +// .single-image { +// display: inline-block; +// width: 100%; +// max-height: 600px; - .attachments { - width: unset; - max-height: unset; - display: inherit; - } -} +// .attachments { +// width: unset; +// max-height: unset; +// display: inherit; +// } +// } .overlay-circle { border-radius: 50%; diff --git a/src/sass/tweet/quote.scss b/src/sass/tweet/quote.scss index 02e9b56..a4ec146 100644 --- a/src/sass/tweet/quote.scss +++ b/src/sass/tweet/quote.scss @@ -5,10 +5,10 @@ border: solid 1px var(--dark_grey); border-radius: 10px; background-color: var(--bg_elements); - overflow: auto; - padding: 6px; - position: relative; + overflow: hidden; pointer-events: all; + position: relative; + width: 100%; &:hover { border-color: var(--grey); @@ -17,91 +17,73 @@ &.unavailable:hover { border-color: var(--dark_grey); } + + .tweet-name-row { + padding: 6px 6px 0px 6px; + } + + .quote-text { + overflow: hidden; + white-space: pre-wrap; + word-wrap: break-word; + padding: 3px 6px 6px 6px; + } + + .show-thread { + padding: 0px 6px 3px 6px; + margin-top: -3px; + } + + .replying-to { + padding: 0px 6px; + margin: unset; + } } .unavailable-quote { - padding: 6px; + padding: 12px; } .quote-link { - height: 100%; width: 100%; + height: 100%; left: 0; top: 0; position: absolute; } -.quote .quote-link { - z-index: 1; -} - -.quote-text { - overflow: hidden; - white-space: pre-wrap; - word-wrap: break-word; -} - .quote-media-container { + max-height: 300px; display: flex; - flex-direction: column; - align-items: center; - overflow: hidden; - max-height: 102px; - width: 102px; - float: left; - margin-right: 7px; - border-radius: 7px; - position: relative; -} -.quote-media { - display: flex; - justify-content: center; - pointer-events: none; + .card { + margin: unset; + } - img { + .media-gif { width: 100%; - height: 100%; - align-self: center; - } -} - -.quote-badge { - left: 0; - bottom: 0; - position: absolute; - z-index: 1; - align-self: flex-end; -} - -.quote-badge-text { - margin: 4px; - background: $shadow; - border-radius: 4px; - color: #fffffff0; - padding: 1px 3px; - font-size: 12px; - font-weight: bold; -} - -.quote-sensitive { - background: var(--darker_grey); - width: 102px; - height: 102px; - border-radius: 12px; - display: flex; - justify-content: center; - align-items: center; -} - -.quote-sensitive-icon { - font-size: 40px; - color: var(--grey); -} - -@media(max-width: 600px) { - .quote-media-container { - width: 70px; - max-height: 70px; + } + + .attachments { + border-radius: 0; + } + + .gallery-gif .attachment { + display: flex; + justify-content: center; + background-color: var(--bg_color); + + video { + height: unset; + width: unset; + } + } + + .gallery-video, .gallery-gif { + max-height: 300px; + } + + .still-image img { + max-height: 250px } } diff --git a/src/views/list.nim b/src/views/list.nim index f66a605..4af3ad7 100644 --- a/src/views/list.nim +++ b/src/views/list.nim @@ -2,7 +2,7 @@ import strformat import karax/[karaxdsl, vdom] import renderutils -import ".."/[types] +import ".."/[types, utils] proc renderListTabs*(query: Query; path: string): VNode = buildHtml(ul(class="tab")): @@ -11,10 +11,18 @@ proc renderListTabs*(query: Query; path: string): VNode = li(class=query.getTabClass(userList)): a(href=(path & "/members")): text "Members" -proc renderList*(body: VNode; query: Query; name, list: string): VNode = +proc renderList*(body: VNode; query: Query; list: List): VNode = buildHtml(tdiv(class="timeline-container")): - tdiv(class="timeline-header"): - text &"\"{list}\" by @{name}" + if list.banner.len > 0: + tdiv(class="timeline-banner"): + a(href=getPicUrl(list.banner), target="_blank"): + genImg(list.banner) - renderListTabs(query, &"/{name}/lists/{list}") + tdiv(class="timeline-header"): + text &"\"{list.name}\" by @{list.username}" + + tdiv(class="timeline-description"): + text list.description + + renderListTabs(query, &"/{list.username}/lists/{list.name}") body diff --git a/src/views/profile.nim b/src/views/profile.nim index aaf86c8..3e1b260 100644 --- a/src/views/profile.nim +++ b/src/views/profile.nim @@ -58,14 +58,15 @@ proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode = renderStat(profile.likes, "likes") proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode = + let count = insertSep($profile.media, ',') buildHtml(tdiv(class="photo-rail-card")): tdiv(class="photo-rail-header"): a(href=(&"/{profile.username}/media")): - icon "picture", $profile.media & " Photos and videos" + icon "picture", count & " Photos and videos" input(id="photo-rail-grid-toggle", `type`="checkbox") label(`for`="photo-rail-grid-toggle", class="photo-rail-header-mobile"): - icon "picture", $profile.media & " Photos and videos" + icon "picture", count & " Photos and videos" icon "down" tdiv(class="photo-rail-grid"): @@ -73,7 +74,7 @@ proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode = if i == 16: break a(href=(&"/{profile.username}/status/{photo.tweetId}#m"), style={backgroundColor: photo.color}): - genImg(photo.url & ":thumb") + genImg(photo.url & (if "format" in photo.url: "" else: ":thumb")) proc renderBanner(profile: Profile): VNode = buildHtml(): @@ -89,7 +90,7 @@ proc renderProtected(username: string): VNode = h2: text "This account's tweets are protected." p: text &"Only confirmed followers have access to @{username}'s tweets." -proc renderProfile*(profile: Profile; timeline: Timeline; +proc renderProfile*(profile: Profile; timeline: var Timeline; photoRail: PhotoRail; prefs: Prefs; path: string): VNode = timeline.query.fromUser = @[profile.username] buildHtml(tdiv(class="profile-tabs")): diff --git a/src/views/renderutils.nim b/src/views/renderutils.nim index bb16de7..db76cab 100644 --- a/src/views/renderutils.nim +++ b/src/views/renderutils.nim @@ -1,7 +1,6 @@ -import strutils, xmltree +import strutils import karax/[karaxdsl, vdom, vstyles] - -import ../types, ../utils +import ".."/[types, utils] proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode = var c = "icon-" & icon diff --git a/src/views/rss.nimf b/src/views/rss.nimf index 50415dc..2ea4bc4 100644 --- a/src/views/rss.nimf +++ b/src/views/rss.nimf @@ -82,24 +82,24 @@ 128 128 - #if timeline != nil: + # if timeline.content.len > 0: ${renderRssTweets(timeline.content, prefs, hostname)} #end if #end proc # -#proc renderListRss*(tweets: seq[Tweet]; name, list, hostname: string): string = +#proc renderListRss*(tweets: seq[Tweet]; list: List; hostname: string): string = #let prefs = Prefs(replaceTwitter: hostname, replaceYouTube: "invidio.us") -#let link = &"https://{hostname}/{name}/lists/{list}" +#let link = &"https://{hostname}/{list.username}/lists/{list.name}" #result = "" - ${list} / @${name} + ${list.name} / @${list.username} ${link} - Twitter feed for: ${list} by @${name}. Generated by ${hostname} + Twitter feed for: ${list.name} by @${list.username}. Generated by ${hostname} en-us 40 ${renderRssTweets(tweets, prefs, hostname)} diff --git a/src/views/search.nim b/src/views/search.nim index 774354e..81bc809 100644 --- a/src/views/search.nim +++ b/src/views/search.nim @@ -1,8 +1,8 @@ import strutils, strformat, sequtils, unicode, tables -import karax/[karaxdsl, vdom, vstyles] +import karax/[karaxdsl, vdom] import renderutils, timeline -import ".."/[types, formatters, query] +import ".."/[types, query] let toggles = { "nativeretweets": "Retweets", @@ -93,13 +93,15 @@ proc renderTweetSearch*(results: Result[Tweet]; prefs: Prefs; path: string): VNo if query.fromUser.len > 1: tdiv(class="timeline-header"): text query.fromUser.join(" | ") + + if query.fromUser.len > 0: + renderProfileTabs(query, query.fromUser.join(",")) + if query.fromUser.len == 0 or query.kind == tweets: tdiv(class="timeline-header"): renderSearchPanel(query) - if query.fromUser.len > 0: - renderProfileTabs(query, query.fromUser.join(",")) - else: + if query.fromUser.len == 0: renderSearchTabs(query) renderTimelineTweets(results, prefs, path) diff --git a/src/views/status.nim b/src/views/status.nim index 24f116d..aec042f 100644 --- a/src/views/status.nim +++ b/src/views/status.nim @@ -1,3 +1,4 @@ +import uri import karax/[karaxdsl, vdom] import ".."/[types, formatters] @@ -13,7 +14,7 @@ proc renderMoreReplies(thread: Chain): VNode = let reply = if thread.more == 1: "reply" else: "replies" let link = getLink(thread.content[^1]) buildHtml(tdiv(class="timeline-item more-replies")): - if link.len > 0: + if thread.content[^1].available: a(class="more-replies-text", href=link): text $num & "more " & reply else: @@ -32,41 +33,45 @@ proc renderReplyThread(thread: Chain; prefs: Prefs; path: string): VNode = proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string): VNode = buildHtml(tdiv(class="replies", id="r")): for thread in replies.content: - if thread == nil: continue + if thread.content.len == 0: continue renderReplyThread(thread, prefs, path) - if replies.hasMore: - renderMore(Query(), replies.minId, focus="#r") + if replies.bottom.len > 0: + renderMore(Query(), encodeUrl(replies.bottom), focus="#r") -proc renderConversation*(conversation: Conversation; prefs: Prefs; path: string): VNode = - let hasAfter = conversation.after != nil - let showReplies = not prefs.hideReplies +proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode = + let hasAfter = conv.after.content.len > 0 + let threadId = conv.tweet.threadId buildHtml(tdiv(class="conversation")): tdiv(class="main-thread"): - if conversation.before != nil: + if conv.before.content.len > 0: tdiv(class="before-tweet thread-line"): - if conversation.before.more == -1: - renderEarlier(conversation.before) - for i, tweet in conversation.before.content: + let first = conv.before.content[0] + if threadId != first.id and (first.replyId > 0 or not first.available): + renderEarlier(conv.before) + for i, tweet in conv.before.content: renderTweet(tweet, prefs, path, index=i) tdiv(class="main-tweet", id="m"): let afterClass = if hasAfter: "thread thread-line" else: "" - renderTweet(conversation.tweet, prefs, path, class=afterClass, - mainTweet=true) + renderTweet(conv.tweet, prefs, path, class=afterClass, mainTweet=true) if hasAfter: tdiv(class="after-tweet thread-line"): - let total = conversation.after.content.high - let more = conversation.after.more - for i, tweet in conversation.after.content: - renderTweet(tweet, prefs, path, index=i, last=(i == total and more == 0)) + let + total = conv.after.content.high + more = conv.after.more + for i, tweet in conv.after.content: + renderTweet(tweet, prefs, path, index=i, + last=(i == total and more == 0), afterTweet=true) if more != 0: - renderMoreReplies(conversation.after) + renderMoreReplies(conv.after) - if conversation.replies != nil and showReplies: - if not conversation.replies.beginning: - renderNewer(Query(), getLink(conversation.tweet)) - if conversation.replies.content.len > 0: - renderReplies(conversation.replies, prefs, path) + if not prefs.hideReplies: + if not conv.replies.beginning: + renderNewer(Query(), getLink(conv.tweet), focus="#r") + if conv.replies.content.len > 0 or conv.replies.bottom.len > 0: + renderReplies(conv.replies, prefs, path) + + renderToTop(focus="#m") diff --git a/src/views/timeline.nim b/src/views/timeline.nim index 58bf339..4457802 100644 --- a/src/views/timeline.nim +++ b/src/views/timeline.nim @@ -10,16 +10,22 @@ proc getQuery(query: Query): string = if result.len > 0: result &= "&" -proc renderNewer*(query: Query; path: string): VNode = - let q = genQueryUrl(query) - let url = if q.len > 0: "?" & q else: "" +proc renderToTop*(focus="#"): VNode = + buildHtml(tdiv(class="top-ref")): + icon "down", href=focus + +proc renderNewer*(query: Query; path: string; focus=""): VNode = + let + q = genQueryUrl(query) + url = if q.len > 0: "?" & q else: "" + p = if focus.len > 0: path.replace("#m", focus) else: path buildHtml(tdiv(class="timeline-item show-more")): - a(href=(path & url)): + a(href=(p & url)): text "Load newest" -proc renderMore*(query: Query; minId: string; focus=""): VNode = +proc renderMore*(query: Query; cursor: string; focus=""): VNode = buildHtml(tdiv(class="show-more")): - a(href=(&"?{getQuery(query)}max_position={minId}{focus}")): + a(href=(&"?{getQuery(query)}cursor={encodeUrl(cursor)}{focus}")): text "Load more" proc renderNoMore(): VNode = @@ -32,10 +38,6 @@ proc renderNoneFound(): VNode = h2(class="timeline-none"): text "No items found" -proc renderToTop(): VNode = - buildHtml(tdiv(class="top-ref")): - icon "down", href="#" - proc renderThread(thread: seq[Tweet]; prefs: Prefs; path: string): VNode = buildHtml(tdiv(class="thread-line")): let sortedThread = thread.sortedByIt(it.id) @@ -43,10 +45,16 @@ proc renderThread(thread: seq[Tweet]; prefs: Prefs; path: string): VNode = let show = i == thread.high and sortedThread[0].id != tweet.threadId let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: "" renderTweet(tweet, prefs, path, class=(header & "thread"), - index=i, total=thread.high, showThread=show) + index=i, last=(i == thread.high), showThread=show) -proc threadFilter(it: Tweet; thread: int64): bool = - it.retweet.isNone and it.reply.len == 0 and it.threadId == thread +proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet): seq[Tweet] = + result = @[it] + if it.retweet.isSome or it.replyId in threads: return + for t in tweets: + if t.id == result[0].replyId: + result.insert t + elif t.replyId == result[0].id: + result.add t proc renderUser(user: Profile; prefs: Prefs): VNode = buildHtml(tdiv(class="timeline-item")): @@ -72,8 +80,8 @@ proc renderTimelineUsers*(results: Result[Profile]; prefs: Prefs; path=""): VNod if results.content.len > 0: for user in results.content: renderUser(user, prefs) - if results.minId != "0": - renderMore(results.query, results.minId) + if results.bottom.len > 0: + renderMore(results.query, results.bottom) renderToTop() elif results.beginning: renderNoneFound() @@ -86,24 +94,31 @@ proc renderTimelineTweets*(results: Result[Tweet]; prefs: Prefs; path: string): renderNewer(results.query, parseUri(path).path) if results.content.len == 0: - renderNoneFound() + if not results.beginning: + renderNoMore() + else: + renderNoneFound() else: - var threads: seq[int64] - var retweets: seq[int64] + var + threads: seq[int64] + retweets: seq[int64] + for tweet in results.content: - if tweet.threadId in threads or tweet.id in retweets: continue - if tweet.pinned and prefs.hidePins: continue - let thread = results.content.filterIt(threadFilter(it, tweet.threadId)) + let rt = if tweet.retweet.isSome: get(tweet.retweet).id else: 0 + + if tweet.id in threads or rt in retweets or + tweet.pinned and prefs.hidePins: continue + + let thread = results.content.threadFilter(threads, tweet) if thread.len < 2: - if tweet.retweet.isSome: - retweets &= tweet.id - renderTweet(tweet, prefs, path, showThread=tweet.hasThread) + var hasThread = tweet.hasThread + if rt != 0: + retweets &= rt + hasThread = get(tweet.retweet).hasThread + renderTweet(tweet, prefs, path, showThread=hasThread) else: renderThread(thread, prefs, path) - threads &= tweet.threadId + threads &= thread.mapIt(it.id) - if results.hasMore or results.query.kind != posts: - renderMore(results.query, results.minId) - else: - renderNoMore() + renderMore(results.query, results.bottom) renderToTop() diff --git a/src/views/tweet.nim b/src/views/tweet.nim index f1061ed..6edacb0 100644 --- a/src/views/tweet.nim +++ b/src/views/tweet.nim @@ -4,11 +4,11 @@ import karax/[karaxdsl, vdom, vstyles] import renderutils import ".."/[types, utils, formatters] -proc renderHeader(tweet: Tweet): VNode = +proc renderHeader(tweet: Tweet; retweet=""): VNode = buildHtml(tdiv): - if tweet.retweet.isSome: + if retweet.len > 0: tdiv(class="retweet-header"): - span: icon "retweet", get(tweet.retweet).by & " retweeted" + span: icon "retweet", retweet & " retweeted" if tweet.pinned: tdiv(class="pinned"): @@ -24,31 +24,24 @@ proc renderHeader(tweet: Tweet): VNode = linkUser(tweet.profile, class="username") span(class="tweet-date"): - a(href=getLink(tweet), title=tweet.getTime()): - text tweet.shortTime + a(href=getLink(tweet), title=tweet.getTime): + text tweet.getShortTime 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) + 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"): + var url = photo + if "=orig" notin url: url &= "?name=orig" + a(href=getPicUrl(url), class="still-image", target="_blank"): + genImg(photo) proc isPlaybackEnabled(prefs: Prefs; video: Video): bool = case video.playbackType @@ -88,7 +81,8 @@ proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode = elif not prefs.isPlaybackEnabled(video): renderVideoDisabled(video, path) else: - let source = getVidUrl(video.url) + let vid = video.variants.filterIt(it.videoType == video.playbackType) + let source = getVidUrl(vid[0].url) case video.playbackType of mp4: if prefs.muteVideos: @@ -138,7 +132,7 @@ proc renderPoll(poll: Poll): VNode = proc renderCardImage(card: Card): VNode = buildHtml(tdiv(class="card-image-container")): tdiv(class="card-image"): - img(src=getPicUrl(get(card.image))) + img(src=getPicUrl(card.image), alt="") if card.kind == player: tdiv(class="card-overlay"): tdiv(class="overlay-circle"): @@ -151,9 +145,8 @@ proc renderCardContent(card: Card): VNode = 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: "" + const smallCards = {player, summary} + let large = if card.kind notin smallCards: " large" else: "" let url = replaceUrl(card.url, prefs) buildHtml(tdiv(class=("card" & large))): @@ -164,7 +157,7 @@ proc renderCard(card: Card; prefs: Prefs; path: string): VNode = renderCardContent(card) else: a(class="card-container", href=url): - if card.image.isSome: + if card.image.len > 0: renderCardImage(card) tdiv(class="card-content-container"): renderCardContent(card) @@ -184,13 +177,6 @@ proc renderReply(tweet: Tweet): VNode = 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))): @@ -206,19 +192,17 @@ proc renderMediaTags(tags: seq[Profile]): VNode = if i < tags.high: text ", " -proc renderQuoteMedia(quote: Quote): VNode = +proc renderQuoteMedia(quote: Tweet; prefs: Prefs; path: string): 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" + if quote.photos.len > 0: + renderAlbum(quote) + # genImg(quote.photos[0]) + elif quote.video.isSome: + renderVideo(quote.video.get(), prefs, path) + elif quote.gif.isSome: + renderGif(quote.gif.get(), prefs) -proc renderQuote(quote: Quote; prefs: Prefs): VNode = +proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode = if not quote.available: return buildHtml(tdiv(class="quote unavailable")): tdiv(class="unavailable-quote"): @@ -227,26 +211,32 @@ proc renderQuote(quote: Quote; prefs: Prefs): VNode = else: text "This tweet is unavailable" - buildHtml(tdiv(class="quote")): + buildHtml(tdiv(class="quote quote-big")): a(class="quote-link", href=getLink(quote)) - if quote.thumb.len > 0 or quote.sensitive: - renderQuoteMedia(quote) + tdiv(class="tweet-name-row"): + tdiv(class="fullname-and-username"): + linkUser(quote.profile, class="fullname") + linkUser(quote.profile, class="username") - tdiv(class="fullname-and-username"): - linkUser(quote.profile, class="fullname") - linkUser(quote.profile, class="username") + span(class="tweet-date"): + a(href=getLink(quote), title=quote.getTime): + text quote.getShortTime if quote.reply.len > 0: renderReply(quote) - tdiv(class="quote-text"): - verbatim replaceUrl(quote.text, prefs) + if quote.text.len > 0: + tdiv(class="quote-text"): + verbatim replaceUrl(quote.text, prefs) if quote.hasThread: a(class="show-thread", href=getLink(quote)): text "Show this thread" + if quote.photos.len > 0 or quote.video.isSome or quote.gif.isSome: + renderQuoteMedia(quote, prefs, path) + proc renderLocation*(tweet: Tweet): string = let (place, url) = tweet.getLocation() if place.len == 0: return @@ -258,11 +248,10 @@ proc renderLocation*(tweet: Tweet): string = text place return $node -proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; - index=0; total=(-1); last=false; showThread=false; - mainTweet=false): VNode = +proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0; + last=false; showThread=false; mainTweet=false; afterTweet=false): VNode = var divClass = class - if index == total or last: + if index == -1 or last: divClass = "thread-last " & class if not tweet.available: @@ -273,15 +262,22 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; else: text "This tweet is unavailable" + let fullTweet = tweet + var retweet: string + var tweet = fullTweet + if tweet.retweet.isSome: + tweet = tweet.retweet.get + retweet = fullTweet.profile.fullname + buildHtml(tdiv(class=("timeline-item " & divClass))): if not mainTweet: a(class="tweet-link", href=getLink(tweet)) tdiv(class="tweet-body"): var views = "" - renderHeader(tweet) + renderHeader(tweet, retweet) - if index == 0 and tweet.reply.len > 0 and + if not afterTweet and index == 0 and tweet.reply.len > 0 and (tweet.reply.len > 1 or tweet.reply[0] != tweet.profile.username): renderReply(tweet) @@ -293,7 +289,8 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; if tweet.card.isSome: renderCard(tweet.card.get(), prefs, path) - elif tweet.photos.len > 0: + + if tweet.photos.len > 0: renderAlbum(tweet) elif tweet.video.isSome: renderVideo(tweet.video.get(), prefs, path) @@ -301,11 +298,12 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; elif tweet.gif.isSome: renderGif(tweet.gif.get(), prefs) views = "GIF" - elif tweet.poll.isSome: + + if tweet.poll.isSome: renderPoll(tweet.poll.get()) if tweet.quote.isSome: - renderQuote(tweet.quote.get(), prefs) + renderQuote(tweet.quote.get(), prefs, path) if mainTweet: p(class="tweet-published"): text getTweetTime(tweet)