Skip to content

Commit

Permalink
add fancy search (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
swyxio committed Jan 15, 2023
1 parent d3aff63 commit d9fabbf
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 23 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ See https://swyxkit.netlify.app/ (see [Deploy Logs](https://app.netlify.com/site

- Blog Index features (`/blog`)
- Blog index truncates at 20 posts to make sure to render quickly
- Blog search/facets serialize to URLs for easy copy paste
- Blog facets serialize to URLs for easy copy paste
- previously [done by @Ak4zh](https://github.com/sw-yx/swyxkit/pull/97)
- but since moved to [`sveltekit-search-params`](https://github.com/paoloricciuti/sveltekit-search-params) [by @paoloricciuti](https://github.com/sw-yx/swyxkit/pull/140)
- Blog search is [fuzzy and highlights matches](https://swyxkit.netlify.app/ufuzzy-search)
- The Error page feature uses this to send you back to a searchable index
- Error page (try going to URL that doesn't exist)
- Including nice error when GitHub API rate limit exceeded (fix by setting `GH_TOKEN`)
Expand Down
2 changes: 1 addition & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
Cache-Control = "public, max-age=4000"
Cache-Control = "public, max-age=31536000"
# cache just over 1 hour for webpagetest to be happy
X-Content-Type-Options = "nosniff"
## Content-Security-Policy = "default-src 'self'; script-src 'nonce-swyx'; img-src *"
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
},
"type": "module",
"dependencies": {
"@leeoniya/ufuzzy": "^0.9.1",
"date-fns": "^2.29.3",
"parse-link-header": "^2.0.0",
"prism-themes": "^1.9.0",
Expand Down
3 changes: 2 additions & 1 deletion src/lib/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ export async function getContent(providedFetch, slug) {
function youtube_parser(url) {
var rx =
/^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/;
return url.match(rx)[1];
if (url.match(rx)) return url.match(rx)[1];
return url.slice(-11);
}
const videoId = x.startsWith('https://') ? youtube_parser(x) : x;
return `<iframe
Expand Down
2 changes: 1 addition & 1 deletion src/routes/+page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { error } from '@sveltejs/kit';

export const prerender = true; // turned off so it refreshes quickly
// export const prerender = true; // turned off bc it causes errors

export async function load({ setHeaders, fetch }) {
const res = await fetch(`/api/listContent.json`);
Expand Down
74 changes: 55 additions & 19 deletions src/routes/blog/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import IndexCard from '../../components/IndexCard.svelte';
import MostPopular from './MostPopular.svelte';
import uFuzzy from '@leeoniya/ufuzzy'
/** @type {import('./$types').PageData} */
export let data;
Expand All @@ -17,6 +19,7 @@
$: items = data.items;
// https://github.com/paoloricciuti/sveltekit-search-params#how-to-use-it
/** @type import('svelte/store').Writable<String[] | null> */
let selectedCategories = queryParam('show', {
encode: (arr) => arr?.toString(),
decode: (str) => str?.split(',')?.filter((e) => e) ?? []
Expand All @@ -31,25 +34,52 @@
if (e.key === '/' && inputEl) inputEl.select();
}
// https://github.com/leeoniya/uFuzzy#options
const u = new uFuzzy({ intraMode: 1 });
const mark = (part, matched) => matched ? '<b style="color:var(--brand-accent)">' + part + '</b>' : part;
let isTruncated = items?.length > 20;
$: list = items
.filter((item) => {
if ($selectedCategories?.length) {
return $selectedCategories
.map((element) => {
return element.toLowerCase();
})
.includes(item.category.toLowerCase());
}
return true;
})
.filter((item) => {
if ($search) {
return item.title.toLowerCase().includes($search.toLowerCase());
}
return true;
})
.slice(0, isTruncated ? 2 : items.length);
let list
$: {
let filteredItems = items.filter((item) => {
if ($selectedCategories?.length) {
return $selectedCategories
.map((element) => {
return element.toLowerCase();
})
.includes(item.category.toLowerCase());
}
return true;
})
if ($search) {
const haystack = filteredItems.map(v => [v.title, v.subtitle, v.tags, v.content, v.description].join(' '))
let idxs = u.filter(haystack, $search);
let info = u.info(idxs, haystack, $search);
let order = u.sort(info, haystack, $search);
list = order.map(i => {
const x = filteredItems[info.idx[order[i]]]
const hl = uFuzzy.highlight(
haystack[info.idx[order[i]]]
// sanitize html as we dont actually want to render it
.replaceAll("<", " ")
.replaceAll("/>", " ")
.replaceAll(">", " "),
info.ranges[order[i]],
mark
)
// highlight whats left
.slice(Math.max(info.ranges[order[i]][0]-200,0), Math.min(info.ranges[order[i]][1]+200, haystack[info.idx[order[i]]].length))
// slice clean words
.split(' ').slice(1,-1).join(' ')
return {...x, highlightedResults: hl}
})
} else {
list = filteredItems
}
}
// .slice(0, isTruncated ? 2 : items.length);
</script>
<svelte:head>
Expand Down Expand Up @@ -138,7 +168,13 @@
ghMetadata={item.ghMetadata}
{item}
>
{item.description}
{#if item.highlightedResults}
<span class="italic">
{@html item.highlightedResults}
</span>
{:else}
{item.description}
{/if}
</IndexCard>
</li>
{/each}
Expand Down

0 comments on commit d9fabbf

Please sign in to comment.