diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..41583e3 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/package.json b/package.json index 66616f0..4d7c19a 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "start": "waku start" }, "dependencies": { + "@tyler/duckhawk": "npm:@jsr/tyler__duckhawk@^0.3.2", "react": "19.0.0", "react-dom": "19.0.0", "react-server-dom-webpack": "19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 634c00a..c81c841 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@tyler/duckhawk': + specifier: npm:@jsr/tyler__duckhawk@^0.3.2 + version: '@jsr/tyler__duckhawk@0.3.2' react: specifier: 19.0.0 version: 19.0.0 @@ -348,6 +351,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jsr/tyler__duckhawk@0.3.2': + resolution: {integrity: sha512-WaPhTKlAJYjROQlMH1UIzNPoZwHNq6o8r9zrPEfgdE6qOjHEqbwkPD3tbgvKtQd3i1PzuKscr2+TXZTT8bgNCQ==, tarball: https://npm.jsr.io/~/11/@jsr/tyler__duckhawk/0.3.2.tgz} + '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} @@ -2284,6 +2290,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jsr/tyler__duckhawk@0.3.2': {} + '@radix-ui/primitive@1.1.1': {} '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.10)(react@19.0.0)': diff --git a/src/_data/feeds.ts b/src/_data/feeds.ts index c245613..6d43a8b 100644 --- a/src/_data/feeds.ts +++ b/src/_data/feeds.ts @@ -1,5 +1,6 @@ import Parser from "rss-parser"; -import { RSSFeed, RSSItem } from "../types"; +import { RSSFeed } from "../types"; +import { allChunked } from "@tyler/duckhawk"; const parser = new Parser(); @@ -15,8 +16,7 @@ const rssFeedUrls = [ "https://francoisbest.com/posts/feed/rss.xml", "https://dzhavat.github.io/feed.xml", "https://bitsofco.de/rss", - "https://www.julian.digital/feed", - "https://joshwcomeau.com/rss.xml", + // "https://joshwcomeau.com/rss.xml", "https://www.premieroctet.com/blog/rss.xml", "https://kyleshevlin.com/rss.xml", "https://simonwillison.net/atom/everything/", @@ -33,55 +33,58 @@ const rssFeedUrls = [ "https://hnblogs.substack.com/feed", "https://romain-clement.net/feed/articles/rss.xml", "https://dzhavat.github.io/feed.xml", - "https://www.julian.digital/feed", + // "https://www.julian.digital/feed", // from lars - "http://feeds.feedburner.com/24ways", - "http://feeds.feedburner.com/2ality", - "http://feeds.feedburner.com/AJAXMagazine", - "http://www.broken-links.com/feed/", - "http://blog.cloudfour.com/feed/", - "http://feeds.feedburner.com/codinghorror", - "http://www.daedtech.com/feed", - "http://feeds.feedburner.com/dropshadows", - "http://gent.ilcore.com/feeds/posts/default", - "http://feeds.feedburner.com/blogspot/amDG", - "http://www.stevesouders.com/blog/feed/", - "http://feeds2.feedburner.com/jsmag", - "http://feeds.feedburner.com/leaverou", - "http://feeds.feedburner.com/lostechies", - "http://feeds.feedburner.com/FunctioningForm", - "http://martinfowler.com/feed.atom", - "http://feeds.feedburner.com/nczonline", - "http://feeds.feedburner.com/nefariousdesigns", - "http://nicolasgallagher.com/feed/", - "http://feeds.feedburner.com/PerfectionKills", - "http://blog.patrickmeenan.com/feeds/posts/default", - "http://www.phpied.com/feed/", - "http://www.quirksmode.org/blog/atom.xml", - "http://feeds.feedburner.com/adobe_developer_center_html5", - "http://royal.pingdom.com/feed/", - "http://rss1.smashingmagazine.com/feed/", - "http://www.standardista.com/feed/", - "http://www.xanthir.com/blog/atom/", - "http://feeds.feedburner.com/WhenCanIUse", - "http://writing.jan.io/feed.xml", - "http://blog.sourcetreeapp.com/feed/", - "http://html5doctor.com/feed/", - "http://feeds.feedburner.com/ScottHanselman", - "http://feeds.feedburner.com/addyosmani", - "https://stackoverflow.blog/newsletter/feed/", - "https://www.webpro.nl/blog/feed.xml", + // "http://feeds.feedburner.com/24ways", + // "http://feeds.feedburner.com/2ality", + // "http://feeds.feedburner.com/AJAXMagazine", + // "http://www.broken-links.com/feed/", + // "http://blog.cloudfour.com/feed/", + // "http://feeds.feedburner.com/codinghorror", + // "http://www.daedtech.com/feed", + // "http://feeds.feedburner.com/dropshadows", + // "http://gent.ilcore.com/feeds/posts/default", + // "http://feeds.feedburner.com/blogspot/amDG", + // "http://www.stevesouders.com/blog/feed/", + // "http://feeds2.feedburner.com/jsmag", + // "http://feeds.feedburner.com/leaverou", + // "http://feeds.feedburner.com/lostechies", + // "http://feeds.feedburner.com/FunctioningForm", + // "http://martinfowler.com/feed.atom", + // "http://feeds.feedburner.com/nczonline", + // "http://feeds.feedburner.com/nefariousdesigns", + // "http://nicolasgallagher.com/feed/", + // "http://feeds.feedburner.com/PerfectionKills", + // "http://blog.patrickmeenan.com/feeds/posts/default", + // "http://www.phpied.com/feed/", + // "http://www.quirksmode.org/blog/atom.xml", + // "http://feeds.feedburner.com/adobe_developer_center_html5", + // "http://royal.pingdom.com/feed/", + // "http://rss1.smashingmagazine.com/feed/", + // "http://www.standardista.com/feed/", + // "http://www.xanthir.com/blog/atom/", + // "http://feeds.feedburner.com/WhenCanIUse", + // "http://writing.jan.io/feed.xml", + // "http://blog.sourcetreeapp.com/feed/", + // "http://html5doctor.com/feed/", + // "http://feeds.feedburner.com/ScottHanselman", + // "http://feeds.feedburner.com/addyosmani", + // "https://stackoverflow.blog/newsletter/feed/", + // "https://www.webpro.nl/blog/feed.xml", ]), ]; -async function getFeedData() { +// not all promises are resolved +export async function getFeedData() { const addedUrls = new Set(); + const feedPromises = rssFeedUrls.map(async (url) => { if (addedUrls.has(url)) { return null; } try { + console.time(`Fetching ${url}`); const feed = await parser.parseURL(url); const items: RSSFeed["items"] = []; const linkSet = new Set(); @@ -95,9 +98,12 @@ async function getFeedData() { description: item.contentSnippet || item.content || "", link: item.link || "", pubDate: item.pubDate || "", + content: item.content || "", + author: item.author || "", }); } - + console.timeEnd(`Fetching ${url}`); + console.log(items.length, "items fetched"); return { title: feed.title || "", description: feed.description || "", @@ -110,11 +116,10 @@ async function getFeedData() { } }); - const results = await Promise.all(feedPromises); + const results = (await allChunked(feedPromises, 10)) as RSSFeed[]; console.log( - results.reduce((acc, cur) => acc + cur!.items.length, 0) + " items fetched" + results.reduce((acc, cur) => acc + (cur?.items.length ?? 0), 0) + + " items fetched" ); return results.filter((feed): feed is RSSFeed => feed !== null); } - -export const feedDataPromise = getFeedData(); diff --git a/src/_lib/search-engine.ts b/src/_lib/search-engine.ts index ef91371..0160765 100644 --- a/src/_lib/search-engine.ts +++ b/src/_lib/search-engine.ts @@ -92,6 +92,8 @@ export class SearchEngine { }); }); + // needs to search Document map + // Convert scores to array and sort const results: SearchResult[] = Object.entries(scores) .map(([docId, score]) => ({ diff --git a/src/components/search.tsx b/src/components/search.tsx index 9aec455..fcf7d50 100644 --- a/src/components/search.tsx +++ b/src/components/search.tsx @@ -3,16 +3,18 @@ import { useState, useEffect, useRef } from "react"; import { Search } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; -import { SearchEngine, SearchIndex, SearchResult } from "../_lib/search-engine"; +import { SearchEngine, SearchResult } from "../_lib/search-engine"; import { useRouter_UNSTABLE } from "waku"; import { ListBox, ListBoxItem, Header, Text } from "react-aria-components"; -const fetchSearchIndex = async (): Promise => { +const fetchSearchIndex = async (): Promise => { const response = await fetch("/api/search.json"); if (!response.ok) { throw new Error("Failed to load search index"); } - return response.json(); + const index = await response.json(); + const engine = new SearchEngine(index); + return engine; }; const ABC = "abcdefghijklmnopqrstuvwxyz"; @@ -27,18 +29,15 @@ export const Searcher = () => { const searchInputRef = useRef(null); const { - data: searchIndex, + data: searchEngine, isLoading, error, } = useQuery({ queryKey: ["searchIndex"], queryFn: fetchSearchIndex, - staleTime: Infinity, // The index doesn't change during a session + staleTime: Infinity, }); - const searchEngine = searchIndex ? new SearchEngine(searchIndex) : null; - - // Perform search when query changes useEffect(() => { if (!searchEngine || query === results.query) { return; diff --git a/src/pages/api/search.json.ts b/src/pages/api/search.json.ts index 1345199..26a6bf6 100644 --- a/src/pages/api/search.json.ts +++ b/src/pages/api/search.json.ts @@ -1,4 +1,4 @@ -import { feedDataPromise } from "../../_data/feeds"; +import { getFeedData } from "../../_data/feeds"; import { RSSFeed } from "../../types"; interface SearchIndex { @@ -129,12 +129,17 @@ function buildSearchIndex(feeds: RSSFeed[]): SearchIndex { // Process title and description to count term frequencies const titleTokens = tokenize(item.title); const descriptionTokens = tokenize(item.description); + const contentTokens = tokenize(item.content); // Count unique terms in this document - const uniqueTerms = new Set([...titleTokens, ...descriptionTokens]); - uniqueTerms.forEach((term) => { + const uniqueTerms = new Set([ + ...titleTokens, + ...descriptionTokens, + ...contentTokens, + ]); + for (const term of uniqueTerms) { termFrequency[term] = (termFrequency[term] || 0) + 1; - }); + } }); }); @@ -183,7 +188,6 @@ function buildSearchIndex(feeds: RSSFeed[]): SearchIndex { }); }); - // Create a lookup table for document retrieval const documentMap: DocumentMap = {}; docIdMap.forEach((location, docId) => { const feed = feeds[location.feedIndex]!; @@ -214,11 +218,11 @@ function buildSearchIndex(feeds: RSSFeed[]): SearchIndex { export const GET = async () => { try { // Get the feed data - const feeds = await feedDataPromise; - + const feeds = await getFeedData(); // Build the search index + console.time("Building search index"); const searchIndex = buildSearchIndex(feeds); - + console.timeEnd("Building search index"); // Return the index as JSON return Response.json(searchIndex); } catch (error) { diff --git a/src/types.ts b/src/types.ts index 839fe3a..09ac583 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,8 @@ export interface RSSFeed { export interface RSSItem { title: string; description: string; + content: string; + author: string; link: string; pubDate: string; } -- 2.51.2 From 1e82fb3d616dfe06d1f3e5ad5e8e2961a83b6623 Mon Sep 17 00:00:00 2001 From: Tyler <26290074+thegitduck@users.noreply.github.com> Date: Tue, 11 Mar 2025 11:51:33 -0700 Subject: [PATCH 2/2] document lookup ready --- package.json | 1 - pnpm-lock.yaml | 429 -------------------------------------- src/_lib/search-engine.ts | 27 ++- src/pages/index.tsx | 2 +- 4 files changed, 23 insertions(+), 436 deletions(-) diff --git a/package.json b/package.json index 4d7c19a..4d7a685 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@tanstack/react-query": "^5.67.2", "@types/react": "19.0.10", "@types/react-dom": "19.0.4", - "cmdk": "^1.0.4", "lucide-react": "^0.479.0", "postcss": "8.5.3", "react-aria-components": "^1.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c81c841..72200f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,9 +36,6 @@ importers: '@types/react-dom': specifier: 19.0.4 version: 19.0.4(@types/react@19.0.10) - cmdk: - specifier: ^1.0.4 - version: 1.0.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) lucide-react: specifier: ^0.479.0 version: 0.479.0(react@19.0.0) @@ -354,168 +351,6 @@ packages: '@jsr/tyler__duckhawk@0.3.2': resolution: {integrity: sha512-WaPhTKlAJYjROQlMH1UIzNPoZwHNq6o8r9zrPEfgdE6qOjHEqbwkPD3tbgvKtQd3i1PzuKscr2+TXZTT8bgNCQ==, tarball: https://npm.jsr.io/~/11/@jsr/tyler__duckhawk/0.3.2.tgz} - '@radix-ui/primitive@1.1.1': - resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} - - '@radix-ui/react-compose-refs@1.1.1': - resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.1.1': - resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.6': - resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.5': - resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.1': - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.2': - resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.0': - resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-portal@1.1.4': - resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.2': - resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.0.2': - resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.1.2': - resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.0': - resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.1.0': - resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.0': - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.0': - resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@react-aria/autocomplete@3.0.0-beta.1': resolution: {integrity: sha512-ZeVR1tKJOZK5/RTuN8eprlP1lyeihdDfDYPBkdg2iT5h775LSZyOingPux9aLtdqt/uj6JIS5amK9ErI7+axug==} peerDependencies: @@ -1476,10 +1311,6 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - aria-hidden@1.2.4: - resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} - engines: {node: '>=10'} - browserslist@4.24.4: resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1502,12 +1333,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cmdk@1.0.4: - resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1534,9 +1359,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} @@ -1598,10 +1420,6 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -1782,26 +1600,6 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.6.3: - resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - react-server-dom-webpack@19.0.0: resolution: {integrity: sha512-hLug9KEXLc8vnU9lDNe2b2rKKDaqrp5gNiES4uyu2Up3FZfZJZmdwLFXlWzdA9gTB/6/cWduSB2K1Lfag2pSvw==} engines: {node: '>=0.10.0'} @@ -1815,16 +1613,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -1924,26 +1712,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - use-sync-external-store@1.4.0: resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} peerDependencies: @@ -2292,141 +2060,6 @@ snapshots: '@jsr/tyler__duckhawk@0.3.2': {} - '@radix-ui/primitive@1.1.1': {} - - '@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.10)(react@19.0.0)': - dependencies: - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-context@1.1.1(@types/react@19.0.10)(react@19.0.0)': - dependencies: - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-dialog@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-context': 1.1.1(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-portal': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.10)(react@19.0.0) - aria-hidden: 1.2.4 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-remove-scroll: 2.6.3(@types/react@19.0.10)(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - - '@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - - '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.10)(react@19.0.0)': - dependencies: - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-focus-scope@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - - '@radix-ui/react-id@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-portal@1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - - '@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - - '@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - - '@radix-ui/react-slot@1.1.2(@types/react@19.0.10)(react@19.0.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.10)(react@19.0.0)': - dependencies: - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - '@react-aria/autocomplete@3.0.0-beta.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@react-aria/combobox': 3.12.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -3812,10 +3445,6 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - aria-hidden@1.2.4: - dependencies: - tslib: 2.8.1 - browserslist@4.24.4: dependencies: caniuse-lite: 1.0.30001702 @@ -3833,18 +3462,6 @@ snapshots: clsx@2.1.1: {} - cmdk@1.0.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@radix-ui/react-dialog': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - use-sync-external-store: 1.4.0(react@19.0.0) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - commander@2.20.3: {} convert-source-map@2.0.0: {} @@ -3859,8 +3476,6 @@ snapshots: detect-libc@1.0.3: {} - detect-node-es@1.1.0: {} - dotenv@16.4.7: {} electron-to-chromium@1.5.112: {} @@ -3928,8 +3543,6 @@ snapshots: gensync@1.0.0-beta.2: {} - get-nonce@1.0.1: {} - glob-to-regexp@0.4.1: {} globals@11.12.0: {} @@ -4133,25 +3746,6 @@ snapshots: react-refresh@0.14.2: {} - react-remove-scroll-bar@2.3.8(@types/react@19.0.10)(react@19.0.0): - dependencies: - react: 19.0.0 - react-style-singleton: 2.2.3(@types/react@19.0.10)(react@19.0.0) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.0.10 - - react-remove-scroll@2.6.3(@types/react@19.0.10)(react@19.0.0): - dependencies: - react: 19.0.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.0.10)(react@19.0.0) - react-style-singleton: 2.2.3(@types/react@19.0.10)(react@19.0.0) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.0.10)(react@19.0.0) - use-sidecar: 1.1.3(@types/react@19.0.10)(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - react-server-dom-webpack@19.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.98.0): dependencies: acorn-loose: 8.4.0 @@ -4191,14 +3785,6 @@ snapshots: '@react-types/shared': 3.28.0(react@19.0.0) react: 19.0.0 - react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0): - dependencies: - get-nonce: 1.0.1 - react: 19.0.0 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.0.10 - react@19.0.0: {} require-from-string@2.0.2: {} @@ -4299,21 +3885,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - use-callback-ref@1.3.3(@types/react@19.0.10)(react@19.0.0): - dependencies: - react: 19.0.0 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.0.10 - - use-sidecar@1.1.3(@types/react@19.0.10)(react@19.0.0): - dependencies: - detect-node-es: 1.1.0 - react: 19.0.0 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.0.10 - use-sync-external-store@1.4.0(react@19.0.0): dependencies: react: 19.0.0 diff --git a/src/_lib/search-engine.ts b/src/_lib/search-engine.ts index 0160765..a7053d1 100644 --- a/src/_lib/search-engine.ts +++ b/src/_lib/search-engine.ts @@ -67,7 +67,7 @@ export class SearchEngine { const scores: Record = {}; // Process each search token - searchTokens.forEach((token) => { + for (const token of searchTokens) { // Look for exact matches if (this.invertedIndex[token]) { const matchingDocs = this.invertedIndex[token]; @@ -79,7 +79,7 @@ export class SearchEngine { } // Look for prefix matches (for partial word search) - Object.keys(this.invertedIndex).forEach((indexedToken) => { + for (const indexedToken of Object.keys(this.invertedIndex)) { if (indexedToken.startsWith(token) && indexedToken !== token) { const matchingDocs = this.invertedIndex[indexedToken]; @@ -89,10 +89,27 @@ export class SearchEngine { scores[docId] = (scores[docId] || 0) + score; } } - }); - }); + } + + // Search directly in document titles and feed titles + for (const docId in this.documentMap) { + const doc = this.documentMap[docId]!; + const titleLower = doc.title.toLowerCase(); + const feedTitleLower = doc.feedTitle.toLowerCase(); + + // Direct title match (highest weight) + if (titleLower.includes(token)) { + const titleScore = 2.0; // Higher weight for title matches + scores[docId] = (scores[docId] || 0) + titleScore; + } - // needs to search Document map + // Feed title match (medium weight) + if (feedTitleLower.includes(token)) { + const feedScore = 1.0; + scores[docId] = (scores[docId] || 0) + feedScore; + } + } + } // Convert scores to array and sort const results: SearchResult[] = Object.entries(scores) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 697bfa1..08c3db2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,4 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { QueryClient } from "@tanstack/react-query"; import { Searcher } from "../components/search"; import { Providers } from "../components/providers";