You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by ro...@apache.org on 2024/06/07 10:28:09 UTC

(incubator-answer-plugins) branch fix/embed created (now f5a8052)

This is an automated email from the ASF dual-hosted git repository.

robin0716 pushed a change to branch fix/embed
in repository https://gitbox.apache.org/repos/asf/incubator-answer-plugins.git


      at f5a8052  refactor(embed-basic): Implement the embed plugin with JSX and add loading status

This branch includes the following new commits:

     new f5a8052  refactor(embed-basic): Implement the embed plugin with JSX and add loading status

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(incubator-answer-plugins) 01/01: refactor(embed-basic): Implement the embed plugin with JSX and add loading status

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

robin0716 pushed a commit to branch fix/embed
in repository https://gitbox.apache.org/repos/asf/incubator-answer-plugins.git

commit f5a80521fc6d1a00c27d713fd95e4d5ca1f1c267
Author: robin <ro...@rnode.me>
AuthorDate: Fri Jun 7 18:26:50 2024 +0800

    refactor(embed-basic): Implement the embed plugin with JSX and add loading status
---
 embed-basic/components/CodePenEmbed/index.tsx    | 37 ++++++++++++
 embed-basic/components/DropboxEmbed/index.tsx    | 36 ++++++++++++
 embed-basic/components/EmbedContainer/index.tsx  | 58 ++++++++++++++++++
 embed-basic/components/ExcalidrawEmbed/index.tsx | 36 ++++++++++++
 embed-basic/components/FigmaEmbed/index.tsx      | 35 +++++++++++
 embed-basic/components/GithubGistEmbed/index.tsx | 33 +++++++++++
 embed-basic/components/JSFiddleEmbed/index.tsx   | 35 +++++++++++
 embed-basic/components/LoomEmbed/index.tsx       | 36 ++++++++++++
 embed-basic/components/TwitterEmbed/index.tsx    | 71 ++++++++++++++++++++++
 embed-basic/components/YouTubeEmbed/index.tsx    | 37 ++++++++++++
 embed-basic/components/index.ts                  | 40 +++++++++++++
 embed-basic/{hooks.ts => hooks.tsx}              | 75 +++++++++++++-----------
 12 files changed, 496 insertions(+), 33 deletions(-)

diff --git a/embed-basic/components/CodePenEmbed/index.tsx b/embed-basic/components/CodePenEmbed/index.tsx
new file mode 100644
index 0000000..2632d5d
--- /dev/null
+++ b/embed-basic/components/CodePenEmbed/index.tsx
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const CodePenEmbed = ({ penId }) => {
+  return (
+    <EmbedContainer>
+      <iframe
+        width="100%"
+        height="100%"
+        src={`https://codepen.io/${penId}/embed/preview/${penId}?height=265&theme-id=0&default-tab=result`}
+        title="CodePen Embed"
+        allowTransparency
+        allowFullScreen
+      />
+    </EmbedContainer>
+  );
+};
+
+export default CodePenEmbed;
diff --git a/embed-basic/components/DropboxEmbed/index.tsx b/embed-basic/components/DropboxEmbed/index.tsx
new file mode 100644
index 0000000..9c678e3
--- /dev/null
+++ b/embed-basic/components/DropboxEmbed/index.tsx
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const DropboxEmbed = ({ dropboxId }) => {
+  return (
+    <EmbedContainer height={380}>
+      <iframe
+        title="Dropbox"
+        width="100%"
+        height="100%"
+        src={`https://www.dropbox.com/s/${dropboxId}?raw=1`}
+        frameBorder="0"
+      />
+    </EmbedContainer>
+  );
+};
+
+export default DropboxEmbed;
diff --git a/embed-basic/components/EmbedContainer/index.tsx b/embed-basic/components/EmbedContainer/index.tsx
new file mode 100644
index 0000000..224584f
--- /dev/null
+++ b/embed-basic/components/EmbedContainer/index.tsx
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { FC, cloneElement, useRef } from 'react';
+import Spinner from 'react-bootstrap/Spinner';
+
+interface EmbedContainerProps {
+  children: React.ReactElement;
+  height?: number | string;
+}
+const EmbedContainer: FC<EmbedContainerProps> = ({
+  children,
+  height = 350,
+}) => {
+  const loadingRef = useRef<HTMLSpanElement>(null);
+
+  const handleLoad = () => {
+    if (loadingRef.current) {
+      const parentElement = loadingRef.current.parentElement;
+      if (parentElement) {
+        parentElement.style.height = height + 'px';
+      }
+      loadingRef.current.remove();
+    }
+  };
+  let Component = children;
+  if (children.type === 'iframe') {
+    Component = cloneElement(children, { onLoad: handleLoad });
+  }
+  return (
+    <>
+      {Component}
+      <span
+        ref={loadingRef}
+        className="loading position-absolute top-0 left-0 w-100 h-100 z-1 bg-white d-flex justify-content-center align-items-center">
+        <Spinner animation="border" variant="secondary" />
+      </span>
+    </>
+  );
+};
+
+export default EmbedContainer;
diff --git a/embed-basic/components/ExcalidrawEmbed/index.tsx b/embed-basic/components/ExcalidrawEmbed/index.tsx
new file mode 100644
index 0000000..1a659bf
--- /dev/null
+++ b/embed-basic/components/ExcalidrawEmbed/index.tsx
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const ExcalidrawEmbed = ({ excalidrawId }) => {
+  return (
+    <EmbedContainer height={380}>
+      <iframe
+        title="Excalidraw"
+        width="100%"
+        height="100%"
+        src={`https://excalidraw.com/${excalidrawId}/embed`}
+        frameBorder="0"
+      />
+    </EmbedContainer>
+  );
+};
+
+export default ExcalidrawEmbed;
diff --git a/embed-basic/components/FigmaEmbed/index.tsx b/embed-basic/components/FigmaEmbed/index.tsx
new file mode 100644
index 0000000..14cb644
--- /dev/null
+++ b/embed-basic/components/FigmaEmbed/index.tsx
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const FigmaEmbed = ({ url }) => {
+  return (
+    <EmbedContainer height={450}>
+      <iframe
+        style={{ border: 'none' }}
+        width="100%"
+        height="100%"
+        src={`https://www.figma.com/embed?embed_host=share&url=${url}`}
+        allowFullScreen></iframe>
+    </EmbedContainer>
+  );
+};
+
+export default FigmaEmbed;
diff --git a/embed-basic/components/GithubGistEmbed/index.tsx b/embed-basic/components/GithubGistEmbed/index.tsx
new file mode 100644
index 0000000..cadb158
--- /dev/null
+++ b/embed-basic/components/GithubGistEmbed/index.tsx
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const GithubGistEmbed = ({ scriptUrl }) => {
+  return (
+    <EmbedContainer>
+      <iframe
+        className="w-100 h-100"
+        src={`data:text/html;charset=utf-8,<head><base target='_blank' /></head><body style='margin:0;'><script src='${scriptUrl}'></script></body>`}
+      />
+    </EmbedContainer>
+  );
+};
+
+export default GithubGistEmbed;
diff --git a/embed-basic/components/JSFiddleEmbed/index.tsx b/embed-basic/components/JSFiddleEmbed/index.tsx
new file mode 100644
index 0000000..3265776
--- /dev/null
+++ b/embed-basic/components/JSFiddleEmbed/index.tsx
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const JSFiddleEmbed = ({ fiddleId }) => {
+  return (
+    <EmbedContainer>
+      <iframe
+        width="100%"
+        height="100%"
+        src={`https://jsfiddle.net/${fiddleId}/embedded/`}
+        allowFullScreen
+        allowTransparency></iframe>
+    </EmbedContainer>
+  );
+};
+
+export default JSFiddleEmbed;
diff --git a/embed-basic/components/LoomEmbed/index.tsx b/embed-basic/components/LoomEmbed/index.tsx
new file mode 100644
index 0000000..00b48df
--- /dev/null
+++ b/embed-basic/components/LoomEmbed/index.tsx
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const LoomEmbed = ({ loomId }) => {
+  return (
+    <EmbedContainer height={380}>
+      <iframe
+        title="Loom"
+        width="100%"
+        height="100%"
+        src={`https://www.loom.com/embed/${loomId}`}
+        frameBorder="0"
+      />
+    </EmbedContainer>
+  );
+};
+
+export default LoomEmbed;
diff --git a/embed-basic/components/TwitterEmbed/index.tsx b/embed-basic/components/TwitterEmbed/index.tsx
new file mode 100644
index 0000000..99f865e
--- /dev/null
+++ b/embed-basic/components/TwitterEmbed/index.tsx
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useRef, useEffect } from 'react';
+import Spinner from 'react-bootstrap/Spinner';
+
+declare global {
+  interface Window {
+    twttr: any;
+  }
+}
+const TwitterEmbed = ({ url, title = '' }) => {
+  const loadingRef = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    if (!loadingRef.current) {
+      return;
+    }
+    const script = document.createElement('script');
+    script.src = 'https://platform.twitter.com/widgets.js';
+    script.async = true;
+    loadingRef.current.before(script);
+
+    script.onload = () => {
+      if (!window?.twttr) {
+        return;
+      }
+      window.twttr.events.bind('rendered', () => {
+        if (loadingRef.current) {
+          const parentElement = loadingRef.current.parentElement;
+          if (parentElement) {
+            parentElement.style.height = 'auto';
+            parentElement.classList.add('d-flex', 'justify-content-center');
+          }
+          loadingRef.current.remove();
+        }
+      });
+    };
+  }, []);
+  return (
+    <>
+      <blockquote className="twitter-tweet ">
+        <a href={url} target="_blank">
+          {title}
+        </a>
+      </blockquote>
+      <span
+        ref={loadingRef}
+        className="loading position-absolute top-0 left-0 w-100 h-100 z-1 bg-white d-flex justify-content-center align-items-center">
+        <Spinner animation="border" variant="secondary" />
+      </span>
+    </>
+  );
+};
+
+export default TwitterEmbed;
diff --git a/embed-basic/components/YouTubeEmbed/index.tsx b/embed-basic/components/YouTubeEmbed/index.tsx
new file mode 100644
index 0000000..9a5879b
--- /dev/null
+++ b/embed-basic/components/YouTubeEmbed/index.tsx
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import EmbedContainer from '../EmbedContainer';
+
+const YouTubeEmbed = ({ videoId }) => {
+  return (
+    <EmbedContainer height={380}>
+      <iframe
+        width="100%"
+        height="100%"
+        src={`https://www.youtube.com/embed/${videoId}`}
+        title="YouTube video player"
+        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+        allowFullScreen
+      />
+    </EmbedContainer>
+  );
+};
+
+export default YouTubeEmbed;
diff --git a/embed-basic/components/index.ts b/embed-basic/components/index.ts
new file mode 100644
index 0000000..c4ad04d
--- /dev/null
+++ b/embed-basic/components/index.ts
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import GithubGistEmbed from './GithubGistEmbed';
+import CodePenEmbed from './CodePenEmbed';
+import YouTubeEmbed from './YouTubeEmbed';
+import JSFiddleEmbed from './JSFiddleEmbed';
+import FigmaEmbed from './FigmaEmbed';
+import ExcalidrawEmbed from './ExcalidrawEmbed';
+import LoomEmbed from './LoomEmbed';
+import DropboxEmbed from './DropboxEmbed';
+import TwitterEmbed from './TwitterEmbed';
+
+export {
+  GithubGistEmbed,
+  CodePenEmbed,
+  YouTubeEmbed,
+  JSFiddleEmbed,
+  FigmaEmbed,
+  ExcalidrawEmbed,
+  LoomEmbed,
+  DropboxEmbed,
+  TwitterEmbed,
+};
diff --git a/embed-basic/hooks.ts b/embed-basic/hooks.tsx
similarity index 73%
rename from embed-basic/hooks.ts
rename to embed-basic/hooks.tsx
index fd88f02..d1a59a3 100644
--- a/embed-basic/hooks.ts
+++ b/embed-basic/hooks.tsx
@@ -17,12 +17,31 @@
  * under the License.
  */
 
-import { useEffect, useState, RefObject } from 'react';
+import {
+  useEffect,
+  useState,
+  RefObject,
+  ReactElement,
+  isValidElement,
+} from 'react';
+import { createRoot } from 'react-dom/client';
+import {
+  GithubGistEmbed,
+  CodePenEmbed,
+  YouTubeEmbed,
+  JSFiddleEmbed,
+  FigmaEmbed,
+  ExcalidrawEmbed,
+  LoomEmbed,
+  DropboxEmbed,
+  TwitterEmbed,
+} from './components';
 
 interface Config {
   platform: string;
   enable: boolean;
 }
+
 const useRenderEmbed = (
   element: HTMLElement | RefObject<HTMLElement> | null,
 ) => {
@@ -37,7 +56,7 @@ const useRenderEmbed = (
         /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/,
       ],
       embed: (videoId: string) => {
-        return `<iframe width="100%" height="380" src="https://www.youtube.com/embed/${videoId}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
+        return <YouTubeEmbed videoId={videoId} />;
       },
     },
     {
@@ -67,7 +86,12 @@ const useRenderEmbed = (
           }
         `;
 
-        return [styleElement, blockquoteElement, scriptElement];
+        return (
+          <TwitterEmbed
+            url={url.replace('x.com', 'twitter.com')}
+            title={title}
+          />
+        );
       },
     },
     {
@@ -77,7 +101,7 @@ const useRenderEmbed = (
         /https:\/\/codepen\.io\/[a-zA-Z0-9_]+\/full\/([a-zA-Z0-9_]+)/,
       ],
       embed: (penId) => {
-        return `<iframe width="100%" height="380" scrolling="no" title="CodePen Embed" src="https://codepen.io/${penId}/embed/preview/${penId}?height=265&theme-id=0&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true"></iframe>`;
+        return <CodePenEmbed penId={penId} />;
       },
     },
     {
@@ -87,7 +111,7 @@ const useRenderEmbed = (
         /https:\/\/jsfiddle\.net\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)\/embed/,
       ],
       embed: (fiddleId: string) => {
-        return `<iframe width="100%" height="380" src="https://jsfiddle.net/${fiddleId}/embedded/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>`;
+        return <JSFiddleEmbed fiddleId={fiddleId} />;
       },
     },
     {
@@ -98,14 +122,7 @@ const useRenderEmbed = (
       ],
       embed: (_, url) => {
         const scriptUrl = url.indexOf('.js') > -1 ? url : `${url}.js`;
-        console.log(scriptUrl);
-        return `<iframe 
-        width="100%"
-        height="350"    
-        src="data:text/html;charset=utf-8,
-        <head><base target='_blank' /></head>
-        <body style='margin:0;'><script src='${scriptUrl}'></script>
-        </body>">`;
+        return <GithubGistEmbed scriptUrl={scriptUrl} />;
       },
     },
     {
@@ -115,8 +132,7 @@ const useRenderEmbed = (
         /https:\/\/www\.figma\.com\/file\/[a-zA-Z0-9_]+\/([a-zA-Z0-9_]+)/,
       ],
       embed: (_, url) => {
-        return `<iframe style="border: none;" width="100%" height="450
-" src="https://www.figma.com/embed?embed_host=share&url=${url}" allowfullscreen></iframe>`;
+        return <FigmaEmbed url={url} />;
       },
     },
     {
@@ -126,7 +142,7 @@ const useRenderEmbed = (
         /https:\/\/excalidraw\.com\/([a-zA-Z0-9_,-]+)/,
       ],
       embed: (excalidrawId: string) => {
-        return `<iframe width="100%" height="380" src="https://excalidraw.com/${excalidrawId}/embed" frameborder="0"></iframe>`;
+        return <ExcalidrawEmbed excalidrawId={excalidrawId} />;
       },
     },
     {
@@ -136,7 +152,7 @@ const useRenderEmbed = (
         /https:\/\/www\.loom\.com\/share\/([a-zA-Z0-9_]+)/,
       ],
       embed: (loomId: string) => {
-        return `<iframe width="100%" height="380" src="https://www.loom.com/embed/${loomId}" frameborder="0"></iframe>`;
+        return <LoomEmbed loomId={loomId} />;
       },
     },
     {
@@ -145,7 +161,7 @@ const useRenderEmbed = (
         /https:\/\/www\.dropbox\.com\/s\/([a-zA-Z0-9_]+)\/[a-zA-Z0-9_]+/,
       ],
       embed: (dropboxId: string) => {
-        return `<iframe width="100%" height="380" src="https://www.dropbox.com/s/${dropboxId}?raw=1" frameborder="0"></iframe>`;
+        return <DropboxEmbed dropboxId={dropboxId} />;
       },
     },
   ];
@@ -161,7 +177,7 @@ const useRenderEmbed = (
     url: string,
     title: string,
   ): string | HTMLElement | HTMLElement[] => {
-    let html: string | HTMLElement | HTMLElement[] = '';
+    let html: string | HTMLElement | HTMLElement[] | ReactElement = '';
 
     filteredEmbeds.forEach((embed) => {
       if (html) return;
@@ -193,23 +209,16 @@ const useRenderEmbed = (
         return;
       }
       const embed = renderEmbed(url, link?.textContent || '');
-      if (embed) {
-        if (typeof embed === 'string') {
-          link.innerHTML = embed;
-        } else if (Array.isArray(embed)) {
-          link.innerHTML = '';
-          embed.forEach((item) => {
-            link.appendChild(item);
-          });
-        } else {
-          link.innerHTML = '';
-          link.appendChild(embed);
-        }
+      if (isValidElement(embed)) {
+        const parentElement = link.parentElement as HTMLElement;
+        parentElement.classList.add('position-relative');
+        parentElement.style.height = '128px';
+        createRoot(parentElement).render(embed);
       } else {
         link.innerHTML = `
           <div class="border rounded p-3">
-            <div class="text-secondary">${url}</div>
-            <div class="text-body">${link.textContent}</div>
+            <div class="text-secondary small mb-1">${url}</div>
+            <div class="text-body fw-bold">${link.textContent}</div>
           </div>
         `;
       }