Forráskód Böngészése

Render tweet quotes

Zed 7 éve
szülő
commit
af9a5d4872
6 módosított fájl, 128 hozzáadás és 18 törlés
  1. 67 1
      public/style.css
  2. 4 3
      src/formatters.nim
  3. 17 10
      src/parser.nim
  4. 11 2
      src/parserutils.nim
  5. 3 2
      src/types.nim
  6. 26 0
      src/views/tweet.nim

+ 67 - 1
public/style.css

@@ -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;
 }

+ 4 - 3
src/formatters.nim

@@ -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 =

+ 17 - 10
src/parser.nim

@@ -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)

+ 11 - 2
src/parserutils.nim

@@ -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())

+ 3 - 2
src/types.nim

@@ -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

+ 26 - 0
src/views/tweet.nim

@@ -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>