# Volltextsuche

<!--category-- Postgres, Alpine -->
<datetime class="hidden">2024-08-21T20:30</datetime>

## Einleitung

In der [letzter Artikel](/blog/textsearchingpt1) Ich zeigte Ihnen, wie man eine Volltextsuche mit dem eingebauten Volltext-Suchfunktionen von Postgres-. einrichtete Während ich ein Suchapi freilegte, habe ich keine Möglichkeit, es tatsächlich zu benutzen, also -...] es war ein bisschen ein Tease, . In diesem Artikel zeigt Ihnen IM'®ll, wie Sie die Suche api verwenden, um nach Text in Ihrer Datenbank zu suchen.

Frühere Teile in dieser Serie

- [Volltextsuche mit Postgres](/blog/textsearchingpt1)

Nächste Teile in dieser Serie

- [Einführung in OpenSearch](/blog/textsearchingpt2)
- [Offene Suche mit C-#](/blog/textsearchingpt3)

Dies wird ein kleines Suchfeld zum Header der Website hinzufügen, das Benutzern erlaubt, nach Text in den Blog-Beiträgen zu suchen.

![Suchen](searchbox.png?format=webp&quality=25)

**Hinweis: Der Elefant im Raum ist, dass ich nicht den besten Weg, um dies zu tun betrachten.. Zur Unterstützung von multi--...Sprach ist super komplex. (.I'.d benötigen eine andere Spalte pro Sprache.). und I'.d müssen mit stammenden und anderen sprachspezifischen Dingen umgehen.MSC7.I.SK8.m wird dies für jetzt ignorieren und einfach nur auf Englisch konzentrieren.**

[TOC]

## Suche nach Text

Um eine Suchfunktion hinzuzufügen, musste ich einige Änderungen an der Suche api.. , Ich fügte die Handhabung für Phrasen mit dem `EF.Functions.WebSearchToTsQuery("english", processedQuery)`

```csharp
    private async Task<List<(string Title, string Slug)>> GetSearchResultForQuery(string query)
    {
        var processedQuery = query;
        var posts = await context.BlogPosts
            .Include(x => x.Categories)
            .Include(x => x.LanguageEntity)
            .Where(x =>
                // Search using the precomputed SearchVector
                (x.SearchVector.Matches(EF.Functions.WebSearchToTsQuery("english", processedQuery)) // Use precomputed SearchVector for title and content
                || x.Categories.Any(c =>
                    EF.Functions.ToTsVector("english", c.Name)
                        .Matches(EF.Functions.WebSearchToTsQuery("english", processedQuery)))) // Search in categories
                && x.LanguageEntity.Name == "en")// Filter by language
            
            .OrderByDescending(x =>
                // Rank based on the precomputed SearchVector
                x.SearchVector.Rank(EF.Functions.WebSearchToTsQuery("english", processedQuery))) // Use precomputed SearchVector for ranking
            .Select(x => new { x.Title, x.Slug,  })
            .Take(5)
            .ToListAsync();
        return posts.Select(x=> (x.Title, x.Slug)).ToList();
    }
```

Dies wird optional verwendet, wenn es einen Raum in der Abfrage gibt.

```csharp
    if (!query.Contains(" "))
        {
            posts = await GetSearchResultForComplete(query);
        }
        else
        {
            posts = await GetSearchResultForQuery(query);
        }
```

Ansonsten verwende ich die bestehende Suchmethode, welche das Präfixzeichen anhängt.

```csharp
EF.Functions.ToTsQuery("english", query + ":*")

```

## Suchsteuerung

Verwendung [Alpen-.-js](https://alpinejs.dev/) Ich habe eine einfache Teilsteuerung gemacht, die ein super einfaches Suchfeld zur Verfügung stellt.

```razor
<div x-data="window.mostlylucid.typeahead()" class="relative"    x-on:click.outside="results = []">

    <label class="input input-sm dark:bg-custom-dark-bg bg-white input-bordered flex items-center gap-2">
       
        
        <input
            type="text"
            x-model="query"

            x-on:input.debounce.300ms="search"
            x-on:keydown.down.prevent="moveDown"
            x-on:keydown.up.prevent="moveUp"
            x-on:keydown.enter.prevent="selectHighlighted"
            placeholder="Search..."
            class="border-0 grow  input-sm text-black dark:text-white bg-transparent w-full"/>
        <i class="bx bx-search"></i>
    </label>
    <!-- Dropdown -->
    <ul x-show="results.length > 0"
        class="absolute z-10 my-2 w-full bg-white dark:bg-custom-dark-bg border border-1 text-black dark:text-white border-b-neutral-600 dark:border-gray-300   rounded-lg shadow-lg">
        <template x-for="(result, index) in results" :key="result.slug">
            <li
                x-on:click="selectResult(result)"
                :class="{
                    'dark:bg-blue-dark bg-blue-light': index === highlightedIndex,
                    'dark:hover:bg-blue-dark hover:bg-blue-light': true
                }"
                class="cursor-pointer text-sm p-2 m-2"
                x-text="result.title"
            ></li>
        </template>
    </ul>
</div>
```

Dies hat eine Reihe von CSS-Klassen, um korrekt für entweder dunklen oder hellen Modus zu rendern.Der Alpine.......js-Code ist ziemlich einfach..... Es ist ein einfaches Typeahead-Steuerelement, das die Suche api ruft, wenn der Benutzer im Suchfeld tippt.
Wir haben auch einen kleinen Code, um unfokussiert zu behandeln, um die Suchergebnisse zu schließen.

```html
   x-on:click.outside="results = []"
```

Beachten Sie, dass wir hier einen Debounce haben, um das Hämmern des Servers mit Requests zu vermeiden.

## Der Typeahead JS

Dies ruft in unsere JS-Funktion ein `src/js/main.js`)

```javascript
window.mostlylucid = window.mostlylucid || {};

window.mostlylucid.typeahead = function () {
    return {
        query: '',
        results: [],
        highlightedIndex: -1, // Tracks the currently highlighted index

        search() {
            if (this.query.length < 2) {
                this.results = [];
                this.highlightedIndex = -1;
                return;
            }

            fetch(`/api/search/${encodeURIComponent(this.query)}`)
                .then(response => response.json())
                .then(data => {
                    this.results = data;
                    this.highlightedIndex = -1; // Reset index on new search
                });
        },

        moveDown() {
            if (this.highlightedIndex < this.results.length - 1) {
                this.highlightedIndex++;
            }
        },

        moveUp() {
            if (this.highlightedIndex > 0) {
                this.highlightedIndex--;
            }
        },

        selectHighlighted() {
            if (this.highlightedIndex >= 0 && this.highlightedIndex < this.results.length) {
                this.selectResult(this.results[this.highlightedIndex]);
            }
        },

        selectResult(result) {
           window.location.href = result.url;
            this.results = []; // Clear the results
            this.highlightedIndex = -1; // Reset the highlighted index
        }
    }
}
```

Wie Sie sehen können, ist dies ziemlich einfach (-Much der Komplexität ist das Handling der up und down-Tasten, um Ergebnisse wählen
Diese Beiträge zu unseren `SearchApi`
Wenn ein Ergebnis ausgewählt wird, navigieren wir zur URL des Ergebnisses.

```javascript
     search() {
            if (this.query.length < 2) {
                this.results = [];
                this.highlightedIndex = -1;
                return;
            }

            fetch(`/api/search/${encodeURIComponent(this.query)}`)
                .then(response => response.json())
                .then(data => {
                    this.results = data;
                    this.highlightedIndex = -1; // Reset index on new search
                });
        },
```

### HTMX

Ich änderte auch das holen, um mit HTMX-, zu arbeiten, dies ändert einfach die `search` Methode zur Verwendung eines HTMX refresh-:

```javascript
    selectResult(result) {
    htmx.ajax('get', result.url, {
        target: '#contentcontainer',  // The container to update
        swap: 'innerHTML', // Replace the content inside the target
    }).then(function() {
        history.pushState(null, '', result.url); // Push the new url to the history
    });

    this.results = []; // Clear the results
    this.highlightedIndex = -1; // Reset the highlighted index
    this.query = ''; // Clear the query
}
```

Beachten Sie, dass wir die innerHTML der `contentcontainer` mit dem Ergebnis der Suche Dies ist ein einfacher Weg, um den Inhalt der Seite mit dem Suchergebnis ohne eine Seitenwiederholung zu aktualisieren
Wir ändern auch die URL in der Geschichte zu den neuen url-.

## Schlussfolgerung

Dies fügt eine leistungsstarke und doch einfache Suchfähigkeit auf die Website .. . Es ist ein guter Weg, um Benutzer zu helfen finden, was sie suchen .
Es gibt dieser Website ein professionelleres Gefühl und macht es einfacher, zu navigieren