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

add comments

This commit is contained in:
Taly 2022-09-09 19:12:27 +03:00
parent a9dd65c964
commit 488d825c03
5 changed files with 125 additions and 52 deletions

View file

@ -1,22 +1,29 @@
import NodeCache from 'node-cache';
import PageData from '../models/page.js'; import PageData from '../models/page.js';
import Pages from '../controllers/pages.js'; import Pages from '../controllers/pages.js';
import urlify from '../utils/urlify.js'; import urlify from '../utils/urlify.js';
import Page from '../models/page.js'; import Page from '../models/page.js';
const globalWords: { [key: string]: {[key: string]: number} } = Object.create(null); let globalWords: { [key: string]: {[key: string]: number} } = Object.create(null);
let globalPages: PageData[] = []; let globalPages: PageData[] = [];
class Search { class Search {
// private words: { [key: string]: {[key: string]: number} } = Object.create(null); /**
// private pages: PageData[] = []; * Initialize search
// private cache: NodeCache = new NodeCache(); */
public async init() { public async init() {
if (globalWords && Object.keys(globalWords).length) { if (globalWords && Object.keys(globalWords).length) {
return Promise.resolve(); return Promise.resolve();
} }
await this.syncDB();
}
/**
* Load all pages from DB and update globalWords
* Use this method when any page was updated
*/
public async syncDB() {
globalWords = Object.create(null);
globalPages = await this.getPages(); globalPages = await this.getPages();
/** /**
@ -56,6 +63,10 @@ class Search {
console.log('Done'); console.log('Done');
} }
/**
* Search for pages by given query
* @param searchString
*/
public async query(searchString: string) { public async query(searchString: string) {
await this.init(); await this.init();
@ -107,41 +118,40 @@ class Search {
}); });
return { return {
suggestions: [], suggestions: ['description', 'about', 'contact'],
pages: returnPages pages: returnPages
.sort((a, b) => b.ratio - a.ratio) .sort((a, b) => b.ratio - a.ratio)
.slice(0, 15) .slice(0, 15)
} }
} }
/**
*
* @private
*/
private async getPages(): Promise<Page[]> { private async getPages(): Promise<Page[]> {
const pages = await Pages.getAll(); return await Pages.getAll();
return pages;
// let pages: Page[] | undefined = this.cache.get("SEARCH:PAGES");
//
// if ( pages === undefined ) {
// console.log('cache for SEARCH:PAGES is missing')
//
// pages = await Pages.getAll();
//
// this.cache.set("SEARCH:PAGES", pages);
// } else {
// console.log('wow SEARCH:PAGES is cached')
// }
//
// return pages;
} }
/**
* Return list of pages with a given words
* @param words
* @private
*/
private async getPagesByWords(words: string[]) { private async getPagesByWords(words: string[]) {
const pagesList: {[key: string]: number} = {}; const pagesList: {[key: string]: number} = {};
/**
* Get list of words starting with a words from the search query
*/
const validWords = Object.keys(globalWords) const validWords = Object.keys(globalWords)
.filter(word => { .filter(word => {
return !!words.filter(searchWord => word.indexOf(searchWord) !== -1).length return !!words.filter(searchWord => word.indexOf(searchWord) !== -1).length
}); });
/**
* For each word get list of pages with this word
*/
validWords.forEach(word => { validWords.forEach(word => {
Object.keys(globalWords[word]) Object.keys(globalWords[word])
.forEach(pageId => { .forEach(pageId => {
@ -153,6 +163,9 @@ class Search {
}) })
}) })
/**
* Sort pages by frequency of given words
*/
const sortedPagesList = Object.keys(pagesList) const sortedPagesList = Object.keys(pagesList)
.map(pageId => { .map(pageId => {
return { return {
@ -165,10 +178,11 @@ class Search {
return sortedPagesList; return sortedPagesList;
} }
private getUnique(elements: string[]) { /**
return [...new Set(elements)].sort(); * Get block's ratio. It is used to calculate the weight of the words in the block
} * @param block
* @private
*/
private getBlockRatio(block: any) { private getBlockRatio(block: any) {
switch (block.type) { switch (block.type) {
case 'header': case 'header':
@ -189,6 +203,11 @@ class Search {
} }
} }
/**
* Return clear text content from block without HTML tags and special characters
* @param block
* @private
*/
private getCleanTextFromBlock(block: any): string { private getCleanTextFromBlock(block: any): string {
let blockContent = ''; let blockContent = '';
@ -215,14 +234,29 @@ class Search {
return blockContent; return blockContent;
} }
/**
* Remove HTML tags from string. Only content inside tags will be left
* @param text
* @private
*/
private removeHTMLTags(text: string) { private removeHTMLTags(text: string) {
return text.replace(/<[^>]*>?/gm, ''); return text.replace(/<[^>]*>?/gm, '');
} }
/**
* Remove special characters from text. For example: &nbsp; &amp; &quot; &lt; &gt;
* @param text
* @private
*/
private removeHTMLSpecialCharacters(text: string) { private removeHTMLSpecialCharacters(text: string) {
return text.replace(/&[^;]*;?/gm, ''); return text.replace(/&[^;]*;?/gm, '');
} }
/**
* Split text to words
* @param text
* @private
*/
private splitTextToWords(text: string): string[] { private splitTextToWords(text: string): string[] {
return text return text
// lowercase all words // lowercase all words
@ -237,6 +271,7 @@ class Search {
// remove multiple spaces // remove multiple spaces
.replace(/\s+/g, ' ') .replace(/\s+/g, ' ')
// remove spaces at the beginning and at the end
.trim() .trim()
// split to words by spaces // split to words by spaces
@ -261,8 +296,7 @@ class Search {
} }
} }
const search = new Search(); /**
* Export initialized instance
export default search; */
export default new Search();
// export default Search;

View file

@ -2,6 +2,7 @@ import express, { Request, Response } from 'express';
import multerFunc from 'multer'; import multerFunc from 'multer';
import Pages from '../../controllers/pages.js'; import Pages from '../../controllers/pages.js';
import PagesOrder from '../../controllers/pagesOrder.js'; import PagesOrder from '../../controllers/pagesOrder.js';
import Search from '../../controllers/search.js';
const router = express.Router(); const router = express.Router();
const multer = multerFunc(); const multer = multerFunc();
@ -70,6 +71,9 @@ router.put('/page', multer.none(), async (req: Request, res: Response) => {
/** push to the orders array */ /** push to the orders array */
await PagesOrder.push(parent, page._id); await PagesOrder.push(parent, page._id);
/** Update search index */
await Search.syncDB();
res.json({ res.json({
success: true, success: true,
result: page, result: page,
@ -127,6 +131,10 @@ router.post('/page/:id', multer.none(), async (req: Request, res: Response) => {
parent, parent,
uri, uri,
}); });
/** Update search index */
await Search.syncDB();
res.json({ res.json({
success: true, success: true,
result: page, result: page,
@ -206,6 +214,9 @@ router.delete('/page/:id', async (req: Request, res: Response) => {
parentPageOrder.remove(req.params.id); parentPageOrder.remove(req.params.id);
await parentPageOrder.save(); await parentPageOrder.save();
/** Update search index */
await Search.syncDB();
res.json({ res.json({
success: true, success: true,
result: pageToRedirect, result: pageToRedirect,

View file

@ -10,31 +10,37 @@ const router = express.Router();
*/ */
router.get('/search', async (req: Request, res: Response) => { router.get('/search', async (req: Request, res: Response) => {
try { try {
/**
* Get search string
*/
const searchString = req.query.text as string; const searchString = req.query.text as string;
/** Start measuring search time */ /**
// const startTime = performance.now(); * Get search results
*/
// const search = new Search();
//
// const searchResponse = await search.query(searchString);
const searchResponse = await Search.query(searchString); const searchResponse = await Search.query(searchString);
/** End measuring search time */ /**
// const endTime = performance.now(); * Compose response
*/
/** Show search time */
// const searchItem = (endTime - startTime).toFixed(6);
// console.log(`🔎 "${searchString}" ⏱ ${searchItem} ms`);
const compactedPages = searchResponse.pages.map(page => { const compactedPages = searchResponse.pages.map(page => {
return { return {
/** Page id */
_id: page._id, _id: page._id,
/** Page title */
title: page.title, title: page.title,
/** Page uri */
uri: page.uri, uri: page.uri,
/** Section heading name for the found fragment */
section: page.section, section: page.section,
/** Section's anchor */
anchor: page.anchor, anchor: page.anchor,
/** Page fragment with searched items */
shortBody: page.shortBody, shortBody: page.shortBody,
}; };
}); });
@ -42,9 +48,11 @@ router.get('/search', async (req: Request, res: Response) => {
res.json({ res.json({
success: true, success: true,
result: { result: {
suggestions: searchResponse.suggestions, /** Found pages */
pages: compactedPages, pages: compactedPages,
// time: searchItem,
/** Typing suggestions */
suggestions: searchResponse.suggestions,
}, },
}); });
} catch (err) { } catch (err) {

View file

@ -57,7 +57,7 @@ export default class Search {
* - type a search string * - type a search string
* - fire search * - fire search
*/ */
// const testString = 'codex description'; // const testString = 'codex descri';
// this.toggleSearchOverlay(true); // this.toggleSearchOverlay(true);
// this.nodes.searchInput.value = testString; // this.nodes.searchInput.value = testString;
// this.debouncedSearch(testString); // this.debouncedSearch(testString);
@ -153,6 +153,10 @@ export default class Search {
showSearchResult({ data }) { showSearchResult({ data }) {
this.clearSearchResults(); this.clearSearchResults();
// if (data.result.suggestions.length) {
// this.showSuggestedWordCompletion(data.result.suggestions[0]);
// }
// const suggestionsWrapper = this.generateSearchSuggestions(data.result.suggestions); // const suggestionsWrapper = this.generateSearchSuggestions(data.result.suggestions);
// //
// this.nodes.searchResultWrapper.appendChild(suggestionsWrapper); // this.nodes.searchResultWrapper.appendChild(suggestionsWrapper);
@ -186,6 +190,20 @@ export default class Search {
}); });
} }
// showSuggestedWordCompletion(word) {
// const typedString = this.nodes.searchInput.value;
// const words = typedString.split(' ');
//
// words.pop();
// words.push(word);
//
// this.nodes.searchInput.value = words.join(' ');
//
// this.nodes.searchInput.select();
// this.nodes.searchInput.selectionStart = typedString.length;
// this.nodes.searchInput.selectionEnd = this.nodes.searchInput.value.length;
// }
// generateSearchSuggestions(suggestions = []) { // generateSearchSuggestions(suggestions = []) {
// const suggestionsWrapper = document.createElement('div'); // const suggestionsWrapper = document.createElement('div');
// //

View file

@ -2068,7 +2068,8 @@ clone-response@^1.0.2:
clone@2.x: clone@2.x:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
code-point-at@^1.0.0: code-point-at@^1.0.0:
version "1.1.0" version "1.1.0"
@ -4262,7 +4263,8 @@ nise@^5.1.1:
node-cache@^5.1.2: node-cache@^5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz" resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d"
integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==
dependencies: dependencies:
clone "2.x" clone "2.x"