Skip to content

Commit

Permalink
added twitter embeds back using twitter's widget script
Browse files Browse the repository at this point in the history
  • Loading branch information
iskaktoltay committed Dec 6, 2024
1 parent 6c0b4f3 commit cfab5a3
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 110 deletions.
142 changes: 111 additions & 31 deletions frontend/apps/desktop/src/editor/web-embed.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import {isValidUrl} from '@/editor/utils'
import {useOpenUrl} from '@/open-url'
import {TwitterXIcon, XPostNotFound, XPostSkeleton, useTheme} from '@shm/ui'
import {Spinner, TwitterXIcon, useTheme} from '@shm/ui'
import {Fragment} from '@tiptap/pm/model'
import {
QuotedTweet,
TweetBody,
TweetHeader,
TweetInReplyTo,
TweetInfo,
TweetMedia,
enrichTweet,
useTweet,
} from 'react-tweet'
import {useEffect, useRef, useState} from 'react'
import {
Block,
BlockNoteEditor,
Expand Down Expand Up @@ -113,28 +104,100 @@ const display = ({
}: DisplayComponentProps) => {
const urlArray = block.props.url.split('/')
const xPostId = urlArray[urlArray.length - 1].split('?')[0]
const {data, error, isLoading} = useTweet(xPostId)
const openUrl = useOpenUrl()
const [loading, setLoading] = useState(false)

// const iframeRef = useRef(null)

// useEffect(() => {
// const handleResize = (event: MessageEvent) => {
// if (
// event.origin === 'https://platform.twitter.com' &&
// iframeRef.current
// ) {
// const {height} = event.data
// console.log(height, event.data)
// iframeRef.current.style.height = `${height}px`
// }
// }

const containerRef = useRef(null)
const isInitialized = useRef(false)
const scriptId = 'twitter-widgets-script'
const createdTweets = useRef(new Set())

let scriptLoadPromise: Promise<any> | null = null

const loadTwitterScript = () => {
// Check if the script was already added to the document
const script = document.getElementById(scriptId) as HTMLScriptElement | null

if (scriptLoadPromise) {
return scriptLoadPromise
}

if (script && window.twttr) {
// If the script is loaded and window.twttr is ready, resolve immediately
return Promise.resolve(window.twttr)
}

// If the script exists but window.twttr is not ready, wait for it
if (script) {
scriptLoadPromise = new Promise((resolve) => {
const checkTwttr = setInterval(() => {
if (window.twttr) {
clearInterval(checkTwttr)
resolve(window.twttr)
}
}, 50) // Retry every 50 ms
})
return scriptLoadPromise
}

// Load the script
scriptLoadPromise = new Promise((resolve) => {
const newScript = document.createElement('script')
newScript.id = scriptId
newScript.src = 'https://platform.twitter.com/widgets.js'
newScript.async = true
newScript.onload = () => resolve(window.twttr)
document.body.appendChild(newScript)
})

let xPostContent

if (isLoading) xPostContent = <XPostSkeleton />
else if (error || !data) {
xPostContent = <XPostNotFound error={error} />
} else {
const xPost = enrichTweet(data)
xPostContent = (
<>
<TweetHeader tweet={xPost} />
{xPost.in_reply_to_status_id_str && <TweetInReplyTo tweet={xPost} />}
<TweetBody tweet={xPost} />
{xPost.mediaDetails?.length ? <TweetMedia tweet={xPost} /> : null}
{xPost.quoted_tweet && <QuotedTweet tweet={xPost.quoted_tweet} />}
<TweetInfo tweet={xPost} />
</>
)
return scriptLoadPromise
}

useEffect(() => {
const initializeTweet = async () => {
const twttr = await loadTwitterScript()
if (!isInitialized.current && twttr) {
console.log(`init tweet: ${block.id}`)
if (!createdTweets.current.has(block.id)) {
createdTweets.current.add(block.id)
await twttr.widgets.createTweet(xPostId, containerRef.current, {
theme: 'dark',
align: 'center',
})
isInitialized.current = true
}
}
}

setLoading(true)
initializeTweet()
.then(() => {
setLoading(false)
})
.catch((error) => {
console.error('Error initializing tweet:', error)
setLoading(false)
})

return () => {
isInitialized.current = false
}
}, [block.props.url])

return (
<MediaContainer
editor={editor}
Expand All @@ -152,9 +215,26 @@ const display = ({
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
fontWeight: '400',
}}
className="x-post-container"
// className="x-post-container"
>
{xPostContent}
{/* <iframe
// ref={iframeRef}
src={`https://platform.twitter.com/embed/index.html?id=${xPostId}&theme=dark`}
width="100%" // Set width to be responsive
height="auto"
style={{border: 'none', overflow: 'hidden'}}
></iframe> */}
{loading && <Spinner />}
<div ref={containerRef} />
</MediaContainer>
)
}

const fetchOEmbed = async (tweetUrl: string) => {
const response = await fetch(
`https://publish.twitter.com/oembed?url=${encodeURIComponent(
tweetUrl,
)}&theme=dark`,
)
return response.json()
}
43 changes: 22 additions & 21 deletions frontend/apps/desktop/src/slash-menu-items.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {TwitterXIcon} from '@shm/ui'
import {
RiArticleFill,
RiCodeBoxFill,
Expand Down Expand Up @@ -258,25 +259,25 @@ export const slashMenuItems = [
},
},
// DISABLE TWITTER/X EMBEDS BECAUSE IT DOES NOT WORK ON WEB
// {
// name: 'X Post',
// aliases: ['tweet', 'twitter', 'x'],
// group: 'Web embeds',
// icon: <TwitterXIcon width={18} height={18} />,
// hint: 'Insert an X Post embed',
// execute: (editor) => {
// insertOrUpdateBlock(
// editor,
// {
// type: 'web-embed',
// props: {
// url: '',
// },
// } as PartialBlock<HMBlockSchema>,
// true,
// )
// const { state, view } = editor._tiptapEditor
// view.dispatch(state.tr.scrollIntoView())
// },
// },
{
name: 'X Post',
aliases: ['tweet', 'twitter', 'web embed', 'x.com'],
group: 'Web embeds',
icon: <TwitterXIcon width={18} height={18} />,
hint: 'Insert an X Post embed',
execute: (editor) => {
insertOrUpdateBlock(
editor,
{
type: 'web-embed',
props: {
url: '',
},
} as PartialBlock<HMBlockSchema>,
true,
)
const {state, view} = editor._tiptapEditor
view.dispatch(state.tr.scrollIntoView())
},
},
]
132 changes: 74 additions & 58 deletions frontend/packages/ui/src/document-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import {contentLayoutUnit, contentTextUnit} from "./document-content-constants";
import "./document-content.css";
import {SeedHeading} from "./heading";
import {Comment} from "./icons";
import {Spinner} from "./spinner";
import {Tooltip} from "./tooltip";
// import {XPostNotFound, XPostSkeleton} from "./x-components";

Expand Down Expand Up @@ -897,9 +898,9 @@ function BlockContent(props: BlockContentProps) {
return <BlockContentButton {...props} {...dataProps} />;
}

// if (props.block.type == "WebEmbed") {
// return <BlockContentXPost {...props} {...dataProps} />;
// }
if (props.block.type == "WebEmbed") {
return <BlockContentXPost {...props} {...dataProps} />;
}

if (props.block.type == "Embed") {
return <BlockContentEmbed {...props} {...dataProps} />;
Expand Down Expand Up @@ -2063,62 +2064,77 @@ export function BlockContentButton({
// );
// }

// export function BlockContentXPost({
// block,
// parentBlockId,
// ...props
// }: BlockContentProps) {
// const {layoutUnit, onLinkClick} = useDocContentContext();
// const urlArray = block.ref?.split("/");
// const xPostId = urlArray?.[urlArray.length - 1].split("?")[0];
// const {data, error, isLoading} = useTweet(xPostId);

// let xPostContent;

// if (isLoading) xPostContent = <XPostSkeleton />;
// else if (error || !data) {
// xPostContent = <XPostNotFound error={error} />;
// } else {
// const xPost = enrichTweet(data);
// xPostContent = (
// <YStack width="100%">
// <TweetHeader tweet={xPost} />
// {xPost.in_reply_to_status_id_str && <TweetInReplyTo tweet={xPost} />}
// <TweetBody tweet={xPost} />
// {xPost.mediaDetails?.length ? <TweetMedia tweet={xPost} /> : null}
// {xPost.quoted_tweet && <QuotedTweet tweet={xPost.quoted_tweet} />}
// <TweetInfo tweet={xPost} />
// </YStack>
// );
// }
export function BlockContentXPost({
block,
parentBlockId,
...props
}: BlockContentProps) {
const {layoutUnit, onLinkClick} = useDocContentContext();
const urlArray = block.link?.split("/");
const xPostId = urlArray?.[urlArray.length - 1].split("?")[0];
const containerRef = useRef(null);
const isInitialized = useRef(false);
const [loading, setLoading] = useState(false);

const loadTwitterScript = () => {
return new Promise((resolve) => {
if (window.twttr) {
resolve(window.twttr);
} else {
const script = document.createElement("script");
script.src = "https://platform.twitter.com/widgets.js";
script.async = true;
script.onload = () => resolve(window.twttr);
document.body.appendChild(script);
}
});
};

// return (
// <YStack
// {...blockStyles}
// {...props}
// borderColor="$color6"
// backgroundColor="$color4"
// borderWidth={1}
// borderRadius={layoutUnit / 4}
// padding={layoutUnit / 2}
// overflow="hidden"
// width="100%"
// marginHorizontal={(-1 * layoutUnit) / 2}
// className="x-post-container"
// data-content-type="web-embed"
// data-url={block.ref}
// onPress={(e) => {
// e.preventDefault();
// e.stopPropagation();
// if (block.ref) {
// onLinkClick(block.ref, e);
// }
// }}
// >
// {xPostContent}
// </YStack>
// );
// }
useEffect(() => {
const initializeTweet = async () => {
const twttr = await loadTwitterScript();
if (!isInitialized.current && twttr) {
twttr.widgets.createTweet(xPostId, containerRef.current, {
theme: "dark",
align: "center",
});
isInitialized.current = true;
}
};
setLoading(true);
initializeTweet()
.then((res) => setLoading(false))
.catch((e) => setLoading(false));
}, [xPostId]);

return (
<YStack
{...blockStyles}
{...props}
borderColor="$color6"
backgroundColor="$color4"
borderWidth={1}
borderRadius={layoutUnit / 4}
padding={layoutUnit / 2}
overflow="hidden"
width="100%"
marginHorizontal={(-1 * layoutUnit) / 2}
className="x-post-container"
data-content-type="web-embed"
data-url={block.link}
onPress={(e) => {
e.preventDefault();
e.stopPropagation();
if (block.link) {
onLinkClick(block.link, e);
}
}}
>
{loading && <Spinner />}
<div ref={containerRef} />
</YStack>
);
}

export function BlockContentCode({
block,
Expand Down

0 comments on commit cfab5a3

Please sign in to comment.