diff --git a/README.md b/README.md index c67f06b..ccdd591 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ The following features are built into the application: - Every hour by default, configurable down to 5 mins. - Custom feed names and colors. - Feed-based tags for categorization. +- Ability to hide feed posts by default. - 3 different post layout modes (card, list, compact). - Fetching of page open-graph images. - Feeds managed via a single plaintext file. @@ -131,6 +132,11 @@ https://example.com/feed-b.xml News_Site #news # Feed color can be set using square brackets after the name. # The color must be a CSS-compatible color value. https://example.com/feed-c.xml Blue_News[#0078b9] #news #blue + +# Feeds starting with a '-' are flagged as hidden. +# Posts for hidden feeds won't be shown on the homepage +# but can be seen via any type of active filter. +- https://example.com/feed-d.xml Cat_Facts #cats #facts ``` ## App Configuration diff --git a/app/Config/ConfiguredFeed.php b/app/Config/ConfiguredFeed.php index a65ebc1..6f3b6c0 100644 --- a/app/Config/ConfiguredFeed.php +++ b/app/Config/ConfiguredFeed.php @@ -16,6 +16,7 @@ public function __construct( public string $url, public string $color, public array $tags, + public bool $hidden, ) { } @@ -26,6 +27,7 @@ public function jsonSerialize(): mixed 'color' => $this->color, 'url' => $this->url, 'tags' => $this->tags, + 'hidden' => $this->hidden, 'reloading' => $this->reloading, 'outdated' => $this->isOutdated(), ]; diff --git a/app/Config/ConfiguredFeedList.php b/app/Config/ConfiguredFeedList.php index b9dd360..b4b87a1 100644 --- a/app/Config/ConfiguredFeedList.php +++ b/app/Config/ConfiguredFeedList.php @@ -2,6 +2,7 @@ namespace App\Config; +use ArrayIterator; use IteratorAggregate; use JsonSerializable; use Traversable; @@ -50,7 +51,7 @@ public function reloadOutdatedFeeds(): int public function getIterator(): Traversable { - return $this->feeds; + return new ArrayIterator($this->feeds); } public function jsonSerialize(): mixed diff --git a/app/Config/ConfiguredFeedProvider.php b/app/Config/ConfiguredFeedProvider.php index a9d756b..418307d 100644 --- a/app/Config/ConfiguredFeedProvider.php +++ b/app/Config/ConfiguredFeedProvider.php @@ -9,7 +9,7 @@ class ConfiguredFeedProvider protected RssConfig $config; /** @var ConfiguredFeed[] */ - protected $feeds = []; + protected array $feeds = []; public function loadFromConfig(): void { @@ -54,7 +54,8 @@ protected function getConfiguredFeeds(): array $this->config->getName($feedUrl), $feedUrl, $this->config->getColor($feedUrl), - $this->config->getTags($feedUrl) + $this->config->getTags($feedUrl), + $this->config->getHidden($feedUrl), ); $configuredFeeds[] = $configured; @@ -78,6 +79,16 @@ public function getAll() return new ConfiguredFeedList($this->feeds); } + public function getVisible() + { + $feeds = array_filter($this->feeds, function (ConfiguredFeed $feed) { + return !$feed->hidden; + }); + + $this->updateLastAccessedForFeeds($feeds); + return new ConfiguredFeedList($feeds); + } + public function get(string $feedUrl): ?ConfiguredFeed { foreach ($this->feeds as $feed) { diff --git a/app/Config/RssConfig.php b/app/Config/RssConfig.php index 4f13678..7a82491 100644 --- a/app/Config/RssConfig.php +++ b/app/Config/RssConfig.php @@ -8,9 +8,9 @@ class RssConfig * The configured feeds. * Array keys are the feed URLs and values are arrays of tags as strings. * Tag strings include their '#' prefix. - * @var array + * @var array */ - protected $feeds = []; + protected array $feeds = []; /** * Get all feed URLs @@ -24,12 +24,13 @@ public function getFeedUrls(): array /** * Add a new feed to the config. */ - public function addFeed(string $feed, string $name, array $tags = [], string $color = ''): void + public function addFeed(string $feed, string $name, array $tags = [], string $color = '', bool $hidden = false): void { $this->feeds[$feed] = [ 'name' => $name, 'tags' => $tags, 'color' => $color, + 'hidden' => $hidden, ]; } @@ -73,6 +74,14 @@ public function getColor(string $feed): string return $this->feeds[$feed]['color'] ?? ''; } + /** + * Get the hidden status for the given feed. + */ + public function getHidden(string $feed): bool + { + return $this->feeds[$feed]['hidden'] ?? false; + } + /** * Get the configuration as a string. */ @@ -92,6 +101,10 @@ public function toString(): string $line .= " {$tag}"; } + if ($details['hidden']) { + $line = '-' . $line; + } + $lines[] = $line; } @@ -107,8 +120,14 @@ public function parseFromString(string $configString): void foreach ($lines as $line) { $line = trim($line); - $parts = explode(' ', $line); + $hidden = false; + if (str_starts_with($line, '-')) { + $hidden = true; + $line = ltrim($line, '- '); + } + + $parts = explode(' ', $line); if (empty($line) || str_starts_with($line, '#') || count($parts) < 2) { continue; } @@ -127,7 +146,7 @@ public function parseFromString(string $configString): void $tags = array_filter(array_slice($parts, 2), fn ($str) => str_starts_with($str, '#')); if (str_starts_with($url, 'http://') || str_starts_with($url, 'https://')) { - $this->addFeed($url, $name, $tags, $color); + $this->addFeed($url, $name, $tags, $color, $hidden); } } } diff --git a/app/Http/Controllers/PostViewController.php b/app/Http/Controllers/PostViewController.php index 978b33e..78c5e1e 100644 --- a/app/Http/Controllers/PostViewController.php +++ b/app/Http/Controllers/PostViewController.php @@ -19,10 +19,12 @@ public function __construct( public function home(Request $request) { - $feeds = $this->feedProvider->getAll(); - $feeds->reloadOutdatedFeeds(); + $displayFeeds = $this->feedProvider->getAll(); + $displayFeeds->reloadOutdatedFeeds(); + + $postFeeds = $this->feedProvider->getVisible(); - return $this->renderPostsView($request, $feeds); + return $this->renderPostsView($request, $displayFeeds, $postFeeds); } public function tag(Request $request, string $tag) @@ -30,7 +32,7 @@ public function tag(Request $request, string $tag) $feeds = $this->feedProvider->getForTag('#' . $tag); $feeds->reloadOutdatedFeeds(); - return $this->renderPostsView($request, $feeds, ['tag' => $tag]); + return $this->renderPostsView($request, $feeds, $feeds, ['tag' => $tag]); } public function feed(Request $request, string $feed) @@ -40,10 +42,10 @@ public function feed(Request $request, string $feed) $feeds = $this->feedProvider->getAsList($feed); $feeds->reloadOutdatedFeeds(); - return $this->renderPostsView($request, $feeds, ['feed' => $feed]); + return $this->renderPostsView($request, $feeds, $feeds, ['feed' => $feed]); } - protected function renderPostsView(Request $request, ConfiguredFeedList $feeds, array $additionalData = []) + protected function renderPostsView(Request $request, ConfiguredFeedList $displayFeeds, ConfiguredFeedList $postFeeds, array $additionalData = []) { $page = max(intval($request->get('page')), 1); $query = $request->get('query', ''); @@ -57,14 +59,14 @@ protected function renderPostsView(Request $request, ConfiguredFeedList $feeds, } $posts = $this->postProvider->getLatest( - $feeds, + $postFeeds, 100, $page, $subFilter ); $coreData = [ - 'feeds' => $feeds, + 'feeds' => $displayFeeds, 'posts' => $posts, 'page' => $page, 'search' => $query, diff --git a/resources/js/Parts/Feed.vue b/resources/js/Parts/Feed.vue index 8d6a5e8..4f70ceb 100644 --- a/resources/js/Parts/Feed.vue +++ b/resources/js/Parts/Feed.vue @@ -2,6 +2,13 @@

{{ feed.name }} + + + + + + +

{{ feed.url }}
diff --git a/tests/Feature/ConfiguredFeedTest.php b/tests/Feature/ConfiguredFeedTest.php index 711090c..7610e28 100644 --- a/tests/Feature/ConfiguredFeedTest.php +++ b/tests/Feature/ConfiguredFeedTest.php @@ -21,7 +21,8 @@ public function test_is_outdated_can_be_controlled_by_config(): void 'My great feed', 'https://example.com', '#fff', - ['#a'] + ['#a'], + false, ); config()->set('app.feed_update_frequency', 60); @@ -45,7 +46,8 @@ public function test_start_reloading_dispatched_refresh_job(): void 'My great feed', 'https://example.com', '#fff', - ['#a'] + ['#a'], + false, ); Queue::fake(); diff --git a/tests/Unit/RssConfigTest.php b/tests/Unit/RssConfigTest.php index af840ad..b15d608 100644 --- a/tests/Unit/RssConfigTest.php +++ b/tests/Unit/RssConfigTest.php @@ -52,10 +52,13 @@ public function test_parse_from_string(): void # A comment https://example-C.com/cats?test=abc#okay +-https://example-hidden-a.com Hidden_A #news +- https://example-hidden-b.com/ Hidden_B + http://beans.com/feed.xml#food d_is_cool #cooking "); - $this->assertCount(3, $config->getFeedUrls()); + $this->assertCount(5, $config->getFeedUrls()); $this->assertCount(0, $config->getTags('https://example-B.com/cats?test=abc#okay')); $this->assertEquals(['#dog', '#cat'], $config->getTags('https://example.com')); $this->assertEquals(['#cooking'], $config->getTags('http://beans.com/feed.xml#food')); @@ -63,6 +66,9 @@ public function test_parse_from_string(): void $this->assertEquals('b', $config->getName('https://example.com')); $this->assertEquals('#000', $config->getColor('https://example.com')); $this->assertEquals('d is cool', $config->getName('http://beans.com/feed.xml#food')); + $this->assertTrue($config->getHidden('https://example-hidden-a.com')); + $this->assertTrue($config->getHidden('https://example-hidden-b.com/')); + $this->assertFalse($config->getHidden('http://beans.com/feed.xml#food')); }