Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Wikiviewer #83

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
63 changes: 61 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import { Analytics } from "@vercel/analytics/react";
import { LanguageSelector } from "./components/LanguageSelector";
import { useLikedArticles } from "./contexts/LikedArticlesContext";
import { useWikiArticles } from "./hooks/useWikiArticles";
import { WikiViewer } from "./components/WikiViewer";
import type { WikiArticle } from "./components/WikiCard";

function App() {
const [showAbout, setShowAbout] = useState(false);
const [showLikes, setShowLikes] = useState(false);
const [currentArticle, setCurrentArticle] = useState<WikiArticle | null>(null);
const [currentIndex, setCurrentIndex] = useState(-1);
const { articles, loading, fetchArticles } = useWikiArticles();
const { likedArticles, toggleLike } = useLikedArticles();
const observerTarget = useRef(null);
Expand Down Expand Up @@ -68,6 +72,44 @@ function App() {
linkElement.click();
};

const handleViewArticle = (article: WikiArticle, index: number) => {
setCurrentArticle(article);
setCurrentIndex(index);
};

const handleNextArticle = () => {
if (currentIndex < articles.length - 1) {
setCurrentArticle(articles[currentIndex + 1]);
setCurrentIndex(currentIndex + 1);
}
};

const handlePreviousArticle = () => {
if (currentIndex > 0) {
setCurrentArticle(articles[currentIndex - 1]);
setCurrentIndex(currentIndex - 1);
}
};

const handleShare = async () => {
if (!currentArticle) return;

if (navigator.share) {
try {
await navigator.share({
title: currentArticle.title,
text: currentArticle.extract,
url: currentArticle.url
});
} catch (error) {
console.error('Error sharing:', error);
}
} else {
await navigator.clipboard.writeText(currentArticle.url);
alert('Link copied to clipboard!');
}
};

return (
<div className="h-screen w-full bg-black text-white overflow-y-scroll snap-y snap-mandatory hide-scroll">
<div className="fixed top-4 left-4 z-50">
Expand Down Expand Up @@ -241,8 +283,25 @@ function App() {
</div>
)}

{articles.map((article) => (
<WikiCard key={article.pageid} article={article} />
{currentArticle && (
<WikiViewer
article={currentArticle}
onClose={() => {
setCurrentArticle(null);
setCurrentIndex(-1);
}}
onNext={handleNextArticle}
onPrevious={handlePreviousArticle}
onShare={handleShare}
/>
)}

{articles.map((article, index) => (
<WikiCard
key={article.pageid}
article={article}
onViewArticle={() => handleViewArticle(article, index)}
/>
))}
<div ref={observerTarget} className="h-10 -mt-1" />
{loading && (
Expand Down
19 changes: 8 additions & 11 deletions frontend/src/components/WikiCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export interface WikiArticle {

interface WikiCardProps {
article: WikiArticle;
onViewArticle: () => void;
}

export function WikiCard({ article }: WikiCardProps) {
export function WikiCard({ article, onViewArticle }: WikiCardProps) {
const [imageLoaded, setImageLoaded] = useState(false);
const { toggleLike, isLiked } = useLikedArticles();

Expand Down Expand Up @@ -74,14 +75,12 @@ export function WikiCard({ article }: WikiCardProps) {
{/* Content container with z-index to ensure it's above the image */}
<div className="absolute backdrop-blur-xs bg-black/30 bottom-[10vh] left-0 right-0 p-6 text-white z-10">
<div className="flex justify-between items-start mb-3">
<a
href={article.url}
target="_blank"
rel="noopener noreferrer"
<button
onClick={onViewArticle}
className="hover:text-gray-200 transition-colors"
>
<h2 className="text-2xl font-bold drop-shadow-lg">{article.title}</h2>
</a>
</button>
<div className="flex gap-2">
<button
onClick={() => toggleLike(article)}
Expand All @@ -105,14 +104,12 @@ export function WikiCard({ article }: WikiCardProps) {
</div>
</div>
<p className="text-gray-100 mb-4 drop-shadow-lg line-clamp-6">{article.extract}</p>
<a
href={article.url}
target="_blank"
rel="noopener noreferrer"
<button
onClick={onViewArticle}
className="inline-block text-white hover:text-gray-200 drop-shadow-lg"
>
Read more →
</a>
</button>
</div>
</div>
</div>
Expand Down
99 changes: 99 additions & 0 deletions frontend/src/components/WikiViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ArrowLeft, ChevronUp, ChevronDown, Share2, Heart } from 'lucide-react';
import { WikiArticle } from './WikiCard';
import { useEffect, useState } from 'react';
import { useLikedArticles } from '../contexts/LikedArticlesContext';

interface WikiViewerProps {
article: WikiArticle;
onClose: () => void;
onNext: () => void;
onPrevious: () => void;
onShare: () => void;
}

export function WikiViewer({ article, onClose, onNext, onPrevious, onShare }: WikiViewerProps) {
const [isMobile, setIsMobile] = useState(false);
const { toggleLike, isLiked } = useLikedArticles();

useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};

// Check initially
checkMobile();

// Add resize listener
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, []);

// Construct the URL based on device type
const wikiUrl = isMobile
? article.url.replace('wikipedia.org', 'm.wikipedia.org')
: article.url;

const handleNext = () => {
onNext();
onClose();
};

const handlePrevious = () => {
onPrevious();
onClose();
};

return (
<div className="fixed inset-0 bg-black z-50">
<iframe
src={wikiUrl}
className="w-full h-full"
title={article.title}
/>
<div className="absolute bottom-0 left-0 right-0 h-20 bg-black/80 backdrop-blur-sm flex items-center justify-between px-6">
<button
onClick={onClose}
className="flex items-center gap-2 text-white hover:text-gray-200 transition-colors"
>
<ArrowLeft className="w-6 h-6" />
<span>Back to WikiTok</span>
</button>

<div className="flex items-center gap-4">
<button
onClick={handlePrevious}
className="p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors text-white"
aria-label="Previous article"
>
<ChevronUp className="w-6 h-6" />
</button>
<button
onClick={handleNext}
className="p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors text-white"
aria-label="Next article"
>
<ChevronDown className="w-6 h-6" />
</button>
<button
onClick={() => toggleLike(article)}
className={`p-2 rounded-full transition-colors ${
isLiked(article.pageid)
? 'bg-red-500 hover:bg-red-600'
: 'bg-white/10 hover:bg-white/20'
}`}
aria-label="Like article"
>
<Heart className={`w-6 h-6 ${isLiked(article.pageid) ? 'fill-white' : ''}`} />
</button>
<button
onClick={onShare}
className="p-2 rounded-full bg-white/10 hover:bg-white/20 transition-colors text-white"
aria-label="Share article"
>
<Share2 className="w-6 h-6" />
</button>
</div>
</div>
</div>
);
}