diff --git a/materialious/src/lib/components/layout/ItemsList.svelte b/materialious/src/lib/components/layout/ItemsList.svelte
index 21b06902..b166242a 100644
--- a/materialious/src/lib/components/layout/ItemsList.svelte
+++ b/materialious/src/lib/components/layout/ItemsList.svelte
@@ -14,7 +14,7 @@
import { SpatialMenu } from 'melt/builders';
import { mergeAttrs } from 'melt';
import { Capacitor } from '@capacitor/core';
- import { filterByBlocklist } from '$lib/filtering';
+ import { isItemFiltered } from '$lib/filtering';
interface Props {
items?: FeedItems;
@@ -30,8 +30,6 @@
classes = 'page right active'
}: Props = $props();
- items = filterByBlocklist(items);
-
async function removePlaylistItem(indexId: string) {
if (!playlistId) return;
await removePlaylistVideo(playlistId, indexId);
@@ -88,42 +86,44 @@
{/if}
{#each items as item, index (index)}
- {@const uniqueItemId = extractUniqueId(item)}
- {@const spatialItem = spatialMenu.getItem(item, { onSelect: () => goToItem(uniqueItemId) })}
-
- goToItem(uniqueItemId),
- id: uniqueItemId
- })}
- class="no-padding item-select border"
- class:item-select-focused={spatialItem.highlighted}
- style="height: 100%;"
- >
- {#if item.type === 'video' || item.type === 'shortVideo' || item.type === 'stream' || item.type === 'historyVideo'}
- {#key item.videoId}
-
- {/key}
- {#if $invidiousAuthStore && decodeURIComponent($invidiousAuthStore.username) === playlistAuthor && 'indexId' in item}
-
-
-
+ {#if !isItemFiltered(item)}
+ {@const uniqueItemId = extractUniqueId(item)}
+ {@const spatialItem = spatialMenu.getItem(item, { onSelect: () => goToItem(uniqueItemId) })}
+
+ goToItem(uniqueItemId),
+ id: uniqueItemId
+ })}
+ class="no-padding item-select border"
+ class:item-select-focused={spatialItem.highlighted}
+ style="height: 100%;"
+ >
+ {#if item.type === 'video' || item.type === 'shortVideo' || item.type === 'stream' || item.type === 'historyVideo'}
+ {#key item.videoId}
+
+ {/key}
+ {#if $invidiousAuthStore && decodeURIComponent($invidiousAuthStore.username) === playlistAuthor && 'indexId' in item}
+
+
+
+ {/if}
+ {:else if item.type === 'channel'}
+
+ {:else if item.type === 'playlist'}
+
+ {:else if item.type === 'hashtag'}
+
{/if}
- {:else if item.type === 'channel'}
-
- {:else if item.type === 'playlist'}
-
- {:else if item.type === 'hashtag'}
-
- {/if}
-
-
+
+
+ {/if}
{/each}
diff --git a/materialious/src/lib/filtering.ts b/materialious/src/lib/filtering.ts
index c41a4e1e..0133aa88 100644
--- a/materialious/src/lib/filtering.ts
+++ b/materialious/src/lib/filtering.ts
@@ -1,7 +1,7 @@
import { z } from 'zod';
import type { FeedItem } from './feed';
import isSafeRegex from 'safe-regex2';
-import { blocklistStore } from './store';
+import { filterContentListStore } from './store';
import { get } from 'svelte/store';
const zFilterOperatorEnum = z.enum([
@@ -33,62 +33,58 @@ const zFilterGroup = z.object({
operator: z.enum(['AND', 'OR']).optional() // Logical grouping of conditions
});
-export const zBlockListSchema = z.array(zFilterGroup);
+export const zFilterSchema = z.array(zFilterGroup);
-const zBlocklistRootSchema = z.object({
+const zFilterRootSchema = z.object({
version: z.literal('v1'),
for: z.literal('materialious'),
- blocklist: zBlockListSchema
+ filterBy: zFilterSchema
});
-export function filterByBlocklist(data: T[]): T[] {
- const blocklist = get(blocklistStore);
- if (!blocklist) return data;
+export function isItemFiltered(item: FeedItem): boolean {
+ const filteredContent = get(filterContentListStore);
+ if (!filteredContent) return false;
- return data.filter((item) => {
- return blocklist.every((filterGroup) => {
- return filterGroup.conditions.every((condition) => {
- if (!(condition.field in item)) return false;
+ return filteredContent.every((filterGroup) => {
+ return filterGroup.conditions.every((condition) => {
+ if (!(condition.field in item)) return false;
- const fieldValue = item[condition.field as keyof T];
+ const fieldValue = item[condition.field as keyof FeedItem];
- switch (condition.operator) {
- case 'equals':
- return fieldValue === condition.value;
- case 'in':
- return (
- Array.isArray(condition.value) && (condition.value as any[]).includes(fieldValue)
- );
- case 'like':
- return (
- typeof fieldValue === 'string' &&
- typeof condition.value === 'string' &&
- fieldValue.includes(condition.value)
- );
- case 'gt':
- return (
- typeof fieldValue === 'number' &&
- typeof condition.value === 'number' &&
- fieldValue > condition.value
- );
- case 'lt':
- return (
- typeof fieldValue === 'number' &&
- typeof condition.value === 'number' &&
- fieldValue < condition.value
- );
- case 'regex':
- if (typeof condition.value !== 'string' || !isSafeRegex(condition.value)) return false;
- return typeof fieldValue === 'string' && new RegExp(condition.value).test(fieldValue);
- default:
- return false;
- }
- });
+ switch (condition.operator) {
+ case 'equals':
+ return fieldValue === condition.value;
+ case 'in':
+ return Array.isArray(condition.value) && (condition.value as any[]).includes(fieldValue);
+ case 'like':
+ return (
+ typeof fieldValue === 'string' &&
+ typeof condition.value === 'string' &&
+ fieldValue.includes(condition.value)
+ );
+ case 'gt':
+ return (
+ typeof fieldValue === 'number' &&
+ typeof condition.value === 'number' &&
+ fieldValue > condition.value
+ );
+ case 'lt':
+ return (
+ typeof fieldValue === 'number' &&
+ typeof condition.value === 'number' &&
+ fieldValue < condition.value
+ );
+ case 'regex':
+ if (typeof condition.value !== 'string' || !isSafeRegex(condition.value)) return false;
+ return typeof fieldValue === 'string' && new RegExp(condition.value).test(fieldValue);
+ default:
+ return false;
+ }
});
});
}
-export async function loadBlockListFromRemote(url: string) {
+export async function loadContentFilterFromURL(url: string) {
const resp = await fetch(url, { method: 'GET', credentials: 'omit' });
if (!resp.ok) throw new Error('Response status code');
@@ -101,9 +97,9 @@ export async function loadBlockListFromRemote(url: string) {
if (!respJson) throw new Error('Invalid JSON');
- const parsedBlocklist = zBlocklistRootSchema.safeParse(respJson);
+ const parsedFilterList = zFilterRootSchema.safeParse(respJson);
- if (!parsedBlocklist.success) throw new Error(parsedBlocklist.error.message);
+ if (!parsedFilterList.success) throw new Error(parsedFilterList.error.message);
- blocklistStore.set(parsedBlocklist.data.blocklist);
+ filterContentListStore.set(parsedFilterList.data.filterBy);
}
diff --git a/materialious/src/lib/store.ts b/materialious/src/lib/store.ts
index d699db3b..63e5e3c5 100644
--- a/materialious/src/lib/store.ts
+++ b/materialious/src/lib/store.ts
@@ -23,7 +23,7 @@ import type { ParsedDescription } from './description';
import { ensureNoTrailingSlash, getPublicEnv } from './misc';
import type { EngineFallback } from './api/misc';
import type z from 'zod';
-import type { zBlockListSchema } from './filtering';
+import type { zFilterSchema } from './filtering';
function createListenerFunctions(): {
callListeners: (eventKey: string, newValue: any) => void;
@@ -341,10 +341,10 @@ export const searchHistoryStore: Writable = persist(
'searchHistory'
);
-export const blocklistStore: Writable | undefined> = persist(
+export const filterContentListStore: Writable | undefined> = persist(
writable(),
createStorage(),
- 'blocklist'
+ 'filterContentList'
);
export const feedLoadingStore: Writable = writable(false);