Skip to content

Commit 539ff42

Browse files
committed
unfurl links without preview
1 parent f75ec55 commit 539ff42

File tree

2 files changed

+80
-17
lines changed

2 files changed

+80
-17
lines changed

src/ext/twitter.tsx

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ export function setupTwitterObserver(
102102
observer.observe(twitterReactRoot, { childList: true, subtree: true });
103103
});
104104
}
105-
106105
async function handleNewNode(
107106
node: Element,
108107
config: ActionAdapter,
@@ -114,17 +113,25 @@ async function handleNewNode(
114113
if (!element || element.localName !== 'div') {
115114
return;
116115
}
117-
const rootElement = findElementByTestId(element, 'card.wrapper');
118-
if (!rootElement) {
119-
return;
120-
}
121-
// handle link preview only, assuming that link preview is a must for actions
122-
const linkPreview = rootElement.children[0] as HTMLDivElement;
123-
if (!linkPreview) {
124-
return;
116+
117+
let anchor;
118+
let card;
119+
let tweetText;
120+
121+
const linkPreview = tryLinkPreview(element);
122+
if (linkPreview) {
123+
anchor = linkPreview.anchor;
124+
card = linkPreview.card;
125+
} else {
126+
const link = tryLinkInText(element);
127+
if (link) {
128+
anchor = link.anchor;
129+
tweetText = link.tweetText;
130+
}
125131
}
126132

127-
const anchor = linkPreview.children[0] as HTMLAnchorElement;
133+
if (!anchor) return;
134+
128135
const shortenedUrl = anchor.href;
129136
const actionUrl = await resolveTwitterShortenedUrl(shortenedUrl);
130137
const interstitialData = isInterstitial(actionUrl);
@@ -168,13 +175,22 @@ async function handleNewNode(
168175
return;
169176
}
170177

178+
//double check if link preview appeared after we assumed it is not there
179+
const isCardPresent = findCardInTweet(element);
180+
if (!card && isCardPresent) {
181+
console.log('found card in tweet');
182+
return;
183+
}
184+
171185
const action = await Action.fetch(actionApiUrl, config).catch(() => null);
172186

173187
if (!action) {
174188
return;
175189
}
176190

177-
rootElement.parentElement?.replaceChildren(
191+
const container = card ? card.parentElement : getContainerForLink(tweetText!);
192+
193+
container?.replaceChildren(
178194
createAction({
179195
originalUrl: actionUrl,
180196
action,
@@ -242,3 +258,50 @@ function findElementByTestId(element: Element, testId: string) {
242258
}
243259
return element.querySelector(`[data-testid="${testId}"]`);
244260
}
261+
262+
function getContainerForLink(tweetText: Element) {
263+
const root = document.createElement('div');
264+
root.style.paddingTop = '12px';
265+
const dm = tweetText.closest(`[data-testid="messageEntry"]`);
266+
if (dm) {
267+
tweetText.parentElement?.parentElement?.prepend(root);
268+
root.style.paddingBottom = '8px';
269+
} else {
270+
tweetText.parentElement?.append(root);
271+
}
272+
return root;
273+
}
274+
275+
function findCardInTweet(element: Element) {
276+
const message =
277+
findElementByTestId(element, 'tweet') ??
278+
findElementByTestId(element, 'messageEntry');
279+
if (message) {
280+
return findElementByTestId(message, 'card.wrapper');
281+
}
282+
}
283+
284+
function tryLinkPreview(element: Element) {
285+
const card = findElementByTestId(element, 'card.wrapper');
286+
if (card) {
287+
const linkPreview = card.children[0];
288+
if (linkPreview) {
289+
const anchor = linkPreview.children[0] as HTMLAnchorElement;
290+
return { anchor, card };
291+
}
292+
}
293+
}
294+
function tryLinkInText(element: Element) {
295+
const tweetText = findElementByTestId(element, 'tweetText');
296+
if (!tweetText || tweetText.classList.contains('dialect-link-tweet')) {
297+
return;
298+
}
299+
300+
const links = tweetText.getElementsByTagName('a');
301+
if (links.length > 0) {
302+
//marking tweet as visited
303+
tweetText.classList.add('dialect-link-tweet');
304+
const anchor = links[links.length - 1] as HTMLAnchorElement;
305+
return { anchor, tweetText };
306+
}
307+
}

src/ui/ActionLayout.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const ActionLayout = ({
9696
}: LayoutProps) => {
9797
return (
9898
<div className={clsx('blink', stylePresetClassMap[stylePreset])}>
99-
<div className="mt-3 w-full cursor-default overflow-hidden rounded-2xl border border-stroke-primary bg-bg-primary shadow-action">
99+
<div className="w-full cursor-default overflow-hidden rounded-2xl border border-stroke-primary bg-bg-primary shadow-action">
100100
{image && (
101101
<Linkable url={websiteUrl} className="block px-5 pt-5">
102102
<img
@@ -119,13 +119,13 @@ export const ActionLayout = ({
119119
rel="noopener noreferrer"
120120
>
121121
<LinkIcon className="mr-2 text-icon-primary transition-colors group-hover:text-icon-primary-hover motion-reduce:transition-none" />
122-
<span className="text-text-link group-hover:text-text-link-hover transition-colors group-hover:underline motion-reduce:transition-none">
122+
<span className="text-text-link transition-colors group-hover:text-text-link-hover group-hover:underline motion-reduce:transition-none">
123123
{websiteText ?? websiteUrl}
124124
</span>
125125
</a>
126126
)}
127127
{websiteText && !websiteUrl && (
128-
<span className="text-text-link -mt-1 inline-flex items-center truncate text-subtext">
128+
<span className="-mt-1 inline-flex items-center truncate text-subtext text-text-link">
129129
{websiteText}
130130
</span>
131131
)}
@@ -259,9 +259,9 @@ const ActionInput = ({
259259
return (
260260
<div
261261
className={clsx(
262-
'border-input-stroke focus-within:border-input-stroke-selected flex items-center gap-2 rounded-input border transition-colors motion-reduce:transition-none',
262+
'flex items-center gap-2 rounded-input border border-input-stroke transition-colors focus-within:border-input-stroke-selected motion-reduce:transition-none',
263263
{
264-
'hover:focus-within:border-input-stroke-selected hover:border-input-stroke-hover':
264+
'hover:border-input-stroke-hover hover:focus-within:border-input-stroke-selected':
265265
!disabled,
266266
},
267267
)}
@@ -271,7 +271,7 @@ const ActionInput = ({
271271
value={value}
272272
disabled={disabled}
273273
onChange={extendedChange}
274-
className="bg-input-bg text-text-input placeholder:text-text-input-placeholder disabled:text-text-input-disabled my-3 ml-4 flex-1 truncate outline-none"
274+
className="my-3 ml-4 flex-1 truncate bg-input-bg text-text-input outline-none placeholder:text-text-input-placeholder disabled:text-text-input-disabled"
275275
/>
276276
{button && (
277277
<div className="my-2 mr-2">

0 commit comments

Comments
 (0)