feat: add support for semantic search using operand
1 files added
6 files modified
1 files renamed
| | |
| | | content/.obsidian |
| | | assets/indices/linkIndex.json |
| | | assets/indices/contentIndex.json |
| | | linkmap |
| File was renamed from assets/js/search.js |
| | |
| | | } |
| | | const allIds = new Set([...getByField("title"), ...getByField("content")]) |
| | | const finalResults = [...allIds].map(formatForDisplay) |
| | | displayResults(finalResults) |
| | | displayResults(finalResults, true) |
| | | }) |
| | | })() |
| New file |
| | |
| | | const apiKey = "{{$.Site.Data.config.operandApiKey}}" |
| | | |
| | | async function searchContents(query) { |
| | | const response = await fetch('https://prod.operand.ai/v3/search/objects', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | Authorization: apiKey, |
| | | }, |
| | | body: JSON.stringify({ |
| | | query, |
| | | max: 10 |
| | | }), |
| | | }); |
| | | return (await response.json()); |
| | | } |
| | | |
| | | function debounce(func, timeout = 300) { |
| | | let timer; |
| | | return (...args) => { |
| | | clearTimeout(timer) |
| | | timer = setTimeout(() => { func.apply(this, args); }, timeout) |
| | | }; |
| | | } |
| | | |
| | | registerHandlers(debounce((e) => { |
| | | term = e.target.value |
| | | searchContents(term) |
| | | .then((res) => res.results.map(entry => ({ |
| | | url: entry.object.metadata.url, |
| | | content: entry.snippet, |
| | | title: entry.object.title |
| | | }))) |
| | | .then(results => displayResults(results)) |
| | | })) |
| | |
| | | } |
| | | |
| | | // Common utilities for search |
| | | const resultToHTML = ({ url, title, content, term }) => { |
| | | const text = removeMarkdown(content) |
| | | const resultTitle = highlight(title, term) |
| | | const resultText = highlight(text, term) |
| | | const resultToHTML = ({ url, title, content }) => { |
| | | const cleaned = removeMarkdown(content) |
| | | return `<button class="result-card" id="${url}"> |
| | | <h3>${resultTitle}</h3> |
| | | <p>${resultText}</p> |
| | | <h3>${title}</h3> |
| | | <p>${cleaned}</p> |
| | | </button>` |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | const displayResults = (finalResults) => { |
| | | const displayResults = (finalResults, extractHighlight = false) => { |
| | | const results = document.getElementById("results-container") |
| | | if (finalResults.length === 0) { |
| | | results.innerHTML = `<button class="result-card"> |
| | |
| | | </button>` |
| | | } else { |
| | | results.innerHTML = finalResults |
| | | .map((result) => |
| | | resultToHTML({ |
| | | ...result, |
| | | term, |
| | | }), |
| | | .map((result) => { |
| | | if (extractHighlight) { |
| | | return resultToHTML({ |
| | | url: result.url, |
| | | title: highlight(result.title, term), |
| | | content: highlight(result.content, term) |
| | | }) |
| | | } else { |
| | | return resultToHTML(result) |
| | | } |
| | | } |
| | | ) |
| | | .join("\n") |
| | | const anchors = [...document.getElementsByClassName("result-card")] |
| | |
| | | |
| | | # whether to display and 'edit' button next to the last edited field |
| | | # that links to github |
| | | enableGitHubEdit: false |
| | | enableGitHubEdit: true |
| | | GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content |
| | | |
| | | # whether to use Operand to power semantic search |
| | | enableSemanticSearch: true |
| | | operandApiKey: "1e47d93b-1468-45b7-98d5-7f733d5e45e2" |
| | | |
| | | # page description used for SEO |
| | | description: |
| | | Host your second brain and digital garden for free. Quartz features extremely fast full-text search, |
| | |
| | | enableFooter: true |
| | | enableContextualBacklinks: true |
| | | enableRecentNotes: false |
| | | enableGitHubEdit: false |
| | | enableGitHubEdit: true |
| | | GitHubLink: https://github.com/jackyzha0/quartz/tree/hugo/content |
| | | enableSemanticSearch: true |
| | | operandApiKey: "1e47d93b-1468-45b7-98d5-7f733d5e45e2" |
| | | description: |
| | | Host your second brain and digital garden for free. Quartz features extremely fast full-text search, |
| | | Wikilink support, backlinks, local graph, tags, and link previews. |
| | |
| | | {{if $.Site.Data.config.enableGitHubEdit}} |
| | | <a href="{{$.Site.Data.config.GitHubLink}}/{{.Path}}" rel="noopener">Edit Source</a> |
| | | <a href="{{$.Site.Data.config.GitHubLink}}/{{.File.Path}}" rel="noopener">Edit Source</a> |
| | | {{end}} |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | {{if $.Site.Data.config.enableSemanticSearch}} |
| | | {{ $js := resources.Get "js/semantic-search.js" | resources.ExecuteAsTemplate "js/semantic-search.js" . | resources.Fingerprint "md5" | resources.Minify }} |
| | | <script defer src="{{ $js.Permalink }}"></script> |
| | | {{else}} |
| | | <script src="https://cdn.jsdelivr.net/npm/flexsearch@0.7.21/dist/flexsearch.bundle.js" |
| | | integrity="sha256-i3A0NZGkhsKjVMzFxv3ksk0DZh3aXqu0l49Bbh0MdjE=" crossorigin="anonymous" defer></script> |
| | | {{ $js := resources.Get "js/search.js" | resources.Fingerprint "md5" | resources.Minify }} |
| | | {{ $js := resources.Get "js/full-text-search.js" | resources.Fingerprint "md5" | resources.Minify }} |
| | | <script defer src="{{ $js.Permalink }}"></script> |
| | | {{end}} |
| | | |