From cfab5a308922b2a6b45456fc290aaf7091f7d3ed Mon Sep 17 00:00:00 2001 From: iskaktoltay Date: Fri, 6 Dec 2024 11:57:52 +0500 Subject: [PATCH] added twitter embeds back using twitter's widget script --- .../apps/desktop/src/editor/web-embed.tsx | 142 ++++++++++++++---- .../apps/desktop/src/slash-menu-items.tsx | 43 +++--- frontend/packages/ui/src/document-content.tsx | 132 +++++++++------- 3 files changed, 207 insertions(+), 110 deletions(-) diff --git a/frontend/apps/desktop/src/editor/web-embed.tsx b/frontend/apps/desktop/src/editor/web-embed.tsx index b6fdd9fc6..7e51665c2 100644 --- a/frontend/apps/desktop/src/editor/web-embed.tsx +++ b/frontend/apps/desktop/src/editor/web-embed.tsx @@ -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, @@ -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 | 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 = - else if (error || !data) { - xPostContent = - } else { - const xPost = enrichTweet(data) - xPostContent = ( - <> - - {xPost.in_reply_to_status_id_str && } - - {xPost.mediaDetails?.length ? : null} - {xPost.quoted_tweet && } - - - ) + 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 ( - {xPostContent} + {/* */} + {loading && } +
) } + +const fetchOEmbed = async (tweetUrl: string) => { + const response = await fetch( + `https://publish.twitter.com/oembed?url=${encodeURIComponent( + tweetUrl, + )}&theme=dark`, + ) + return response.json() +} diff --git a/frontend/apps/desktop/src/slash-menu-items.tsx b/frontend/apps/desktop/src/slash-menu-items.tsx index 1ef918838..250986057 100644 --- a/frontend/apps/desktop/src/slash-menu-items.tsx +++ b/frontend/apps/desktop/src/slash-menu-items.tsx @@ -1,3 +1,4 @@ +import {TwitterXIcon} from '@shm/ui' import { RiArticleFill, RiCodeBoxFill, @@ -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: , - // hint: 'Insert an X Post embed', - // execute: (editor) => { - // insertOrUpdateBlock( - // editor, - // { - // type: 'web-embed', - // props: { - // url: '', - // }, - // } as PartialBlock, - // 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: , + hint: 'Insert an X Post embed', + execute: (editor) => { + insertOrUpdateBlock( + editor, + { + type: 'web-embed', + props: { + url: '', + }, + } as PartialBlock, + true, + ) + const {state, view} = editor._tiptapEditor + view.dispatch(state.tr.scrollIntoView()) + }, + }, ] diff --git a/frontend/packages/ui/src/document-content.tsx b/frontend/packages/ui/src/document-content.tsx index 9f7ab49d0..af009ec8b 100644 --- a/frontend/packages/ui/src/document-content.tsx +++ b/frontend/packages/ui/src/document-content.tsx @@ -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"; @@ -897,9 +898,9 @@ function BlockContent(props: BlockContentProps) { return ; } - // if (props.block.type == "WebEmbed") { - // return ; - // } + if (props.block.type == "WebEmbed") { + return ; + } if (props.block.type == "Embed") { return ; @@ -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 = ; -// else if (error || !data) { -// xPostContent = ; -// } else { -// const xPost = enrichTweet(data); -// xPostContent = ( -// -// -// {xPost.in_reply_to_status_id_str && } -// -// {xPost.mediaDetails?.length ? : null} -// {xPost.quoted_tweet && } -// -// -// ); -// } +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 ( -// { -// e.preventDefault(); -// e.stopPropagation(); -// if (block.ref) { -// onLinkClick(block.ref, e); -// } -// }} -// > -// {xPostContent} -// -// ); -// } + 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 ( + { + e.preventDefault(); + e.stopPropagation(); + if (block.link) { + onLinkClick(block.link, e); + } + }} + > + {loading && } +
+ + ); +} export function BlockContentCode({ block,