Ver Fonte

Support captions in articles

Fixes #1418
Zed há 2 meses atrás
pai
commit
7f7092fbf5

+ 1 - 0
src/experimental/parser/article.nim

@@ -61,6 +61,7 @@ proc parseGraphArticle*(json: string): Article =
     of "MEDIA":
       for mi in entry.value.data.mediaItems:
         entity.mediaIds.add mi.mediaId
+      entity.caption = entry.value.data.caption
     of "TWEET": entity.tweetId = entry.value.data.tweetId
     of "MARKDOWN": entity.markdown = entry.value.data.markdown
     else: discard

+ 1 - 0
src/experimental/types/article.nim

@@ -48,6 +48,7 @@ type
     mediaItems*: seq[tuple[mediaId: string]]
     tweetId*: string
     markdown*: string
+    caption*: string
 
   RawMediaEntity* = object
     mediaId*: string

+ 6 - 0
src/sass/_article.scss

@@ -189,6 +189,12 @@
         max-width: 100%;
         border-radius: 12px;
       }
+
+      .article-media-caption {
+        color: var(--fg_faded);
+        font-size: 0.875rem;
+        margin-top: 6px;
+      }
     }
 
     > a,

+ 1 - 0
src/types.nim

@@ -253,6 +253,7 @@ type
     mediaIds*: seq[string]
     tweetId*: string
     markdown*: string
+    caption*: string
 
   ArticleMedia* = object
     kind*: string

+ 3 - 1
src/views/article.nim

@@ -32,7 +32,9 @@ proc renderAtomicParagraph(paragraph: ArticleParagraph; article: Article;
           video(src=getVidUrl(media.url), controls="")
         else:
           a(href=getOrigPicUrl(media.url), target="_blank"):
-            img(src=getSmallPic(media.url), alt="", loading="lazy")
+            img(src=getSmallPic(media.url), alt=entity.caption, loading="lazy")
+      if entity.caption.len > 0:
+        p(class="article-media-caption"): text entity.caption
   of "TWEET":
     let tweet = tweets.getOrDefault(
       try: parseBiggestInt(entity.tweetId)

+ 1 - 1
src/views/general.nim

@@ -50,7 +50,7 @@ proc renderHead*(prefs: Prefs; cfg: Config; req: Request; titleText=""; desc="";
   let opensearchUrl = getUrlPrefix(cfg) & "/opensearch"
 
   buildHtml(head):
-    link(rel="stylesheet", type="text/css", href="/css/style.css?v=44")
+    link(rel="stylesheet", type="text/css", href="/css/style.css?v=45")
     link(rel="stylesheet", type="text/css", href="/css/fontello.css?v=7")
 
     if theme.len > 0:

+ 25 - 7
tests/test_article.py

@@ -14,6 +14,7 @@ class ArticleSelectors:
     avatar = '.article-author img.avatar'
     verified = '.article-author .verified-icon'
     media = '.article-media'
+    caption = '.article-media-caption'
     divider = '.article-divider'
 
 
@@ -30,10 +31,6 @@ articles = [
      'Consciousness and AI: The Problem of Inner Experience',
      'CosmicOrFun', 'Cosmic Orphan'],
 
-    ['2064696491948777658',
-     'NC Push for Data Centers + Stablecoin Crypto= Data Centers are defacto BAILOUT OF Fed Reserve System',
-     'June_12_1776', 'June_12_1776'],
-
     ['2064755789391110154',
      'DeFi Markets Update 2026-06-10',
      'SteakhouseFi', 'Steakhouse Financial'],
@@ -126,11 +123,11 @@ class ArticleContentTest(BaseTestCase):
         self.assertGreater(len(italic), 0)
 
     def test_article_has_blockquotes(self):
-        self.open_nitter('i/article/2064696491948777658')
+        self.open_nitter('i/article/2064166507438059759')
         self.assert_element_visible('.article-body blockquote')
 
     def test_article_has_lists(self):
-        self.open_nitter('i/article/2064696491948777658')
+        self.open_nitter('i/article/2064166507438059759')
         self.assert_element_visible('.article-body ul')
 
     def test_article_has_emoji_text(self):
@@ -186,6 +183,27 @@ class ArticleMediaTest(BaseTestCase):
         tweets = self.find_elements('.article-body .timeline-item')
         self.assertGreaterEqual(len(tweets), 3)
 
+    def test_media_caption_displayed(self):
+        self.open_nitter('i/article/2064689664213041529')
+        self.assert_element_visible(ArticleSelectors.caption)
+        captions = self.find_elements(ArticleSelectors.caption)
+        self.assertGreaterEqual(len(captions), 5)
+
+    def test_media_caption_text(self):
+        self.open_nitter('i/article/2064689664213041529')
+        self.assert_text_visible('FIGURE 1', ArticleSelectors.caption)
+
+    def test_media_caption_alt_attribute(self):
+        self.open_nitter('i/article/2064689664213041529')
+        img = self.find_element(f'{ArticleSelectors.media} img')
+        alt = img.get_attribute('alt')
+        self.assertGreater(len(alt), 0)
+
+    def test_no_caption_when_absent(self):
+        self.open_nitter('i/article/2062858677149675788')
+        captions = self.find_elements(ArticleSelectors.caption)
+        self.assertEqual(len(captions), 0)
+
 
 class ArticleMentionTest(BaseTestCase):
     def test_mention_linkified(self):
@@ -209,7 +227,7 @@ class ArticleMentionTest(BaseTestCase):
 
     def test_no_spurious_whitespace_in_styled_paragraph(self):
         """Styled paragraphs should not have extra whitespace from VNode serialization."""
-        self.open_nitter('i/article/2064696491948777658')
+        self.open_nitter('i/article/2064166507438059759')
         source = self.get_page_source()
         self.assertNotIn('white-space: pre-wrap', source)
         self.assertNotIn('white-space:pre-wrap', source)