├── src ├── lib │ ├── components │ │ ├── loading │ │ │ ├── general │ │ │ │ └── main.svelte │ │ │ └── images │ │ │ │ └── main.svelte │ │ ├── preconnect │ │ │ └── main.svelte │ │ ├── header │ │ │ ├── stats.svelte │ │ │ └── main.svelte │ │ ├── results │ │ │ ├── paginator │ │ │ │ ├── main.svelte │ │ │ │ └── number.svelte │ │ │ ├── general │ │ │ │ ├── main.svelte │ │ │ │ └── single.svelte │ │ │ ├── zero │ │ │ │ └── main.svelte │ │ │ ├── images │ │ │ │ ├── single.svelte │ │ │ │ ├── main.svelte │ │ │ │ └── preview.svelte │ │ │ └── infiniteloading │ │ │ │ └── main.svelte │ │ ├── error │ │ │ └── main.svelte │ │ ├── footer │ │ │ └── main.svelte │ │ ├── gadgets │ │ │ ├── timer │ │ │ │ ├── beeper.svelte │ │ │ │ └── main.svelte │ │ │ └── exchange │ │ │ │ └── main.svelte │ │ ├── themetoggle │ │ │ └── main.svelte │ │ └── searchbox │ │ │ └── main.svelte │ ├── index.js │ ├── functions │ │ ├── sleep │ │ │ └── sleep.js │ │ ├── encoding │ │ │ └── base64.js │ │ ├── api │ │ │ ├── concatparams.js │ │ │ ├── exchange.js │ │ │ ├── createurl.js │ │ │ ├── proxyimage.js │ │ │ ├── fetchapiversion.js │ │ │ ├── fetchapi.js │ │ │ ├── additionalresults.js │ │ │ └── fetchapigeneric.js │ │ ├── categories │ │ │ ├── science.js │ │ │ ├── images.js │ │ │ ├── suggestions.js │ │ │ ├── convert.js │ │ │ ├── thorough.js │ │ │ └── web.js │ │ ├── gadgets │ │ │ ├── timer.js │ │ │ └── exchange.js │ │ └── query │ │ │ ├── category.js │ │ │ └── gadgets │ │ │ ├── exchange.js │ │ │ └── timer.js │ ├── types │ │ └── search │ │ │ ├── assert.js │ │ │ ├── categoryenum.js │ │ │ ├── response.js │ │ │ ├── category.js │ │ │ └── result.js │ └── assets │ │ └── logo.svg ├── app.css ├── routes │ ├── healthz │ │ └── +server.js │ ├── +error.svelte │ ├── search │ │ ├── +error.svelte │ │ ├── +page.svelte │ │ └── +page.js │ ├── +layout.svelte │ ├── +layout.js │ ├── +page.svelte │ └── opensearch.xml │ │ └── +server.js ├── index.test.js ├── app.d.ts ├── hooks.server.js └── app.html ├── static ├── robots.txt ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-150x150.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── browserconfig.xml ├── site.webmanifest ├── safari-pinned-tab.svg └── favicon.svg ├── .npmrc ├── .dockerignore ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_search_engine.md │ └── bug_report.md └── workflows │ ├── testingci.yml │ ├── release.yml │ └── dockercicd.yml ├── postcss.config.js ├── .prettierignore ├── .gitignore ├── README.md ├── .prettierrc ├── vite.config.js ├── docker └── Dockerfile ├── playwright.config.js ├── svelte.config.auto.js ├── svelte.config.node.js ├── .env.example ├── SECURITY.md ├── svelte.config.js ├── svelte.config.aws.js ├── eslint.config.js ├── Makefile ├── jsconfig.json ├── tailwind.config.js ├── package.json └── docker-compose.yaml /src/lib/components/loading/general/main.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/components/loading/images/main.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | @jsr:registry=https://npm.jsr.io 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | !docker/ 3 | !build/ 4 | !package.json 5 | !pnpm-lock.yaml -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /src/lib/index.js: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/mstile-150x150.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [hearchco, aleksasiriski] 2 | ko_fi: aleksasiriski 3 | liberapay: hearchco 4 | -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/apple-touch-icon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hearchco/frontend/HEAD/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | docker-compose.yaml -------------------------------------------------------------------------------- /src/routes/healthz/+server.js: -------------------------------------------------------------------------------- 1 | /** @type {import('./$types').RequestHandler} */ 2 | export async function GET() { 3 | return new Response('OK'); 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hearchco frontend repository built using SvelteKit & TailwindCSS 2 | 3 | Open source is hard, maintaining a metasearch engine while corruption kills your country even harder. 4 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/+error.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Not Found | Hearchco 6 | 7 | 8 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/lib/functions/sleep/sleep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} duration - The duration to sleep in milliseconds. 3 | * @returns {Promise} - A promise that resolves after the duration. 4 | */ 5 | export async function sleep(duration) { 6 | await new Promise((r) => setTimeout(r, duration)); 7 | } 8 | -------------------------------------------------------------------------------- /static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/nodejs22-debian12:nonroot 2 | 3 | WORKDIR /app 4 | 5 | ENV NODE_ENV=production 6 | 7 | COPY ["package.json", "build/", "./"] 8 | 9 | CMD [ "index.js" ] 10 | 11 | EXPOSE 3000 12 | 13 | LABEL org.opencontainers.image.source="https://github.com/hearchco/frontend" 14 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@playwright/test').PlaywrightTestConfig} */ 2 | const config = { 3 | webServer: { 4 | command: 'npm run build && npm run preview', 5 | port: 4173 6 | }, 7 | testDir: 'tests', 8 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /src/routes/search/+error.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | API Error | Hearchco 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | - package-ecosystem: 'docker' 8 | directory: '/' 9 | schedule: 10 | interval: 'daily' 11 | - package-ecosystem: 'npm' 12 | directory: '/' 13 | schedule: 14 | interval: 'daily' 15 | -------------------------------------------------------------------------------- /svelte.config.auto.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: vitePreprocess(), 7 | kit: { 8 | adapter: adapter() 9 | }, 10 | compilerOptions: { 11 | runes: true 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /svelte.config.node.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-node'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: vitePreprocess(), 7 | kit: { 8 | adapter: adapter() 9 | }, 10 | compilerOptions: { 11 | runes: true 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PUBLIC_UI_VERSION=dev 2 | PUBLIC_URI=http://localhost:5173 3 | # use local frontend with local backend 4 | API_URI=http://localhost:8000 # server reachable 5 | PUBLIC_API_URI=http://localhost:8000 # client reachable 6 | # use local frontend with official backend (CORS is enabled for http://localhost:5173) 7 | # API_URI=https://api.hearch.co 8 | # PUBLIC_API_URI=https://api.hearch.co 9 | -------------------------------------------------------------------------------- /src/lib/components/preconnect/main.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | We love responsible reports of (potential) security issues in Hearchco. 4 | 5 | You can contact us at [security@hearch.co](mailto:security@hearch.co). 6 | 7 | Be sure to provide as much information as possible and if found 8 | also reproduction steps of the identified vulnerability. Also 9 | add the specific URL of the project as well as code you found 10 | the issue in to your report. 11 | -------------------------------------------------------------------------------- /src/lib/types/search/assert.js: -------------------------------------------------------------------------------- 1 | import { CategoryEnum } from './categoryenum'; 2 | 3 | /** 4 | * Asserts that the given results are images result type using the category. 5 | * @param {WebResultType[] | ImagesResultType[]} results 6 | * @param {CategoryEnum} category 7 | * @returns {results is ImagesResultType[]} 8 | */ 9 | export function assertImagesResultType(results, category) { 10 | return category === CategoryEnum.IMAGES; 11 | } 12 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@hearchco/sveltekit-adapter-aws'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: vitePreprocess(), 7 | kit: { 8 | adapter: adapter({ 9 | edge: false, 10 | stream: false 11 | }) 12 | }, 13 | compilerOptions: { 14 | runes: true 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hearchco", 3 | "short_name": "Hearchco", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffb581", 17 | "background_color": "#ffb581", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /svelte.config.aws.js: -------------------------------------------------------------------------------- 1 | import adapter from '@hearchco/sveltekit-adapter-aws'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | preprocess: vitePreprocess(), 7 | kit: { 8 | adapter: adapter({ 9 | edge: false, 10 | stream: false 11 | }) 12 | }, 13 | compilerOptions: { 14 | runes: true 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_search_engine.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Ask for adding a new feature 4 | title: '[FEAT]' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | **Describe your feature request** 10 | A clear and concise description of what the feature request is. 11 | 12 | **Screenshots** 13 | If applicable, add screenshots to help explain your request. 14 | 15 | **Additional context** 16 | Add any other context about the problem here. 17 | -------------------------------------------------------------------------------- /src/lib/functions/encoding/base64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert any object to it's JSON.stringify() representation and then Base64 encode it. 3 | * @param {object} obj - The object to encode. 4 | * @returns {string} The Base64 encoded object. 5 | */ 6 | export function objectToBase64(obj) { 7 | // Detect whether running in Node.js or browser 8 | if (typeof window === 'undefined') { 9 | // Node.js 10 | return Buffer.from(JSON.stringify(obj)).toString('base64'); 11 | } else { 12 | // Browser 13 | return btoa(JSON.stringify(obj)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/components/header/stats.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |

