diff --git a/src/experimental/parser/article.nim b/src/experimental/parser/article.nim index 2ae7f4a..ae5de7c 100644 --- a/src/experimental/parser/article.nim +++ b/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 diff --git a/src/experimental/types/article.nim b/src/experimental/types/article.nim index 024c69f..946f8c3 100644 --- a/src/experimental/types/article.nim +++ b/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 diff --git a/src/sass/_article.scss b/src/sass/_article.scss index 9cc728a..9488658 100644 --- a/src/sass/_article.scss +++ b/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, diff --git a/src/types.nim b/src/types.nim index 2ccad10..a490316 100644 --- a/src/types.nim +++ b/src/types.nim @@ -253,6 +253,7 @@ type mediaIds*: seq[string] tweetId*: string markdown*: string + caption*: string ArticleMedia* = object kind*: string diff --git a/src/views/article.nim b/src/views/article.nim index 1ba9c8b..eedd4cf 100644 --- a/src/views/article.nim +++ b/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) diff --git a/src/views/general.nim b/src/views/general.nim index d49804e..37213c2 100644 --- a/src/views/general.nim +++ b/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: diff --git a/tests/test_article.py b/tests/test_article.py index 15a87c7..652ec76 100644 --- a/tests/test_article.py +++ b/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)