1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-19 13:19:42 +02:00

enable search for cyrillic letters

This commit is contained in:
Taly 2022-08-15 18:53:57 +03:00
parent bab89c979f
commit cb877398a6
4 changed files with 163 additions and 51 deletions

View file

@ -1,12 +1,6 @@
import Page from '../models/page';
import Pages from '../controllers/pages';
type SearchResponse = {
completions: string[];
pages: Page[];
}
class Search {
/**
* Prepare words database
@ -23,7 +17,7 @@ class Search {
page.body.blocks.forEach((block: any) => {
let blockContent = '';
const validBlocks = ['header', 'paragraph'];
const validBlocks = ['header', 'paragraph', 'list'];
if (!validBlocks.includes(block.type)) {
return;
}
@ -36,17 +30,21 @@ class Search {
case 'paragraph':
blockContent = block.data.text
break;
case 'list':
blockContent = block.data.items.join(' ');
break;
}
const blockWords: string[] = blockContent
// @todo get text from inline code elements and remove html tags
// left only letters and numbers
.replace(/[^a-z0-9]/gi, ' ')
// lowercase all words
.toLowerCase()
// left only letters (+cyrillic) and numbers
.replace(/[^a-zа-я0-9]/gi, ' ')
// remove multiple spaces
.replace(/\s+/g, ' ')
@ -67,7 +65,7 @@ class Search {
return pagesWords;
}
public async query(searchString: string): Promise<SearchResponse> {
public async query(searchString: string) {
const pages = await Pages.getAll();
const pagesWords = await this.index();
@ -95,10 +93,47 @@ class Search {
});
const foundPages = goodPages
.filter(({ successRatio }) => successRatio > 50)
.filter(({ successRatio }) => successRatio > 75)
.sort((a, b) => b.successRatio - a.successRatio)
.slice(0, 10);
const returnPages = pages.filter(page => foundPages.some(({ id }) => id === page._id))
.map(page => {
let shortBody = '...';
let score = 1;
page.body.blocks.forEach((block: any) => {
let blockContent = '';
switch (block.type) {
case 'header':
blockContent = block.data.text;
break;
// case 'paragraph':
// blockContent = block.data.text
// break;
//
// case 'list':
// blockContent = block.data.items.join(' ');
// break;
}
searchWords.forEach(word => {
blockContent = blockContent.replace(word, `<span class="search-word">${word}</span>`);
})
// shortBody += blockContent;
});
return {
...page,
shortBody
};
});
// --------- START test ---------
@ -113,8 +148,8 @@ class Search {
return {
completions: uniqWords.filter(word => word.indexOf(searchWords.slice(-1)[0]) === 0),
pages: pages.filter(page => foundPages.some(({ id }) => id === page._id))
suggestions: uniqWords.filter(word => word.indexOf(searchWords.slice(-1)[0]) === 0),
pages: returnPages
}
}

View file

@ -32,13 +32,14 @@ router.get('/search', async (req: Request, res: Response) => {
uri: page.uri,
// body: page.body,
// parent: page.parent,
shortBody: page.shortBody,
};
});
res.json({
success: true,
result: {
completions: searchResponse.completions,
suggestions: searchResponse.suggestions,
pages: compactedPages,
time: searchItem,
},

View file

@ -2,23 +2,44 @@ import { debounce } from '../utils/decorators';
import Shortcut from '@codexteam/shortcuts';
import axios from 'axios';
export default class Search {
constructor() {
this.nodes = {
overlay: null,
searchWrapper: null,
searchInput: null,
searchResultsWrapper: null
searchResultWrapper: null
};
this.isVisible = false;
this.shortcut = null;
this.TOGGLER_SHORTCUT = 'CMD+SHIFT+F';
this.PLACEHOLDER = 'Find in documents...';
this.TOGGLER_SHORTCUT = 'CMD+SHIFT+F';
this.shortcut = null;
this.DEBOUNCE_TIME = 300;
this.debouncedSearch = null;
this.DEBOUNCE_TIME = 500;
this.MIN_SEARCH_LENGTH = 1;
this.CSS = {
overlay: 'search-overlay',
overlayVisible: 'search-overlay--visible',
searchWrapper: 'search-wrapper',
searchInput: 'search-input',
searchResultWrapper: 'search-result-wrapper',
searchResultSuggestions: 'search-result-suggestions',
searchResultSuggestionItem: 'search-result-suggestions-item',
searchResultItem: 'search-result-item',
searchResultItemTitle: 'search-result-item__title',
searchResultItemDescription: 'search-result-item__description',
blur: 'blurred',
noscroll: 'noscroll'
};
}
init(settings = {}, moduleEl) {
@ -30,27 +51,30 @@ export default class Search {
// ! force open search overlay
// this.toggleSearchOverlay(true);
// const testString = 'api';
// this.nodes.searchInput.value = testString;
// this.debouncedSearch(testString);
}
createSearchOverlay() {
this.nodes.overlay = document.createElement('div');
this.nodes.overlay.classList.add('search-overlay');
this.nodes.overlay.classList.add(this.CSS.overlay);
this.nodes.overlay.addEventListener('click', this.searchOverlayClickProcessor.bind(this));
this.nodes.searchWrapper = document.createElement('div');
this.nodes.searchWrapper.classList.add('search-wrapper');
this.nodes.searchWrapper.classList.add(this.CSS.searchWrapper);
this.nodes.searchInput = document.createElement('input');
this.nodes.searchInput.classList.add('search-input');
this.nodes.searchInput.classList.add(this.CSS.searchInput);
this.nodes.searchInput.setAttribute('type', 'search');
this.nodes.searchInput.setAttribute('placeholder', 'Find in documents...');
this.nodes.searchInput.setAttribute('placeholder', this.PLACEHOLDER);
this.nodes.searchInput.setAttribute('autocomplete', 'off');
this.nodes.searchInput.addEventListener('input', this.searchInputOnchangeProcessor.bind(this));
this.nodes.searchWrapper.appendChild(this.nodes.searchInput);
this.nodes.searchResultsWrapper = document.createElement('div');
this.nodes.searchResultsWrapper.classList.add('search-results-wrapper');
this.nodes.searchWrapper.appendChild(this.nodes.searchResultsWrapper);
this.nodes.searchResultWrapper = document.createElement('div');
this.nodes.searchResultWrapper.classList.add(this.CSS.searchResultWrapper);
this.nodes.searchWrapper.appendChild(this.nodes.searchResultWrapper);
this.nodes.overlay.appendChild(this.nodes.searchWrapper);
document.body.appendChild(this.nodes.overlay);
@ -65,15 +89,14 @@ export default class Search {
}
searchInputOnchangeProcessor(event) {
// close search overlay if ESC key is pressed
if (event.keyCode === 27) {
this.toggleSearchOverlay(false);
event.preventDefault();
const text = event.target.value;
if (text.length < this.MIN_SEARCH_LENGTH) {
this.clearSearchResults();
return;
}
console.log(event.target.value);
this.debouncedSearch(event.target.value);
this.debouncedSearch(text);
}
enableShortcutListening() {
@ -89,12 +112,13 @@ export default class Search {
toggleSearchOverlay(force) {
this.isVisible = force || !this.isVisible;
this.nodes.overlay.classList.toggle('search-overlay--visible', this.isVisible);
document.body.classList.toggle('noscroll', this.isVisible);
this.nodes.overlay.classList.toggle(this.CSS.overlayVisible, this.isVisible);
document.body.classList.toggle(this.CSS.noscroll, this.isVisible);
// blur everything except search overlay
try {
document.getElementsByClassName('docs-header')[0].classList.toggle('blurred', this.isVisible);
document.getElementsByClassName('docs')[0].classList.toggle('blurred', this.isVisible);
document.getElementsByClassName('docs-header')[0].classList.toggle(this.CSS.blurred, this.isVisible);
document.getElementsByClassName('docs')[0].classList.toggle(this.CSS.blurred, this.isVisible);
} catch (e) {}
this.nodes.searchInput.focus();
@ -105,41 +129,74 @@ export default class Search {
}
getSearchResults(text) {
if (!text) {
this.clearSearchResults();
return;
}
axios.get('/api/search', {
params: {
text: text
text
}
})
.then(this.showSearchResult.bind(this));
}
clearSearchResults() {
this.nodes.searchResultsWrapper.innerHTML = '';
this.nodes.searchResultWrapper.innerHTML = '';
}
showSearchResult({ data }) {
this.clearSearchResults();
// const suggestionsWrapper = this.generateSearchSuggestions(data.result.suggestions);
//
// this.nodes.searchResultWrapper.appendChild(suggestionsWrapper);
data.result.pages.forEach(page => {
const result = document.createElement('a');
result.classList.add('search-results-item');
result.classList.add(this.CSS.searchResultItem);
result.setAttribute('href', `/${page.uri}`);
const title = document.createElement('div');
title.classList.add('search-results-item__title');
title.classList.add(this.CSS.searchResultItemTitle);
title.innerHTML = page.title;
result.appendChild(title);
// const description = document.createElement('div');
// description.classList.add('search-results-item__description');
const description = document.createElement('div');
description.classList.add(this.CSS.searchResultItemDescription);
description.innerHTML = `${page.shortBody}`;
// result.appendChild(description);
this.nodes.searchResultsWrapper.appendChild(result);
this.nodes.searchResultWrapper.appendChild(result);
});
}
// generateSearchSuggestions(suggestions = []) {
// const suggestionsWrapper = document.createElement('div');
//
// suggestionsWrapper.classList.add(this.CSS.searchResultSuggestions);
//
// suggestions.forEach(suggestion => {
// const suggestionItem = document.createElement('span');
//
// suggestionItem.classList.add(this.CSS.searchResultSuggestionItem);
// suggestionItem.innerHTML = suggestion;
// suggestionItem.addEventListener('click', this.searchSuggestionClickProcessor.bind(this));
//
// suggestionsWrapper.appendChild(suggestionItem);
// });
//
// return suggestionsWrapper;
// }
//
// searchSuggestionClickProcessor(event) {
// const word = event.target.innerHTML;
//
// const searchString = this.nodes.searchInput.value;
// const searchStringWords = searchString.split(' ');
//
// searchStringWords.pop();
// searchStringWords.push(word);
//
// this.nodes.searchInput.value = searchStringWords.join(' ');
//
// this.debouncedSearch(this.nodes.searchInput.value);
// }
}

View file

@ -44,11 +44,29 @@
}
}
&-results {
&-result {
&-wrapper {
}
&-suggestions {
padding: 5px 25px;
overflow-x: scroll;
&-item {
margin-right: 12px;
padding: 2px 8px;
background: #ccedf6;
border-radius: 50px;
border: 1px solid #b5d8e2;
&:hover {
background: #aad4e0;
}
}
}
&-item {
padding: 15px 25px;
display: block;
@ -66,6 +84,7 @@
}
&__description {
margin-top: 8px;
}
}
}
@ -77,7 +96,7 @@
}
.blurred {
filter: blur(2px);
filter: blur(3px);
}