mirror of
https://github.com/zedeus/nitter.git
synced 2024-12-12 19:16:28 +00:00
html and css updates
This commit is contained in:
parent
762d00b21d
commit
2950c0de35
14 changed files with 246 additions and 224 deletions
|
@ -42,7 +42,8 @@
|
||||||
|
|
||||||
img {
|
img {
|
||||||
display: block;
|
display: block;
|
||||||
width: calc(100% - 8px);
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 4px solid var(--darker_grey);
|
border: 4px solid var(--darker_grey);
|
||||||
|
|
|
@ -14,9 +14,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-header-mobile {
|
&-header-mobile {
|
||||||
padding: 5px 12px 0;
|
|
||||||
display: none;
|
display: none;
|
||||||
width: calc(100% - 24px);
|
box-sizing: border-box;
|
||||||
|
padding: 5px 12px 0;
|
||||||
|
width: 100%;
|
||||||
float: unset;
|
float: unset;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -13,18 +13,28 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-header {
|
.timeline-header {
|
||||||
|
width: 100%;
|
||||||
background-color: var(--bg_panel);
|
background-color: var(--bg_panel);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
float: unset;
|
float: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-banner img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-description {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: var(--fg_color);
|
background-color: var(--bg_overlays);
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
.still-image {
|
.still-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,17 +67,17 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.single-image {
|
// .single-image {
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
width: 100%;
|
// width: 100%;
|
||||||
max-height: 600px;
|
// max-height: 600px;
|
||||||
|
|
||||||
.attachments {
|
// .attachments {
|
||||||
width: unset;
|
// width: unset;
|
||||||
max-height: unset;
|
// max-height: unset;
|
||||||
display: inherit;
|
// display: inherit;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
.overlay-circle {
|
.overlay-circle {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
border: solid 1px var(--dark_grey);
|
border: solid 1px var(--dark_grey);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: var(--bg_elements);
|
background-color: var(--bg_elements);
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
padding: 6px;
|
|
||||||
position: relative;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--grey);
|
border-color: var(--grey);
|
||||||
|
@ -17,91 +17,73 @@
|
||||||
&.unavailable:hover {
|
&.unavailable:hover {
|
||||||
border-color: var(--dark_grey);
|
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 {
|
.unavailable-quote {
|
||||||
padding: 6px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quote-link {
|
.quote-link {
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quote .quote-link {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-text {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote-media-container {
|
.quote-media-container {
|
||||||
|
max-height: 300px;
|
||||||
display: flex;
|
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 {
|
.card {
|
||||||
display: flex;
|
margin: unset;
|
||||||
justify-content: center;
|
}
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
img {
|
.media-gif {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
}
|
||||||
align-self: center;
|
|
||||||
}
|
.attachments {
|
||||||
}
|
border-radius: 0;
|
||||||
|
}
|
||||||
.quote-badge {
|
|
||||||
left: 0;
|
.gallery-gif .attachment {
|
||||||
bottom: 0;
|
display: flex;
|
||||||
position: absolute;
|
justify-content: center;
|
||||||
z-index: 1;
|
background-color: var(--bg_color);
|
||||||
align-self: flex-end;
|
|
||||||
}
|
video {
|
||||||
|
height: unset;
|
||||||
.quote-badge-text {
|
width: unset;
|
||||||
margin: 4px;
|
}
|
||||||
background: $shadow;
|
}
|
||||||
border-radius: 4px;
|
|
||||||
color: #fffffff0;
|
.gallery-video, .gallery-gif {
|
||||||
padding: 1px 3px;
|
max-height: 300px;
|
||||||
font-size: 12px;
|
}
|
||||||
font-weight: bold;
|
|
||||||
}
|
.still-image img {
|
||||||
|
max-height: 250px
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import strformat
|
||||||
import karax/[karaxdsl, vdom]
|
import karax/[karaxdsl, vdom]
|
||||||
|
|
||||||
import renderutils
|
import renderutils
|
||||||
import ".."/[types]
|
import ".."/[types, utils]
|
||||||
|
|
||||||
proc renderListTabs*(query: Query; path: string): VNode =
|
proc renderListTabs*(query: Query; path: string): VNode =
|
||||||
buildHtml(ul(class="tab")):
|
buildHtml(ul(class="tab")):
|
||||||
|
@ -11,10 +11,18 @@ proc renderListTabs*(query: Query; path: string): VNode =
|
||||||
li(class=query.getTabClass(userList)):
|
li(class=query.getTabClass(userList)):
|
||||||
a(href=(path & "/members")): text "Members"
|
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")):
|
buildHtml(tdiv(class="timeline-container")):
|
||||||
tdiv(class="timeline-header"):
|
if list.banner.len > 0:
|
||||||
text &"\"{list}\" by @{name}"
|
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
|
body
|
||||||
|
|
|
@ -58,14 +58,15 @@ proc renderProfileCard*(profile: Profile; prefs: Prefs): VNode =
|
||||||
renderStat(profile.likes, "likes")
|
renderStat(profile.likes, "likes")
|
||||||
|
|
||||||
proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode =
|
proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode =
|
||||||
|
let count = insertSep($profile.media, ',')
|
||||||
buildHtml(tdiv(class="photo-rail-card")):
|
buildHtml(tdiv(class="photo-rail-card")):
|
||||||
tdiv(class="photo-rail-header"):
|
tdiv(class="photo-rail-header"):
|
||||||
a(href=(&"/{profile.username}/media")):
|
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")
|
input(id="photo-rail-grid-toggle", `type`="checkbox")
|
||||||
label(`for`="photo-rail-grid-toggle", class="photo-rail-header-mobile"):
|
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"
|
icon "down"
|
||||||
|
|
||||||
tdiv(class="photo-rail-grid"):
|
tdiv(class="photo-rail-grid"):
|
||||||
|
@ -73,7 +74,7 @@ proc renderPhotoRail(profile: Profile; photoRail: PhotoRail): VNode =
|
||||||
if i == 16: break
|
if i == 16: break
|
||||||
a(href=(&"/{profile.username}/status/{photo.tweetId}#m"),
|
a(href=(&"/{profile.username}/status/{photo.tweetId}#m"),
|
||||||
style={backgroundColor: photo.color}):
|
style={backgroundColor: photo.color}):
|
||||||
genImg(photo.url & ":thumb")
|
genImg(photo.url & (if "format" in photo.url: "" else: ":thumb"))
|
||||||
|
|
||||||
proc renderBanner(profile: Profile): VNode =
|
proc renderBanner(profile: Profile): VNode =
|
||||||
buildHtml():
|
buildHtml():
|
||||||
|
@ -89,7 +90,7 @@ proc renderProtected(username: string): VNode =
|
||||||
h2: text "This account's tweets are protected."
|
h2: text "This account's tweets are protected."
|
||||||
p: text &"Only confirmed followers have access to @{username}'s tweets."
|
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 =
|
photoRail: PhotoRail; prefs: Prefs; path: string): VNode =
|
||||||
timeline.query.fromUser = @[profile.username]
|
timeline.query.fromUser = @[profile.username]
|
||||||
buildHtml(tdiv(class="profile-tabs")):
|
buildHtml(tdiv(class="profile-tabs")):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import strutils, xmltree
|
import strutils
|
||||||
import karax/[karaxdsl, vdom, vstyles]
|
import karax/[karaxdsl, vdom, vstyles]
|
||||||
|
import ".."/[types, utils]
|
||||||
import ../types, ../utils
|
|
||||||
|
|
||||||
proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode =
|
proc icon*(icon: string; text=""; title=""; class=""; href=""): VNode =
|
||||||
var c = "icon-" & icon
|
var c = "icon-" & icon
|
||||||
|
|
|
@ -82,24 +82,24 @@
|
||||||
<width>128</width>
|
<width>128</width>
|
||||||
<height>128</height>
|
<height>128</height>
|
||||||
</image>
|
</image>
|
||||||
#if timeline != nil:
|
# if timeline.content.len > 0:
|
||||||
${renderRssTweets(timeline.content, prefs, hostname)}
|
${renderRssTweets(timeline.content, prefs, hostname)}
|
||||||
#end if
|
#end if
|
||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
#end proc
|
#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 prefs = Prefs(replaceTwitter: hostname, replaceYouTube: "invidio.us")
|
||||||
#let link = &"https://{hostname}/{name}/lists/{list}"
|
#let link = &"https://{hostname}/{list.username}/lists/{list.name}"
|
||||||
#result = ""
|
#result = ""
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
||||||
<channel>
|
<channel>
|
||||||
<atom:link href="${link}" rel="self" type="application/rss+xml" />
|
<atom:link href="${link}" rel="self" type="application/rss+xml" />
|
||||||
<title>${list} / @${name}</title>
|
<title>${list.name} / @${list.username}</title>
|
||||||
<link>${link}</link>
|
<link>${link}</link>
|
||||||
<description>Twitter feed for: ${list} by @${name}. Generated by ${hostname}</description>
|
<description>Twitter feed for: ${list.name} by @${list.username}. Generated by ${hostname}</description>
|
||||||
<language>en-us</language>
|
<language>en-us</language>
|
||||||
<ttl>40</ttl>
|
<ttl>40</ttl>
|
||||||
${renderRssTweets(tweets, prefs, hostname)}
|
${renderRssTweets(tweets, prefs, hostname)}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import strutils, strformat, sequtils, unicode, tables
|
import strutils, strformat, sequtils, unicode, tables
|
||||||
import karax/[karaxdsl, vdom, vstyles]
|
import karax/[karaxdsl, vdom]
|
||||||
|
|
||||||
import renderutils, timeline
|
import renderutils, timeline
|
||||||
import ".."/[types, formatters, query]
|
import ".."/[types, query]
|
||||||
|
|
||||||
let toggles = {
|
let toggles = {
|
||||||
"nativeretweets": "Retweets",
|
"nativeretweets": "Retweets",
|
||||||
|
@ -93,13 +93,15 @@ proc renderTweetSearch*(results: Result[Tweet]; prefs: Prefs; path: string): VNo
|
||||||
if query.fromUser.len > 1:
|
if query.fromUser.len > 1:
|
||||||
tdiv(class="timeline-header"):
|
tdiv(class="timeline-header"):
|
||||||
text query.fromUser.join(" | ")
|
text query.fromUser.join(" | ")
|
||||||
|
|
||||||
|
if query.fromUser.len > 0:
|
||||||
|
renderProfileTabs(query, query.fromUser.join(","))
|
||||||
|
|
||||||
if query.fromUser.len == 0 or query.kind == tweets:
|
if query.fromUser.len == 0 or query.kind == tweets:
|
||||||
tdiv(class="timeline-header"):
|
tdiv(class="timeline-header"):
|
||||||
renderSearchPanel(query)
|
renderSearchPanel(query)
|
||||||
|
|
||||||
if query.fromUser.len > 0:
|
if query.fromUser.len == 0:
|
||||||
renderProfileTabs(query, query.fromUser.join(","))
|
|
||||||
else:
|
|
||||||
renderSearchTabs(query)
|
renderSearchTabs(query)
|
||||||
|
|
||||||
renderTimelineTweets(results, prefs, path)
|
renderTimelineTweets(results, prefs, path)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import uri
|
||||||
import karax/[karaxdsl, vdom]
|
import karax/[karaxdsl, vdom]
|
||||||
|
|
||||||
import ".."/[types, formatters]
|
import ".."/[types, formatters]
|
||||||
|
@ -13,7 +14,7 @@ proc renderMoreReplies(thread: Chain): VNode =
|
||||||
let reply = if thread.more == 1: "reply" else: "replies"
|
let reply = if thread.more == 1: "reply" else: "replies"
|
||||||
let link = getLink(thread.content[^1])
|
let link = getLink(thread.content[^1])
|
||||||
buildHtml(tdiv(class="timeline-item more-replies")):
|
buildHtml(tdiv(class="timeline-item more-replies")):
|
||||||
if link.len > 0:
|
if thread.content[^1].available:
|
||||||
a(class="more-replies-text", href=link):
|
a(class="more-replies-text", href=link):
|
||||||
text $num & "more " & reply
|
text $num & "more " & reply
|
||||||
else:
|
else:
|
||||||
|
@ -32,41 +33,45 @@ proc renderReplyThread(thread: Chain; prefs: Prefs; path: string): VNode =
|
||||||
proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string): VNode =
|
proc renderReplies*(replies: Result[Chain]; prefs: Prefs; path: string): VNode =
|
||||||
buildHtml(tdiv(class="replies", id="r")):
|
buildHtml(tdiv(class="replies", id="r")):
|
||||||
for thread in replies.content:
|
for thread in replies.content:
|
||||||
if thread == nil: continue
|
if thread.content.len == 0: continue
|
||||||
renderReplyThread(thread, prefs, path)
|
renderReplyThread(thread, prefs, path)
|
||||||
|
|
||||||
if replies.hasMore:
|
if replies.bottom.len > 0:
|
||||||
renderMore(Query(), replies.minId, focus="#r")
|
renderMore(Query(), encodeUrl(replies.bottom), focus="#r")
|
||||||
|
|
||||||
proc renderConversation*(conversation: Conversation; prefs: Prefs; path: string): VNode =
|
proc renderConversation*(conv: Conversation; prefs: Prefs; path: string): VNode =
|
||||||
let hasAfter = conversation.after != nil
|
let hasAfter = conv.after.content.len > 0
|
||||||
let showReplies = not prefs.hideReplies
|
let threadId = conv.tweet.threadId
|
||||||
buildHtml(tdiv(class="conversation")):
|
buildHtml(tdiv(class="conversation")):
|
||||||
tdiv(class="main-thread"):
|
tdiv(class="main-thread"):
|
||||||
if conversation.before != nil:
|
if conv.before.content.len > 0:
|
||||||
tdiv(class="before-tweet thread-line"):
|
tdiv(class="before-tweet thread-line"):
|
||||||
if conversation.before.more == -1:
|
let first = conv.before.content[0]
|
||||||
renderEarlier(conversation.before)
|
if threadId != first.id and (first.replyId > 0 or not first.available):
|
||||||
for i, tweet in conversation.before.content:
|
renderEarlier(conv.before)
|
||||||
|
for i, tweet in conv.before.content:
|
||||||
renderTweet(tweet, prefs, path, index=i)
|
renderTweet(tweet, prefs, path, index=i)
|
||||||
|
|
||||||
tdiv(class="main-tweet", id="m"):
|
tdiv(class="main-tweet", id="m"):
|
||||||
let afterClass = if hasAfter: "thread thread-line" else: ""
|
let afterClass = if hasAfter: "thread thread-line" else: ""
|
||||||
renderTweet(conversation.tweet, prefs, path, class=afterClass,
|
renderTweet(conv.tweet, prefs, path, class=afterClass, mainTweet=true)
|
||||||
mainTweet=true)
|
|
||||||
|
|
||||||
if hasAfter:
|
if hasAfter:
|
||||||
tdiv(class="after-tweet thread-line"):
|
tdiv(class="after-tweet thread-line"):
|
||||||
let total = conversation.after.content.high
|
let
|
||||||
let more = conversation.after.more
|
total = conv.after.content.high
|
||||||
for i, tweet in conversation.after.content:
|
more = conv.after.more
|
||||||
renderTweet(tweet, prefs, path, index=i, last=(i == total and more == 0))
|
for i, tweet in conv.after.content:
|
||||||
|
renderTweet(tweet, prefs, path, index=i,
|
||||||
|
last=(i == total and more == 0), afterTweet=true)
|
||||||
|
|
||||||
if more != 0:
|
if more != 0:
|
||||||
renderMoreReplies(conversation.after)
|
renderMoreReplies(conv.after)
|
||||||
|
|
||||||
if conversation.replies != nil and showReplies:
|
if not prefs.hideReplies:
|
||||||
if not conversation.replies.beginning:
|
if not conv.replies.beginning:
|
||||||
renderNewer(Query(), getLink(conversation.tweet))
|
renderNewer(Query(), getLink(conv.tweet), focus="#r")
|
||||||
if conversation.replies.content.len > 0:
|
if conv.replies.content.len > 0 or conv.replies.bottom.len > 0:
|
||||||
renderReplies(conversation.replies, prefs, path)
|
renderReplies(conv.replies, prefs, path)
|
||||||
|
|
||||||
|
renderToTop(focus="#m")
|
||||||
|
|
|
@ -10,16 +10,22 @@ proc getQuery(query: Query): string =
|
||||||
if result.len > 0:
|
if result.len > 0:
|
||||||
result &= "&"
|
result &= "&"
|
||||||
|
|
||||||
proc renderNewer*(query: Query; path: string): VNode =
|
proc renderToTop*(focus="#"): VNode =
|
||||||
let q = genQueryUrl(query)
|
buildHtml(tdiv(class="top-ref")):
|
||||||
let url = if q.len > 0: "?" & q else: ""
|
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")):
|
buildHtml(tdiv(class="timeline-item show-more")):
|
||||||
a(href=(path & url)):
|
a(href=(p & url)):
|
||||||
text "Load newest"
|
text "Load newest"
|
||||||
|
|
||||||
proc renderMore*(query: Query; minId: string; focus=""): VNode =
|
proc renderMore*(query: Query; cursor: string; focus=""): VNode =
|
||||||
buildHtml(tdiv(class="show-more")):
|
buildHtml(tdiv(class="show-more")):
|
||||||
a(href=(&"?{getQuery(query)}max_position={minId}{focus}")):
|
a(href=(&"?{getQuery(query)}cursor={encodeUrl(cursor)}{focus}")):
|
||||||
text "Load more"
|
text "Load more"
|
||||||
|
|
||||||
proc renderNoMore(): VNode =
|
proc renderNoMore(): VNode =
|
||||||
|
@ -32,10 +38,6 @@ proc renderNoneFound(): VNode =
|
||||||
h2(class="timeline-none"):
|
h2(class="timeline-none"):
|
||||||
text "No items found"
|
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 =
|
proc renderThread(thread: seq[Tweet]; prefs: Prefs; path: string): VNode =
|
||||||
buildHtml(tdiv(class="thread-line")):
|
buildHtml(tdiv(class="thread-line")):
|
||||||
let sortedThread = thread.sortedByIt(it.id)
|
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 show = i == thread.high and sortedThread[0].id != tweet.threadId
|
||||||
let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: ""
|
let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: ""
|
||||||
renderTweet(tweet, prefs, path, class=(header & "thread"),
|
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 =
|
proc threadFilter(tweets: openArray[Tweet]; threads: openArray[int64]; it: Tweet): seq[Tweet] =
|
||||||
it.retweet.isNone and it.reply.len == 0 and it.threadId == thread
|
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 =
|
proc renderUser(user: Profile; prefs: Prefs): VNode =
|
||||||
buildHtml(tdiv(class="timeline-item")):
|
buildHtml(tdiv(class="timeline-item")):
|
||||||
|
@ -72,8 +80,8 @@ proc renderTimelineUsers*(results: Result[Profile]; prefs: Prefs; path=""): VNod
|
||||||
if results.content.len > 0:
|
if results.content.len > 0:
|
||||||
for user in results.content:
|
for user in results.content:
|
||||||
renderUser(user, prefs)
|
renderUser(user, prefs)
|
||||||
if results.minId != "0":
|
if results.bottom.len > 0:
|
||||||
renderMore(results.query, results.minId)
|
renderMore(results.query, results.bottom)
|
||||||
renderToTop()
|
renderToTop()
|
||||||
elif results.beginning:
|
elif results.beginning:
|
||||||
renderNoneFound()
|
renderNoneFound()
|
||||||
|
@ -86,24 +94,31 @@ proc renderTimelineTweets*(results: Result[Tweet]; prefs: Prefs; path: string):
|
||||||
renderNewer(results.query, parseUri(path).path)
|
renderNewer(results.query, parseUri(path).path)
|
||||||
|
|
||||||
if results.content.len == 0:
|
if results.content.len == 0:
|
||||||
renderNoneFound()
|
if not results.beginning:
|
||||||
|
renderNoMore()
|
||||||
|
else:
|
||||||
|
renderNoneFound()
|
||||||
else:
|
else:
|
||||||
var threads: seq[int64]
|
var
|
||||||
var retweets: seq[int64]
|
threads: seq[int64]
|
||||||
|
retweets: seq[int64]
|
||||||
|
|
||||||
for tweet in results.content:
|
for tweet in results.content:
|
||||||
if tweet.threadId in threads or tweet.id in retweets: continue
|
let rt = if tweet.retweet.isSome: get(tweet.retweet).id else: 0
|
||||||
if tweet.pinned and prefs.hidePins: continue
|
|
||||||
let thread = results.content.filterIt(threadFilter(it, tweet.threadId))
|
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 thread.len < 2:
|
||||||
if tweet.retweet.isSome:
|
var hasThread = tweet.hasThread
|
||||||
retweets &= tweet.id
|
if rt != 0:
|
||||||
renderTweet(tweet, prefs, path, showThread=tweet.hasThread)
|
retweets &= rt
|
||||||
|
hasThread = get(tweet.retweet).hasThread
|
||||||
|
renderTweet(tweet, prefs, path, showThread=hasThread)
|
||||||
else:
|
else:
|
||||||
renderThread(thread, prefs, path)
|
renderThread(thread, prefs, path)
|
||||||
threads &= tweet.threadId
|
threads &= thread.mapIt(it.id)
|
||||||
|
|
||||||
if results.hasMore or results.query.kind != posts:
|
renderMore(results.query, results.bottom)
|
||||||
renderMore(results.query, results.minId)
|
|
||||||
else:
|
|
||||||
renderNoMore()
|
|
||||||
renderToTop()
|
renderToTop()
|
||||||
|
|
|
@ -4,11 +4,11 @@ import karax/[karaxdsl, vdom, vstyles]
|
||||||
import renderutils
|
import renderutils
|
||||||
import ".."/[types, utils, formatters]
|
import ".."/[types, utils, formatters]
|
||||||
|
|
||||||
proc renderHeader(tweet: Tweet): VNode =
|
proc renderHeader(tweet: Tweet; retweet=""): VNode =
|
||||||
buildHtml(tdiv):
|
buildHtml(tdiv):
|
||||||
if tweet.retweet.isSome:
|
if retweet.len > 0:
|
||||||
tdiv(class="retweet-header"):
|
tdiv(class="retweet-header"):
|
||||||
span: icon "retweet", get(tweet.retweet).by & " retweeted"
|
span: icon "retweet", retweet & " retweeted"
|
||||||
|
|
||||||
if tweet.pinned:
|
if tweet.pinned:
|
||||||
tdiv(class="pinned"):
|
tdiv(class="pinned"):
|
||||||
|
@ -24,31 +24,24 @@ proc renderHeader(tweet: Tweet): VNode =
|
||||||
linkUser(tweet.profile, class="username")
|
linkUser(tweet.profile, class="username")
|
||||||
|
|
||||||
span(class="tweet-date"):
|
span(class="tweet-date"):
|
||||||
a(href=getLink(tweet), title=tweet.getTime()):
|
a(href=getLink(tweet), title=tweet.getTime):
|
||||||
text tweet.shortTime
|
text tweet.getShortTime
|
||||||
|
|
||||||
proc renderAlbum(tweet: Tweet): VNode =
|
proc renderAlbum(tweet: Tweet): VNode =
|
||||||
let
|
let
|
||||||
groups = if tweet.photos.len < 3: @[tweet.photos]
|
groups = if tweet.photos.len < 3: @[tweet.photos]
|
||||||
else: tweet.photos.distribute(2)
|
else: tweet.photos.distribute(2)
|
||||||
|
|
||||||
if groups.len == 1 and groups[0].len == 1:
|
buildHtml(tdiv(class="attachments")):
|
||||||
buildHtml(tdiv(class="single-image")):
|
for i, photos in groups:
|
||||||
tdiv(class="attachments gallery-row"):
|
let margin = if i > 0: ".25em" else: ""
|
||||||
a(href=getPicUrl(groups[0][0] & "?name=orig"), class="still-image",
|
tdiv(class="gallery-row", style={marginTop: margin}):
|
||||||
target="_blank"):
|
for photo in photos:
|
||||||
genImg(groups[0][0])
|
tdiv(class="attachment image"):
|
||||||
else:
|
var url = photo
|
||||||
buildHtml(tdiv(class="attachments")):
|
if "=orig" notin url: url &= "?name=orig"
|
||||||
for i, photos in groups:
|
a(href=getPicUrl(url), class="still-image", target="_blank"):
|
||||||
let margin = if i > 0: ".25em" else: ""
|
genImg(photo)
|
||||||
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 =
|
proc isPlaybackEnabled(prefs: Prefs; video: Video): bool =
|
||||||
case video.playbackType
|
case video.playbackType
|
||||||
|
@ -88,7 +81,8 @@ proc renderVideo*(video: Video; prefs: Prefs; path: string): VNode =
|
||||||
elif not prefs.isPlaybackEnabled(video):
|
elif not prefs.isPlaybackEnabled(video):
|
||||||
renderVideoDisabled(video, path)
|
renderVideoDisabled(video, path)
|
||||||
else:
|
else:
|
||||||
let source = getVidUrl(video.url)
|
let vid = video.variants.filterIt(it.videoType == video.playbackType)
|
||||||
|
let source = getVidUrl(vid[0].url)
|
||||||
case video.playbackType
|
case video.playbackType
|
||||||
of mp4:
|
of mp4:
|
||||||
if prefs.muteVideos:
|
if prefs.muteVideos:
|
||||||
|
@ -138,7 +132,7 @@ proc renderPoll(poll: Poll): VNode =
|
||||||
proc renderCardImage(card: Card): VNode =
|
proc renderCardImage(card: Card): VNode =
|
||||||
buildHtml(tdiv(class="card-image-container")):
|
buildHtml(tdiv(class="card-image-container")):
|
||||||
tdiv(class="card-image"):
|
tdiv(class="card-image"):
|
||||||
img(src=getPicUrl(get(card.image)))
|
img(src=getPicUrl(card.image), alt="")
|
||||||
if card.kind == player:
|
if card.kind == player:
|
||||||
tdiv(class="card-overlay"):
|
tdiv(class="card-overlay"):
|
||||||
tdiv(class="overlay-circle"):
|
tdiv(class="overlay-circle"):
|
||||||
|
@ -151,9 +145,8 @@ proc renderCardContent(card: Card): VNode =
|
||||||
span(class="card-destination"): text card.dest
|
span(class="card-destination"): text card.dest
|
||||||
|
|
||||||
proc renderCard(card: Card; prefs: Prefs; path: string): VNode =
|
proc renderCard(card: Card; prefs: Prefs; path: string): VNode =
|
||||||
const largeCards = {summaryLarge, liveEvent, promoWebsite,
|
const smallCards = {player, summary}
|
||||||
promoVideo, promoVideoConvo}
|
let large = if card.kind notin smallCards: " large" else: ""
|
||||||
let large = if card.kind in largeCards: " large" else: ""
|
|
||||||
let url = replaceUrl(card.url, prefs)
|
let url = replaceUrl(card.url, prefs)
|
||||||
|
|
||||||
buildHtml(tdiv(class=("card" & large))):
|
buildHtml(tdiv(class=("card" & large))):
|
||||||
|
@ -164,7 +157,7 @@ proc renderCard(card: Card; prefs: Prefs; path: string): VNode =
|
||||||
renderCardContent(card)
|
renderCardContent(card)
|
||||||
else:
|
else:
|
||||||
a(class="card-container", href=url):
|
a(class="card-container", href=url):
|
||||||
if card.image.isSome:
|
if card.image.len > 0:
|
||||||
renderCardImage(card)
|
renderCardImage(card)
|
||||||
tdiv(class="card-content-container"):
|
tdiv(class="card-content-container"):
|
||||||
renderCardContent(card)
|
renderCardContent(card)
|
||||||
|
@ -184,13 +177,6 @@ proc renderReply(tweet: Tweet): VNode =
|
||||||
if i > 0: text " "
|
if i > 0: text " "
|
||||||
a(href=("/" & u)): text "@" & u
|
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 =
|
proc renderAttribution(profile: Profile): VNode =
|
||||||
let avatarUrl = getPicUrl(profile.getUserpic("_200x200"))
|
let avatarUrl = getPicUrl(profile.getUserpic("_200x200"))
|
||||||
buildHtml(a(class="attribution", href=("/" & profile.username))):
|
buildHtml(a(class="attribution", href=("/" & profile.username))):
|
||||||
|
@ -206,19 +192,17 @@ proc renderMediaTags(tags: seq[Profile]): VNode =
|
||||||
if i < tags.high:
|
if i < tags.high:
|
||||||
text ", "
|
text ", "
|
||||||
|
|
||||||
proc renderQuoteMedia(quote: Quote): VNode =
|
proc renderQuoteMedia(quote: Tweet; prefs: Prefs; path: string): VNode =
|
||||||
buildHtml(tdiv(class="quote-media-container")):
|
buildHtml(tdiv(class="quote-media-container")):
|
||||||
if quote.thumb.len > 0:
|
if quote.photos.len > 0:
|
||||||
tdiv(class="quote-media"):
|
renderAlbum(quote)
|
||||||
genImg(quote.thumb)
|
# genImg(quote.photos[0])
|
||||||
if quote.badge.len > 0:
|
elif quote.video.isSome:
|
||||||
tdiv(class="quote-badge"):
|
renderVideo(quote.video.get(), prefs, path)
|
||||||
tdiv(class="quote-badge-text"): text quote.badge
|
elif quote.gif.isSome:
|
||||||
elif quote.sensitive:
|
renderGif(quote.gif.get(), prefs)
|
||||||
tdiv(class="quote-sensitive"):
|
|
||||||
icon "attention", class="quote-sensitive-icon"
|
|
||||||
|
|
||||||
proc renderQuote(quote: Quote; prefs: Prefs): VNode =
|
proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode =
|
||||||
if not quote.available:
|
if not quote.available:
|
||||||
return buildHtml(tdiv(class="quote unavailable")):
|
return buildHtml(tdiv(class="quote unavailable")):
|
||||||
tdiv(class="unavailable-quote"):
|
tdiv(class="unavailable-quote"):
|
||||||
|
@ -227,26 +211,32 @@ proc renderQuote(quote: Quote; prefs: Prefs): VNode =
|
||||||
else:
|
else:
|
||||||
text "This tweet is unavailable"
|
text "This tweet is unavailable"
|
||||||
|
|
||||||
buildHtml(tdiv(class="quote")):
|
buildHtml(tdiv(class="quote quote-big")):
|
||||||
a(class="quote-link", href=getLink(quote))
|
a(class="quote-link", href=getLink(quote))
|
||||||
|
|
||||||
if quote.thumb.len > 0 or quote.sensitive:
|
tdiv(class="tweet-name-row"):
|
||||||
renderQuoteMedia(quote)
|
tdiv(class="fullname-and-username"):
|
||||||
|
linkUser(quote.profile, class="fullname")
|
||||||
|
linkUser(quote.profile, class="username")
|
||||||
|
|
||||||
tdiv(class="fullname-and-username"):
|
span(class="tweet-date"):
|
||||||
linkUser(quote.profile, class="fullname")
|
a(href=getLink(quote), title=quote.getTime):
|
||||||
linkUser(quote.profile, class="username")
|
text quote.getShortTime
|
||||||
|
|
||||||
if quote.reply.len > 0:
|
if quote.reply.len > 0:
|
||||||
renderReply(quote)
|
renderReply(quote)
|
||||||
|
|
||||||
tdiv(class="quote-text"):
|
if quote.text.len > 0:
|
||||||
verbatim replaceUrl(quote.text, prefs)
|
tdiv(class="quote-text"):
|
||||||
|
verbatim replaceUrl(quote.text, prefs)
|
||||||
|
|
||||||
if quote.hasThread:
|
if quote.hasThread:
|
||||||
a(class="show-thread", href=getLink(quote)):
|
a(class="show-thread", href=getLink(quote)):
|
||||||
text "Show this thread"
|
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 =
|
proc renderLocation*(tweet: Tweet): string =
|
||||||
let (place, url) = tweet.getLocation()
|
let (place, url) = tweet.getLocation()
|
||||||
if place.len == 0: return
|
if place.len == 0: return
|
||||||
|
@ -258,11 +248,10 @@ proc renderLocation*(tweet: Tweet): string =
|
||||||
text place
|
text place
|
||||||
return $node
|
return $node
|
||||||
|
|
||||||
proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class="";
|
proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
||||||
index=0; total=(-1); last=false; showThread=false;
|
last=false; showThread=false; mainTweet=false; afterTweet=false): VNode =
|
||||||
mainTweet=false): VNode =
|
|
||||||
var divClass = class
|
var divClass = class
|
||||||
if index == total or last:
|
if index == -1 or last:
|
||||||
divClass = "thread-last " & class
|
divClass = "thread-last " & class
|
||||||
|
|
||||||
if not tweet.available:
|
if not tweet.available:
|
||||||
|
@ -273,15 +262,22 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class="";
|
||||||
else:
|
else:
|
||||||
text "This tweet is unavailable"
|
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))):
|
buildHtml(tdiv(class=("timeline-item " & divClass))):
|
||||||
if not mainTweet:
|
if not mainTweet:
|
||||||
a(class="tweet-link", href=getLink(tweet))
|
a(class="tweet-link", href=getLink(tweet))
|
||||||
|
|
||||||
tdiv(class="tweet-body"):
|
tdiv(class="tweet-body"):
|
||||||
var views = ""
|
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):
|
(tweet.reply.len > 1 or tweet.reply[0] != tweet.profile.username):
|
||||||
renderReply(tweet)
|
renderReply(tweet)
|
||||||
|
|
||||||
|
@ -293,7 +289,8 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class="";
|
||||||
|
|
||||||
if tweet.card.isSome:
|
if tweet.card.isSome:
|
||||||
renderCard(tweet.card.get(), prefs, path)
|
renderCard(tweet.card.get(), prefs, path)
|
||||||
elif tweet.photos.len > 0:
|
|
||||||
|
if tweet.photos.len > 0:
|
||||||
renderAlbum(tweet)
|
renderAlbum(tweet)
|
||||||
elif tweet.video.isSome:
|
elif tweet.video.isSome:
|
||||||
renderVideo(tweet.video.get(), prefs, path)
|
renderVideo(tweet.video.get(), prefs, path)
|
||||||
|
@ -301,11 +298,12 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class="";
|
||||||
elif tweet.gif.isSome:
|
elif tweet.gif.isSome:
|
||||||
renderGif(tweet.gif.get(), prefs)
|
renderGif(tweet.gif.get(), prefs)
|
||||||
views = "GIF"
|
views = "GIF"
|
||||||
elif tweet.poll.isSome:
|
|
||||||
|
if tweet.poll.isSome:
|
||||||
renderPoll(tweet.poll.get())
|
renderPoll(tweet.poll.get())
|
||||||
|
|
||||||
if tweet.quote.isSome:
|
if tweet.quote.isSome:
|
||||||
renderQuote(tweet.quote.get(), prefs)
|
renderQuote(tweet.quote.get(), prefs, path)
|
||||||
|
|
||||||
if mainTweet:
|
if mainTweet:
|
||||||
p(class="tweet-published"): text getTweetTime(tweet)
|
p(class="tweet-published"): text getTweetTime(tweet)
|
||||||
|
|
Loading…
Reference in a new issue