diff --git a/core/current-page.ts b/core/current-page.ts index 0b85a840..695df2db 100644 --- a/core/current-page.ts +++ b/core/current-page.ts @@ -4,6 +4,10 @@ import { getEnvironment } from './environment.ts' import { type Page, pages } from './pages/index.ts' import { type Route, router } from './router.ts' +/** + * Iterates over all parameters of a page, calling the iterator function + * for each parameter store and its corresponding value from the route + */ function eachParam( page: Page, route: SomeRoute, @@ -22,6 +26,9 @@ function eachParam( } } +/** + * Extracts current parameter values from all parameter stores of a page + */ function getPageParams( page: Page ): SomeRoute['params'] { diff --git a/core/download.ts b/core/download.ts index 01d0a215..40c26bee 100644 --- a/core/download.ts +++ b/core/download.ts @@ -20,6 +20,10 @@ export interface DownloadTask { text(...args: Parameters): Promise } +/** + * Detects content type by analyzing the text content for common patterns + * like HTML doctype, XML declarations, RSS/Atom feed elements, and JSON Feed format + */ function detectType(text: string): string | undefined { let lower = text.toLowerCase() let beginning = lower.slice(0, 100) diff --git a/core/environment.ts b/core/environment.ts index f0a322bb..9d268c6b 100644 --- a/core/environment.ts +++ b/core/environment.ts @@ -131,6 +131,9 @@ export function setIsMobile(isSmallScreen: boolean): void { const testRouter = atom() +/** + * Ensures a route has a hash property, adding an empty string if missing + */ export function addHashToBaseRoute( route: BaseRoute | Omit | undefined ): BaseRoute | undefined { @@ -144,6 +147,9 @@ export function setBaseTestRoute( testRouter.set(addHashToBaseRoute(route)) } +/** + * Converts popup routes to a hash string format (popup=param,popup2=param2) + */ export function stringifyPopups(popups: PopupRoute[]): string { return popups .map(({ param, popup }) => `${popup}=${param}`) diff --git a/core/feed.ts b/core/feed.ts index f6624fbe..c7ba9237 100644 --- a/core/feed.ts +++ b/core/feed.ts @@ -66,6 +66,7 @@ export async function changeFeed( await recalcPostsReading(feedId) } +/** Subscribes to a feed which is currently being previewed */ export async function addCandidate( candidate: FeedLoader, fields: Partial = {}, diff --git a/core/filter.ts b/core/filter.ts index ff311206..82fa8e4a 100644 --- a/core/filter.ts +++ b/core/filter.ts @@ -119,6 +119,10 @@ export function sortFilters(filters: FilterValue[]): FilterValue[] { }) } +/** + * Moves a filter up or down in priority by recalculating its priority value + * relative to neighboring filters to maintain sort order + */ async function move(filterId: string, diff: -1 | 1): Promise { let store = Filter(filterId, getClient()) let filter = await loadValue(store) diff --git a/core/loader/index.ts b/core/loader/index.ts index 92a5097b..0f2ba01c 100644 --- a/core/loader/index.ts +++ b/core/loader/index.ts @@ -5,10 +5,30 @@ import { jsonFeed } from './json-feed.ts' import { rss } from './rss.ts' export type Loader = { + /** + * Returns all urls found in the html document itself and in its http headers. + * Response here is the website response with a html document. + */ getMineLinksFromText(response: TextResponse): string[] + /** + * Extracts the feed's posts, given feed download task or the response with + * the feed's xml. + */ getPosts(task: DownloadTask, url: string, text?: TextResponse): PostsList + /** + * Given the website html response, returns the default feed url, + * e.g. https://example.com/rss for rss feed. + */ getSuggestedLinksFromText(response: TextResponse): string[] + /** + * Returns feed title, if any + */ isMineText(response: TextResponse): false | string + /** + * For instance, YouTube loader will return true for youtube.com links or Telegram loader for t.me links. + * It detects that URL is 100% for this loader. + * It is not used right now because there is no way to detect RSS/Atom link just by URL. + */ isMineUrl(url: URL): false | string | undefined } @@ -29,6 +49,9 @@ export interface FeedLoader { url: string } +/** + * Decides which loader to use for the given feed response. + */ export function getLoaderForText(response: TextResponse): false | FeedLoader { let names = Object.keys(loaders) as LoaderName[] let parsed = new URL(response.url) diff --git a/core/loader/utils.ts b/core/loader/utils.ts index 368fca71..168ba986 100644 --- a/core/loader/utils.ts +++ b/core/loader/utils.ts @@ -4,10 +4,15 @@ export function isString(attr: null | string): attr is string { return typeof attr === 'string' && attr.length > 0 } +/** Detects that the server responded with a html document */ export function isHTML(text: TextResponse): boolean { return text.text.toLocaleLowerCase().includes(' elements + */ export function findDocumentLinks(text: TextResponse, type: string): string[] { let document = text.parseXml() if (!document) return [] @@ -42,6 +50,9 @@ export function findDocumentLinks(text: TextResponse, type: string): string[] { .map(link => buildFullURL(link, text.url)) } +/** + * Returns full urls found in the document's elements + */ export function findAnchorHrefs( text: TextResponse, hrefPattern: RegExp, @@ -89,6 +100,7 @@ export function findHeaderLinks( }, []) } +/** Returns the unix timestamp of a date */ export function toTime(date: null | string | undefined): number | undefined { if (!date) return undefined let time = new Date(date).getTime() / 1000 @@ -109,6 +121,7 @@ export function findImageByAttr( }, []) } +/** Returns the unique elements without nulls from array */ export function unique( collection: Iterable ): T[] { diff --git a/core/menu.ts b/core/menu.ts index ffdd54c0..9ae962af 100644 --- a/core/menu.ts +++ b/core/menu.ts @@ -37,6 +37,10 @@ export let fastMenu = atom([]) export let slowMenu = atom([]) export let menuLoading = atom(true) +/** + * Rebuilds the menu state by categorizing feeds and posts into fast/slow menus + * with unread counts and proper category organization + */ async function rebuild(): Promise { let [posts, feeds, categories, fastFilters] = await Promise.all([ loadValue(getPosts()), diff --git a/core/opened-popups.ts b/core/opened-popups.ts index 9273d0cc..4007b5bc 100644 --- a/core/opened-popups.ts +++ b/core/opened-popups.ts @@ -5,6 +5,10 @@ import { router } from './router.ts' let prevPopups: Popup[] = [] +/** + * Manages popup lifecycle by reusing existing popup instances when possible + * and destroying unused ones to prevent memory leaks + */ export const openedPopups: ReadableAtom = computed(router, route => { let lastIndex = 0 let nextPopups = route.popups.map((popup, index) => { diff --git a/core/pages/add.ts b/core/pages/add.ts index f56d6195..e629de86 100644 --- a/core/pages/add.ts +++ b/core/pages/add.ts @@ -40,8 +40,13 @@ export type AddLinksValue = Record< export const addPage = createPage('add', () => { let $url = atom() + /** + * Map of all urls found in the document with urls as keys and loading state + * as values. + */ let $links = map({}) + /** List of pending feed urls extracted from the given url */ let $candidates = atom([]) let $error = computed( @@ -111,6 +116,10 @@ export const addPage = createPage('add', () => { $url.set(normalizedUrl) }) + /** + * Extracts links to all known feed types from the http response containing + * the html document + */ function getLinksFromText(response: TextResponse): string[] { let names = Object.keys(loaders) as LoaderName[] return names.reduce((links, name) => { @@ -118,6 +127,7 @@ export const addPage = createPage('add', () => { }, []) } + /** Returns a list of default / fallback links for all feed types */ function getSuggestedLinksFromText(response: TextResponse): string[] { let names = Object.keys(loaders) as LoaderName[] return names.reduce((links, name) => { @@ -125,6 +135,9 @@ export const addPage = createPage('add', () => { }, []) } + /** + * Adds a possible feed url, its meta and type, to the list of possible urls. + */ function addCandidate(url: string, candidate: FeedLoader): void { if ($candidates.get().some(i => i.url === url)) return @@ -132,6 +145,11 @@ export const addPage = createPage('add', () => { $candidates.set([...$candidates.get(), candidate]) } + /** + * Given the link to the document, checks every link found in the document + * for a feed. Populates the list of pending urls, "candidates". + * Also accepts a direct feed link. + */ async function addLink( task: DownloadTask, url: string, diff --git a/core/refresh.ts b/core/refresh.ts index a7b5c883..7b88add1 100644 --- a/core/refresh.ts +++ b/core/refresh.ts @@ -41,6 +41,10 @@ export const refreshProgress = computed(refreshStatistics, stats => { let task: DownloadTask let queue: Queue<{ feed: FeedValue }> +/** + * Determines if a post was already added to a feed by comparing timestamps + * or origin IDs to prevent duplicate posts during refresh + */ function wasAlreadyAdded(feed: FeedValue, origin: OriginPost): boolean { if (origin.publishedAt && feed.lastPublishedAt) { return origin.publishedAt <= feed.lastPublishedAt diff --git a/core/router.ts b/core/router.ts index 3722e5b2..6d8a7abe 100644 --- a/core/router.ts +++ b/core/router.ts @@ -130,6 +130,10 @@ function checkPopupName( return !!popup && popup in popupNames } +/** + * Parses popup routes from hash string format (popup=param,popup2=param2) + * into an array of popup route objects + */ export function parsePopups(hash: string): PopupRoute[] { let popups: PopupRoute[] = [] let parts = hash.split(',')