From 4410f17eb4979a1477b5837a7f517431f97daa50 Mon Sep 17 00:00:00 2001 From: Chilfish Date: Sun, 27 Sep 2026 14:20:32 +0800 Subject: [PATCH 1/8] feat(article): parse, render and translate X articles on a reader page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - request article rich content via TweetRequests.details() fieldToggles (JSON string, not object — X silently ignores the axios-serialized form) - map the article node to a TweetArticle block document parsed from Draft.js content_state (paragraphs, headings, lists, markdown, media, embeds); split inline runs at entity AND style boundaries so partial Bold/Italic ranges are not smeared across the whole block - render blocks on a dedicated /article/:id page with reader toolbar, three-state translation and real inline-tweet embeds (parallel fetch, graceful fallback to a link card) - translate articles per block key with placeholder anchoring; persist to localCache + new tweet_article_translations table (migration) - add react-markdown + remark-gfm for MARKDOWN (GFM) entities - backfill flat article caches at the getLocalTweet exit Co-authored-by: CommandCodeBot --- app/components/article/ArticleBody.tsx | 178 ++++++++++ app/components/article/ArticleEmbedTweet.tsx | 19 ++ .../article/ArticleEmbeddedTweet.tsx | 16 + app/components/article/ArticleImage.tsx | 31 ++ app/components/article/ArticleMarkdown.tsx | 55 +++ app/components/article/ArticleOptionsMenu.tsx | 138 ++++++++ app/components/article/ArticleRuns.tsx | 30 ++ app/components/article/ArticleToolbar.tsx | 94 ++++++ .../article/ArticleTranslateToggle.tsx | 55 +++ .../article/ArticleTranslatedText.tsx | 43 +++ app/components/article/index.ts | 10 + app/components/tweet/PlainTweet.tsx | 3 + app/components/tweet/TweetArticleCard.tsx | 74 +++++ app/components/tweet/TweetNode.tsx | 10 + app/components/tweet/index.ts | 1 + app/hooks/use-auto-translate-article.ts | 115 +++++++ app/lib/article/index.ts | 2 + app/lib/article/markdown.ts | 98 ++++++ app/lib/article/parse.ts | 312 ++++++++++++++++++ app/lib/article/translate.ts | 224 +++++++++++++ app/lib/database/schema.ts | 20 ++ app/lib/localCache.ts | 2 +- app/lib/obs-log.ts | 1 + .../react-tweet/twitter-theme/tweet-body.tsx | 5 + app/lib/react-tweet/utils/article.ts | 118 +++++++ app/lib/react-tweet/utils/index.ts | 1 + app/lib/react-tweet/utils/parseTweet.ts | 5 +- app/lib/rettiwt-api/requests/Tweet.ts | 3 +- .../rettiwt-api/types/raw/tweet/Details.ts | 100 ++++++ app/lib/service/articleTranslationSync.ts | 20 ++ app/lib/service/getTweet.server.ts | 101 +++++- app/lib/stores/hooks.ts | 13 + app/lib/stores/translation.ts | 21 ++ app/lib/validations/tweet.ts | 23 +- app/routes.ts | 1 + app/routes/api/ai/ai-translation.ts | 64 ++++ app/routes/api/tweet/set.ts | 8 +- app/routes/article.tsx | 209 ++++++++++++ app/types/article.ts | 64 ++++ app/types/index.ts | 3 + bun.lock | 82 +++++ drizzle/0002_greedy_omega_red.sql | 9 + drizzle/meta/0002_snapshot.json | 311 +++++++++++++++++ drizzle/meta/_journal.json | 7 + package.json | 2 + 45 files changed, 2693 insertions(+), 8 deletions(-) create mode 100644 app/components/article/ArticleBody.tsx create mode 100644 app/components/article/ArticleEmbedTweet.tsx create mode 100644 app/components/article/ArticleEmbeddedTweet.tsx create mode 100644 app/components/article/ArticleImage.tsx create mode 100644 app/components/article/ArticleMarkdown.tsx create mode 100644 app/components/article/ArticleOptionsMenu.tsx create mode 100644 app/components/article/ArticleRuns.tsx create mode 100644 app/components/article/ArticleToolbar.tsx create mode 100644 app/components/article/ArticleTranslateToggle.tsx create mode 100644 app/components/article/ArticleTranslatedText.tsx create mode 100644 app/components/article/index.ts create mode 100644 app/components/tweet/TweetArticleCard.tsx create mode 100644 app/hooks/use-auto-translate-article.ts create mode 100644 app/lib/article/index.ts create mode 100644 app/lib/article/markdown.ts create mode 100644 app/lib/article/parse.ts create mode 100644 app/lib/article/translate.ts create mode 100644 app/lib/react-tweet/utils/article.ts create mode 100644 app/lib/service/articleTranslationSync.ts create mode 100644 app/routes/article.tsx create mode 100644 app/types/article.ts create mode 100644 drizzle/0002_greedy_omega_red.sql create mode 100644 drizzle/meta/0002_snapshot.json diff --git a/app/components/article/ArticleBody.tsx b/app/components/article/ArticleBody.tsx new file mode 100644 index 00000000..af77b68a --- /dev/null +++ b/app/components/article/ArticleBody.tsx @@ -0,0 +1,178 @@ +import type { ReactNode } from 'react' +import type { ArticleBlock, ArticleRun } from '~/types' +import { Separator } from '~/components/ui/separator' +import { TweetLink } from '~/lib/react-tweet/twitter-theme/tweet-link' +import { cn } from '~/lib/utils' +import { ArticleEmbedTweet } from './ArticleEmbedTweet' +import { ArticleImage } from './ArticleImage' +import { ArticleMarkdown } from './ArticleMarkdown' +import { ArticleRuns } from './ArticleRuns' +import { ArticleTranslatedText } from './ArticleTranslatedText' + +export type ArticleViewMode = 'original' | 'translation' | 'bilingual' + +/** 非列表块(列表项由外层归组为