Skip to content

Commit

Permalink
Merge pull request #2031 from hydephp/recreate-the-hydesearch-plugin-…
Browse files Browse the repository at this point in the history
…with-alpine

[2.x] Extract vendor file for the HydeSearch script
  • Loading branch information
caendesilva authored Nov 16, 2024
2 parents 1f4e3d4 + 8b6ab34 commit f814d4f
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 71 deletions.
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ This serves two purposes:
- Moved the Vite build step to run before the site build to prevent duplicate media asset transfers in https://github.com/hydephp/develop/pull/2013
- Ported the HydeSearch plugin used for the documentation search to be an Alpine.js implementation in https://github.com/hydephp/develop/pull/2029
- Renamed Blade component `hyde::components.docs.search-widget` to `hyde::components.docs.search-modal` in https://github.com/hydephp/develop/pull/2029
- Added support for customizing the search implementation by creating a `resources/js/HydeSearch.js` file in https://github.com/hydephp/develop/pull/2031

### Deprecated

Expand Down
78 changes: 78 additions & 0 deletions packages/framework/resources/js/HydeSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
function initHydeSearch(searchIndexUrl) {
return {
searchIndex: [],
searchTerm: '',
results: [],
isLoading: true,
statusMessage: '',

async init() {
const response = await fetch(searchIndexUrl);
if (!response.ok) {
console.error('Could not load search index');
return;
}
this.searchIndex = await response.json();
this.isLoading = false;
},

search() {
const startTime = performance.now();
this.results = [];

if (!this.searchTerm) {
this.statusMessage = '';
window.dispatchEvent(new CustomEvent('search-results-updated', { detail: { hasResults: false } }));
return;
}

const searchResults = this.searchIndex.filter(entry =>
entry.title.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
entry.content.toLowerCase().includes(this.searchTerm.toLowerCase())
);

if (searchResults.length === 0) {
this.statusMessage = 'No results found.';
window.dispatchEvent(new CustomEvent('search-results-updated', { detail: { hasResults: false } }));
return;
}

const totalMatches = searchResults.reduce((acc, result) => {
return acc + (result.content.match(new RegExp(this.searchTerm, 'gi')) || []).length;
}, 0);

searchResults.sort((a, b) => {
return (b.content.match(new RegExp(this.searchTerm, 'gi')) || []).length
- (a.content.match(new RegExp(this.searchTerm, 'gi')) || []).length;
});

this.results = searchResults.map(result => {
const matches = (result.content.match(new RegExp(this.searchTerm, 'gi')) || []).length;
const context = this.getSearchContext(result.content);
return { ...result, matches, context };
});

const timeMs = Math.round((performance.now() - startTime) * 100) / 100;
this.statusMessage = `Found ${totalMatches} result${totalMatches !== 1 ? 's' : ''} in ${searchResults.length} pages. ~${timeMs}ms`;

window.dispatchEvent(new CustomEvent('search-results-updated', { detail: { hasResults: true } }));
},

getSearchContext(content) {
const searchTermPos = content.toLowerCase().indexOf(this.searchTerm.toLowerCase());
const sentenceStart = content.lastIndexOf('.', searchTermPos) + 1;
const sentenceEnd = content.indexOf('.', searchTermPos) + 1;
const sentence = content.substring(sentenceStart, sentenceEnd).trim();
const template = document.getElementById('search-highlight-template');

return sentence.replace(
new RegExp(this.searchTerm, 'gi'),
match => {
const mark = template.content.querySelector('mark').cloneNode();
mark.textContent = match;
return mark.outerHTML;
}
);
}
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@props(['modal' => true])

<div id="hyde-search" x-data="hydeSearch">
<template id="search-highlight-template">
<mark class="bg-yellow-400 dark:bg-yellow-300"></mark>
</template>

<div class="relative">
<input type="search" name="search" id="search-input" x-model="searchTerm" @input="search()" placeholder="Search..." autocomplete="off" autofocus
{{ $attributes->merge(['class' => 'w-full rounded text-base leading-normal bg-gray-100 dark:bg-gray-700 py-2 px-3']) }}
Expand All @@ -27,78 +31,15 @@
</div>

<script>
{!! file_get_contents(file_exists(Hyde::path('resources/js/HydeSearch.js'))
? Hyde::path('resources/js/HydeSearch.js')
: Hyde::vendorPath('resources/js/HydeSearch.js')
) !!}
document.addEventListener('alpine:init', () => {
Alpine.data('hydeSearch', () => ({
searchIndex: [],
searchTerm: '',
results: [],
isLoading: true,
statusMessage: '',
async init() {
const response = await fetch('{{ Hyde::relativeLink(\Hyde\Framework\Features\Documentation\DocumentationSearchIndex::outputPath()) }}');
if (!response.ok) {
console.error('Could not load search index');
return;
}
this.searchIndex = await response.json();
this.isLoading = false;
},
search() {
const startTime = performance.now();
this.results = [];
if (!this.searchTerm) {
this.statusMessage = '';
window.dispatchEvent(new CustomEvent('search-results-updated', { detail: { hasResults: false } }));
return;
}
const searchResults = this.searchIndex.filter(entry =>
entry.title.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
entry.content.toLowerCase().includes(this.searchTerm.toLowerCase())
);
if (searchResults.length === 0) {
this.statusMessage = 'No results found.';
window.dispatchEvent(new CustomEvent('search-results-updated', { detail: { hasResults: false } }));
return;
}
const totalMatches = searchResults.reduce((acc, result) => {
return acc + (result.content.match(new RegExp(this.searchTerm, 'gi')) || []).length;
}, 0);
searchResults.sort((a, b) => {
return (b.content.match(new RegExp(this.searchTerm, 'gi')) || []).length
- (a.content.match(new RegExp(this.searchTerm, 'gi')) || []).length;
});
this.results = searchResults.map(result => {
const matches = (result.content.match(new RegExp(this.searchTerm, 'gi')) || []).length;
const context = this.getSearchContext(result.content);
return { ...result, matches, context };
});
const timeMs = Math.round((performance.now() - startTime) * 100) / 100;
this.statusMessage = `Found ${totalMatches} result${totalMatches !== 1 ? 's' : ''} in ${searchResults.length} pages. ~${timeMs}ms`;
window.dispatchEvent(new CustomEvent('search-results-updated', { detail: { hasResults: true } }));
},
getSearchContext(content) {
const searchTermPos = content.toLowerCase().indexOf(this.searchTerm.toLowerCase());
const sentenceStart = content.lastIndexOf('.', searchTermPos) + 1;
const sentenceEnd = content.indexOf('.', searchTermPos) + 1;
const sentence = content.substring(sentenceStart, sentenceEnd).trim();
return sentence.replace(
new RegExp(this.searchTerm, 'gi'),
match => `<mark class="bg-yellow-400 dark:bg-yellow-300">${match}</mark>`
);
}
}));
Alpine.data('hydeSearch', () =>
initHydeSearch('{{ Hyde::relativeLink(\Hyde\Framework\Features\Documentation\DocumentationSearchIndex::outputPath()) }}')
);
});
</script>
</div>

0 comments on commit f814d4f

Please sign in to comment.