Revert "Fix RSS time filters and add draggable grid layout"
This reverts commit 4ee365cfc0.
This commit is contained in:
@@ -4,13 +4,6 @@ import Parser from 'rss-parser';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
function normalizeDate(item: { isoDate?: string; pubDate?: string }): string | null {
|
||||
const raw = item.isoDate || item.pubDate;
|
||||
if (!raw) return null;
|
||||
const d = new Date(raw);
|
||||
return isNaN(d.getTime()) ? null : d.toISOString();
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const db = await getDb();
|
||||
const feeds = await db.all(`
|
||||
@@ -63,7 +56,7 @@ export async function POST(req: NextRequest) {
|
||||
feedId,
|
||||
item.title,
|
||||
item.link,
|
||||
normalizeDate(item),
|
||||
item.pubDate || item.isoDate || null,
|
||||
item.creator || item.author || null,
|
||||
(item.contentSnippet || item.content || '').substring(0, 500) || null
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
if (since) {
|
||||
const sinceMap: Record<string, string> = {
|
||||
'12h': '-12 hours',
|
||||
'1h': '-1 hours',
|
||||
'24h': '-24 hours',
|
||||
'7d': '-7 days',
|
||||
'30d': '-30 days',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "react-grid-layout/css/styles.css";
|
||||
@import "react-resizable/css/styles.css";
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
@@ -45,27 +43,3 @@ body {
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background: #525252;
|
||||
}
|
||||
|
||||
/* react-grid-layout dark theme overrides */
|
||||
.react-grid-item > .react-resizable-handle::after {
|
||||
border-right-color: #525252;
|
||||
border-bottom-color: #525252;
|
||||
}
|
||||
.react-grid-item > .react-resizable-handle:hover::after {
|
||||
border-right-color: #a1a1aa;
|
||||
border-bottom-color: #a1a1aa;
|
||||
}
|
||||
.react-grid-item.react-grid-placeholder {
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
border: 1px dashed rgba(245, 158, 11, 0.4);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.react-grid-item {
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
.widget-drag-handle {
|
||||
cursor: grab;
|
||||
}
|
||||
.widget-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
13
app/page.tsx
13
app/page.tsx
@@ -7,10 +7,15 @@ import { NewsFeed } from "@/components/widgets/NewsFeed";
|
||||
export default function Home() {
|
||||
return (
|
||||
<GridShell>
|
||||
<div key="uptime"><UptimeCard /></div>
|
||||
<div key="weather"><WeatherCard /></div>
|
||||
<div key="globe"><GlobeCard /></div>
|
||||
<div key="newsfeed"><NewsFeed /></div>
|
||||
{/* Row 1 */}
|
||||
<UptimeCard />
|
||||
<WeatherCard />
|
||||
|
||||
{/* Row 1 & 2 (Globe spans 2 rows) */}
|
||||
<GlobeCard />
|
||||
|
||||
{/* Row 2-4: NewsFeed spans 4 cols, 3 rows */}
|
||||
<NewsFeed />
|
||||
</GridShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,168 +1,29 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { ResponsiveGridLayout, useContainerWidth, verticalCompactor, type ResponsiveLayouts, type Layout } from 'react-grid-layout';
|
||||
import { RotateCcw } from 'lucide-react';
|
||||
|
||||
const STORAGE_KEY = 'dashboard-layouts';
|
||||
|
||||
const BREAKPOINTS = { lg: 1024, md: 768, sm: 0 };
|
||||
const COLS = { lg: 6, md: 4, sm: 1 };
|
||||
const ROW_HEIGHT = 200;
|
||||
const MARGIN: [number, number] = [16, 16];
|
||||
|
||||
const DEFAULT_LAYOUTS: ResponsiveLayouts = {
|
||||
lg: [
|
||||
{ i: 'uptime', x: 0, y: 0, w: 2, h: 1, minW: 2, minH: 1 },
|
||||
{ i: 'weather', x: 2, y: 0, w: 2, h: 1, minW: 2, minH: 1 },
|
||||
{ i: 'globe', x: 4, y: 0, w: 2, h: 2, minW: 2, minH: 2 },
|
||||
{ i: 'newsfeed', x: 0, y: 1, w: 4, h: 3, minW: 2, minH: 2 },
|
||||
],
|
||||
md: [
|
||||
{ i: 'uptime', x: 0, y: 0, w: 2, h: 1, minW: 2, minH: 1 },
|
||||
{ i: 'weather', x: 2, y: 0, w: 2, h: 1, minW: 2, minH: 1 },
|
||||
{ i: 'globe', x: 0, y: 1, w: 2, h: 2, minW: 2, minH: 2 },
|
||||
{ i: 'newsfeed', x: 2, y: 1, w: 2, h: 3, minW: 2, minH: 2 },
|
||||
],
|
||||
sm: [
|
||||
{ i: 'uptime', x: 0, y: 0, w: 1, h: 1, minW: 1, minH: 1 },
|
||||
{ i: 'weather', x: 0, y: 1, w: 1, h: 1, minW: 1, minH: 1 },
|
||||
{ i: 'globe', x: 0, y: 2, w: 1, h: 2, minW: 1, minH: 2 },
|
||||
{ i: 'newsfeed', x: 0, y: 4, w: 1, h: 3, minW: 1, minH: 2 },
|
||||
],
|
||||
};
|
||||
|
||||
const DRAG_CONFIG = { handle: '.widget-drag-handle' };
|
||||
|
||||
// --- Layout Context ---
|
||||
|
||||
interface LayoutContextValue {
|
||||
updateWidgetSize: (key: string, w: number, h: number) => void;
|
||||
}
|
||||
|
||||
const LayoutContext = createContext<LayoutContextValue>({
|
||||
updateWidgetSize: () => {},
|
||||
});
|
||||
|
||||
export function useLayoutContext() {
|
||||
return useContext(LayoutContext);
|
||||
}
|
||||
|
||||
// --- GridShell ---
|
||||
import React from 'react';
|
||||
|
||||
interface GridShellProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function loadSavedLayouts(): ResponsiveLayouts | null {
|
||||
if (typeof window === 'undefined') return null;
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function persistLayouts(layouts: ResponsiveLayouts) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(layouts));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function GridShell({ children }: GridShellProps) {
|
||||
const [layouts, setLayouts] = useState<ResponsiveLayouts>(DEFAULT_LAYOUTS);
|
||||
const [ready, setReady] = useState(false);
|
||||
const { width, containerRef, mounted } = useContainerWidth({ initialWidth: 1280 });
|
||||
|
||||
useEffect(() => {
|
||||
const saved = loadSavedLayouts();
|
||||
if (saved) setLayouts(saved);
|
||||
setReady(true);
|
||||
}, []);
|
||||
|
||||
const handleLayoutChange = useCallback((_layout: Layout, allLayouts: ResponsiveLayouts) => {
|
||||
setLayouts(allLayouts);
|
||||
persistLayouts(allLayouts);
|
||||
}, []);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
setLayouts(DEFAULT_LAYOUTS);
|
||||
persistLayouts(DEFAULT_LAYOUTS);
|
||||
}, []);
|
||||
|
||||
const updateWidgetSize = useCallback((key: string, w: number, h: number) => {
|
||||
setLayouts((prev) => {
|
||||
const next: ResponsiveLayouts = {};
|
||||
for (const bp of Object.keys(prev)) {
|
||||
const bpLayout = prev[bp];
|
||||
if (!bpLayout) continue;
|
||||
next[bp] = bpLayout.map((item) =>
|
||||
item.i === key ? { ...item, w, h } : item
|
||||
);
|
||||
}
|
||||
persistLayouts(next);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({ updateWidgetSize }),
|
||||
[updateWidgetSize]
|
||||
);
|
||||
|
||||
const childArray = React.Children.toArray(children);
|
||||
|
||||
return (
|
||||
<LayoutContext.Provider value={contextValue}>
|
||||
<div className="min-h-screen bg-neutral-950 text-neutral-200 p-4 md:p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<header className="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight text-white mb-1">Mission Control</h1>
|
||||
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="flex items-center gap-1.5 text-xs text-neutral-500 hover:text-neutral-300 transition-colors px-2 py-1 rounded-lg hover:bg-neutral-800"
|
||||
title="Reset layout"
|
||||
>
|
||||
<RotateCcw size={13} />
|
||||
Reset layout
|
||||
</button>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex gap-2">
|
||||
{/* Future header controls */}
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span className="text-xs font-mono text-emerald-500">SYSTEM ONLINE</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div ref={containerRef as React.RefObject<HTMLDivElement>}>
|
||||
{mounted && ready && (
|
||||
<ResponsiveGridLayout
|
||||
width={width}
|
||||
layouts={layouts}
|
||||
breakpoints={BREAKPOINTS}
|
||||
cols={COLS}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
margin={MARGIN}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
dragConfig={DRAG_CONFIG}
|
||||
compactor={verticalCompactor}
|
||||
>
|
||||
{childArray.map((child) => {
|
||||
if (!React.isValidElement(child)) return null;
|
||||
const key = child.key as string;
|
||||
return (
|
||||
<div key={key} className="h-full">
|
||||
{child}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</ResponsiveGridLayout>
|
||||
)}
|
||||
<main className="grid grid-cols-1 md:grid-cols-4 lg:grid-cols-6 gap-4 auto-rows-[200px]">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ export function GlobeCard() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full bg-neutral-900 border border-neutral-800 rounded-xl relative overflow-hidden group hover:border-neutral-700 transition-colors">
|
||||
<div className="widget-drag-handle absolute top-6 left-6 right-6 z-10">
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-2 bg-neutral-900 border border-neutral-800 rounded-xl relative overflow-hidden group hover:border-neutral-700 transition-colors">
|
||||
<div className="absolute top-6 left-6 z-10 pointer-events-none">
|
||||
<div className="flex items-center gap-2 text-neutral-400">
|
||||
<Globe size={18} />
|
||||
<span className="text-sm font-medium">Visitor Map</span>
|
||||
|
||||
@@ -150,10 +150,10 @@ function FeedFilterPills({
|
||||
);
|
||||
}
|
||||
|
||||
type TimeFilter = '12h' | '24h' | '7d' | '30d';
|
||||
type TimeFilter = '1h' | '24h' | '7d' | '30d';
|
||||
|
||||
const TIME_FILTER_OPTIONS: { value: TimeFilter; label: string }[] = [
|
||||
{ value: '12h', label: '12h' },
|
||||
{ value: '1h', label: '1h' },
|
||||
{ value: '24h', label: '24h' },
|
||||
{ value: '7d', label: '7d' },
|
||||
{ value: '30d', label: '30d' },
|
||||
@@ -479,9 +479,9 @@ export function NewsFeed() {
|
||||
const hasFeeds = feeds.length > 0;
|
||||
|
||||
return (
|
||||
<div className="h-full bg-neutral-900 border border-neutral-800 rounded-xl p-5 flex flex-col hover:border-neutral-700 transition-colors overflow-hidden">
|
||||
<div className="col-span-1 md:col-span-4 lg:col-span-4 row-span-3 bg-neutral-900 border border-neutral-800 rounded-xl p-5 flex flex-col hover:border-neutral-700 transition-colors overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="widget-drag-handle flex items-center justify-between gap-3 shrink-0 mb-3">
|
||||
<div className="flex items-center justify-between gap-3 shrink-0 mb-3">
|
||||
<div className="flex items-center gap-2 text-neutral-400">
|
||||
<Rss size={18} />
|
||||
<span className="text-sm font-medium">Feed</span>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { Activity, RefreshCcw, AlertCircle, X, Plus, Trash2, ExternalLink, ChevronDown, ChevronUp, Pencil, Check } from 'lucide-react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { format, subHours, subDays, subMonths } from 'date-fns';
|
||||
import { useLayoutContext } from '@/components/dashboard/GridShell';
|
||||
|
||||
// --- Types ---
|
||||
|
||||
@@ -593,20 +592,14 @@ export function UptimeCard() {
|
||||
}
|
||||
};
|
||||
|
||||
const { updateWidgetSize } = useLayoutContext();
|
||||
const rowSpan = expanded ? 'row-span-2' : 'row-span-1';
|
||||
const [leftLabel, rightLabel] = rangeEdgeLabels(timeRange);
|
||||
|
||||
const handleToggleExpand = useCallback(() => {
|
||||
const next = !expanded;
|
||||
setExpanded(next);
|
||||
updateWidgetSize('uptime', 2, next ? 2 : 1);
|
||||
}, [expanded, updateWidgetSize]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full bg-neutral-900 border border-neutral-800 rounded-xl p-5 flex flex-col hover:border-neutral-700 transition-all duration-300 overflow-hidden">
|
||||
<div className={`col-span-1 md:col-span-2 lg:col-span-2 ${rowSpan} bg-neutral-900 border border-neutral-800 rounded-xl p-5 flex flex-col hover:border-neutral-700 transition-all duration-300 overflow-hidden`}>
|
||||
{/* Header */}
|
||||
<div className="widget-drag-handle flex justify-between items-center shrink-0 mb-3">
|
||||
<div className="flex justify-between items-center shrink-0 mb-3">
|
||||
<div className="flex items-center gap-2 text-neutral-400">
|
||||
<Activity size={18} />
|
||||
<span className="text-sm font-medium">Uptime Monitor</span>
|
||||
@@ -615,7 +608,7 @@ export function UptimeCard() {
|
||||
<div className="flex items-center gap-2">
|
||||
<TimeRangeSelector value={timeRange} onChange={handleTimeRangeChange} />
|
||||
<button
|
||||
onClick={handleToggleExpand}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="text-neutral-500 hover:text-neutral-300 transition-colors p-1 rounded-lg hover:bg-neutral-800"
|
||||
title={expanded ? 'Collapse' : 'Expand'}
|
||||
>
|
||||
|
||||
@@ -59,7 +59,7 @@ export function WeatherCard() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="h-full bg-neutral-900 border border-neutral-800 rounded-xl p-6 relative overflow-hidden animate-pulse">
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 relative overflow-hidden animate-pulse">
|
||||
<div className="h-4 w-24 bg-neutral-800 rounded mb-4" />
|
||||
<div className="h-10 w-16 bg-neutral-800 rounded" />
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ export function WeatherCard() {
|
||||
if (!weather?.current) {
|
||||
// ... error state
|
||||
return (
|
||||
<div className="h-full bg-neutral-900 border border-neutral-800 rounded-xl p-6 flex items-center justify-center text-red-500">
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 flex items-center justify-center text-red-500">
|
||||
Failed to load weather
|
||||
</div>
|
||||
);
|
||||
@@ -79,8 +79,8 @@ export function WeatherCard() {
|
||||
const today = weather.daily;
|
||||
|
||||
return (
|
||||
<div className="h-full bg-neutral-900 border border-neutral-800 rounded-xl p-6 relative overflow-hidden group hover:border-neutral-700 transition-colors">
|
||||
<div className="widget-drag-handle flex justify-between items-start mb-2">
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 relative overflow-hidden group hover:border-neutral-700 transition-colors">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div className="flex items-center gap-2 text-neutral-400">
|
||||
<Cloud size={18} />
|
||||
<span className="text-sm font-medium">Local Weather</span>
|
||||
|
||||
18
lib/db.ts
18
lib/db.ts
@@ -80,23 +80,5 @@ export async function getDb() {
|
||||
);
|
||||
}
|
||||
|
||||
// Migrate existing non-ISO pub_date values to ISO 8601
|
||||
try {
|
||||
const rows = await db.all(
|
||||
"SELECT id, pub_date FROM rss_items WHERE pub_date IS NOT NULL AND pub_date NOT LIKE '____-__-__T%'"
|
||||
);
|
||||
for (const row of rows) {
|
||||
const d = new Date(row.pub_date);
|
||||
if (!isNaN(d.getTime())) {
|
||||
await db.run('UPDATE rss_items SET pub_date = ? WHERE id = ?', d.toISOString(), row.id);
|
||||
}
|
||||
}
|
||||
if (rows.length > 0) {
|
||||
console.log(`Migrated ${rows.length} rss_items pub_date values to ISO 8601`);
|
||||
}
|
||||
} catch (e) {
|
||||
// Table may not have data yet, ignore
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -85,13 +85,6 @@ async function setupRssTables(db) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDate(item) {
|
||||
const raw = item.isoDate || item.pubDate;
|
||||
if (!raw) return null;
|
||||
const d = new Date(raw);
|
||||
return isNaN(d.getTime()) ? null : d.toISOString();
|
||||
}
|
||||
|
||||
async function syncRssFeeds(db) {
|
||||
if (!RSSParser) return;
|
||||
const parser = new RSSParser({ timeout: 5000 });
|
||||
@@ -108,7 +101,7 @@ async function syncRssFeeds(db) {
|
||||
feed.id,
|
||||
item.title,
|
||||
item.link,
|
||||
normalizeDate(item),
|
||||
item.pubDate || item.isoDate || null,
|
||||
item.creator || item.author || null,
|
||||
(item.contentSnippet || item.content || '').substring(0, 500) || null
|
||||
);
|
||||
|
||||
84
package-lock.json
generated
84
package-lock.json
generated
@@ -18,7 +18,6 @@
|
||||
"open": "^11.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-grid-layout": "^2.2.2",
|
||||
"rss-parser": "^3.13.0",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
@@ -28,7 +27,6 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-grid-layout": "^1.3.6",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.15",
|
||||
"tailwindcss": "^4",
|
||||
@@ -852,16 +850,6 @@
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-grid-layout": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.6.tgz",
|
||||
"integrity": "sha512-Cw7+sb3yyjtmxwwJiXtEXcu5h4cgs+sCGkHwHXsFmPyV30bf14LeD/fa2LwQovuD2HWxCcjIdNhDlcYGj95qGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sqlite3": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/sqlite3/-/sqlite3-5.1.0.tgz",
|
||||
@@ -2021,15 +2009,6 @@
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cobe": {
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/cobe/-/cobe-0.6.5.tgz",
|
||||
@@ -3000,12 +2979,6 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
|
||||
"integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
@@ -5184,6 +5157,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -5592,6 +5566,7 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -5681,56 +5656,11 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-draggable": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz",
|
||||
"integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0",
|
||||
"react-dom": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-grid-layout": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-2.2.2.tgz",
|
||||
"integrity": "sha512-yNo9pxQWoxHWRAwHGSVT4DEGELYPyQ7+q9lFclb5jcqeFzva63/2F72CryS/jiTIr/SBIlTaDdyjqH+ODg8oBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"fast-equals": "^4.0.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-resizable": "^3.0.5",
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0",
|
||||
"react-dom": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-resizable": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.1.3.tgz",
|
||||
"integrity": "sha512-liJBNayhX7qA4tBJiBD321FDhJxgGTJ07uzH5zSORXoE8h7PyEZ8mLqmosST7ppf6C4zUsbd2gzDMmBCfFp9Lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "15.x",
|
||||
"react-draggable": "^4.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3",
|
||||
"react-dom": ">= 16.3"
|
||||
}
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
@@ -5787,12 +5717,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"open": "^11.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-grid-layout": "^2.2.2",
|
||||
"rss-parser": "^3.13.0",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
@@ -29,7 +28,6 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-grid-layout": "^1.3.6",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.15",
|
||||
"tailwindcss": "^4",
|
||||
|
||||
Reference in New Issue
Block a user