refactor: monorepo setup with lerna (#243)
This commit is contained in:
committed by
GitHub
parent
dac29fbf3c
commit
504d95c34f
23
packages/docs/src/util/node.util.ts
Normal file
23
packages/docs/src/util/node.util.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { isNotEmpty } from './string.util';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const getNodeText = (node: ReactNode): string => {
|
||||
if (['string', 'number'].includes(typeof node)) {
|
||||
return `${node}`;
|
||||
}
|
||||
|
||||
if (node instanceof Array) {
|
||||
return node.map(getNodeText).filter(isNotEmpty).join('');
|
||||
}
|
||||
|
||||
if (typeof node === 'object' && node && 'props' in node) {
|
||||
return getNodeText(node.props.children);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const useNodeText = (node: ReactNode) => useMemo(() => getNodeText(node), [node]);
|
11
packages/docs/src/util/null.util.ts
Normal file
11
packages/docs/src/util/null.util.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export function isNotNullish<T>(value: T | null | undefined): value is T {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
export function isNullish<T>(value: T | null | undefined): value is null | undefined {
|
||||
return value === undefined || value === null;
|
||||
}
|
||||
|
||||
export function filterNullish<T>(value: (T | null | undefined)[] | null | undefined): T[] {
|
||||
return value?.filter<T>(isNotNullish) ?? [];
|
||||
}
|
89
packages/docs/src/util/search.util.ts
Normal file
89
packages/docs/src/util/search.util.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { isEmpty } from './string.util';
|
||||
|
||||
import type { DocsPageHeading, SearchablePage } from '../interface';
|
||||
|
||||
const PARTIAL_MATCH_WORD_LENGTH_THRESHOLD = 5;
|
||||
const WHOLE_WORD_MATCH_FAVOR_WEIGHT = 2;
|
||||
const TITLE_FAVOR_WEIGHT = 15;
|
||||
|
||||
export interface SearchScore {
|
||||
entry: SearchablePage;
|
||||
metaScore: number;
|
||||
score: number;
|
||||
isExactTitleMatch: boolean;
|
||||
matchedHeader: DocsPageHeading | undefined;
|
||||
}
|
||||
|
||||
function getSearchScore(words: string[], entry: SearchablePage): SearchScore {
|
||||
let score = 0;
|
||||
let metaScore = 0;
|
||||
|
||||
for (const word of words) {
|
||||
score +=
|
||||
(entry.title.match(new RegExp(`\\b${word}\\b`, 'gi')) ?? []).length *
|
||||
TITLE_FAVOR_WEIGHT *
|
||||
WHOLE_WORD_MATCH_FAVOR_WEIGHT;
|
||||
score +=
|
||||
(entry.textContent.match(new RegExp(`\\b${word}\\b`, 'gi')) ?? []).length *
|
||||
WHOLE_WORD_MATCH_FAVOR_WEIGHT;
|
||||
|
||||
if (word.length >= PARTIAL_MATCH_WORD_LENGTH_THRESHOLD) {
|
||||
score += (entry.title.match(new RegExp(`${word}`, 'gi')) ?? []).length * TITLE_FAVOR_WEIGHT;
|
||||
score += (entry.textContent.match(new RegExp(`${word}`, 'gi')) ?? []).length;
|
||||
}
|
||||
}
|
||||
|
||||
const exactMatchFavorWeight = words.length;
|
||||
const exactSearch = words.join(' ').toLowerCase();
|
||||
const isExactTitleMatch = entry.title.toLowerCase().includes(exactSearch);
|
||||
|
||||
const exactTitleMatchScore =
|
||||
(isExactTitleMatch ? 1 : 0) *
|
||||
TITLE_FAVOR_WEIGHT *
|
||||
exactMatchFavorWeight *
|
||||
WHOLE_WORD_MATCH_FAVOR_WEIGHT;
|
||||
|
||||
if (isExactTitleMatch) {
|
||||
metaScore += 1;
|
||||
}
|
||||
|
||||
score += exactTitleMatchScore;
|
||||
score +=
|
||||
(entry.textContent.match(new RegExp(`\\b${exactSearch}\\b`, 'gi')) ?? []).length *
|
||||
exactMatchFavorWeight *
|
||||
WHOLE_WORD_MATCH_FAVOR_WEIGHT;
|
||||
|
||||
return {
|
||||
score,
|
||||
metaScore,
|
||||
entry,
|
||||
isExactTitleMatch: exactTitleMatchScore > 0,
|
||||
matchedHeader: entry.headings.find(header => header.title.toLowerCase().includes(exactSearch)),
|
||||
};
|
||||
}
|
||||
|
||||
export function useSearchScores(query: string | null, entries: SearchablePage[]): SearchScore[] {
|
||||
return useMemo(() => {
|
||||
if (!query || isEmpty(query.trim())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const queryWords = query.split(' ').filter(word => word.trim().length > 0);
|
||||
|
||||
const scores = entries
|
||||
.map(entry => getSearchScore(queryWords, entry))
|
||||
.filter(result => result.score > 0);
|
||||
|
||||
scores.sort((a, b) => {
|
||||
if (a.metaScore !== b.metaScore) {
|
||||
return b.metaScore - a.metaScore;
|
||||
}
|
||||
|
||||
return b.score - a.score;
|
||||
});
|
||||
|
||||
return scores;
|
||||
}, [entries, query]);
|
||||
}
|
24
packages/docs/src/util/string.util.ts
Normal file
24
packages/docs/src/util/string.util.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { isNotNullish, isNullish } from './null.util';
|
||||
|
||||
export function isEmpty(value: string | null | undefined): value is null | undefined {
|
||||
return isNullish(value) || value === '';
|
||||
}
|
||||
|
||||
export function isNotEmpty(value: string | null | undefined): value is string {
|
||||
return isNotNullish(value) && value !== '';
|
||||
}
|
||||
|
||||
export function toTitleCase(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
export function toTitleCaseFromKey(str: string) {
|
||||
return str.replace(/_/g, ' ').replace(/\w\S*/g, toTitleCase);
|
||||
}
|
||||
|
||||
export function toTitleCaseFromVariableName(str: string) {
|
||||
return str
|
||||
.split(/(?=[A-Z])/)
|
||||
.join(' ')
|
||||
.replace(/\w\S*/g, toTitleCase);
|
||||
}
|
7
packages/docs/src/util/transientOptions.ts
Normal file
7
packages/docs/src/util/transientOptions.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { CreateMUIStyled } from '@mui/material/styles';
|
||||
|
||||
const transientOptions: Parameters<CreateMUIStyled>[1] = {
|
||||
shouldForwardProp: (propName: string) => !propName.startsWith('$'),
|
||||
};
|
||||
|
||||
export default transientOptions;
|
Reference in New Issue
Block a user