From e0056864af61f6081248582062b4b99ea7b38048 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Fri, 7 Aug 2026 02:14:58 +0200 Subject: [PATCH] 113 - Add per-feed colour dots to article routing --- app/Enums/FeedColorEnum.php | 36 +++++++++++++++++++ app/Models/Feed.php | 9 +++++ ..._01_01_000016_add_color_to_feeds_table.php | 22 ++++++++++++ resources/views/livewire/articles.blade.php | 6 ++-- tailwind.config.js | 11 ++++++ tests/Unit/Models/FeedTest.php | 34 +++++++++++++++++- 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 app/Enums/FeedColorEnum.php create mode 100644 database/migrations/2024_01_01_000016_add_color_to_feeds_table.php diff --git a/app/Enums/FeedColorEnum.php b/app/Enums/FeedColorEnum.php new file mode 100644 index 00000000..96ce6721 --- /dev/null +++ b/app/Enums/FeedColorEnum.php @@ -0,0 +1,36 @@ + 'bg-slate-500', + self::AMBER => 'bg-amber-500', + self::SKY => 'bg-sky-500', + self::VIOLET => 'bg-violet-500', + self::TEAL => 'bg-teal-500', + self::ROSE => 'bg-rose-500', + self::INDIGO => 'bg-indigo-500', + self::ORANGE => 'bg-orange-500', + }; + } + + public static function forId(int $id): self + { + $cases = self::cases(); + + return $cases[$id % count($cases)]; + } +} diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 51537ae0..f5f3d9be 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\FeedColorEnum; use Database\Factories\FeedFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -20,6 +21,7 @@ * @property int|null $language_id * @property Language|null $language * @property string $description + * @property FeedColorEnum|null $color * @property array $settings * @property bool $is_active * @property Carbon|null $last_fetched_at @@ -46,6 +48,7 @@ class Feed extends Model 'provider', 'language_id', 'description', + 'color', 'settings', 'is_active', 'last_fetched_at', @@ -55,8 +58,14 @@ class Feed extends Model 'settings' => 'array', 'is_active' => 'boolean', 'last_fetched_at' => 'datetime', + 'color' => FeedColorEnum::class, ]; + public function displayColor(): FeedColorEnum + { + return $this->color ?? FeedColorEnum::forId($this->id); + } + public function getTypeDisplayAttribute(): string { return match ($this->type) { diff --git a/database/migrations/2024_01_01_000016_add_color_to_feeds_table.php b/database/migrations/2024_01_01_000016_add_color_to_feeds_table.php new file mode 100644 index 00000000..c06f6f25 --- /dev/null +++ b/database/migrations/2024_01_01_000016_add_color_to_feeds_table.php @@ -0,0 +1,22 @@ +string('color')->nullable()->after('description'); + }); + } + + public function down(): void + { + Schema::table('feeds', function (Blueprint $table) { + $table->dropColumn('color'); + }); + } +}; diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 7a92e7cb..49ec9be8 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -75,9 +75,9 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline- class="flex items-center flex-wrap gap-x-1.5 text-xs font-medium mb-1.5" aria-label="Routed from {{ $routeArticle->feed?->name ?? 'Unknown feed' }} to {{ $routeArticle->platformChannel?->name ?? 'Unknown channel' }}" > - - - + @if ($routeArticle->feed) + + @endif {{ $routeArticle->feed?->name ?? 'Unknown feed' }} {{ $routeArticle->platformChannel?->name ?? 'Unknown channel' }} diff --git a/tailwind.config.js b/tailwind.config.js index 94ed3525..ce93ead3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,17 @@ export default { './vendor/livewire/livewire/dist/livewire.esm.js', './storage/framework/views/*.php', ], + // Feed dot colours are built in PHP; nothing here is reachable by the scanner. + safelist: [ + 'bg-slate-500', + 'bg-amber-500', + 'bg-sky-500', + 'bg-violet-500', + 'bg-teal-500', + 'bg-rose-500', + 'bg-indigo-500', + 'bg-orange-500', + ], theme: { extend: {}, }, diff --git a/tests/Unit/Models/FeedTest.php b/tests/Unit/Models/FeedTest.php index 274659c6..62e4812e 100644 --- a/tests/Unit/Models/FeedTest.php +++ b/tests/Unit/Models/FeedTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Models; +use App\Enums\FeedColorEnum; use App\Models\Article; use App\Models\Feed; use App\Models\Language; @@ -17,12 +18,43 @@ class FeedTest extends TestCase public function test_fillable_fields(): void { - $fillableFields = ['name', 'url', 'type', 'provider', 'language_id', 'description', 'settings', 'is_active', 'last_fetched_at']; + $fillableFields = ['name', 'url', 'type', 'provider', 'language_id', 'description', 'color', 'settings', 'is_active', 'last_fetched_at']; $feed = new Feed; $this->assertEquals($fillableFields, $feed->getFillable()); } + public function test_display_color_falls_back_to_a_deterministic_color(): void + { + $feed = Feed::factory()->create(['color' => null]); + + $this->assertSame(FeedColorEnum::forId($feed->id), $feed->displayColor()); + $this->assertSame(FeedColorEnum::forId($feed->id), $feed->fresh()->displayColor()); + } + + public function test_display_color_prefers_the_stored_color(): void + { + $feed = Feed::factory()->create(['color' => FeedColorEnum::ROSE]); + + $this->assertSame(FeedColorEnum::ROSE, $feed->fresh()->displayColor()); + } + + public function test_color_fallback_wraps_around_the_palette(): void + { + $paletteSize = count(FeedColorEnum::cases()); + + $this->assertSame(FeedColorEnum::forId(0), FeedColorEnum::forId($paletteSize)); + } + + public function test_every_palette_dot_class_is_safelisted_for_tailwind(): void + { + $config = (string) file_get_contents(base_path('tailwind.config.js')); + + foreach (FeedColorEnum::cases() as $color) { + $this->assertStringContainsString("'{$color->dotClass()}'", $config); + } + } + public function test_casts_settings_to_array(): void { $settings = ['key1' => 'value1', 'key2' => ['nested' => 'value']];