chore: fixed build errors
6c435a35
4 file(s) · +61 −21
| 31 | 31 | const allFeeds = useQuery(allFeedsQuery); |
|
| 32 | 32 | const hasFeeds = allFeeds.length > 0; |
|
| 33 | 33 | const [isInitialLoading, setIsInitialLoading] = React.useState(true); |
|
| 34 | + | const [isInitialized, setIsInitialized] = React.useState(() => { |
|
| 35 | + | return localStorage.getItem("alcove_isInitialized") === "true"; |
|
| 36 | + | }); |
|
| 34 | 37 | const [urlInput, setUrlInput] = React.useState(""); |
|
| 35 | 38 | const [isAddingFeed, setIsAddingFeed] = React.useState(false); |
|
| 36 | 39 | const [errorMessage, setErrorMessage] = React.useState(""); |
|
| 49 | 52 | }, 500); |
|
| 50 | 53 | return () => clearTimeout(timer); |
|
| 51 | 54 | }, []); |
|
| 55 | + | ||
| 56 | + | // Mark as initialized when feeds are added |
|
| 57 | + | React.useEffect(() => { |
|
| 58 | + | if (allFeeds.length > 0 && !isInitialized) { |
|
| 59 | + | localStorage.setItem("alcove_isInitialized", "true"); |
|
| 60 | + | setIsInitialized(true); |
|
| 61 | + | } |
|
| 62 | + | }, [allFeeds.length, isInitialized]); |
|
| 52 | 63 | ||
| 53 | 64 | function handleRestoreDialogOpenChange(open: boolean) { |
|
| 54 | 65 | setIsRestoreDialogOpen(open); |
|
| 103 | 114 | category: feed.category || "Uncategorized", |
|
| 104 | 115 | dateUpdated: new Date().toISOString(), |
|
| 105 | 116 | }); |
|
| 117 | + | ||
| 118 | + | if (!result.ok) { |
|
| 119 | + | continue; |
|
| 120 | + | } |
|
| 106 | 121 | ||
| 107 | 122 | for (const post of posts) { |
|
| 108 | 123 | evolu.insert("rssPost", { |
|
| 21 | 21 | import { use, useState, useRef } from "react"; |
|
| 22 | 22 | import { useEvolu, reset, allFeedsQuery } from "@/lib/evolu"; |
|
| 23 | 23 | import { useQuery } from "@evolu/react"; |
|
| 24 | - | import { |
|
| 25 | - | generateOPML, |
|
| 26 | - | parseOPML, |
|
| 27 | - | downloadOPML, |
|
| 28 | - | type OPMLFeed, |
|
| 29 | - | } from "@/lib/opml"; |
|
| 24 | + | import { generateOPML, parseOPML, downloadOPML } from "@/lib/opml"; |
|
| 30 | 25 | import { |
|
| 31 | 26 | fetchFeedWithFallback, |
|
| 32 | 27 | parseFeedXml, |
|
| 127 | 122 | category: feed.category || "Uncategorized", |
|
| 128 | 123 | dateUpdated: new Date().toISOString(), |
|
| 129 | 124 | }); |
|
| 125 | + | ||
| 126 | + | if (!result.ok) { |
|
| 127 | + | continue; |
|
| 128 | + | } |
|
| 130 | 129 | ||
| 131 | 130 | for (const post of posts) { |
|
| 132 | 131 | evolu.insert("rssPost", { |
|
| 31 | 31 | }); |
|
| 32 | 32 | ||
| 33 | 33 | export const allFeedsQuery = evolu.createQuery((db) => |
|
| 34 | - | db.selectFrom("rssFeed").selectAll().where("isDeleted", "is not", sqliteTrue), |
|
| 34 | + | db |
|
| 35 | + | .selectFrom("rssFeed") |
|
| 36 | + | .selectAll() |
|
| 37 | + | .where("isDeleted", "is not", sqliteTrue) |
|
| 38 | + | // Filter out null titles and feedUrls (required fields) |
|
| 39 | + | .where("title", "is not", null) |
|
| 40 | + | .where("feedUrl", "is not", null) |
|
| 41 | + | .$narrowType<{ |
|
| 42 | + | title: import("@evolu/common").kysely.NotNull; |
|
| 43 | + | feedUrl: import("@evolu/common").kysely.NotNull; |
|
| 44 | + | }>() |
|
| 45 | + | .orderBy("createdAt"), |
|
| 35 | 46 | ); |
|
| 36 | 47 | ||
| 37 | 48 | export const postsByFeedQuery = (feedId: string) => |
|
| 41 | 52 | .selectAll() |
|
| 42 | 53 | .where("feedId", "=", feedId as RSSFeedId) |
|
| 43 | 54 | .where("isDeleted", "is not", sqliteTrue) |
|
| 55 | + | // Filter out null required fields |
|
| 56 | + | .where("title", "is not", null) |
|
| 57 | + | .where("link", "is not", null) |
|
| 58 | + | .$narrowType<{ |
|
| 59 | + | title: import("@evolu/common").kysely.NotNull; |
|
| 60 | + | link: import("@evolu/common").kysely.NotNull; |
|
| 61 | + | }>() |
|
| 44 | 62 | .orderBy("id", "desc"), |
|
| 45 | 63 | ); |
|
| 46 | 64 | ||
| 49 | 67 | .selectFrom("rssPost") |
|
| 50 | 68 | .selectAll() |
|
| 51 | 69 | .where("isDeleted", "is not", sqliteTrue) |
|
| 70 | + | // Filter out null required fields |
|
| 71 | + | .where("title", "is not", null) |
|
| 72 | + | .where("link", "is not", null) |
|
| 73 | + | .$narrowType<{ |
|
| 74 | + | title: import("@evolu/common").kysely.NotNull; |
|
| 75 | + | link: import("@evolu/common").kysely.NotNull; |
|
| 76 | + | }>() |
|
| 52 | 77 | .orderBy("id", "desc"), |
|
| 53 | 78 | ); |
|
| 54 | 79 | ||
| 57 | 82 | .selectFrom("rssFeed") |
|
| 58 | 83 | .selectAll() |
|
| 59 | 84 | .where("isDeleted", "is not", sqliteTrue) |
|
| 85 | + | // Filter out null required fields |
|
| 86 | + | .where("title", "is not", null) |
|
| 87 | + | .where("feedUrl", "is not", null) |
|
| 88 | + | .$narrowType<{ |
|
| 89 | + | title: import("@evolu/common").kysely.NotNull; |
|
| 90 | + | feedUrl: import("@evolu/common").kysely.NotNull; |
|
| 91 | + | }>() |
|
| 60 | 92 | .orderBy("category", "asc"), |
|
| 61 | 93 | ); |
|
| 62 | 94 | ||
| 1 | - | import type { RSSFeedId } from "./scheme"; |
|
| 1 | + | import { allFeedsQuery } from "./evolu"; |
|
| 2 | + | ||
| 3 | + | type Feed = typeof allFeedsQuery.Row; |
|
| 2 | 4 | ||
| 5 | + | /** |
|
| 6 | + | * Type for parsed OPML feed data |
|
| 7 | + | */ |
|
| 3 | 8 | export interface OPMLFeed { |
|
| 4 | 9 | title: string; |
|
| 5 | 10 | feedUrl: string; |
|
| 7 | 12 | category?: string; |
|
| 8 | 13 | } |
|
| 9 | 14 | ||
| 10 | - | export interface RSSFeed { |
|
| 11 | - | id: RSSFeedId; |
|
| 12 | - | feedUrl: string; |
|
| 13 | - | title: string; |
|
| 14 | - | description: string | null; |
|
| 15 | - | category: string | null; |
|
| 16 | - | dateUpdated: string | null; |
|
| 17 | - | isDeleted: number; |
|
| 18 | - | } |
|
| 19 | - | ||
| 20 | 15 | /** |
|
| 21 | 16 | * Generate OPML XML from feeds |
|
| 22 | 17 | */ |
|
| 23 | - | export function generateOPML(feeds: readonly RSSFeed[]): string { |
|
| 18 | + | export function generateOPML(feeds: readonly Feed[]): string { |
|
| 24 | 19 | const now = new Date().toUTCString(); |
|
| 25 | - | const categories = new Map<string, RSSFeed[]>(); |
|
| 20 | + | const categories = new Map<string, Feed[]>(); |
|
| 26 | 21 | ||
| 27 | 22 | // Group feeds by category |
|
| 28 | 23 | for (const feed of feeds) { |
|
| 75 | 70 | const outlines = xmlDoc.querySelectorAll("outline"); |
|
| 76 | 71 | ||
| 77 | 72 | for (const outline of outlines) { |
|
| 78 | - | const type = outline.getAttribute("type"); |
|
| 79 | 73 | const xmlUrl = outline.getAttribute("xmlUrl"); |
|
| 80 | 74 | ||
| 81 | 75 | // If this outline has an xmlUrl, it's a feed |
|