mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
* feat: Add missing shared schema types and fix TypeScript imports - Add AuthorWithStats and AnnotationWithUser schemas with relations - Add corresponding TypeScript type exports - Update tsconfig.json with @shared/* path mapping - Fix @shared/schema import issues across components - Resolve major TypeScript compilation errors Next: Fix remaining type mismatches and component prop issues * fix: resolve major TypeScript errors and type mismatches - Add AuthorWithStats and AnnotationWithUser schemas with proper relations - Fix AnnotationSystem component type issues (string IDs, nested user objects) - Update component props to match schema expectations - Fix function parameter types for annotation operations - Resolve null/undefined type assignments - Add missing required properties (type, isOfficial) to annotations Remaining issues: Test ES module configuration and some component prop type mismatches * fix: resolve remaining TypeScript errors and improve type safety - Fix tag-manager component to work with string IDs from schema - Update author-stats component to use schema-based AuthorWithStats type - Add missing utility functions (formatNumber, formatRating) to author utils - Fix WorkCard test to use correct schema types with string IDs - Resolve type mismatches in component props and form handling - Update interface definitions to match schema requirements Linting: ✅ 90%+ resolved, remaining minor issues Testing: ⚠️ ES module configuration needs refinement * fix: complete TypeScript fixes and testing refinements - Fix remaining AnnotationSystem component type issues - Update FilterSidebar to use string tag IDs - Resolve all major TypeScript compilation errors - Testing infrastructure fully functional with Jest + ES modules - Linting errors reduced to minor unused variable warnings All critical type safety and testing issues resolved! * Fix annotation types and author utils * Fix TypeScript and testing infrastructure issues - Fix AnnotationSystem component types (string IDs, user objects, liked/likes properties) - Add formatNumber and formatRating utilities for author components - Update FilterSidebar to use correct tag ID types (string vs number) - Fix EnhancedReadingView translation and work ID type mismatches - Resolve Playwright dependency issues in testing setup - Update Jest configuration for ES module compatibility - Fix import paths and type conflicts across components All unit tests now pass and major TypeScript compilation errors resolved. * Fix Vite build configuration for CI - Set root to 'client' directory to find index.html - Configure path aliases (@/* and @shared/*) for proper module resolution - Set build output directory to '../dist' to place files in frontend root Resolves CI build failure: 'Could not resolve entry module index.html' * Fix Docker build for Yarn v4 - Replace deprecated 'yarn install --immutable --production' with 'yarn workspaces focus --production' - This resolves the YN0050 error in CI Docker builds Yarn v4 deprecated the --production flag on install command.
246 lines
6.8 KiB
TypeScript
246 lines
6.8 KiB
TypeScript
import { Bookmark, Copy, MessageSquare } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
interface EnhancedLineNumberedTextProps {
|
|
content: string;
|
|
fontSizeClass?: string;
|
|
onAnnotate: (lineNumber: number) => void;
|
|
highlightedLine?: number;
|
|
workId: string;
|
|
}
|
|
|
|
export function EnhancedLineNumberedText({
|
|
content,
|
|
fontSizeClass = "text-size-md",
|
|
onAnnotate,
|
|
highlightedLine,
|
|
workId,
|
|
}: EnhancedLineNumberedTextProps) {
|
|
const { toast } = useToast();
|
|
const [hoveredLine, setHoveredLine] = useState<number | null>(null);
|
|
const [bookmarkedLines, setBookmarkedLines] = useState<Set<number>>(
|
|
new Set(),
|
|
);
|
|
const [lineAnnotationCounts, _setLineAnnotationCounts] = useState<
|
|
Record<number, number>
|
|
>({
|
|
// Mock annotation counts - in a real app this would come from an API
|
|
2: 3,
|
|
5: 1,
|
|
8: 7,
|
|
});
|
|
|
|
// Split content into lines
|
|
const lines = content.split("\n");
|
|
|
|
const handleLineHover = (lineNumber: number) => {
|
|
setHoveredLine(lineNumber);
|
|
};
|
|
|
|
const handleLineLeave = () => {
|
|
setHoveredLine(null);
|
|
};
|
|
|
|
const handleCopyLine = (_lineNumber: number, lineText: string) => {
|
|
navigator.clipboard.writeText(lineText);
|
|
toast({
|
|
description: "Line copied to clipboard",
|
|
duration: 2000,
|
|
});
|
|
};
|
|
|
|
const handleCopyLineLink = (lineNumber: number) => {
|
|
const url = new URL(window.location.href);
|
|
url.hash = `line-${lineNumber}`;
|
|
navigator.clipboard.writeText(url.toString());
|
|
toast({
|
|
description: "Link to line copied to clipboard",
|
|
duration: 2000,
|
|
});
|
|
};
|
|
|
|
const handleToggleBookmark = async (lineNumber: number) => {
|
|
try {
|
|
const isBookmarked = bookmarkedLines.has(lineNumber);
|
|
|
|
// Optimistically update UI
|
|
setBookmarkedLines((prev) => {
|
|
const newBookmarks = new Set(prev);
|
|
if (isBookmarked) {
|
|
newBookmarks.delete(lineNumber);
|
|
} else {
|
|
newBookmarks.add(lineNumber);
|
|
}
|
|
return newBookmarks;
|
|
});
|
|
|
|
// In a real app, this would make an API call
|
|
// await apiRequest('POST', '/api/reading-bookmarks', {
|
|
// userId: 1, // Mock user ID
|
|
// workId,
|
|
// lineNumber,
|
|
// isBookmarked: !isBookmarked
|
|
// });
|
|
|
|
toast({
|
|
description: isBookmarked ? "Bookmark removed" : "Line bookmarked",
|
|
duration: 2000,
|
|
});
|
|
} catch (_error) {
|
|
// Revert on error
|
|
toast({
|
|
title: "Error",
|
|
description: "Could not update bookmark",
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={`reading-text ${fontSizeClass}`}>
|
|
{lines.map((line, index) => {
|
|
const lineNumber = index + 1;
|
|
const isHighlighted = lineNumber === highlightedLine;
|
|
const _isHovered = lineNumber === hoveredLine;
|
|
const isBookmarked = bookmarkedLines.has(lineNumber);
|
|
const annotationCount = lineAnnotationCounts[lineNumber] || 0;
|
|
|
|
// For blank lines, render a smaller empty line
|
|
if (!line.trim()) {
|
|
return (
|
|
<div
|
|
key={`line-${lineNumber}`}
|
|
id={`line-${lineNumber}`}
|
|
className="text-line-empty h-4"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={`line-${lineNumber}`}
|
|
id={`line-${lineNumber}`}
|
|
className={`text-line group ${
|
|
isHighlighted
|
|
? "bg-navy/10 dark:bg-cream/10"
|
|
: "hover:bg-navy/5 dark:hover:bg-cream/5"
|
|
} py-1.5 rounded flex relative transition-colors`}
|
|
onMouseEnter={() => handleLineHover(lineNumber)}
|
|
onMouseLeave={handleLineLeave}
|
|
>
|
|
{/* Line number indicator with bookmark feature */}
|
|
<div
|
|
className="line-number-container w-12 flex-shrink-0 flex justify-center items-center relative"
|
|
onClick={() => handleToggleBookmark(lineNumber)}
|
|
>
|
|
{isBookmarked ? (
|
|
<Bookmark
|
|
className="h-4 w-4 text-russet cursor-pointer"
|
|
fill="currentColor"
|
|
/>
|
|
) : (
|
|
<span className="line-number text-navy/40 dark:text-cream/40 text-sm select-none">
|
|
{lineNumber}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Line content */}
|
|
<div className="line-content flex-1 relative">
|
|
<p>{line}</p>
|
|
|
|
{/* Annotation indicator - if the line has annotations */}
|
|
{annotationCount > 0 && (
|
|
<div
|
|
className="annotation-indicator absolute -right-6 top-1/2 transform -translate-y-1/2 cursor-pointer"
|
|
onClick={() => onAnnotate(lineNumber)}
|
|
>
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div className="flex items-center">
|
|
<MessageSquare className="h-4 w-4 text-russet" />
|
|
<span className="text-xs text-russet ml-0.5">
|
|
{annotationCount}
|
|
</span>
|
|
</div>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p className="text-xs">Click to view annotations</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
</div>
|
|
)}
|
|
|
|
{/* Action buttons that appear on hover */}
|
|
<div
|
|
className={`absolute right-0 top-1/2 transform -translate-y-1/2 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity bg-cream/80 dark:bg-dark-surface/80 backdrop-blur-sm px-1 rounded ${isHighlighted ? "opacity-100" : ""}`}
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-7 w-7"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleCopyLine(lineNumber, line);
|
|
}}
|
|
>
|
|
<Copy className="h-4 w-4 text-navy/70 dark:text-cream/70" />
|
|
<span className="sr-only">Copy line</span>
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-7 w-7"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleCopyLineLink(lineNumber);
|
|
}}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
className="h-4 w-4 text-navy/70 dark:text-cream/70"
|
|
>
|
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
</svg>
|
|
<span className="sr-only">Copy link to line</span>
|
|
</Button>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-7 w-7"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onAnnotate(lineNumber);
|
|
}}
|
|
>
|
|
<MessageSquare className="h-4 w-4 text-navy/70 dark:text-cream/70" />
|
|
<span className="sr-only">Annotate line</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|