Render tweet quotes
This commit is contained in:
parent
1213220ef0
commit
af9a5d4872
6 changed files with 128 additions and 18 deletions
|
@ -3,6 +3,7 @@ body {
|
|||
color: #f8f8f2;
|
||||
margin: 0;
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#tweets {
|
||||
|
@ -575,6 +576,11 @@ nav {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.video-overlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -594,7 +600,67 @@ nav {
|
|||
font-size: 20px;
|
||||
}
|
||||
|
||||
video {
|
||||
.quote {
|
||||
margin-top: 10px;
|
||||
border: solid 1px #404040;
|
||||
border-radius: 10px;
|
||||
padding: 6px;
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
.quote:hover {
|
||||
border-color: #808080;
|
||||
}
|
||||
|
||||
.quote-container {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.quote-link {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.quote-text {
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.quote-media-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
max-height: 102px;
|
||||
width: 102px;
|
||||
float: left;
|
||||
margin-right: 7px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.quote-media {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quote-media img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.quote-badge {
|
||||
background: rgba(0,0,0,0.25);
|
||||
border-radius: 4px;
|
||||
bottom: 8px;
|
||||
box-sizing: border-box;
|
||||
color: #fffffff0;
|
||||
left: 8px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
line-height: 16px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ proc toLink*(url, text: string; class="timeline-link"): string =
|
|||
|
||||
proc reUrlToLink*(m: RegexMatch; s: string): string =
|
||||
let url = s[m.group(0)[0]]
|
||||
toLink(url, " " & shortLink(url))
|
||||
toLink(url, shortLink(url))
|
||||
|
||||
proc reEmailToLink*(m: RegexMatch; s: string): string =
|
||||
let url = s[m.group(0)[0]]
|
||||
|
@ -44,12 +44,13 @@ proc reUsernameToLink*(m: RegexMatch; s: string): string =
|
|||
pretext & toLink("/" & username, "@" & username)
|
||||
|
||||
proc linkifyText*(text: string): string =
|
||||
result = text.replace("\n", "<br>")
|
||||
result = text.strip()
|
||||
result = result.replace("\n", "<br>")
|
||||
result = result.replace(ellipsisRegex, "")
|
||||
result = result.replace(usernameRegex, reUsernameToLink)
|
||||
result = result.replace(emailRegex, reEmailToLink)
|
||||
result = result.replace(urlRegex, reUrlToLink)
|
||||
result = result.replace(re"</a>\s+", "</a> ")
|
||||
result = result.replace(re"([A-z0-9])<a>", "$1 <a>")
|
||||
result = result.replace(re"</a> ([.,\)])", "</a>$1")
|
||||
|
||||
proc stripTwitterUrls*(text: string): string =
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import xmltree, sequtils, strtabs, strutils, strformat, json
|
||||
import nimquery
|
||||
|
||||
import ./types, ./parserutils
|
||||
import ./types, ./parserutils, ./formatters
|
||||
|
||||
proc parsePopupProfile*(node: XmlNode): Profile =
|
||||
let profile = node.querySelector(".profile-card")
|
||||
|
@ -16,6 +16,7 @@ proc parsePopupProfile*(node: XmlNode): Profile =
|
|||
protected: isProtected(profile),
|
||||
banner: getBanner(profile)
|
||||
)
|
||||
|
||||
result.getPopupStats(profile)
|
||||
|
||||
proc parseIntentProfile*(profile: XmlNode): Profile =
|
||||
|
@ -28,6 +29,7 @@ proc parseIntentProfile*(profile: XmlNode): Profile =
|
|||
protected: not profile.querySelector("li.protected").isNil,
|
||||
banner: getBanner(profile)
|
||||
)
|
||||
|
||||
result.getIntentStats(profile)
|
||||
|
||||
proc parseTweetProfile*(profile: XmlNode): Profile =
|
||||
|
@ -38,20 +40,21 @@ proc parseTweetProfile*(profile: XmlNode): Profile =
|
|||
verified: isVerified(profile)
|
||||
)
|
||||
|
||||
proc parseQuote*(tweet: XmlNode): Tweet =
|
||||
let tweet = tweet.querySelector(".QuoteTweet-innerContainer")
|
||||
result = Tweet(
|
||||
id: tweet.getAttr("data-item-id"),
|
||||
link: tweet.getAttr("href"),
|
||||
text: tweet.selectText(".QuoteTweet-text")
|
||||
proc parseQuote*(quote: XmlNode): Quote =
|
||||
result = Quote(
|
||||
id: quote.getAttr("data-item-id"),
|
||||
link: quote.getAttr("href"),
|
||||
text: quote.selectText(".QuoteTweet-text").stripTwitterUrls()
|
||||
)
|
||||
|
||||
result.profile = Profile(
|
||||
fullname: tweet.getAttr("data-screen-name"),
|
||||
username: tweet.selectText(".QuteTweet-fullname"),
|
||||
verified: isVerified(tweet)
|
||||
fullname: quote.selectText(".QuoteTweet-fullname"),
|
||||
username: quote.getAttr("data-screen-name"),
|
||||
verified: isVerified(quote)
|
||||
)
|
||||
|
||||
result.getQuoteMedia(quote)
|
||||
|
||||
proc parseTweet*(tweet: XmlNode): Tweet =
|
||||
result = Tweet(
|
||||
id: tweet.getAttr("data-item-id"),
|
||||
|
@ -71,6 +74,10 @@ proc parseTweet*(tweet: XmlNode): Tweet =
|
|||
result.retweetBy = some(by)
|
||||
result.retweetId = some(tweet.getAttr("data-retweet-id"))
|
||||
|
||||
let quote = tweet.querySelector(".QuoteTweet-innerContainer")
|
||||
if not quote.isNil:
|
||||
result.quote = some(parseQuote(quote))
|
||||
|
||||
proc parseTweets*(node: XmlNode): Tweets =
|
||||
if node.isNil: return
|
||||
node.querySelectorAll(".tweet").map(parseTweet)
|
||||
|
|
|
@ -34,10 +34,10 @@ proc getUsername*(profile: XmlNode; selector: string): string =
|
|||
proc getTweetText*(tweet: XmlNode): string =
|
||||
let selector = ".tweet-text > a.twitter-timeline-link.u-hidden"
|
||||
let link = tweet.selectAttr(selector, "data-expanded-url")
|
||||
var text =tweet.selectText(".tweet-text")
|
||||
var text = tweet.selectText(".tweet-text")
|
||||
|
||||
if link.len > 0 and link in text:
|
||||
text = text.replace(link, " " & link)
|
||||
text = text.replace(link, "")
|
||||
|
||||
stripTwitterUrls(text)
|
||||
|
||||
|
@ -114,3 +114,12 @@ proc getTweetMedia*(tweet: Tweet; node: XmlNode) =
|
|||
tweet.gif = some(getGif(player.querySelector(".PlayableMedia-player")))
|
||||
elif "video" in player.getAttr("class"):
|
||||
tweet.video = some(Video())
|
||||
|
||||
proc getQuoteMedia*(quote: var Quote; node: XmlNode) =
|
||||
let media = node.querySelector(".QuoteMedia")
|
||||
if not media.isNil:
|
||||
quote.thumb = some(media.selectAttr("img", "src"))
|
||||
|
||||
let badge = node.querySelector(".AdaptiveMedia-badgeText")
|
||||
if not badge.isNil:
|
||||
quote.badge = some(badge.innerText())
|
||||
|
|
|
@ -43,12 +43,13 @@ type
|
|||
url*: string
|
||||
thumb*: string
|
||||
|
||||
Quote* = ref object
|
||||
Quote* = object
|
||||
id*: string
|
||||
profile*: Profile
|
||||
link*: string
|
||||
text*: string
|
||||
video*: Option[Video]
|
||||
thumb*: Option[string]
|
||||
badge*: Option[string]
|
||||
|
||||
Tweet* = ref object
|
||||
id*: string
|
||||
|
|
|
@ -29,6 +29,30 @@
|
|||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderQuote(quote: Quote): string =
|
||||
#let hasMedia = quote.thumb.isSome()
|
||||
<div class="quote">
|
||||
<div class="quote-container" href="${quote.link}">
|
||||
<a class="quote-link" href="${quote.link}"></a>
|
||||
#if hasMedia:
|
||||
<div class="quote-media-container">
|
||||
<div class="quote-media">
|
||||
<img src=${quote.thumb.get().getSigUrl("pic")}>
|
||||
#if quote.badge.isSome:
|
||||
<div class="quote-badge">${quote.badge.get()}</div>
|
||||
#end if
|
||||
</div>
|
||||
</div>
|
||||
#end if
|
||||
<div class="profile-card-name">
|
||||
${linkUser(quote.profile, "b", class="username", username=false)}
|
||||
${linkUser(quote.profile, "span", class="account-name")}
|
||||
</div>
|
||||
<div class="quote-text">${linkifyText(xmltree.escape(quote.text))}</div>
|
||||
</div>
|
||||
</div>
|
||||
#end proc
|
||||
#
|
||||
#proc renderMediaGroup(tweet: Tweet): string =
|
||||
#let groups = if tweet.photos.len > 2: tweet.photos.distribute(2) else: @[tweet.photos]
|
||||
#let display = if groups.len == 1 and groups[0].len == 1: "display: table-caption;" else: ""
|
||||
|
@ -105,6 +129,8 @@
|
|||
${renderVideo(tweet.video.get())}
|
||||
#elif tweet.gif.isSome:
|
||||
${renderGif(tweet.gif.get())}
|
||||
#elif tweet.quote.isSome:
|
||||
${renderQuote(tweet.quote.get())}
|
||||
#end if
|
||||
${renderStats(tweet)}
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue