{"version":3,"sources":["useSearch.ts"],"sourcesContent":["// @ts-ignore\nimport { useLocaleInfo } from \"framer\"\nimport { clamp } from \"framer-motion\"\nimport { useEffect, useMemo, useRef, useState, useTransition } from \"react\"\nimport { SearchResultTitleType } from \"./SearchModal.tsx\"\nimport {\n    getCachedIndex,\n    setCachedIndex,\n    isDefaultLocaleId,\n} from \"./cachedIndex.ts\"\nimport { fakeResults } from \"./fakeResults.ts\"\nimport { distance } from \"./fuzzySearch.ts\"\nimport { SearchIndexItem, SearchItem, SearchStatus } from \"./types.ts\"\nimport {\n    createLogger,\n    localStorageDebugFlag,\n    safeDocument,\n    safeWindow,\n    stripLocaleSlugFromPath,\n    yieldToMain,\n} from \"./utils.ts\"\n\nconst { log, time, timeEnd } = createLogger(localStorageDebugFlag)\n\nexport interface SearchIndex {\n    [url: string]: SearchIndexItem\n}\n\ninterface SearchSettings {\n    minimumScore?: number\n    urlScope?: string\n    titleType: SearchResultTitleType\n}\n\nfunction isValidUrl(url: string) {\n    try {\n        new URL(url)\n        return true\n    } catch (_error) {\n        return false\n    }\n}\n\nconst splitWordsRegex = (() => {\n    try {\n        // Regex lookbehind is used to ignore ampersands when splitting\n        // words. For example \"H&M\" will not be split and is considered as\n        // one word, but \"H & M\" will be split.\n        // However, some browsers (like Safari iOS 15) don't support\n        // lookbehind and will crash. When it's not supported, fallback to\n        // a safer regex that always splits ampersands.\n        const regex = /[\\s.,;!?\\p{P}\\p{Z}]+(?<!\\p{L}&)(?!&\\p{L})/u\n        \"\".split(regex)\n        return regex\n    } catch {\n        log(\"Falling back to regex without lookbehind\")\n        return /[\\s.,;!?\\p{P}\\p{Z}]+/u\n    }\n})()\n\nfunction splitWords(text: string) {\n    return text.split(splitWordsRegex)\n}\n\nfunction getUniqueWords(str: string) {\n    const words = splitWords(str).filter(\n        (word) => word.trim() && word.length > 0\n    )\n\n    return new Set(words)\n}\n\nconst normalizeRegex = /[\\u0300-\\u036f]/g\n\n/**\n * Replace accented characters with equivilant non-accented versions and\n * make everything lowercase.\n */\nfunction getNormalizedString(text: string | string[]) {\n    if (Array.isArray(text)) {\n        return text.map(getNormalizedString)\n    }\n\n    return (\n        text\n            .normalize(\"NFD\")\n            // From: https://stackoverflow.com/a/37511463\n            .replace(normalizeRegex, \"\")\n            .toLowerCase()\n    )\n}\n\nconst normalizedItemCache = new WeakMap<SearchIndexItem, SearchIndexItem>()\n\nfunction getNormalizedItemFromCache(item: SearchIndexItem): SearchIndexItem {\n    const cached = normalizedItemCache.get(item)\n    if (cached) return cached\n    const normalizedItem = getNormalizedItem(item)\n    normalizedItemCache.set(item, normalizedItem)\n    return normalizedItem\n}\n\nfunction getNormalizedItem(item: SearchIndexItem) {\n    const normalizedItem = {}\n\n    for (const key in item) {\n        if (item.hasOwnProperty(key)) {\n            const value = item[key]\n\n            if (typeof value === \"string\") {\n                normalizedItem[key] = getNormalizedString(value)\n                continue\n            }\n\n            if (Array.isArray(value)) {\n                normalizedItem[key] = getNormalizedString(value)\n                continue\n            }\n\n            normalizedItem[key] = value\n        }\n    }\n\n    return normalizedItem as SearchIndexItem\n}\n\ninterface MatchRange {\n    start: number\n    end: number\n}\n\ninterface SearchIndexItemMatch {\n    title: MatchRange\n    description: MatchRange\n}\n\nfunction getMatchRange(\n    currentRange: MatchRange,\n    start: number,\n    end: number\n): MatchRange {\n    const result = { ...currentRange }\n\n    if (start < result.start) {\n        result.start = start\n    }\n\n    if (end > result.end) {\n        result.end = end\n    }\n\n    return result\n}\n\n/**\n * Score index item based on the contents of it's fields such as title, description, headings etc.\n *\n * Note that this does not normalize the item or query. Normalization is expected to happen\n * before passing the data into this.\n */\nfunction getScoreForSearchIndexItem(\n    item: SearchIndexItem,\n    query: string,\n    words: Set<string>,\n    fullQuery: string\n): { score: number; match: SearchIndexItemMatch } {\n    let score = 0\n\n    const match: SearchIndexItemMatch = {\n        title: { start: Infinity, end: 0 },\n        description: { start: Infinity, end: 0 },\n    }\n\n    const urlWords = getUniqueWords(item.url)\n\n    // Match query based on words in the URL so that random strings inside\n    // other strings are not matched.\n    if (urlWords.has(query)) {\n        score += 10\n    }\n\n    // Really boost single word queries that match single word URLs.\n    if (\n        words.size === 1 &&\n        urlWords.size === 1 &&\n        urlWords.values().next().value === query\n    ) {\n        score += score * 5\n    }\n\n    // Score shorter URLs higher so `/pricing` is before `/lala/pricing`.\n    if (score > 0) {\n        const splitLength = item.url.split(\"/\").length\n        score += clamp(10 - splitLength, 0, splitLength)\n    }\n\n    const titleWords = getUniqueWords(item.title)\n\n    // Prefer full word matches in the title.\n    if (titleWords.has(query)) {\n        score += 10\n    }\n\n    const titleIndex = item.title.indexOf(query)\n\n    if (titleIndex !== -1) {\n        score += 10\n\n        // TODO: Matches are currently not used, but they can be used in the\n        // future to add text highlighting.\n        match.title = getMatchRange(\n            match.title,\n            titleIndex,\n            titleIndex + query.length\n        )\n    }\n\n    // If the full query is close to being the heading, score this highly as\n    // the user is most likely looking for that exact title.\n    if (distance(item.title, fullQuery) <= 2) {\n        score += score * 10\n    }\n\n    // Fuzzy match full words in the title.\n    for (const titleWord of titleWords) {\n        const distanceScore = distance(query, titleWord)\n\n        // Small distance score helps with small typos.\n        if (distanceScore <= 2) {\n            score += 10\n        }\n    }\n\n    const headings = [\n        ...item.h1,\n        ...item.h2,\n        ...item.h3,\n        ...item.h4,\n        ...item.h5,\n        ...item.h6,\n    ]\n\n    for (const heading of headings) {\n        const headingWords = getUniqueWords(heading)\n\n        // If the full query is close to being the heading, score this highly as\n        // the user is most likely looking for that exact title.\n        if (distance(heading, fullQuery) <= 2) {\n            score += score * 10\n        }\n\n        // Bias headings that start with the query as this helps when\n        // you know the title you are searching for.\n        if (heading.startsWith(query)) {\n            score += 10\n        }\n\n        if (headingWords.has(query)) {\n            score += 10\n        }\n\n        if (heading.includes(query)) {\n            score += 1\n        }\n\n        // Fuzzy match full words in headings.\n        for (const headingWord of headingWords) {\n            const distanceScore = distance(query, headingWord)\n\n            if (distanceScore <= 2) {\n                score += 1\n            }\n        }\n    }\n\n    const descriptionIndex = item.description.indexOf(query)\n\n    if (descriptionIndex !== -1) {\n        score += 10\n\n        match.description = getMatchRange(\n            match.description,\n            descriptionIndex,\n            descriptionIndex + query.length\n        )\n    }\n\n    for (const p of item.p) {\n        if (p.includes(query)) {\n            score += 0.5\n        }\n    }\n\n    for (const codeblock of item.codeblock) {\n        // If the full query is close to being the codeblock, score this highly as\n        // the user is most likely looking for that exact code.\n        if (distance(codeblock, fullQuery) <= 2) {\n            score *= 10\n        }\n\n        if (codeblock.includes(fullQuery)) {\n            score += 10\n        }\n\n        if (codeblock.includes(query)) {\n            score += 0.5\n        }\n    }\n\n    return { score, match }\n}\n\nfunction getSearchIndexItemScore(\n    item: SearchIndexItem,\n    normalizedQuery: string\n): number {\n    const normalizedItem = getNormalizedItemFromCache(item)\n    const queryWords = getUniqueWords(normalizedQuery)\n\n    let total = 0\n\n    for (const queryWord of queryWords) {\n        const { score } = getScoreForSearchIndexItem(\n            normalizedItem,\n            queryWord,\n            queryWords,\n            normalizedQuery\n        )\n\n        total += score\n    }\n\n    return total\n}\n\nfunction useRawSearch(\n    index: SearchIndex,\n    query: string,\n    settings?: SearchSettings\n): { results: SearchItem[] } {\n    const [results, setResults] = useState<SearchItem[] | null>(null)\n    const [, startTransition] = useTransition()\n\n    useEffect(() => {\n        const abortController = new AbortController()\n\n        executeRawSearch(index, query, settings, abortController.signal)\n            .then((res) => {\n                if (!abortController.signal.aborted) {\n                    startTransition(() => {\n                        setResults(res)\n                    })\n                }\n            })\n            .catch((err) => {\n                if (err.name !== \"AbortError\") {\n                    console.error(\"Search failed:\", err)\n                }\n            })\n\n        return () => {\n            abortController.abort()\n        }\n    }, [index, query])\n\n    return { results: results ?? [] }\n}\n\nconst QUANTUM = 32 // ms, 2*16ms (2 frames on 60 hz)\n\nasync function executeRawSearch(\n    index: SearchIndex,\n    query: string,\n    settings?: SearchSettings,\n    signal?: AbortSignal\n) {\n    const path = safeWindow?.location.pathname\n\n    time(\"query\")\n\n    const normalizedQuery = getNormalizedString(query)\n    const results: SearchItem[] = []\n\n    const items = Object.values(index)\n    let deadline = performance.now() + QUANTUM\n\n    async function yieldToMainIfNecessary() {\n        if (performance.now() >= deadline) {\n            await yieldToMain()\n            deadline = performance.now() + QUANTUM\n        }\n    }\n\n    for (let i = 0; i < items.length; ++i) {\n        if (performance.now() >= deadline) {\n            await yieldToMainIfNecessary()\n            deadline = performance.now() + QUANTUM\n        }\n        if (signal?.aborted) return []\n\n        const item = items[i]\n        const score = getSearchIndexItemScore(item, normalizedQuery)\n\n        if (\n            score > (settings.minimumScore || 0) &&\n            (!path || item.url !== path)\n        ) {\n            const heading = item.h1.length && item.h1[0]\n            const title =\n                settings?.titleType === SearchResultTitleType.Title\n                    ? item.title\n                    : heading\n                      ? heading\n                      : item.title\n\n            // Convert index item to result item.\n            results.push({\n                url: item.url,\n                title,\n                description: item.description,\n                body: [...item.p, item.codeblock].join(\" \"),\n                score,\n            })\n        }\n    }\n\n    await yieldToMainIfNecessary()\n    if (signal?.aborted) return []\n    const sorted = results.sort((itemA, itemB) => itemB.score - itemA.score)\n\n    timeEnd(\"query\")\n\n    await yieldToMainIfNecessary()\n    if (signal?.aborted) return []\n\n    return results.slice(0, 20)\n}\n\nfunction getIndexedScopedToUrl(\n    index: SearchIndex,\n    rawUrlScope: string,\n    localeSlug: string | undefined\n): SearchIndex {\n    const scopedIndex = {}\n    const baseScopeUrlHasVariable = rawUrlScope.includes(\":\")\n    const urlUpToPathVariable = rawUrlScope.split(\":\")[0]\n    const urlScope = urlUpToPathVariable.length > 1 ? urlUpToPathVariable : \"\"\n\n    for (const url in index) {\n        const strippedURL = stripLocaleSlugFromPath(url, localeSlug)\n        if (!strippedURL.startsWith(urlScope)) {\n            continue\n        }\n        if (baseScopeUrlHasVariable && url.length <= urlScope.length) {\n            continue\n        }\n\n        scopedIndex[url] = index[url]\n    }\n\n    return scopedIndex\n}\n\ninterface SetSearchIndexOptions {\n    // Disable URL scoping when setting the index.\n    ignoreScope: boolean\n}\n\nexport function useSearch(\n    query: string,\n    settings?: SearchSettings\n): { results: SearchItem[]; status: SearchStatus } {\n    const [searchIndex, _setSearchIndex] = useState<SearchIndex>({})\n    const [status, setStatus] = useState<SearchStatus>(\"loading\")\n    const { results } = useRawSearch(searchIndex, query, settings)\n    const { activeLocale } = useLocaleInfo()\n    const localeId = activeLocale?.id\n\n    // Seperate setter function so that the URL scope is always applied\n    // to indexes loaded from either the cache or network.\n    function setSearchIndex(\n        index: SearchIndex,\n        options: SetSearchIndexOptions = { ignoreScope: false }\n    ) {\n        let scopedIndex = index\n\n        if (settings.urlScope && !options.ignoreScope) {\n            scopedIndex = getIndexedScopedToUrl(\n                index,\n                settings.urlScope,\n                activeLocale?.slug\n            )\n            log(\"Using URL scope\", settings.urlScope)\n        }\n\n        _setSearchIndex(scopedIndex)\n    }\n\n    useEffect(() => {\n        async function loadSearchIndex() {\n            setStatus(\"loading\")\n\n            const metaTag = safeDocument?.querySelector(\n                'meta[name=\"framer-search-index\"]'\n            )\n\n            if (!metaTag) {\n                setStatus(\"no-meta-tag-found\")\n                setSearchIndex(fakeResults, { ignoreScope: true })\n                log(\"No meta tag found\")\n                return\n            }\n\n            const metaTagContent = metaTag.getAttribute(\"content\")\n            const cacheResult = await getCachedIndex(localeId, metaTagContent)\n\n            const isOverLimit = metaTagContent === \"limit-reached\"\n\n            if (isOverLimit) {\n                log(\"Page limit for plan exceeded\")\n            }\n\n            // If a cached index exists, use the cached version until latest one\n            // from the network loads.\n            if (cacheResult.status !== \"miss\" && !isOverLimit) {\n                setSearchIndex(cacheResult.searchIndex)\n                setStatus(\"loading-with-cache\")\n                log(\"Using cached index\")\n\n                if (cacheResult.status === \"fresh\") return\n            }\n\n            // Return early and do not make a fetch request if the URL is not valid.\n            if (!metaTagContent || !isValidUrl(metaTagContent)) {\n                log(\"Meta tag exists but URL is not valid yet\")\n\n                // If there is no cached index, show the pending index message.\n                // Otherwise use the cache to as the index to search.\n                if (cacheResult.status === \"miss\") {\n                    setStatus(\"pending-index-generation\")\n                    log(\"No cache to use\")\n                } else {\n                    log(\"Continue using cache\")\n                }\n\n                return\n            }\n\n            const searchIndexURL = getSearchIndexURL(metaTagContent, localeId)\n            const response = await fetch(searchIndexURL)\n\n            // If the file doesn't exist yet, it might be still generating.\n            if (response.status === 403 || response.status === 404) {\n                log(\"Index not found\")\n\n                // If there is no cached index, show the pending index message.\n                // Otherwise use the cache to as the index to search.\n                if (cacheResult.status === \"miss\") {\n                    setStatus(\"pending-index-generation\")\n                    log(\"No cache to use\")\n                } else {\n                    log(\"Continue using cache\")\n                }\n\n                return\n            } else if (!response.ok) {\n                throw new Error(response.statusText)\n            }\n\n            const downloadedIndex = (await response.json()) as SearchIndex\n\n            setSearchIndex(downloadedIndex)\n            setCachedIndex(localeId, downloadedIndex, metaTagContent)\n            setStatus(\"success\")\n            log(\"Using downloaded index\")\n        }\n\n        loadSearchIndex().catch((error) => {\n            // TODO: Check for error type here. If it's a network error,\n            // we could do a few retries.\n            setStatus(\"error\")\n            log(\"Failed to load search index\", error)\n        })\n    }, [localeId])\n\n    log({ status, results })\n\n    return { results, status }\n}\n\nfunction getSearchIndexURL(baseURL: string, localeId: string | undefined) {\n    if (isDefaultLocaleId(localeId)) return baseURL\n    return baseURL.replace(\".json\", `-${localeId}.json`)\n}\n"],"names":[],"mappings":"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"}