mirror of
https://github.com/Lissy93/awesome-privacy.git
synced 2026-03-11 08:55:33 +00:00
Fixes remaining lint issues, updates footer date
This commit is contained in:
parent
19ae398e7f
commit
e1c0318d9b
18 changed files with 173 additions and 93 deletions
|
|
@ -1,8 +1,13 @@
|
|||
---
|
||||
const year = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<footer>
|
||||
<a href="/about">Awesome Privacy</a> is licensed under <a
|
||||
href="https://github.com/Lissy93/awesome-privacy/blob/main/LICENSE">MIT</a
|
||||
href="https://github.com/Lissy93/awesome-privacy/blob/main/LICENSE"
|
||||
>CC0 1.0 Universal</a
|
||||
>
|
||||
© <a href="https://aliciasykes.com">Alicia Sykes</a> 2024 | Source code available
|
||||
© <a href="https://aliciasykes.com">Alicia Sykes</a> 2018 - {year || 'Present'} | Source code available
|
||||
on <a href="https://github.com/Lissy93/awesome-privacy">GitHub</a>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,24 +20,31 @@
|
|||
const serviceCrypto = writable(false);
|
||||
const additionalInfo = writable('');
|
||||
|
||||
let codeBlock: any;
|
||||
let codeBlock: HTMLElement | undefined;
|
||||
let interactiveActivated = false;
|
||||
|
||||
$: (yamlText, updateHighlighting());
|
||||
|
||||
/* eslint-disable svelte/no-dom-manipulating -- hljs requires direct DOM access for syntax highlighting */
|
||||
function updateHighlighting() {
|
||||
if (codeBlock) {
|
||||
codeBlock.textContent = yamlText;
|
||||
codeBlock.dataset.highlighted && delete codeBlock.dataset.highlighted;
|
||||
if (window && (window as any).hljs) {
|
||||
(window as any).hljs.highlightElement(codeBlock);
|
||||
const hljs = (
|
||||
window as Window & {
|
||||
hljs?: { highlightElement: (el: HTMLElement) => void };
|
||||
}
|
||||
).hljs;
|
||||
if (hljs) {
|
||||
hljs.highlightElement(codeBlock);
|
||||
interactiveActivated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable svelte/no-dom-manipulating */
|
||||
|
||||
const filterEmptyValues = (obj: Record<string, any>) => {
|
||||
const filteredObj: Record<string, any> = {};
|
||||
const filterEmptyValues = (obj: Record<string, unknown>) => {
|
||||
const filteredObj: Record<string, unknown> = {};
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (obj[key] || ['name', 'url', 'icon', 'description'].includes(key)) {
|
||||
filteredObj[key] = obj[key];
|
||||
|
|
@ -397,6 +404,7 @@
|
|||
upon approval.
|
||||
</p>
|
||||
{#if !interactiveActivated || !codeBlock}
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -- yamlText is generated from user form input via yaml.dump, not arbitrary HTML -->
|
||||
<pre><code class="language-yaml">{@html yamlText}</code></pre>
|
||||
{/if}
|
||||
<pre><code bind:this={codeBlock} class="language-yaml"></code></pre>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
import type { AndroidInfo } from '@utils/fetch-android-info';
|
||||
import { formatDate, timeAgo } from '@utils/dates-n-stuff';
|
||||
import { formatDate } from '@utils/dates-n-stuff';
|
||||
import FontAwesome from '@components/form/FontAwesome.svelte';
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
---
|
||||
import type { DiscordInfo } from '@utils/fetch-discord-info';
|
||||
import { formatDate, timeAgo } from '@utils/dates-n-stuff';
|
||||
import FontAwesome from '@components/form/FontAwesome.svelte';
|
||||
|
||||
interface Props {
|
||||
discordData: DiscordInfo;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
|
||||
import type { IoSApiResponse } from '@utils/fetch-ios-info';
|
||||
import { formatDate, timeAgo } from '@utils/dates-n-stuff';
|
||||
import { formatDate } from '@utils/dates-n-stuff';
|
||||
import FontAwesome from "@components/form/FontAwesome.svelte"
|
||||
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ const makeRatingPercentage = (rating: number) => (rating / 5) * 100;
|
|||
|
||||
const roundRatings = (rating: number) => Math.round(rating * 100) / 100;
|
||||
|
||||
const putCommaInNumber = (num: number | any) => {
|
||||
const putCommaInNumber = (num: number | string | undefined) => {
|
||||
if (!num) return 'Unknown';
|
||||
return typeof num === 'number' ? num.toLocaleString() : num;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,19 @@
|
|||
import FontAwesome from "@components/form/FontAwesome.svelte";
|
||||
const { github } = Astro.props;
|
||||
|
||||
// const [user, repo] = github.split("/");
|
||||
interface GitHubRepoData {
|
||||
stargazers_count?: number;
|
||||
forks_count?: number;
|
||||
open_issues_count?: number;
|
||||
language?: string;
|
||||
license?: { spdx_id?: string; name?: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given `user/repo` fetch repository stats from the GitHub API data
|
||||
* If API key is available through env var, use it to increase rate limit
|
||||
* Returns the response data and status code (200 == success)
|
||||
* @param repo
|
||||
* @param repo
|
||||
*/
|
||||
const fetchGitHubData = async (repo: string) => {
|
||||
const apiKey = import.meta.env.GITHUB_API_KEY;
|
||||
|
|
@ -17,8 +23,8 @@ const fetchGitHubData = async (repo: string) => {
|
|||
headers.append("Authorization", `token ${apiKey}`);
|
||||
}
|
||||
|
||||
let data = {};
|
||||
let statusCode = 0;
|
||||
let data: GitHubRepoData = {};
|
||||
let statusCode;
|
||||
|
||||
const response = await fetch(`https://api.github.com/repos/${repo}`, {
|
||||
headers: headers,
|
||||
|
|
@ -50,7 +56,7 @@ const fetchGitHubData = async (repo: string) => {
|
|||
* Given a license object, return SPDX ID, or a formatted name
|
||||
* @param license
|
||||
*/
|
||||
const formatLicense = (license: { spdx_id?: string, name?: string }) => {
|
||||
const formatLicense = (license?: { spdx_id?: string, name?: string }) => {
|
||||
if (!license) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
|
@ -65,7 +71,8 @@ const formatLicense = (license: { spdx_id?: string, name?: string }) => {
|
|||
* E.g. If greater than thousand, then return in k format
|
||||
* @param num
|
||||
*/
|
||||
const formatBigNumber = (num: number) => {
|
||||
const formatBigNumber = (num: number | undefined) => {
|
||||
if (num == null) return 0;
|
||||
if (num > 1000) {
|
||||
return `${(num / 1000).toFixed(1)}k`;
|
||||
}
|
||||
|
|
@ -73,7 +80,7 @@ const formatBigNumber = (num: number) => {
|
|||
}
|
||||
|
||||
// Initiate GitHub fetch, and make available to the component
|
||||
const stats = (await fetchGitHubData(github)) as any;
|
||||
const stats = await fetchGitHubData(github);
|
||||
|
||||
const {
|
||||
stargazers_count, forks_count, open_issues_count, language, license,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
import type { RedditData } from '@utils/fetch-reddit-info';
|
||||
import { timestampToDate, timeAgo } from '@utils/dates-n-stuff';
|
||||
import FontAwesome from '@components/form/FontAwesome.svelte';
|
||||
import { timestampToDate } from '@utils/dates-n-stuff';
|
||||
|
||||
interface Props {
|
||||
redditData: RedditData;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
<div>
|
||||
{#if $savedServices.length > 0}
|
||||
<div class="saved-services">
|
||||
{#each $savedServices as thingy}
|
||||
{#each $savedServices as thingy (thingy.service.name + thingy.section)}
|
||||
<ServiceCard
|
||||
categoryName={thingy.category}
|
||||
sectionName={thingy.section}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,17 @@
|
|||
import { onMount } from 'svelte';
|
||||
import Fuse from 'fuse.js';
|
||||
import { slugify } from '@utils/fetch-data';
|
||||
import type {
|
||||
Category,
|
||||
Section,
|
||||
Service,
|
||||
ShortService,
|
||||
} from '../../types/Service';
|
||||
import type { Category } from '../../types/Service';
|
||||
import { formatLink } from '@utils/parse-markdown';
|
||||
import { prepareSearchItems, searchOptions } from '@utils/do-searchy-searchy';
|
||||
import type { SearchItem } from '@utils/do-searchy-searchy';
|
||||
|
||||
export let data: Category[];
|
||||
export let previousSearch: string | undefined = undefined;
|
||||
|
||||
let fuse: Fuse<any>;
|
||||
let fuse: Fuse<SearchItem>;
|
||||
let searchQuery = '';
|
||||
let results: any[] = [];
|
||||
let results: SearchItem[];
|
||||
|
||||
// Initialize Fuse.js
|
||||
onMount(() => {
|
||||
|
|
@ -91,7 +87,7 @@
|
|||
{#if searchQuery.length > 0}
|
||||
<div class="suggestions">
|
||||
<ul>
|
||||
{#each results as result}
|
||||
{#each results as result (result.name + result.category + result.sectionName)}
|
||||
<li class="result-row">
|
||||
<a
|
||||
href={makeResultLink(
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ const { service, sectionName, categoryName } = Astro.props;
|
|||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import './service-card.scss';
|
||||
@use './service-card.scss';
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
src={service.icon || `https://icon.horse/icon/${formatLink(service.url)}`}
|
||||
/>
|
||||
<div class="service-body">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -- description is from curated YAML data, not user input -->
|
||||
<p>{@html service.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -64,5 +65,5 @@
|
|||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import './service-card.scss';
|
||||
@use './service-card.scss';
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ interface Props {
|
|||
keywords?: string; // Overide keywords tag
|
||||
hideNav?: boolean; // Don't show the navbar (just homepage)
|
||||
author?: string; // Author of the content
|
||||
customSchemaJson?: any; // Custom schema item
|
||||
customSchemaJson?: Record<string, unknown>; // Custom schema item
|
||||
breadcrumbs?: Array<{
|
||||
name: string;
|
||||
item: string;
|
||||
|
|
@ -121,6 +121,7 @@ const makeSearchLd = () => {
|
|||
{
|
||||
breadcrumbs && (
|
||||
<script
|
||||
is:inline
|
||||
type="application/ld+json"
|
||||
set:html={JSON.stringify(makeBreadcrumbs())}
|
||||
/>
|
||||
|
|
@ -129,12 +130,14 @@ const makeSearchLd = () => {
|
|||
{
|
||||
customSchemaJson && (
|
||||
<script
|
||||
is:inline
|
||||
type="application/ld+json"
|
||||
set:html={JSON.stringify(customSchemaJson)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<script
|
||||
is:inline
|
||||
type="application/ld+json"
|
||||
set:html={JSON.stringify(makeSearchLd)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,25 +12,44 @@ import {
|
|||
appDescription,
|
||||
} from '../site-config';
|
||||
|
||||
const contributorsResource = async () => {
|
||||
interface GitHubContributor {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
interface Sponsor {
|
||||
login: string;
|
||||
avatarUrl: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
const githubHeaders: Record<string, string> = {
|
||||
'User-Agent': 'awesome-privacy',
|
||||
};
|
||||
const apiKey = import.meta.env.GITHUB_API_KEY;
|
||||
if (apiKey) {
|
||||
githubHeaders['Authorization'] = `Bearer ${apiKey}`;
|
||||
}
|
||||
|
||||
const contributorsResource = async (): Promise<GitHubContributor[] | null> => {
|
||||
const url =
|
||||
'https://api.github.com/repos/lissy93/personal-security-checklist/contributors?per_page=100';
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch contributors');
|
||||
}
|
||||
const response = await fetch(url, { headers: githubHeaders });
|
||||
if (!response.ok) return null;
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const sponsorsResource = async () => {
|
||||
const sponsorsResource = async (): Promise<Sponsor[] | null> => {
|
||||
const url = 'https://github-sponsors.as93.workers.dev/lissy93';
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch sponsors');
|
||||
}
|
||||
if (!response.ok) return null;
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const contributors = await contributorsResource();
|
||||
const sponsors = await sponsorsResource();
|
||||
|
||||
const licenseContent = async () => {
|
||||
const url =
|
||||
'https://raw.githubusercontent.com/Lissy93/awesome-privacy/HEAD/LICENSE';
|
||||
|
|
@ -98,10 +117,10 @@ const licenseContent = async () => {
|
|||
<h2 id="acknowledgements">Acknowledgements</h2>
|
||||
<h3 id="sponsors">Sponsors</h3>
|
||||
<p>Huge thanks to the following sponsors, for their ongoing support 💖</p>
|
||||
<div class="user-list">
|
||||
{
|
||||
sponsorsResource().then((sponsors) => {
|
||||
return sponsors.map((sponsor: any) => (
|
||||
{
|
||||
sponsors ? (
|
||||
<div class="user-list">
|
||||
{sponsors.map((sponsor) => (
|
||||
<a
|
||||
class="user"
|
||||
href={`https://github.com/${sponsor.login}`}
|
||||
|
|
@ -111,10 +130,16 @@ const licenseContent = async () => {
|
|||
<img src={sponsor.avatarUrl} alt={sponsor.login} />
|
||||
<p>{sponsor.name || sponsor.login}</p>
|
||||
</a>
|
||||
));
|
||||
})
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
class="fallback-img"
|
||||
src="https://readme-contribs.as93.net/sponsors/lissy93?perRow=12&shape=squircle&textColor=ffffff&limit=96"
|
||||
alt="Sponsors"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<h3 id="contributors">Contributors</h3>
|
||||
<p>
|
||||
|
|
@ -122,23 +147,29 @@ const licenseContent = async () => {
|
|||
maintain it.<br />
|
||||
Special thanks to the below, top-100 contributors 🌟
|
||||
</p>
|
||||
<div class="user-list">
|
||||
{
|
||||
contributorsResource().then((sponsors) => {
|
||||
return sponsors.map((sponsor: any) => (
|
||||
{
|
||||
contributors ? (
|
||||
<div class="user-list">
|
||||
{contributors.map((contributor) => (
|
||||
<a
|
||||
class="user"
|
||||
href={sponsor.html_url}
|
||||
href={contributor.html_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img src={sponsor.avatar_url} alt={sponsor.login} />
|
||||
<p>{sponsor.login}</p>
|
||||
<img src={contributor.avatar_url} alt={contributor.login} />
|
||||
<p>{contributor.login}</p>
|
||||
</a>
|
||||
));
|
||||
})
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
class="fallback-img"
|
||||
src="https://readme-contribs.as93.net/contributors/lissy93/awesome-privacy?perRow=12&shape=squircle&textColor=ffffff&limit=96"
|
||||
alt="Contributors"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<hr />
|
||||
|
||||
|
|
@ -281,7 +312,7 @@ const licenseContent = async () => {
|
|||
<style lang="scss">
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
margin: 2rem 0 1rem 0;
|
||||
margin: 1rem 0 0.5rem 0;
|
||||
}
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
|
|
@ -343,18 +374,25 @@ const licenseContent = async () => {
|
|||
.user-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
gap: 0.25rem;
|
||||
margin: 2rem 0;
|
||||
.user {
|
||||
width: 6rem;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--curve-sm);
|
||||
transition: background 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background: var(--background-form);
|
||||
}
|
||||
img {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: var(--curve-md);
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem auto 0 auto;
|
||||
font-size: 1rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
|
|
@ -364,6 +402,12 @@ const licenseContent = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
.fallback-img {
|
||||
max-width: 100%;
|
||||
margin: 2rem 0;
|
||||
border-radius: var(--curve-sm);
|
||||
}
|
||||
|
||||
.license-content {
|
||||
max-height: 500px;
|
||||
overflow: scroll;
|
||||
|
|
@ -372,7 +416,7 @@ const licenseContent = async () => {
|
|||
border-radius: var(--curve-sm);
|
||||
padding: 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
font-family: mono;
|
||||
font-family: 'Lekton', monospace;
|
||||
max-width: 100vw;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
---
|
||||
import Layout from '@layouts/Layout.astro';
|
||||
import SavedServices from '@components/things/SavedServices.svelte';
|
||||
import GetSharableLink from '@components/things/GetSharableLink.svelte';
|
||||
|
||||
import { fetchData } from '@utils/fetch-data';
|
||||
import Button from '@components/form/Button.astro';
|
||||
import EditableTitle from '@components/form/EditableTitle.svelte';
|
||||
import type { Category } from '../../types/Service';
|
||||
|
||||
const categories = (await fetchData())?.categories || ([] as Category[]);
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@ import Fuse from 'fuse.js';
|
|||
import Layout from '@layouts/Layout.astro';
|
||||
import { fetchData, slugify } from '@utils/fetch-data';
|
||||
import { prepareSearchItems, searchOptions } from '@utils/do-searchy-searchy';
|
||||
import type { SearchItem } from '@utils/do-searchy-searchy';
|
||||
import Search from '@components/things/Search.svelte';
|
||||
import SmartSuggestions from '@components/things/SmartSuggestions.svelte';
|
||||
import FontAwesome from '@components/form/FontAwesome.svelte';
|
||||
|
||||
import type { Service } from '../../types/Service';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
let fuse: Fuse<any>;
|
||||
let fuse: Fuse<SearchItem>;
|
||||
|
||||
const categories = (await fetchData())?.categories;
|
||||
|
||||
|
|
@ -28,26 +27,35 @@ const searchResults = fuse
|
|||
|
||||
const services = searchResults.filter((result) => result.type === 'Service');
|
||||
|
||||
interface GroupedSection {
|
||||
sectionName: string;
|
||||
items: SearchItem[];
|
||||
}
|
||||
|
||||
interface GroupedCategory {
|
||||
categoryName: string;
|
||||
sections: Record<string, GroupedSection>;
|
||||
}
|
||||
|
||||
const putResultsIntoGroups = () => {
|
||||
const grouped = services.reduce((acc, item) => {
|
||||
const { category: categoryName, sectionName, ...service } = item;
|
||||
const grouped: Record<string, GroupedCategory> = {};
|
||||
|
||||
if (!acc[categoryName]) {
|
||||
acc[categoryName] = { categoryName, sections: {} };
|
||||
for (const item of services) {
|
||||
const categoryName = item.category;
|
||||
const sectionName = item.sectionName || '';
|
||||
|
||||
if (!grouped[categoryName]) {
|
||||
grouped[categoryName] = { categoryName, sections: {} };
|
||||
}
|
||||
|
||||
if (!acc[categoryName].sections[sectionName]) {
|
||||
acc[categoryName].sections[sectionName] = { sectionName, items: [] };
|
||||
if (!grouped[categoryName].sections[sectionName]) {
|
||||
grouped[categoryName].sections[sectionName] = { sectionName, items: [] };
|
||||
}
|
||||
|
||||
acc[categoryName].sections[sectionName].items.push(service);
|
||||
grouped[categoryName].sections[sectionName].items.push(item);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Convert the grouped object into the desired array structure.
|
||||
// And fuck it, let's use `any`
|
||||
return Object.values(grouped).map((category: any) => ({
|
||||
return Object.values(grouped).map((category) => ({
|
||||
categoryName: category.categoryName,
|
||||
sections: Object.values(category.sections),
|
||||
}));
|
||||
|
|
@ -74,7 +82,7 @@ const beer = putResultsIntoGroups();
|
|||
</section>
|
||||
<div class="results">
|
||||
{
|
||||
beer.map((category: any) => (
|
||||
beer.map((category) => (
|
||||
<div class="category">
|
||||
<a class="category-title" href={`/${slugify(category.categoryName)}`}>
|
||||
<h3>{category.categoryName}</h3>
|
||||
|
|
@ -83,11 +91,11 @@ const beer = putResultsIntoGroups();
|
|||
<FontAwesome iconName={slugify(category.categoryName)} />
|
||||
</span>
|
||||
<ul class="section-list">
|
||||
{category.sections.map((section: any) => (
|
||||
{category.sections.map((section) => (
|
||||
<li class="section-item">
|
||||
<h4>{section.sectionName}</h4>
|
||||
<ul class="service-list">
|
||||
{section.items.map((item: Service) => (
|
||||
{section.items.map((item) => (
|
||||
<li>
|
||||
<a href={item.url}>{item.name}</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { prepareSearchItems } from './do-searchy-searchy';
|
||||
import type { SearchItem } from './do-searchy-searchy';
|
||||
import type { Category } from '../types/Service';
|
||||
|
||||
const makeCategory = (overrides: Partial<Category> = {}): Category =>
|
||||
|
|
@ -35,7 +36,7 @@ describe('prepareSearchItems', () => {
|
|||
}),
|
||||
] as Category[];
|
||||
const items = prepareSearchItems(categories);
|
||||
const section = items.find((i: { type: string }) => i.type === 'Section');
|
||||
const section = items.find((i: SearchItem) => i.type === 'Section');
|
||||
expect(section).toMatchObject({
|
||||
type: 'Section',
|
||||
sectionName: 'Messaging',
|
||||
|
|
@ -66,7 +67,7 @@ describe('prepareSearchItems', () => {
|
|||
}),
|
||||
] as Category[];
|
||||
const items = prepareSearchItems(categories);
|
||||
const service = items.find((i: { type: string }) => i.type === 'Service');
|
||||
const service = items.find((i: SearchItem) => i.type === 'Service');
|
||||
expect(service).toMatchObject({
|
||||
type: 'Service',
|
||||
name: 'Signal',
|
||||
|
|
@ -99,7 +100,7 @@ describe('prepareSearchItems', () => {
|
|||
}),
|
||||
] as Category[];
|
||||
const items = prepareSearchItems(categories);
|
||||
const cat = items.find((i: { type: string }) => i.type === 'Category');
|
||||
expect(cat.itemCount).toBe(3);
|
||||
const cat = items.find((i: SearchItem) => i.type === 'Category');
|
||||
expect(cat?.itemCount).toBe(3);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
import type { Category } from '../types/Service';
|
||||
|
||||
export const prepareSearchItems = (categories: Category[]) => {
|
||||
const items: any = [];
|
||||
export interface SearchItem {
|
||||
type: 'Category' | 'Section' | 'Service';
|
||||
category: string;
|
||||
itemCount?: number;
|
||||
sectionName?: string;
|
||||
description?: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
github?: string;
|
||||
logo?: string;
|
||||
}
|
||||
|
||||
export const prepareSearchItems = (categories: Category[]): SearchItem[] => {
|
||||
const items: SearchItem[] = [];
|
||||
// Add each category
|
||||
categories.forEach((category) => {
|
||||
items.push({
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ interface Redirection {
|
|||
found: boolean;
|
||||
external: boolean;
|
||||
url: string;
|
||||
redirects: any[];
|
||||
redirects: string[];
|
||||
}
|
||||
|
||||
interface ResponseHeaders {
|
||||
|
|
|
|||
Loading…
Reference in a new issue