aboutsummaryrefslogtreecommitdiffstats
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/FeedItem.css16
-rw-r--r--frontend/src/components/FeedItem.test.tsx18
-rw-r--r--frontend/src/components/FeedItem.tsx33
3 files changed, 64 insertions, 3 deletions
diff --git a/frontend/src/components/FeedItem.css b/frontend/src/components/FeedItem.css
index fc2c850..3becb20 100644
--- a/frontend/src/components/FeedItem.css
+++ b/frontend/src/components/FeedItem.css
@@ -107,4 +107,20 @@
color: var(--text-color);
opacity: 0.8;
margin-left: 0;
+}
+
+.scrape-btn {
+ background: var(--bg-color);
+ border: 1px solid var(--border-color, #ccc);
+ color: blue;
+ cursor: pointer;
+ font-family: 'Helvetica Neue';
+ font-weight: bold;
+ font-size: 0.8rem;
+ padding: 2px 6px;
+ margin-left: 0.5rem;
+}
+
+.scrape-btn:hover {
+ background: var(--sidebar-bg);
} \ No newline at end of file
diff --git a/frontend/src/components/FeedItem.test.tsx b/frontend/src/components/FeedItem.test.tsx
index cb9aafa..4c7d887 100644
--- a/frontend/src/components/FeedItem.test.tsx
+++ b/frontend/src/components/FeedItem.test.tsx
@@ -71,4 +71,22 @@ describe('FeedItem Component', () => {
expect(listItem).toHaveClass('read');
expect(listItem).not.toHaveClass('unread');
});
+
+ it('loads full content', async () => {
+ (global.fetch as any).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ ...mockItem, full_content: '<p>Full Content Loaded</p>' }),
+ });
+
+ render(<FeedItem item={mockItem} />);
+
+ const scrapeBtn = screen.getByTitle('Load Full Content');
+ fireEvent.click(scrapeBtn);
+
+ await waitFor(() => {
+ expect(screen.getByText('Full Content Loaded')).toBeInTheDocument();
+ });
+
+ expect(global.fetch).toHaveBeenCalledWith('/api/item/1', expect.anything());
+ });
});
diff --git a/frontend/src/components/FeedItem.tsx b/frontend/src/components/FeedItem.tsx
index ae4462a..ac142dc 100644
--- a/frontend/src/components/FeedItem.tsx
+++ b/frontend/src/components/FeedItem.tsx
@@ -56,6 +56,24 @@ export default function FeedItem({ item: initialItem }: FeedItemProps) {
});
};
+ const loadFullContent = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ setLoading(true);
+ apiFetch(`/api/item/${item._id}`)
+ .then((res) => {
+ if (!res.ok) throw new Error('Failed to fetch full content');
+ return res.json();
+ })
+ .then((data) => {
+ setItem({ ...item, ...data });
+ setLoading(false);
+ })
+ .catch((err) => {
+ console.error('Error fetching full content:', err);
+ setLoading(false);
+ });
+ };
+
return (
<li className={`feed-item ${item.read ? 'read' : 'unread'} ${loading ? 'loading' : ''}`}>
<div className="item-header">
@@ -78,10 +96,19 @@ export default function FeedItem({ item: initialItem }: FeedItemProps) {
{new Date(item.publish_date).toLocaleDateString()}
{item.feed_title && ` - ${item.feed_title}`}
</a>
- <div className="item-actions" style={{ display: 'inline-block', float: 'right' }}></div>
+ <div className="item-actions" style={{ display: 'inline-block', float: 'right' }}>
+ {!item.full_content && (
+ <button onClick={loadFullContent} className="scrape-btn" title="Load Full Content">
+ text
+ </button>
+ )}
+ </div>
</div>
- {item.description && (
- <div className="item-description" dangerouslySetInnerHTML={{ __html: item.description }} />
+ {(item.full_content || item.description) && (
+ <div
+ className="item-description"
+ dangerouslySetInnerHTML={{ __html: item.full_content || item.description }}
+ />
)}
</li>
);