Skip to content

Commit 00ab849

Browse files
authored
feat: find more rss sources (#155)
1 parent 4020995 commit 00ab849

7 files changed

Lines changed: 77 additions & 31 deletions

File tree

bun.lock

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
},
6666
"dependencies": {
6767
"@lucide/svelte": "^1.7.0",
68-
"fast-xml-parser": "^5.5.8",
68+
"fast-xml-parser": "^5.7.1",
6969
"fuse.js": "^7.3.0",
7070
"isomorphic-dompurify": "^3.4.0",
7171
"js-yaml": "^4.1.1",

src/lib/data/rss-sources.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@
1717
"name": "Techlore",
1818
"url": "https://techlore.tech",
1919
"feedUrl": "https://techlore.tech/rss/"
20+
},
21+
{
22+
"id": "proton",
23+
"name": "Proton",
24+
"url": "https://proton.me/blog",
25+
"feedUrl": "https://proton.me/blog/feed"
26+
},
27+
{
28+
"id": "mullvad",
29+
"name": "Mullvad",
30+
"url": "https://mullvad.net/en/blog",
31+
"feedUrl": "https://mullvad.net/en/blog/feed/rss/"
2032
}
2133
]
2234
}
Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,63 @@
11
<script lang="ts">
22
import type { HTMLAttributes } from 'svelte/elements';
33
4+
import { goto } from '$app/navigation';
45
import { resolve } from '$app/paths';
56
import { page } from '$app/state';
67
import rssSources from '$lib/data/rss-sources.json';
78
import type { SourceSearchParam } from '$lib/features/feed/types';
89
import { cn } from '$lib/utils/cn';
910
11+
type ArticleFiltersProps = HTMLAttributes<HTMLDivElement>;
12+
13+
let { class: klass, ...props }: ArticleFiltersProps = $props();
14+
1015
const filters = [{ id: 'all', name: 'All' }, ...rssSources.data];
1116
1217
let activeFilter = $state(page.url.searchParams.get('source') as SourceSearchParam);
1318
14-
type ArticleFiltersProps = HTMLAttributes<HTMLDivElement>;
19+
function createHref(filterId: string | null) {
20+
return filterId === 'all' || filterId === null
21+
? '/privacy-news'
22+
: (`/privacy-news?source=${filterId}` as const);
23+
}
1524
16-
let { class: klass, ...props }: ArticleFiltersProps = $props();
25+
function handleFilterChange(event: Event) {
26+
const target = event.target as HTMLSelectElement;
27+
const value = target.value as SourceSearchParam;
28+
29+
activeFilter = value;
30+
31+
void goto(resolve(createHref(value)));
32+
}
1733
</script>
1834

19-
<div role="tablist" class={cn('tabs-border mb-6 tabs ', klass)} {...props}>
20-
{#each filters as filter (filter.id)}
21-
{@const isActive = activeFilter === filter.id || (filter.id === 'all' && activeFilter === null)}
22-
{@const href =
23-
filter.id === 'all' ? '/privacy-news' : (`/privacy-news?source=${filter.id}` as const)}
24-
<a
25-
role="tab"
26-
href={resolve(href)}
27-
class={cn('tab', isActive && 'tab-active text-primary')}
28-
onclick={() => {
29-
activeFilter = filter.id as SourceSearchParam;
30-
}}
31-
>
32-
{filter.name}
33-
</a>
34-
{/each}
35+
<div class={cn('mb-6', klass)} {...props}>
36+
<select
37+
class="select-bordered select w-full sm:hidden"
38+
aria-label="Filter by source"
39+
value={activeFilter ?? 'all'}
40+
onchange={handleFilterChange}
41+
>
42+
{#each filters as filter (filter.id)}
43+
<option value={filter.id}>{filter.name}</option>
44+
{/each}
45+
</select>
46+
47+
<div role="tablist" class="tabs-border tabs hidden gap-1 sm:flex">
48+
{#each filters as filter (filter.id)}
49+
{@const isActive =
50+
activeFilter === filter.id || (filter.id === 'all' && activeFilter === null)}
51+
<a
52+
role="tab"
53+
href={resolve(createHref(filter.id))}
54+
class={cn('tab', isActive && 'tab-active text-primary')}
55+
onclick={() => {
56+
activeFilter = filter.id as SourceSearchParam;
57+
}}
58+
>
59+
{filter.name}
60+
</a>
61+
{/each}
62+
</div>
3563
</div>

src/lib/features/feed/components/source-badge.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313
const variant = {
1414
tuta: 'badge-error',
1515
techlore: 'badge-info',
16-
privacyguides: 'badge-warning'
16+
privacyguides: 'badge-warning',
17+
proton: 'bg-violet-950 border-violet-950 text-violet-300',
18+
mullvad: 'bg-yellow-500 border-yellow-500 text-blue-800'
1719
} satisfies Record<Source['id'], string>;
1820
</script>
1921

20-
<div class={cn('badge badge-soft', variant[source.id], klass)} {...props}>{source.name}</div>
22+
<div class={cn('badge badge-soft font-semibold', variant[source.id], klass)} {...props}>
23+
{source.name}
24+
</div>

src/lib/features/feed/service.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,20 @@ describe('RSS', () => {
9494
it('returns all sources when source param is "all"', async () => {
9595
const result = await rss.getSources({ source: 'all' });
9696

97-
expect(result.length).toBe(3);
98-
expect(result).toHaveLength(3);
97+
expect(result.length).toBe(5);
98+
expect(result).toHaveLength(5);
9999
});
100100

101101
it('returns all sources when called with default param', async () => {
102102
const result = await rss.getSources();
103103

104-
expect(result.length).toBe(3);
104+
expect(result.length).toBe(5);
105105
});
106106

107107
it('returns all sources when source is null', async () => {
108108
const result = await rss.getSources({ source: null });
109109

110-
expect(result.length).toBe(3);
110+
expect(result.length).toBe(5);
111111
});
112112

113113
it('returns only the matching source when a specific source id is provided', async () => {

src/lib/features/feed/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// source
33
//----------------------------------------------------------------------
44

5-
export type SourceId = 'tuta' | 'privacyguides' | 'techlore';
5+
export type SourceId = 'tuta' | 'privacyguides' | 'techlore' | 'proton' | 'mullvad';
66
export type SourceSearchParam = SourceId | 'all' | null;
77

88
export type Source = {

0 commit comments

Comments
 (0)