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:
parent
a9dd65c964
commit
488d825c03
5 changed files with 125 additions and 52 deletions
|
@ -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: & " < >
|
||||||
|
* @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;
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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');
|
||||||
//
|
//
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue