mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
feat: suppress rendered media attachment URLs from tweet text
renderTweetTextJSX now accepts skipSpans=[] as third param. Skip-span boundaries are added to the pts split set so a trailing media t.co inside a plain segment still gets isolated and suppressed— not re-linked by linkifyText. Early return only when both anns and skipSpans are empty. TweetCard computes mediaSkipSpans after building photos/videoItems: for each rawMedia item whose src resolved (photo src match; any video), resolveEntityBounds(m, ft, m.url) gives the precise [s,e] span using indices/fromIndex first, indexOf fallback—then the span is passed to renderTweetTextJSX so the t.co attachment URL is silently dropped.
This commit is contained in:
parent
1589eb68b5
commit
46a5251316
3 changed files with 44 additions and 16 deletions
File diff suppressed because one or more lines are too long
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Archivr</title>
|
||||
<script type="module" crossorigin src="/assets/index-CBWT1bsB.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Gm5awz10.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C89vW3ol.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -523,25 +523,37 @@ function linkifyText(text, linkStyle) {
|
|||
return parts;
|
||||
}
|
||||
|
||||
function renderTweetTextJSX(fullText, entities) {
|
||||
function renderTweetTextJSX(fullText, entities, skipSpans = []) {
|
||||
if (!fullText) return null;
|
||||
|
||||
const anns = [
|
||||
...(entities.urls || []).map(u => normalizeUrlAnn(u, fullText)).filter(Boolean),
|
||||
...(entities.user_mentions || []).map(m => normalizeMentionAnn(m, fullText)).filter(Boolean),
|
||||
];
|
||||
if (anns.length === 0) return linkifyText(decodeEnt(fullText), S.link);
|
||||
|
||||
// If there are no annotations and no spans to skip, fast path.
|
||||
if (anns.length === 0 && skipSpans.length === 0) return linkifyText(decodeEnt(fullText), S.link);
|
||||
|
||||
const pts = new Set([0, fullText.length]);
|
||||
for (const a of anns) {
|
||||
if (a.s >= 0 && a.s <= fullText.length) pts.add(a.s);
|
||||
if (a.e >= 0 && a.e <= fullText.length) pts.add(a.e);
|
||||
}
|
||||
// Skip-span boundaries MUST be in pts so a media t.co that lives inside a
|
||||
// larger plain-text segment still gets split out and suppressed.
|
||||
for (const [ss, se] of skipSpans) {
|
||||
if (ss >= 0 && ss <= fullText.length) pts.add(ss);
|
||||
if (se >= 0 && se <= fullText.length) pts.add(se);
|
||||
}
|
||||
const isSkipped = (s, e) => skipSpans.some(([ss, se]) => ss <= s && se >= e);
|
||||
|
||||
const sorted = [...pts].sort((a, b) => a - b);
|
||||
|
||||
return sorted.slice(0, -1).map((s, i) => {
|
||||
const e = sorted[i + 1];
|
||||
// Suppress spans whose range is covered by a rendered media item.
|
||||
if (isSkipped(s, e)) return null;
|
||||
|
||||
const seg = fullText.slice(s, e);
|
||||
const active = anns.filter(a => a.s <= s && a.e >= e);
|
||||
|
||||
|
|
@ -570,7 +582,7 @@ function renderTweetTextJSX(fullText, entities) {
|
|||
}
|
||||
|
||||
return <span key={i}>{linkifyText(decodeEnt(seg), S.link)}</span>;
|
||||
});
|
||||
}).filter(n => n !== null);
|
||||
}
|
||||
|
||||
// ── Article inline text renderer ───────────────────────────────────────────────
|
||||
|
|
@ -1025,6 +1037,7 @@ function TweetCard({ tweet, isInThread, isLast, artifactMap }) {
|
|||
const showConnector = isInThread && !isLast;
|
||||
const isQT = tweet.is_quote_status === true;
|
||||
const rowStyle = isInThread ? S.tweetRowThread : S.tweetRow;
|
||||
const ft = tweet.full_text || '';
|
||||
|
||||
// Build resolved media lists first; skip the grid entirely if every item resolves to null.
|
||||
const rawMedia = (tweet.extended_entities?.media?.length
|
||||
|
|
@ -1049,6 +1062,21 @@ function TweetCard({ tweet, isInThread, isLast, artifactMap }) {
|
|||
}
|
||||
}
|
||||
|
||||
// Suppress media attachment t.co links from the tweet text when the media
|
||||
// actually rendered. Uses resolveEntityBounds (prefers indices/fromIndex,
|
||||
// falls back to indexOf) so the span is always precise.
|
||||
const mediaSkipSpans = [];
|
||||
for (const m of rawMedia) {
|
||||
if (!m.url) continue;
|
||||
const rendered =
|
||||
m.type === 'photo'
|
||||
? photos.some(p => p.src === resolveUrl(m.local_path, m.media_url_https, artifactMap))
|
||||
: videoItems.length > 0;
|
||||
if (!rendered) continue;
|
||||
const b = resolveEntityBounds(m, ft, m.url);
|
||||
if (b) mediaSkipSpans.push([b.s, b.e]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{lightboxIdx !== null && (
|
||||
|
|
@ -1080,7 +1108,7 @@ function TweetCard({ tweet, isInThread, isLast, artifactMap }) {
|
|||
</div>
|
||||
|
||||
<div style={S.tweetText}>
|
||||
{renderTweetTextJSX(tweet.full_text || '', entities)}
|
||||
{renderTweetTextJSX(ft, entities, mediaSkipSpans)}
|
||||
</div>
|
||||
|
||||
{photos.length > 0 && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue