Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const config = {

// { to: "/event", label: "Event", position: "left" },
{ to: "/tools", label: "Tools", position: "left" },
{ to: "/test"},
// { to: "/test"},
// { to: "/data1", label: "Data1", position: "left" },
// { to: "/data2", label: "Data2", position: "left" },

Expand Down
144 changes: 120 additions & 24 deletions src/components/MastodonFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,87 @@ type Post = {
content: string;
pubDate: string;
imageUrl: string | null;
authorName: string;
authorHandle: string;
authorUrl: string;
};


function AuthorHeader({
name,
handle,
url,
avatar,
}: {
name: string;
handle: string;
url: string;
avatar: string | null;
}) {
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
style={{
display: "flex",
alignItems: "center",
gap: "10px",
textDecoration: "none",
color: "inherit",
marginBottom: "8px",
}}
>
{/* Real profile image */}
{avatar ? (
<img
src={avatar}
alt={name}
style={{
width: "42px",
height: "42px",
borderRadius: "10px",
objectFit: "cover",
}}
/>
) : (
<div
style={{
width: "42px",
height: "42px",
borderRadius: "10px",
background: "#999",
}}
/>
)}

{/* Text */}
<div style={{ display: "flex", flexDirection: "column" }}>
<span style={{ fontWeight: 600 }}>{name}</span>
<span style={{ fontSize: "12px", color: "#666" }}>{handle}</span>
</div>
</a>
);
}


export function MastodonFeed() {
const [posts, setPosts] = useState<Post[]>([]);
const RSS_URL = "https://mastodon.social/@rawanabdellatif.rss";
const [avatar, setAvatar] = useState<string | null>(null);

const RSS_URL = "https://mastodon.social/@daslab.rss";



useEffect(() => {
fetch(
"https://mastodon.social/api/v1/accounts/lookup?acct=daslab"
)
.then((res) => res.json())
.then((data) => setAvatar(data.avatar))
.catch((err) => console.error("Avatar fetch error:", err));
}, []);


useEffect(() => {
fetch(RSS_URL)
Expand All @@ -26,42 +102,33 @@ export function MastodonFeed() {
const link = item.querySelector("link")?.textContent || "";
const pubDate = item.querySelector("pubDate")?.textContent || "";

// content
const contentNode =
item.querySelector("content\\:encoded") ||
item.querySelector("description");

const content = contentNode?.textContent || "";

// -------------------------
// IMAGE EXTRACTION (FIXED)
// -------------------------

let imageUrl: string | null = null;

// 1. enclosure (BEST SOURCE)
const enclosure = item.querySelector("enclosure");
if (enclosure?.getAttribute("url")) {
imageUrl = enclosure.getAttribute("url");
}

// 2. media:content fallback
if (!imageUrl) {
const mediaContent = item.getElementsByTagName("media:content")[0];
const mediaContent =
item.getElementsByTagName("media:content")[0];
if (mediaContent?.getAttribute("url")) {
imageUrl = mediaContent.getAttribute("url");
}
}

// 3. HTML <img> fallback
if (!imageUrl) {
const contentDoc = new DOMParser().parseFromString(
content,
"text/html"
);

const img = contentDoc.querySelector("img");

if (img) {
imageUrl =
img.getAttribute("src") ||
Expand All @@ -70,7 +137,16 @@ export function MastodonFeed() {
}
}

return { title, link, content, pubDate, imageUrl };
return {
title,
link,
content,
pubDate,
imageUrl,
authorName: "DASLAB",
authorHandle: "@daslab",
authorUrl: "https://mastodon.social/@daslab",
};
});

setPosts(latestPosts);
Expand All @@ -90,39 +166,59 @@ export function MastodonFeed() {
gap: "15px",
marginBottom: "20px",
border: "1px solid #ddd",
padding: "10px",
borderRadius: "8px",
padding: "12px",
borderRadius: "10px",
}}
>
{/* Image */}
{/* Post image */}
{post.imageUrl && (
<img
src={post.imageUrl}
alt={post.title || "Mastodon media"}
alt={post.title}
style={{
width: "120px",
height: "120px",
objectFit: "cover",
borderRadius: "6px",
borderRadius: "8px",
}}
/>
)}

{/* Content */}
<div style={{ flex: 1 }}>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
{/* Author with REAL avatar */}
<AuthorHeader
name={post.authorName}
handle={post.authorHandle}
url={post.authorUrl}
avatar={avatar}
/>

{/* Text */}
<div style={{ marginBottom: "6px" }}>
{post.content
? post.content.replace(/<[^>]+>/g, "").slice(0, 200) +
"..."
: ""}
</div>

{/* Date */}
<small style={{ color: "#555" }}>
{post.pubDate
? new Date(post.pubDate).toLocaleDateString()
: ""}
</small>

{/* <br /> */}
<div>
<a href={post.link} target="_blank" rel="noopener noreferrer">
View on Mastodon
</a></div>
{/* Link */}
<div style={{ marginTop: "6px" }}>
<a
href={post.link}
target="_blank"
rel="noopener noreferrer"
>
View on Mastodon
</a>
</div>
</div>
</div>
))}
Expand Down
6 changes: 4 additions & 2 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import styles from "./index.module.css";
import logo from "../../static/img/logo.png";
import { TwitterFeed } from "../components/Twitter";
import { MastodonFeed } from "../components/MastodonFeed";
import HomeNotes from "./_home.mdx";

function HomepageHeader() {
Expand Down Expand Up @@ -34,11 +35,12 @@ export default function Home(): JSX.Element {
<HomeNotes />
</div>
<div className={clsx("col col--5 margin-bottom--lg")}>
<TwitterFeed
{/* <TwitterFeed
username="DASLabConcordia"
accountName="DAS Lab"
tweetLimit="2"
/>
/> */}
<MastodonFeed />
</div>
</div>
</div>
Expand Down
Loading