Skip to content

Commit 5934c7c

Browse files
authored
Merge pull request #9 from dialectlabs/feature/unfurl-links-without-preview
unfurl links without preview
2 parents f050515 + 55b9553 commit 5934c7c

File tree

4 files changed

+103
-29
lines changed

4 files changed

+103
-29
lines changed

bun.lockb

3.76 KB
Binary file not shown.

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,26 @@
3939
"devDependencies": {
4040
"@types/react": "^18.3.3",
4141
"@types/react-dom": "^18.3.0",
42-
"@typescript-eslint/eslint-plugin": "^7.5.0",
43-
"@typescript-eslint/parser": "^7.5.0",
42+
"@typescript-eslint/eslint-plugin": "^7.16.1",
43+
"@typescript-eslint/parser": "^7.16.1",
4444
"autoprefixer": "^10.4.19",
4545
"eslint": "^8.57.0",
4646
"eslint-config-prettier": "^9.1.0",
47-
"eslint-plugin-react": "^7.33.2",
48-
"eslint-plugin-react-hooks": "^4.6.0",
49-
"postcss": "^8.4.38",
47+
"eslint-plugin-react": "^7.34.4",
48+
"eslint-plugin-react-hooks": "^4.6.2",
49+
"postcss": "^8.4.39",
5050
"postcss-prefix-selector": "^1.16.1",
51-
"prettier": "^3.2.5",
52-
"prettier-plugin-organize-imports": "^3.2.4",
53-
"prettier-plugin-tailwindcss": "^0.5.13",
51+
"prettier": "^3.3.3",
52+
"prettier-plugin-organize-imports": "^4.0.0",
53+
"prettier-plugin-tailwindcss": "^0.6.5",
5454
"tailwindcss": "^3.4.3",
55-
"tsup": "^8.1.0",
56-
"typescript": "^5.0.0"
55+
"tsup": "^8.2.0",
56+
"typescript": "^5.5.3"
5757
},
5858
"peerDependencies": {
5959
"@solana/wallet-adapter-react": "^0.15.0",
6060
"@solana/wallet-adapter-react-ui": "^0.9.0",
61-
"@solana/web3.js": "^1.91.0",
61+
"@solana/web3.js": "^1.95.1",
6262
"react": ">=18",
6363
"react-dom": ">=18"
6464
},

src/ext/twitter.tsx

Lines changed: 85 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,32 @@ 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+
119+
const linkPreview = findLinkPreview(element);
120+
121+
let container = findContainerInTweet(
122+
linkPreview?.card ?? element,
123+
Boolean(linkPreview),
124+
);
125+
if (linkPreview) {
126+
anchor = linkPreview.anchor;
127+
container && container.remove();
128+
container = linkPreview.card.parentElement as HTMLElement;
129+
} else {
130+
if (container) {
131+
return;
132+
}
133+
const link = findLastLinkInText(element);
134+
if (link) {
135+
anchor = link.anchor;
136+
container = getContainerForLink(link.tweetText);
137+
}
125138
}
126139

127-
const anchor = linkPreview.children[0] as HTMLAnchorElement;
140+
if (!anchor || !container) return;
141+
128142
const shortenedUrl = anchor.href;
129143
const actionUrl = await resolveTwitterShortenedUrl(shortenedUrl);
130144
const interstitialData = isInterstitial(actionUrl);
@@ -174,7 +188,7 @@ async function handleNewNode(
174188
return;
175189
}
176190

177-
rootElement.parentElement?.replaceChildren(
191+
addMargin(container).replaceChildren(
178192
createAction({
179193
originalUrl: actionUrl,
180194
action,
@@ -242,3 +256,63 @@ function findElementByTestId(element: Element, testId: string) {
242256
}
243257
return element.querySelector(`[data-testid="${testId}"]`);
244258
}
259+
260+
function findContainerInTweet(element: Element, searchUp?: boolean) {
261+
const message = searchUp
262+
? (element.closest(`[data-testid="tweet"]`) ??
263+
element.closest(`[data-testid="messageEntry"]`))
264+
: (findElementByTestId(element, 'tweet') ??
265+
findElementByTestId(element, 'messageEntry'));
266+
267+
if (message) {
268+
return message.querySelector('.dialect-wrapper') as HTMLElement;
269+
}
270+
return null;
271+
}
272+
273+
function findLinkPreview(element: Element) {
274+
const card = findElementByTestId(element, 'card.wrapper');
275+
if (!card) {
276+
return null;
277+
}
278+
279+
const anchor = card.children[0]?.children[0] as HTMLAnchorElement;
280+
281+
return anchor ? { anchor, card } : null;
282+
}
283+
function findLastLinkInText(element: Element) {
284+
const tweetText = findElementByTestId(element, 'tweetText');
285+
if (!tweetText) {
286+
return null;
287+
}
288+
289+
const links = tweetText.getElementsByTagName('a');
290+
if (links.length > 0) {
291+
const anchor = links[links.length - 1] as HTMLAnchorElement;
292+
return { anchor, tweetText };
293+
}
294+
return null;
295+
}
296+
297+
function getContainerForLink(tweetText: Element) {
298+
const root = document.createElement('div');
299+
root.className = 'dialect-wrapper';
300+
const dm = tweetText.closest(`[data-testid="messageEntry"]`);
301+
if (dm) {
302+
root.classList.add('dialect-dm');
303+
tweetText.parentElement?.parentElement?.prepend(root);
304+
} else {
305+
tweetText.parentElement?.append(root);
306+
}
307+
return root;
308+
}
309+
310+
function addMargin(element: HTMLElement) {
311+
if (element && element.classList.contains('dialect-wrapper')) {
312+
element.style.marginTop = '12px';
313+
if (element.classList.contains('dialect-dm')) {
314+
element.style.marginBottom = '8px';
315+
}
316+
}
317+
return element;
318+
}

src/ui/ActionLayout.tsx

Lines changed: 7 additions & 7 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
@@ -115,17 +115,17 @@ export const ActionLayout = ({
115115
<a
116116
href={websiteUrl}
117117
target="_blank"
118-
className="group -mt-1 inline-flex items-center truncate text-subtext hover:cursor-pointer"
118+
className="group inline-flex items-center truncate text-subtext hover:cursor-pointer"
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="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)