Page type-check: types for what the API sends, and for S (#235)

web/src/types.ts mirrors FeedRow, EntryRow, EncRow, /api/me and PopularRow; types only, so
swc strips it and it is not in build.mjs's list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 15:52:22 +00:00
parent 78704e40f2
commit af6eae9baf
2 changed files with 116 additions and 1 deletions

115
web/src/types.ts Normal file
View File

@@ -0,0 +1,115 @@
// What ipx's API sends the page, as types for the type-check (#235), each named after the Rust
// struct it mirrors in crates/ipx/src. Types only: swc strips this file to nothing, so it is not
// in web/build.mjs's list; tsconfig.json's glob reads it. A field added there goes here too.
// An Option is `T | null`, as serde writes None.
/// A feed in your list, `/api/feeds` and the live `feed_row` (web.rs FeedRow).
interface Feed {
id: string;
url: string;
title: string | null;
image: string | null;
folder: string | null;
category: string | null;
feed_category: string | null;
keywords: string[];
blocked: string[];
deadair: boolean;
deadair_pause: number;
allow_explicit: boolean;
auto_download: boolean;
max_new_per_check: number | null;
/// The OPML subscription it came from.
group: string | null;
orphaned: boolean;
managed: boolean;
schedule: string | null;
schedule_mins: number | null;
every_mins: number;
last_checked: number | null;
next_check: number | null;
last_error: string | null;
failing: {reason: string, new_url: string | null} | null;
entries: number;
downloaded: number;
unread: number;
subscribers: number;
pinned: boolean;
private: boolean;
private_by_address: boolean;
}
/// An item, in a page of `/api/entries` (db.rs EntryRow).
interface Entry {
guid: string;
feed_id: string;
title: string | null;
link: string | null;
published: number | null;
description: string | null;
read: boolean;
flagged: boolean;
image: string | null;
duration: number | null;
episode: number | null;
season: number | null;
position: number;
enclosures: Enclosure[];
}
/// An item's file (db.rs EncRow).
interface Enclosure {
id: number;
/// Seconds DeadAir's copy saves, when you play it from one.
deadair?: number | null;
deadair_pending?: boolean;
feed_id: string;
guid: string;
url: string;
mime: string | null;
length: number | null;
path: string | null;
state: string;
last_error: string | null;
}
/// `/api/me`.
interface Me {
name: string;
admin: boolean;
sign_out: string;
blocked: string[];
deadair_saved: number;
theme: {name: string, mode: string} | null;
version: string;
}
/// A feed as the Directory lists it (web.rs PopularRow): never its address.
interface DirFeed {
id: string;
title: string | null;
image: string | null;
subscribers: number;
subscribed: boolean;
/// Its id in your list, when it is yours.
feed: string | null;
category: string | null;
subcategory: string | null;
podcast: boolean;
}
/// The page's own state (util.ts `S`).
interface State {
feeds: Feed[];
/// The feed or place open: a feed's id, or a view such as ':all'.
feed: string | null;
entries: Entry[];
total: number;
offset: number;
filter: string;
q: string;
/// The selected item's guid.
sel: string | null;
me: Me | null;
sort: {col: string, dir: 'asc' | 'desc'};
}

View File

@@ -215,7 +215,7 @@ function folderArt(f,kids){
function nav(on){ $('#sidebar').classList.toggle('open',on); $('#scrim').hidden=!on; }
/* ---------------- state ---------------- */
const S = {
const S: State = {
feeds:[],
// Which feed (or place) and which tab were open last time, so a refresh lands back where
// you were instead of jumping to the first feed alphabetically.