diff --git a/web/src/app/sok/page.tsx b/web/src/app/sok/page.tsx
index 73d7b07..2405a09 100644
--- a/web/src/app/sok/page.tsx
+++ b/web/src/app/sok/page.tsx
@@ -17,6 +17,8 @@ export default async function Page({
}) {
const { q: query } = (await searchParams) ?? {};
let results: SearchResult[] = [];
+ let totalCount = 0;
+ const RESULT_LIMIT = 500;
if (query) {
const searchQuery = graphql(`
@@ -64,13 +66,21 @@ export default async function Page({
`);
const { data } = await getClient().query(searchQuery, { query });
- results = (data?.results ?? []) as SearchResult[];
+ const all = (data?.results ?? []) as SearchResult[];
+ totalCount = all.length;
+ results = all.slice(0, RESULT_LIMIT);
}
return (
- {query ? : null}
+ {query ? (
+
+ ) : null}
);
diff --git a/web/src/components/search/SearchResults.tsx b/web/src/components/search/SearchResults.tsx
index 380f776..ef44a74 100644
--- a/web/src/components/search/SearchResults.tsx
+++ b/web/src/components/search/SearchResults.tsx
@@ -76,28 +76,64 @@ function getResultSnippet(result: SupportedResult): string | null {
}
}
-export function SearchResults({ results }: { results: SearchResult[] }) {
+function highlight(text: string, query: string): React.ReactNode {
+ if (query.length < 2) return text;
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const pattern = new RegExp(escaped, "gi");
+ const nodes: React.ReactNode[] = [];
+ let lastIndex = 0;
+ for (const match of text.matchAll(pattern)) {
+ if (match.index > lastIndex) {
+ nodes.push(text.slice(lastIndex, match.index));
+ }
+ nodes.push({match[0]});
+ lastIndex = match.index + match[0].length;
+ }
+ if (lastIndex < text.length) {
+ nodes.push(text.slice(lastIndex));
+ }
+ return nodes;
+}
+
+export function SearchResults({
+ results,
+ totalCount,
+ query,
+}: {
+ results: SearchResult[];
+ totalCount: number;
+ query: string;
+}) {
if (!results.length) {
return (
- Ingen resultater
+
Ingen treff på «{query}»
);
}
const supportedResults = results.filter(isSupported);
+ const truncated = totalCount > results.length;
return (
- {results.length} resultater
+ {truncated
+ ? `Viser de første ${results.length} av ${totalCount} treff — prøv et mer spesifikt søk.`
+ : `${results.length} resultater`}
{supportedResults.map((result) => (
-
+
))}
);
}
-function ResultRow({ result }: { result: SupportedResult }) {
+function ResultRow({
+ result,
+ query,
+}: {
+ result: SupportedResult;
+ query: string;
+}) {
const image = getResultImage(result);
const snippet = getResultSnippet(result);
const date = getResultDate(result);
@@ -108,9 +144,11 @@ function ResultRow({ result }: { result: SupportedResult }) {
{resultType}
-
{result.title}
+
{highlight(result.title, query)}
{date &&
{date}
}
- {snippet &&
{snippet}
}
+ {snippet && (
+
{highlight(snippet, query)}
+ )}
{image?.url && (
diff --git a/web/src/components/search/SearchShell.tsx b/web/src/components/search/SearchShell.tsx
index dad6e4d..b59ea01 100644
--- a/web/src/components/search/SearchShell.tsx
+++ b/web/src/components/search/SearchShell.tsx
@@ -22,8 +22,9 @@ export function SearchShell({
}) {
const { replace } = useRouter();
const [inputValue, setInputValue] = useState(initialQuery);
- const [, startTransition] = useTransition();
+ const [isPending, startTransition] = useTransition();
const lastPushedRef = useRef(initialQuery);
+ const fetching = isPending || inputValue !== lastPushedRef.current;
const pushQuery = useDebouncedCallback((next: string) => {
lastPushedRef.current = next;
@@ -73,7 +74,12 @@ export function SearchShell({
- {children}
+
+ {children}
+
);
}
diff --git a/web/src/components/search/searchContainer.module.scss b/web/src/components/search/searchContainer.module.scss
index 810a225..5e76175 100644
--- a/web/src/components/search/searchContainer.module.scss
+++ b/web/src/components/search/searchContainer.module.scss
@@ -5,6 +5,13 @@
a {
text-decoration: none;
}
+
+ mark {
+ background: var(--color-goldenBeige);
+ color: inherit;
+ padding: 0 .05em;
+ border-radius: 2px;
+ }
}
.searchField {
@@ -51,6 +58,16 @@
.snippet {
margin-top: 0.25em;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.fetching {
+ opacity: 0.5;
+ transition: opacity .15s ease;
}
.thumb {