refactor: monorepo setup with lerna (#243)

This commit is contained in:
Daniel Lautzenheiser
2022-12-15 13:44:49 -05:00
committed by GitHub
parent dac29fbf3c
commit 504d95c34f
706 changed files with 16571 additions and 142 deletions

View 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]);

View 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) ?? [];
}

View 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]);
}

View 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);
}

View 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;