show the exact inflection results seperate from the fuzzy inflection results
This commit is contained in:
parent
ffef1dd4bb
commit
229599860a
|
@ -115,8 +115,7 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
language: "Pashto",
|
language: "Pashto",
|
||||||
searchType: "fuzzy",
|
searchType: "fuzzy",
|
||||||
searchBarStickyFocus: false,
|
searchBarStickyFocus: false,
|
||||||
theme: /* istanbul ignore next */ (window.matchMedia &&
|
theme: (window.matchMedia?.("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
|
|
||||||
textOptionsRecord: {
|
textOptionsRecord: {
|
||||||
lastModified: Date.now() as AT.TimeStamp,
|
lastModified: Date.now() as AT.TimeStamp,
|
||||||
textOptions: defaultTextOptions,
|
textOptions: defaultTextOptions,
|
||||||
|
@ -224,32 +223,50 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
// shortcuts to isolote word in search results
|
// shortcuts to isolote word in search results
|
||||||
Mousetrap.bind(["1", "2", "3", "4", "5", "6", "7", "8", "9"], (e) => {
|
Mousetrap.bind(["1", "2", "3", "4", "5", "6", "7", "8", "9"], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) return;
|
if (e.repeat) {
|
||||||
if (this.props.location.pathname !== "/search") return;
|
return;
|
||||||
|
}
|
||||||
|
if (this.props.location.pathname !== "/search") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const toIsolate = this.state.results[Number(e.key) - 1];
|
const toIsolate = this.state.results[Number(e.key) - 1];
|
||||||
if (!toIsolate) return;
|
if (!toIsolate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.handleIsolateEntry(toIsolate.ts);
|
this.handleIsolateEntry(toIsolate.ts);
|
||||||
})
|
})
|
||||||
Mousetrap.bind(["ctrl+down", "ctrl+up", "command+down", "command+up"], (e) => {
|
Mousetrap.bind(["ctrl+down", "ctrl+up", "command+down", "command+up"], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) return;
|
if (e.repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.handleOptionsUpdate({ type: "toggleLanguage" });
|
this.handleOptionsUpdate({ type: "toggleLanguage" });
|
||||||
});
|
});
|
||||||
Mousetrap.bind(["ctrl+b", "command+b"], (e) => {
|
Mousetrap.bind(["ctrl+b", "command+b"], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) return;
|
if (e.repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.handleSearchValueChange("");
|
this.handleSearchValueChange("");
|
||||||
});
|
});
|
||||||
Mousetrap.bind(["ctrl+i", "command+i"], (e) => {
|
Mousetrap.bind(["ctrl+i", "command+i"], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) return;
|
if (e.repeat) {
|
||||||
if (!this.state.searchValue) return;
|
return;
|
||||||
|
}
|
||||||
|
if (!this.state.searchValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.handleInflectionSearch();
|
this.handleInflectionSearch();
|
||||||
});
|
});
|
||||||
Mousetrap.bind(["ctrl+s", "command+s"], (e) => {
|
Mousetrap.bind(["ctrl+s", "command+s"], (e) => {
|
||||||
if (this.state.user?.level === "basic") return;
|
if (this.state.user?.level === "basic") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!this.state.isolatedEntry) return;
|
if (!this.state.isolatedEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const toAdd = {
|
const toAdd = {
|
||||||
entry: this.state.isolatedEntry,
|
entry: this.state.isolatedEntry,
|
||||||
notes: "",
|
notes: "",
|
||||||
|
@ -258,8 +275,12 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
});
|
});
|
||||||
Mousetrap.bind(["ctrl+\\", "command+\\"], (e) => {
|
Mousetrap.bind(["ctrl+\\", "command+\\"], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) return;
|
if (e.repeat) {
|
||||||
if (this.state.user?.level === "basic") return;
|
return;
|
||||||
|
}
|
||||||
|
if (this.state.user?.level === "basic") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.props.location.pathname !== "/wordlist") {
|
if (this.props.location.pathname !== "/wordlist") {
|
||||||
this.props.history.push("/wordlist");
|
this.props.history.push("/wordlist");
|
||||||
} else {
|
} else {
|
||||||
|
@ -319,8 +340,12 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
try {
|
try {
|
||||||
const prevUser = this.state.user;
|
const prevUser = this.state.user;
|
||||||
const user = await getUser();
|
const user = await getUser();
|
||||||
if (user === "offline") return;
|
if (user === "offline") {
|
||||||
if (user) sendSubmissions();
|
return;
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
sendSubmissions();
|
||||||
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
if (this.state.user) {
|
if (this.state.user) {
|
||||||
console.log("setting state user because user is newly undefined");
|
console.log("setting state user because user is newly undefined");
|
||||||
|
@ -652,7 +677,7 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
state={this.state}
|
state={this.state}
|
||||||
optionsDispatch={this.handleOptionsUpdate}
|
optionsDispatch={this.handleOptionsUpdate}
|
||||||
handleSearchValueChange={this.handleSearchValueChange}
|
handleSearchValueChange={this.handleSearchValueChange}
|
||||||
onBottom
|
onBottom={true}
|
||||||
/>}
|
/>}
|
||||||
</footer>
|
</footer>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -46,7 +46,7 @@ function InflectionFormMatchDisplay(
|
||||||
<div className="mb-2"><strong>{displayFormResult(form.path)}</strong></div>
|
<div className="mb-2"><strong>{displayFormResult(form.path)}</strong></div>
|
||||||
{form.matches.map((match, i) => <div className="ml-2" key={i}>
|
{form.matches.map((match, i) => <div className="ml-2" key={i}>
|
||||||
<InlinePs opts={textOptions}>{match.ps}</InlinePs>
|
<InlinePs opts={textOptions}>{match.ps}</InlinePs>
|
||||||
<div className="ml-3 my-2">
|
<div className="ml-4 my-2">
|
||||||
<em>
|
<em>
|
||||||
{(transitivity === "grammatically transitive" && isPast)
|
{(transitivity === "grammatically transitive" && isPast)
|
||||||
? "Always 3rd pers. masc. plur."
|
? "Always 3rd pers. masc. plur."
|
||||||
|
|
|
@ -51,7 +51,7 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
||||||
data-testid="languageToggle"
|
data-testid="languageToggle"
|
||||||
>
|
>
|
||||||
<div aria-label={`language-choice-${language === "Pashto" ? "ps-to-en" : "en-to-ps"}`}>
|
<div aria-label={`language-choice-${language === "Pashto" ? "ps-to-en" : "en-to-ps"}`}>
|
||||||
Ps <span className={`fa fa-arrow-${arrowDirection}`} ></span> En
|
Ps <span className={`fa fa-arrow-${arrowDirection}`} /> En
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -64,7 +64,7 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
||||||
onClick={() => optionsDispatch({ type: "toggleSearchType" })}
|
onClick={() => optionsDispatch({ type: "toggleSearchType" })}
|
||||||
data-testid="searchTypeToggle"
|
data-testid="searchTypeToggle"
|
||||||
>
|
>
|
||||||
<span className={`fa fa-${icon}`} ></span>
|
<span className={`fa fa-${icon}`} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -73,7 +73,7 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
||||||
? "Browse alphabetically"
|
? "Browse alphabetically"
|
||||||
: `Search ${state.options.language === "Pashto" ? "Pashto" : "English"}`;
|
: `Search ${state.options.language === "Pashto" ? "Pashto" : "English"}`;
|
||||||
return (
|
return (
|
||||||
<nav className={`navbar bg-light${!onBottom ? " fixed-top" : ""}`} style={{ zIndex: 50, width: "100%" }}>
|
<nav className={`navbar bg-light${onBottom ? "" : " fixed-top"}`} style={{ zIndex: 50, width: "100%" }}>
|
||||||
<div className="form-inline my-1 my-lg-1">
|
<div className="form-inline my-1 my-lg-1">
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { searchPile } from "../lib/search-pile";
|
import { searchPile } from "../lib/search-pile";
|
||||||
import {
|
import {
|
||||||
isNounAdjOrVerb, removeAccents,
|
isNounAdjOrVerb, removeAccents, standardizePashto,
|
||||||
} from "@lingdocs/ps-react";
|
} from "@lingdocs/ps-react";
|
||||||
import { dictionary } from "../lib/dictionary";
|
import { dictionary } from "../lib/dictionary";
|
||||||
import {
|
import {
|
||||||
|
@ -26,7 +26,65 @@ const relevancySorter = new relevancy.Sorter();
|
||||||
// That's so much better I'm removing the option of skipping compounds
|
// That's so much better I'm removing the option of skipping compounds
|
||||||
// ~4th iteration:~ ignore perfective or imperfective if wasn't present in verb info (not worth it - scrapped)
|
// ~4th iteration:~ ignore perfective or imperfective if wasn't present in verb info (not worth it - scrapped)
|
||||||
|
|
||||||
export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue: string): InflectionSearchResult[] {
|
export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue: string): {
|
||||||
|
exact: InflectionSearchResult[],
|
||||||
|
fuzzy: InflectionSearchResult[],
|
||||||
|
} {
|
||||||
|
// pretty ugly function to seperate the exact and fuzzy inflection result matches
|
||||||
|
function getEntryMatchIndex(r: InflectionSearchResult[], entry: T.DictionaryEntry): number {
|
||||||
|
return r.findIndex(rs => rs.entry.ts === entry.ts);
|
||||||
|
}
|
||||||
|
function getFormIndex(r: InflectionSearchResult, path: string[]): number {
|
||||||
|
const joinedPath = path.join("");
|
||||||
|
return r.forms.findIndex(rs => rs.path.join("") === joinedPath);
|
||||||
|
}
|
||||||
|
const v = standardizePashto(searchValue);
|
||||||
|
const allResults = searchAllInflectionsCore(allDocs, searchValue);
|
||||||
|
const results: { exact: InflectionSearchResult[], fuzzy: InflectionSearchResult[] } = {
|
||||||
|
exact: [],
|
||||||
|
fuzzy: [],
|
||||||
|
};
|
||||||
|
allResults.forEach((result) => {
|
||||||
|
const entryMatches: { exact: InflectionSearchResult[], fuzzy: InflectionSearchResult[] } = {
|
||||||
|
exact: [],
|
||||||
|
fuzzy: [],
|
||||||
|
};
|
||||||
|
result.forms.forEach((form) => {
|
||||||
|
form.matches.forEach((match) => {
|
||||||
|
if (match.ps.p === v || match.ps.f === v) {
|
||||||
|
addToEntryMatches("exact");
|
||||||
|
} else {
|
||||||
|
addToEntryMatches("fuzzy");
|
||||||
|
}
|
||||||
|
function addToEntryMatches(t: "exact" | "fuzzy") {
|
||||||
|
let entryMatchIndex = getEntryMatchIndex(entryMatches[t], result.entry);
|
||||||
|
if (entryMatchIndex === -1) {
|
||||||
|
entryMatches[t].push({
|
||||||
|
entry: result.entry,
|
||||||
|
forms: [],
|
||||||
|
});
|
||||||
|
entryMatchIndex = entryMatches[t].length - 1;
|
||||||
|
}
|
||||||
|
let formIndex = getFormIndex(entryMatches[t][entryMatchIndex], form.path);
|
||||||
|
if (formIndex === -1) {
|
||||||
|
entryMatches[t][entryMatchIndex].forms.push({
|
||||||
|
path: form.path,
|
||||||
|
matches: [match],
|
||||||
|
});
|
||||||
|
formIndex = entryMatches[t][entryMatchIndex].forms.length - 1;
|
||||||
|
} else {
|
||||||
|
entryMatches[t][entryMatchIndex].forms[formIndex].matches.push(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
results.exact.push(...entryMatches.exact);
|
||||||
|
results.fuzzy.push(...entryMatches.fuzzy);
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function searchAllInflectionsCore(allDocs: T.DictionaryEntry[], searchValue: string): InflectionSearchResult[] {
|
||||||
const index = isPashtoScript(searchValue) ? "p" : "f"
|
const index = isPashtoScript(searchValue) ? "p" : "f"
|
||||||
function sortResultsByRelevancy(arr: InflectionSearchResult[]): InflectionSearchResult[] {
|
function sortResultsByRelevancy(arr: InflectionSearchResult[]): InflectionSearchResult[] {
|
||||||
return relevancySorter.sort(arr, searchValue, (obj: InflectionSearchResult, calc: any) => (
|
return relevancySorter.sort(arr, searchValue, (obj: InflectionSearchResult, calc: any) => (
|
||||||
|
@ -65,7 +123,7 @@ export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue:
|
||||||
);
|
);
|
||||||
const preSearchFun = (ps: T.PsString) => !!ps[script].match(begRegex);
|
const preSearchFun = (ps: T.PsString) => !!ps[script].match(begRegex);
|
||||||
const searchRegex = new RegExp(
|
const searchRegex = new RegExp(
|
||||||
makeAWeeBitFuzzy(searchValue, script, true) + "$",
|
`${makeAWeeBitFuzzy(searchValue, script, true)}$`,
|
||||||
"i",
|
"i",
|
||||||
);
|
);
|
||||||
// add little bit fuzzy
|
// add little bit fuzzy
|
||||||
|
@ -110,21 +168,18 @@ export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue:
|
||||||
}, []);
|
}, []);
|
||||||
// console.timeEnd(timerLabel);
|
// console.timeEnd(timerLabel);
|
||||||
// TODO!!: Sorting on this as well
|
// TODO!!: Sorting on this as well
|
||||||
if (["را", "ور", "در"].includes(searchValue.slice(0, 2))) {
|
const allResults = (["را", "ور", "در"].includes(searchValue.slice(0, 2)))
|
||||||
return [
|
? [
|
||||||
...results,
|
...results,
|
||||||
// also search without the directionary pronoun
|
// also search without the directional pronoun
|
||||||
...searchAllInflections(allDocs, searchValue.slice(2)),
|
...searchAllInflectionsCore(allDocs, searchValue.slice(2)),
|
||||||
];
|
] : (["raa", "war", "dar", "wăr", "dăr"].includes(searchValue.slice(0, 3)))
|
||||||
}
|
? [
|
||||||
if (["raa", "war", "dar", "wăr", "dăr"].includes(searchValue.slice(0, 3))) {
|
|
||||||
return [
|
|
||||||
...results,
|
...results,
|
||||||
// also search without the directionary pronoun
|
// also search without the directional pronoun
|
||||||
...searchAllInflections(allDocs, searchValue.slice(3)),
|
...searchAllInflectionsCore(allDocs, searchValue.slice(3)),
|
||||||
];
|
] : results;
|
||||||
}
|
|
||||||
// because we used a bit of a fuzzy search, sort the results by relevancy
|
// because we used a bit of a fuzzy search, sort the results by relevancy
|
||||||
// this is a bit complicated...
|
// this is a bit complicated...
|
||||||
return sortResultsByRelevancy(results.map(sortMatchesByRelevancy));
|
return sortResultsByRelevancy(allResults.map(sortMatchesByRelevancy));
|
||||||
}
|
}
|
|
@ -88,12 +88,15 @@ function Results({ state, isolateEntry, handleInflectionSearch }: {
|
||||||
{inflectionResults === "searching" && <div>
|
{inflectionResults === "searching" && <div>
|
||||||
<p className="lead mt-1">Searching conjugations/inflections... <i className="fas fa-hourglass-half" /></p>
|
<p className="lead mt-1">Searching conjugations/inflections... <i className="fas fa-hourglass-half" /></p>
|
||||||
</div>}
|
</div>}
|
||||||
{Array.isArray(inflectionResults) && <div>
|
{inflectionResults && inflectionResults !== "searching" && <div>
|
||||||
<h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4>
|
<h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4>
|
||||||
{inflectionResults.length === 0 && <div className="mt-4">
|
{inflectionResults.exact.length === 0 && inflectionResults.fuzzy.length === 0 && <div className="mt-4">
|
||||||
<div>No conjugation/inflection matches found for <strong>{state.searchValue}</strong></div>
|
<div>No conjugation/inflection matches found for <strong>{state.searchValue}</strong></div>
|
||||||
</div>}
|
</div>}
|
||||||
{inflectionResults.map((p) => (
|
{(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => {
|
||||||
|
return (inflectionResults[t].length !== 0) ? <>
|
||||||
|
<h5 className="mb-3">{t === "exact" ? "Exact" : "Approximate"} Matches</h5>
|
||||||
|
{inflectionResults[t].map((p) => (
|
||||||
<div key={p.entry.ts}>
|
<div key={p.entry.ts}>
|
||||||
<Entry
|
<Entry
|
||||||
key={p.entry.i}
|
key={p.entry.i}
|
||||||
|
@ -104,7 +107,7 @@ function Results({ state, isolateEntry, handleInflectionSearch }: {
|
||||||
<div className="mb-3 ml-2">
|
<div className="mb-3 ml-2">
|
||||||
{p.forms.map((form, i) => (
|
{p.forms.map((form, i) => (
|
||||||
<InflectionFormMatchDisplay
|
<InflectionFormMatchDisplay
|
||||||
key={"inf-form" + i}
|
key={`inf-form${i}`}
|
||||||
textOptions={textOptions}
|
textOptions={textOptions}
|
||||||
form={form}
|
form={form}
|
||||||
entry={p.entry}
|
entry={p.entry}
|
||||||
|
@ -113,6 +116,8 @@ function Results({ state, isolateEntry, handleInflectionSearch }: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</> : null;
|
||||||
|
})}
|
||||||
</div>}
|
</div>}
|
||||||
{inflectionResults === undefined && suggestionState === "none" && state.results.map((entry) => (
|
{inflectionResults === undefined && suggestionState === "none" && state.results.map((entry) => (
|
||||||
<Entry
|
<Entry
|
||||||
|
|
|
@ -11,7 +11,10 @@ export type State = {
|
||||||
reviewTasks: import("./functions-types").ReviewTask[],
|
reviewTasks: import("./functions-types").ReviewTask[],
|
||||||
dictionaryInfo: import("@lingdocs/ps-react").Types.DictionaryInfo | undefined,
|
dictionaryInfo: import("@lingdocs/ps-react").Types.DictionaryInfo | undefined,
|
||||||
user: undefined | import("./account-types").LingdocsUser,
|
user: undefined | import("./account-types").LingdocsUser,
|
||||||
inflectionSearchResults: undefined | "searching" | InflectionSearchResult[],
|
inflectionSearchResults: undefined | "searching" | {
|
||||||
|
exact: InflectionSearchResult[],
|
||||||
|
fuzzy: InflectionSearchResult[],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DictionaryAPI = {
|
export type DictionaryAPI = {
|
||||||
|
|
Loading…
Reference in New Issue