diff --git a/api_feeds.go b/api_feeds.go index b97dfb6..770cd64 100644 --- a/api_feeds.go +++ b/api_feeds.go @@ -20,12 +20,14 @@ func (c *Crawler) handleAPIFeedInfo(w http.ResponseWriter, r *http.Request) { type FeedDetails struct { URL string `json:"url"` Type string `json:"type,omitempty"` + Category string `json:"category,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` Language string `json:"language,omitempty"` SiteURL string `json:"siteUrl,omitempty"` DiscoveredAt string `json:"discoveredAt,omitempty"` LastCrawledAt string `json:"lastCrawledAt,omitempty"` + NextCrawlAt string `json:"nextCrawlAt,omitempty"` LastBuildDate string `json:"lastBuildDate,omitempty"` TTLMinutes int `json:"ttlMinutes,omitempty"` UpdatePeriod string `json:"updatePeriod,omitempty"` @@ -42,8 +44,8 @@ func (c *Crawler) handleAPIFeedInfo(w http.ResponseWriter, r *http.Request) { } var f FeedDetails - var title, description, language, siteUrl *string - var lastCrawledAt, lastBuildDate *time.Time + var category, title, description, language, siteUrl *string + var lastCrawledAt, nextCrawlAt, lastBuildDate *time.Time var updatePeriod, status, lastError *string var oldestItemDate, newestItemDate *time.Time var ttlMinutes, updateFreq, errorCount, itemCount *int @@ -52,16 +54,16 @@ func (c *Crawler) handleAPIFeedInfo(w http.ResponseWriter, r *http.Request) { var publishStatus, publishAccount *string err := c.db.QueryRow(` - SELECT url, type, title, description, language, site_url, - discovered_at, last_crawled_at, last_build_date, + SELECT url, type, category, title, description, language, site_url, + discovered_at, last_crawled_at, next_crawl_at, last_build_date, ttl_minutes, update_period, update_freq, status, error_count, last_error, item_count, avg_post_freq_hrs, oldest_item_date, newest_item_date, publish_status, publish_account FROM feeds WHERE url = $1 `, feedURL).Scan( - &f.URL, &f.Type, &title, &description, &language, &siteUrl, - &discoveredAt, &lastCrawledAt, &lastBuildDate, + &f.URL, &f.Type, &category, &title, &description, &language, &siteUrl, + &discoveredAt, &lastCrawledAt, &nextCrawlAt, &lastBuildDate, &ttlMinutes, &updatePeriod, &updateFreq, &status, &errorCount, &lastError, &itemCount, &avgPostFreqHrs, &oldestItemDate, &newestItemDate, @@ -77,6 +79,7 @@ func (c *Crawler) handleAPIFeedInfo(w http.ResponseWriter, r *http.Request) { return } + f.Category = StringValue(category) f.Title = StringValue(title) f.Description = StringValue(description) f.Language = StringValue(language) @@ -85,6 +88,9 @@ func (c *Crawler) handleAPIFeedInfo(w http.ResponseWriter, r *http.Request) { if lastCrawledAt != nil { f.LastCrawledAt = lastCrawledAt.Format(time.RFC3339) } + if nextCrawlAt != nil { + f.NextCrawlAt = nextCrawlAt.Format(time.RFC3339) + } if lastBuildDate != nil { f.LastBuildDate = lastBuildDate.Format(time.RFC3339) } diff --git a/static/dashboard.js b/static/dashboard.js index 9fce5d2..40fcd6c 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -28,11 +28,130 @@ function initDashboard() { if (feedsDiv) { const isVisible = feedsDiv.style.display !== 'none'; feedsDiv.style.display = isVisible ? 'none' : 'block'; + // Load items for all feeds when opening + if (!isVisible) { + feedsDiv.querySelectorAll('.inline-feed-block').forEach(feedBlock => { + const itemsDiv = feedBlock.querySelector('.feed-items'); + if (itemsDiv && !itemsDiv.dataset.loaded) { + itemsDiv.dataset.loaded = 'true'; + loadFeedItems(feedBlock.dataset.url, itemsDiv); + } + }); + } } } } }); + // Event delegation for feed-url-toggle clicks (toggle feed info) + document.addEventListener('click', (e) => { + const urlToggle = e.target.closest('.feed-url-toggle'); + if (urlToggle) { + const feedBlock = urlToggle.closest('.inline-feed-block'); + if (feedBlock) { + const infoDiv = feedBlock.querySelector('.feed-info'); + if (infoDiv) { + const isVisible = infoDiv.style.display !== 'none'; + infoDiv.style.display = isVisible ? 'none' : 'block'; + if (!isVisible && !infoDiv.dataset.loaded) { + infoDiv.dataset.loaded = 'true'; + loadFeedInfo(feedBlock.dataset.url, infoDiv); + } + } + } + } + }); + + // Event delegation for feed-title-toggle and feed-filler-toggle clicks (toggle items) + document.addEventListener('click', (e) => { + const titleToggle = e.target.closest('.feed-title-toggle'); + const fillerToggle = e.target.closest('.feed-filler-toggle'); + if (titleToggle || fillerToggle) { + const feedBlock = (titleToggle || fillerToggle).closest('.inline-feed-block'); + if (feedBlock) { + const itemsDiv = feedBlock.querySelector('.feed-items'); + if (itemsDiv) { + const isVisible = itemsDiv.style.display !== 'none'; + itemsDiv.style.display = isVisible ? 'none' : 'block'; + } + } + } + }); + + // Load feed info into info div + async function loadFeedInfo(feedUrl, infoDiv) { + infoDiv.innerHTML = 'Loading feed info...'; + try { + const resp = await fetch(`/api/feedInfo?url=${encodeURIComponent(feedUrl)}`); + if (!resp.ok) throw new Error('Failed to load'); + const f = await resp.json(); + + let html = '