From ac9e3861e497afd6586b987cb86b66b16dcb39e1 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Wed, 30 Apr 2025 03:50:08 +0300 Subject: [PATCH 1/7] Update dependencies --- package-lock.json | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebd0afcd5..6578f823a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "@freearhey/core": "^0.8.2", "@freearhey/search-js": "^0.1.2", "@sveltejs/adapter-static": "^3.0.8", - "@sveltejs/kit": "^2.20.4", + "@sveltejs/kit": "^2.20.7", "@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/vite": "^4.1.3", "@types/qs": "^6.9.18", @@ -947,9 +947,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.20.4", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.4.tgz", - "integrity": "sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q==", + "version": "2.20.7", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz", + "integrity": "sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg==", "dev": true, "dependencies": { "@types/cookie": "^0.6.0", diff --git a/package.json b/package.json index 932fd2bfd..f1a7be0d7 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@freearhey/core": "^0.8.2", "@freearhey/search-js": "^0.1.2", "@sveltejs/adapter-static": "^3.0.8", - "@sveltejs/kit": "^2.20.4", + "@sveltejs/kit": "^2.20.7", "@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/vite": "^4.1.3", "@types/qs": "^6.9.18", diff --git a/yarn.lock b/yarn.lock index 8c1dff5d2..aab5df121 100644 --- a/yarn.lock +++ b/yarn.lock @@ -157,10 +157,10 @@ resolved "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz" integrity sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg== -"@sveltejs/kit@^2.0.0", "@sveltejs/kit@^2.20.4": - version "2.20.4" - resolved "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.4.tgz" - integrity sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q== +"@sveltejs/kit@^2.0.0", "@sveltejs/kit@^2.20.7": + version "2.20.7" + resolved "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz" + integrity sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg== dependencies: "@types/cookie" "^0.6.0" cookie "^0.6.0" From 24e13c482ce6ec126bdecbc6dc3bbbf327f6a4ea Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Wed, 30 Apr 2025 03:50:28 +0300 Subject: [PATCH 2/7] Update tests/__data__ --- tests/__data__/input/feeds.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/__data__/input/feeds.json b/tests/__data__/input/feeds.json index f1f4049eb..ee63822ce 100644 --- a/tests/__data__/input/feeds.json +++ b/tests/__data__/input/feeds.json @@ -49,8 +49,8 @@ }, { "channel": "13MaxTelevision.ar", - "id": "SD", - "name": "SD", + "id": "Panregional", + "name": "Panregional", "is_main": true, "broadcast_area": [ "s/AR-W" From e411cec5450420f10473f4fc4d3f8d6da1598fa4 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Wed, 30 Apr 2025 03:50:36 +0300 Subject: [PATCH 3/7] Update tests --- tests/core/searchEngine.test.js | 340 ++++++++++++++++++++++++++++++ tests/store.test.js | 361 -------------------------------- 2 files changed, 340 insertions(+), 361 deletions(-) create mode 100644 tests/core/searchEngine.test.js delete mode 100644 tests/store.test.js diff --git a/tests/core/searchEngine.test.js b/tests/core/searchEngine.test.js new file mode 100644 index 000000000..17ce43612 --- /dev/null +++ b/tests/core/searchEngine.test.js @@ -0,0 +1,340 @@ +import { ApiClient, DataProcessor, DataLoader, SearchEngine } from '../../src/core' +import { expect, it, describe, beforeEach } from 'vitest' +import AxiosMockAdapter from 'axios-mock-adapter' +import axios from 'axios' +import path from 'path' +import fs from 'fs' + +const searchEngine = new SearchEngine() + +beforeEach(async () => { + const client = new ApiClient() + const processor = new DataProcessor() + const dataLoader = new DataLoader({ client, processor }) + + client.instance = axios.create({ + baseURL: 'https://iptv-org.github.io/api' + }) + + const mockAxios = new AxiosMockAdapter(client.instance) + + mockAxios.onGet(`categories.json`).reply(200, loadJson('categories.json')) + mockAxios.onGet(`countries.json`).reply(200, loadJson('countries.json')) + mockAxios.onGet(`languages.json`).reply(200, loadJson('languages.json')) + mockAxios.onGet(`blocklist.json`).reply(200, loadJson('blocklist.json')) + mockAxios.onGet(`timezones.json`).reply(200, loadJson('timezones.json')) + mockAxios.onGet(`channels.json`).reply(200, loadJson('channels.json')) + mockAxios.onGet(`regions.json`).reply(200, loadJson('regions.json')) + mockAxios.onGet(`streams.json`).reply(200, loadJson('streams.json')) + mockAxios.onGet(`guides.json`).reply(200, loadJson('guides.json')) + mockAxios.onGet(`feeds.json`).reply(200, loadJson('feeds.json')) + mockAxios.onGet(`subdivisions.json`).reply(200, loadJson('subdivisions.json')) + + const data = await dataLoader.load() + const searchableData = data.channels.map(channel => channel.getSearchable()) + + searchEngine.createIndex(searchableData) +}) + +describe('search', () => { + it('returns empty list if there is no such channel', () => { + let results = searchEngine.search('lorem') + + expect(results.count()).toBe(0) + }) + + it('can find channel by name', () => { + let results = searchEngine.search('name:002') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + }) + + it('can find channels by multiple words', () => { + let results = searchEngine.search('Xtrema Cartoons') + + expect(results.count()).toBe(2) + expect(results.first()).toMatchObject({ + id: 'XtremaCartoons.ar' + }) + expect(results.all()[1]).toMatchObject({ + id: 'XtremaRetroCartoons.ar' + }) + }) + + it('can search for one of two words', () => { + let results = searchEngine.search('Johannesburg,002') + + expect(results.count()).toBe(2) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + expect(results.all()[1]).toMatchObject({ + id: 'FashionTVJohannesburg.fr' + }) + }) + + it('can search for exact word matches', () => { + let results = searchEngine.search('"Xtrema Cartoons"') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'XtremaCartoons.ar' + }) + }) + + it('can find channels by id', () => { + let results = searchEngine.search('id:002RadioTV.do') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + }) + + it('can find channels by feed name', () => { + let results = searchEngine.search('Panregional') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '13MaxTelevision.ar' + }) + }) + + it('can find channels by alternative names', () => { + let results = searchEngine.search('alt_names:التلفزيون') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'TV1.dz' + }) + }) + + it('can find channels by network', () => { + let results = searchEngine.search('network:Hope') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'K11UUD1.as' + }) + }) + + it('can find channels without the owner', () => { + let results = searchEngine.search('owners:^$') + + expect(results.count()).toBe(7) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + }) + + it('can find channels by country code', () => { + let results = searchEngine.search('country:DO') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + }) + + it('can find channels that are broadcast from the same region', () => { + let results = searchEngine.search('subdivision:AR-W') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '13MaxTelevision.ar' + }) + }) + + it('can find channels that are broadcast from the same city', () => { + let results = searchEngine.search('city:Corrientes') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '13MaxTelevision.ar' + }) + }) + + it('can find channels that have the same category', () => { + let results = searchEngine.search('categories:lifestyle') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'FashionTVJohannesburg.fr' + }) + }) + + it('can find channels with website', () => { + let results = searchEngine.search('website:.') + + expect(results.count()).toBe(14) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + }) + + it('can find channels marked as NSFW', () => { + let results = searchEngine.search('is_nsfw:true') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'Bizarre.al' + }) + }) + + it('can find closed channels', () => { + let results = searchEngine.search('is_closed:true') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'AynaTV.af' + }) + }) + + it('can find blocked channels', () => { + let results = searchEngine.search('is_blocked:true') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'Bizarre.al' + }) + }) + + it('can find channels by query in lower case', () => { + let results = searchEngine.search('tv2') + + expect(results.count()).toBe(2) + expect(results.first()).toMatchObject({ + id: 'SEN502.us' + }) + expect(results.all()[1]).toMatchObject({ + id: 'CFCNTV2.ca' + }) + }) + + it('can find channel by alternative name after another query', () => { + searchEngine.search('tv2') + let results = searchEngine.search('alt_names:tv2') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'SEN502.us' + }) + }) + + it('can find channels that have streams', () => { + let results = searchEngine.search('streams:>0') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'XtremaCartoons.ar' + }) + }) + + it('can find channels that have guides', () => { + let results = searchEngine.search('guides:>0') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'LaLiganaZap.ao' + }) + }) + + it('can find channel by country name', () => { + let results = searchEngine.search('"dominican republic"') + + expect(results.count()).toBe(3) + expect(results.first()).toMatchObject({ + id: '002RadioTV.do' + }) + }) + + it('can find channel by display name from the guides', () => { + let results = searchEngine.search('La Liga HD') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'LaLiganaZap.ao' + }) + }) + + it('can find channel by stream url', () => { + let results = searchEngine.search( + 'https://stmv6.voxtvhd.com.br/xtremacartoons/xtremacartoons/playlist.m3u8' + ) + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'XtremaCartoons.ar' + }) + }) + + it('can find channels by broadcast area code', () => { + let results = searchEngine.search('broadcast_area:s/AR-W') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: '13MaxTelevision.ar' + }) + }) + + it('can find channel by broadcast location code', () => { + let results = searchEngine.search('eur') + + expect(results.count()).toBe(2) + expect(results.first()).toMatchObject({ + id: 'Bizarre.al' + }) + }) + + it('can find channel by broadcast location name', () => { + let results = searchEngine.search('europe') + + expect(results.count()).toBe(2) + expect(results.first()).toMatchObject({ + id: 'Bizarre.al' + }) + }) + + it('can find channels by exact language code', () => { + let results = searchEngine.search('language:fra') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'SEN502.us' + }) + }) + + it('can find channels by language name', () => { + let results = searchEngine.search('french') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'SEN502.us' + }) + }) + + it('can find channels by video format', () => { + let results = searchEngine.search('video_format:576i') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'Bizarre.al' + }) + }) + + it('can find channels by timezone id', () => { + let results = searchEngine.search('timezone:Europe/London') + + expect(results.count()).toBe(1) + expect(results.first()).toMatchObject({ + id: 'Bizarre.al' + }) + }) +}) + +function loadJson(filepath) { + return JSON.parse(fs.readFileSync(path.resolve('tests/__data__/input/', filepath), 'utf8')) +} diff --git a/tests/store.test.js b/tests/store.test.js deleted file mode 100644 index 7f83ff4aa..000000000 --- a/tests/store.test.js +++ /dev/null @@ -1,361 +0,0 @@ -import { loadData, search, searchResults } from '../src/store' -import { expect, it, describe, beforeEach, afterEach, vi } from 'vitest' -import { get } from 'svelte/store' -import path from 'path' -import fs from 'fs' -import AxiosMockAdapter from 'axios-mock-adapter' -import axios from 'axios' -import { ApiClient, DataProcessor } from '../src/core' - -beforeEach(async () => { - const client = new ApiClient() - const processor = new DataProcessor() - - client.instance = axios.create({ - baseURL: 'https://iptv-org.github.io/api' - }) - - const mockAxios = new AxiosMockAdapter(client.instance) - - mockAxios.onGet(`categories.json`).reply(200, loadJson('categories.json')) - mockAxios.onGet(`countries.json`).reply(200, loadJson('countries.json')) - mockAxios.onGet(`languages.json`).reply(200, loadJson('languages.json')) - mockAxios.onGet(`blocklist.json`).reply(200, loadJson('blocklist.json')) - mockAxios.onGet(`timezones.json`).reply(200, loadJson('timezones.json')) - mockAxios.onGet(`channels.json`).reply(200, loadJson('channels.json')) - mockAxios.onGet(`regions.json`).reply(200, loadJson('regions.json')) - mockAxios.onGet(`streams.json`).reply(200, loadJson('streams.json')) - mockAxios.onGet(`guides.json`).reply(200, loadJson('guides.json')) - mockAxios.onGet(`feeds.json`).reply(200, loadJson('feeds.json')) - mockAxios.onGet(`subdivisions.json`).reply(200, loadJson('subdivisions.json')) - - await loadData({ client, processor }) -}) - -describe('search', () => { - it('return all channels by default', () => { - const results = get(searchResults).all() - expect(results.length).toBe(15) - }) - - it('returns empty list if there is no such channel', () => { - search('lorem') - - const results = get(searchResults).all() - expect(results.length).toBe(0) - }) - - it('can find channel by name', () => { - search('name:002') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - }) - - it('can find channels by multiple words', () => { - search('Xtrema Cartoons') - - const results = get(searchResults).all() - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - id: 'XtremaCartoons.ar' - }) - expect(results[1]).toMatchObject({ - id: 'XtremaRetroCartoons.ar' - }) - }) - - it('can search for one of two words', () => { - search('Johannesburg,002') - - const results = get(searchResults).all() - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - expect(results[1]).toMatchObject({ - id: 'FashionTVJohannesburg.fr' - }) - }) - - it('can search for exact word matches', () => { - search('"Xtrema Cartoons"') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'XtremaCartoons.ar' - }) - }) - - it('can find channels by id', () => { - search('id:002RadioTV.do') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - }) - - it('can find channels by alternative names', () => { - search('alt_names:التلفزيون') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'TV1.dz' - }) - }) - - it('can find channels by network', () => { - search('network:Hope') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'K11UUD1.as' - }) - }) - - it('can find channels without the owner', () => { - search('owners:^$') - - const results = get(searchResults).all() - expect(results.length).toBe(7) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - }) - - it('can find channels by country code', () => { - search('country:DO') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - }) - - it('can find channels that are broadcast from the same region', () => { - search('subdivision:AR-W') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: '13MaxTelevision.ar' - }) - }) - - it('can find channels that are broadcast from the same city', () => { - search('city:Corrientes') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: '13MaxTelevision.ar' - }) - }) - - it('can find channels that have the same category', () => { - search('categories:lifestyle') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'FashionTVJohannesburg.fr' - }) - }) - - it('can find channels with website', () => { - search('website:.') - - const results = get(searchResults).all() - expect(results.length).toBe(14) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - }) - - it('can find channels marked as NSFW', () => { - search('is_nsfw:true') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'Bizarre.al' - }) - }) - - it('can find closed channels', () => { - search('is_closed:true') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'AynaTV.af' - }) - }) - - it('can find blocked channels', () => { - search('is_blocked:true') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'Bizarre.al' - }) - }) - - it('can find channels by query in lower case', () => { - search('tv2') - - const results = get(searchResults).all() - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - id: 'SEN502.us' - }) - expect(results[1]).toMatchObject({ - id: 'CFCNTV2.ca' - }) - }) - - it('can find channel by alternative name after another query', () => { - search('tv2') - search('alt_names:tv2') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'SEN502.us' - }) - }) - - it('can find channels that have streams', () => { - search('streams:>0') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'XtremaCartoons.ar' - }) - }) - - it('can find channels that have guides', () => { - search('guides:>0') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'LaLiganaZap.ao' - }) - }) - - it('can find channel by country name', () => { - search('"dominican republic"') - - const results = get(searchResults).all() - expect(results.length).toBe(3) - expect(results[0]).toMatchObject({ - id: '002RadioTV.do' - }) - }) - - it('can find channel by display name from the guides', () => { - search('La Liga HD') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'LaLiganaZap.ao' - }) - }) - - it('can find channel by stream url', () => { - search('https://stmv6.voxtvhd.com.br/xtremacartoons/xtremacartoons/playlist.m3u8') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'XtremaCartoons.ar' - }) - }) - - it('can find channels by broadcast area code', () => { - search('broadcast_area:s/AR-W') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: '13MaxTelevision.ar' - }) - }) - - it('can find channel by broadcast location code', () => { - search('eur') - - const results = get(searchResults).all() - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - id: 'Bizarre.al' - }) - }) - - it('can find channel by broadcast location name', () => { - search('europe') - - const results = get(searchResults).all() - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - id: 'Bizarre.al' - }) - }) - - it('can find channels by exact language code', () => { - search('language:fra') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'SEN502.us' - }) - }) - - it('can find channels by language name', () => { - search('french') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'SEN502.us' - }) - }) - - it('can find channels by video format', () => { - search('video_format:576i') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'Bizarre.al' - }) - }) - - it('can find channels by timezone id', () => { - search('timezone:Europe/London') - - const results = get(searchResults).all() - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - id: 'Bizarre.al' - }) - }) -}) - -function loadJson(filepath) { - return JSON.parse(fs.readFileSync(path.resolve('tests/__data__/input/', filepath), 'utf8')) -} From 2a893a827bd7963acb97a727ca7dbdafdad740c8 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Wed, 30 Apr 2025 04:06:54 +0300 Subject: [PATCH 4/7] Update src/ --- src/components/BottomBar.svelte | 8 +- src/components/Button.svelte | 4 +- src/components/ChannelMenu.svelte | 23 ++++ src/components/ChannelPopup.svelte | 24 +--- src/components/DownloadButton.svelte | 4 +- src/components/FeedAddButton.svelte | 6 +- src/components/FeedAddIconButton.svelte | 28 ++++ src/components/FeedItem.svelte | 30 +--- src/components/FeedMenu.svelte | 30 ++++ src/components/FeedPopup.svelte | 11 +- src/components/GuidesPopup.svelte | 6 +- src/components/HTMLPreview.svelte | 128 +++++++++--------- src/components/IconButton.svelte | 8 +- src/components/Menu.svelte | 4 +- src/components/ResetButton.svelte | 4 +- src/components/SelectAllButton.svelte | 6 +- src/components/StreamAddButton.svelte | 29 ++++ src/components/StreamAddIconButton.svelte | 28 ++++ src/components/StreamEditButton.svelte | 29 ++++ src/components/StreamItem.svelte | 16 +-- src/components/StreamMenu.svelte | 23 ++++ src/components/StreamReportButton.svelte | 29 ++++ src/components/StreamsPopup.svelte | 11 +- src/components/index.ts | 8 ++ src/icons/AddCircle.svelte | 19 +++ src/icons/Alert.svelte | 16 +++ src/icons/index.ts | 2 + src/models/channel.ts | 7 +- src/models/feed.ts | 4 + src/models/stream.ts | 48 +++---- .../channels/[country]/[name]/+page.svelte | 6 +- src/types/channel.d.ts | 2 + src/types/htmlPreviewField.ts | 1 + 33 files changed, 416 insertions(+), 186 deletions(-) create mode 100644 src/components/ChannelMenu.svelte create mode 100644 src/components/FeedAddIconButton.svelte create mode 100644 src/components/FeedMenu.svelte create mode 100644 src/components/StreamAddButton.svelte create mode 100644 src/components/StreamAddIconButton.svelte create mode 100644 src/components/StreamEditButton.svelte create mode 100644 src/components/StreamMenu.svelte create mode 100644 src/components/StreamReportButton.svelte create mode 100644 src/icons/AddCircle.svelte create mode 100644 src/icons/Alert.svelte diff --git a/src/components/BottomBar.svelte b/src/components/BottomBar.svelte index 5d951b926..52c38d8c4 100644 --- a/src/components/BottomBar.svelte +++ b/src/components/BottomBar.svelte @@ -13,14 +13,14 @@ {$selected.count()} selected
-
- {field.name}
-
- |
-
-
- {#if field.type === 'image'}
-
+
- {#each field.value as value, i}
- {#if i > 0},
- {/if}
+ {#if field}
+
+ {:else if field.type === 'link[]'}
+
+ |
+
+ {field.name}
+
+
+ |
-
+ {#if field.type === 'image'}
+
-
- {value.label}
+ {field.value.label}
- {/each}
-
- {:else if field.type === 'external_link'}
-
- {:else if field.name === 'id'}
- {field.value}
- {:else if field.type === 'string[]'}
-
- {#each field.value as value, i}
- {#if i > 0},
- {/if}
- {value}
- {/each}
-
- {:else if field.type === 'string'}
- {field.value}
- {/if}
-
+ {#each field.value as value, i}
+ {#if i > 0},
+ {/if}
+
+ {value.label}
+
+ {/each}
+
+ {:else if field.type === 'external_link'}
+
+ {:else if field.name === 'id'}
+ {field.value}
+ {:else if field.type === 'string[]'}
+
+ {#each field.value as value, i}
+ {#if i > 0},
+ {/if}
+ {value}
+ {/each}
+
+ {:else if field.type === 'string'}
+ {field.value}
+ {/if}
+ |
+