15 | Hearched {numOfResults} results in {durationString}s 🐹 16 |

17 |
18 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import svelte from 'eslint-plugin-svelte'; 3 | import prettier from 'eslint-config-prettier'; 4 | import globals from 'globals'; 5 | 6 | /** @type {import('eslint').Linter.FlatConfig[]} */ 7 | export default [ 8 | js.configs.recommended, 9 | ...svelte.configs['flat/recommended'], 10 | prettier, 11 | ...svelte.configs['flat/prettier'], 12 | { 13 | languageOptions: { 14 | globals: { 15 | ...globals.browser, 16 | ...globals.node 17 | } 18 | } 19 | }, 20 | { 21 | ignores: ['build/', '.svelte-kit/', 'dist/'] 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | pnpm install --frozen-lockfile 3 | sed -i -E 's|npm:@sveltejs/kit@[^/"]+|@sveltejs/kit|g' ./node_modules/@hearchco/sveltekit-adapter-aws/handler/index.js 4 | 5 | update: 6 | pnpm update 7 | 8 | dev: 9 | pnpm run dev 10 | 11 | compile: 12 | pnpm run build 13 | 14 | preview: 15 | pnpm run preview 16 | 17 | check: 18 | pnpm run check 19 | 20 | test: 21 | pnpm run test:unit 22 | 23 | lint: 24 | pnpm run lint 25 | 26 | format: 27 | pnpm run format 28 | 29 | adapter-aws: 30 | cp svelte.config.aws.js svelte.config.js 31 | 32 | adapter-node: 33 | cp svelte.config.node.js svelte.config.js 34 | 35 | adapter-auto: 36 | cp svelte.config.auto.js svelte.config.js -------------------------------------------------------------------------------- /src/lib/functions/api/concatparams.js: -------------------------------------------------------------------------------- 1 | import { error } from '@sveltejs/kit'; 2 | 3 | /** 4 | * Converts an array of search params into a URLSearchParams object and sorts it. 5 | * @param {string[][]} params - Search params. 6 | * @returns {URLSearchParams} - Sorted search params object. 7 | */ 8 | export function concatSearchParams(params) { 9 | const nonEmptyParams = params.filter((param) => param[1] !== ''); 10 | let paramsObj; 11 | 12 | try { 13 | paramsObj = new URLSearchParams(nonEmptyParams); 14 | } catch (err) { 15 | // Internal Server Error. 16 | throw error(500, `Failed to create URLSearchParams: ${err}`); 17 | } 18 | 19 | paramsObj.sort(); 20 | return paramsObj; 21 | } 22 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |