Compare commits

..

No commits in common. "5a90557ebb818f160b7f4b8345cf088df1217a37" and "35a093c14d2295f90c6a6d3add876069bcad37d1" have entirely different histories.

18 changed files with 299 additions and 3141 deletions

View File

@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@lingdocs/inflect": "7.0.8", "@lingdocs/inflect": "7.0.3",
"base64url": "^3.0.1", "base64url": "^3.0.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"connect-redis": "^6.0.0", "connect-redis": "^6.0.0",
@ -126,9 +126,9 @@
} }
}, },
"node_modules/@lingdocs/inflect": { "node_modules/@lingdocs/inflect": {
"version": "7.0.8", "version": "7.0.3",
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.8.tgz", "resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
"integrity": "sha512-+RuZ2Tcw4gGjWwNOpZb/C7kwgWBAILY4ZRSzZBrG3KN+jEDrW++wgB5LfaKkzHLIurI73L5rNc4FKYUw8zXn3A==", "integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
"dependencies": { "dependencies": {
"fp-ts": "^2.16.0", "fp-ts": "^2.16.0",
"pbf": "^3.2.1", "pbf": "^3.2.1",
@ -3109,9 +3109,9 @@
} }
}, },
"@lingdocs/inflect": { "@lingdocs/inflect": {
"version": "7.0.8", "version": "7.0.3",
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.8.tgz", "resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
"integrity": "sha512-+RuZ2Tcw4gGjWwNOpZb/C7kwgWBAILY4ZRSzZBrG3KN+jEDrW++wgB5LfaKkzHLIurI73L5rNc4FKYUw8zXn3A==", "integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
"requires": { "requires": {
"fp-ts": "^2.16.0", "fp-ts": "^2.16.0",
"pbf": "^3.2.1", "pbf": "^3.2.1",

View File

@ -12,7 +12,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@lingdocs/inflect": "7.0.8", "@lingdocs/inflect": "7.0.3",
"base64url": "^3.0.1", "base64url": "^3.0.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"connect-redis": "^6.0.0", "connect-redis": "^6.0.0",

View File

@ -45,10 +45,10 @@
"@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/sourcemap-codec" "^1.4.10"
"@lingdocs/inflect@7.0.8": "@lingdocs/inflect@7.0.3":
"integrity" "sha512-+RuZ2Tcw4gGjWwNOpZb/C7kwgWBAILY4ZRSzZBrG3KN+jEDrW++wgB5LfaKkzHLIurI73L5rNc4FKYUw8zXn3A==" "integrity" "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA=="
"resolved" "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.8.tgz" "resolved" "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz"
"version" "7.0.8" "version" "7.0.3"
dependencies: dependencies:
"fp-ts" "^2.16.0" "fp-ts" "^2.16.0"
"pbf" "^3.2.1" "pbf" "^3.2.1"

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,8 @@
}, },
"main": "lib/functions/src/index.js", "main": "lib/functions/src/index.js",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.474.0", "@google-cloud/storage": "^5.8.1",
"@lingdocs/inflect": "7.0.8", "@lingdocs/inflect": "7.0.3",
"@types/cors": "^2.8.10", "@types/cors": "^2.8.10",
"@types/google-spreadsheet": "^3.0.2", "@types/google-spreadsheet": "^3.0.2",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",

View File

@ -13,20 +13,15 @@ import {
} from "@lingdocs/inflect"; } from "@lingdocs/inflect";
import { getWordList } from "./word-list-maker"; import { getWordList } from "./word-list-maker";
import { PublishDictionaryResponse } from "../../website/src/types/functions-types"; import { PublishDictionaryResponse } from "../../website/src/types/functions-types";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import { Storage } from "@google-cloud/storage";
import zlib from "zlib"; const storage = new Storage({
const s3Client = new S3Client({ projectId: "lingdocs",
region: "auto",
endpoint: functions.config().r2.endpoint,
credentials: {
accessKeyId: functions.config().r2.access_key_id,
secretAccessKey: functions.config().r2.secret_access_key,
},
}); });
const title = "LingDocs Pashto Dictionary"; const title = "LingDocs Pashto Dictionary";
const license = `Copyright © ${new Date().getFullYear()} lingdocs.com All Rights Reserved - Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - https://creativecommons.org/licenses/by-nc-sa/4.0/`; const license = `Copyright © ${new Date().getFullYear()} lingdocs.com All Rights Reserved - Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - https://creativecommons.org/licenses/by-nc-sa/4.0/`;
const baseUrl = `https://storage.lingdocs.com/dictionary/`; const bucketName = "lingdocs";
const baseUrl = `https://storage.googleapis.com/${bucketName}/`;
const dictionaryFilename = "dictionary"; const dictionaryFilename = "dictionary";
const dictionaryInfoFilename = "dictionary-info"; const dictionaryInfoFilename = "dictionary-info";
// const hunspellAffFileFilename = "ps_AFF.aff"; // const hunspellAffFileFilename = "ps_AFF.aff";
@ -225,36 +220,19 @@ function checkForErrors(
async function upload(content: Buffer | string, filename: string) { async function upload(content: Buffer | string, filename: string) {
const isBuffer = typeof content !== "string"; const isBuffer = typeof content !== "string";
// upload to r2 (new destination) const file = storage.bucket(bucketName).file(filename);
if (isBuffer) { await file.save(content, {
const putObjectCommand = new PutObjectCommand({ gzip: isBuffer ? false : true,
Bucket: functions.config().r2.bucket_name, predefinedAcl: "publicRead",
Key: `dictionary/${filename}`, metadata: {
Body: content, contentType: isBuffer
CacheControl: "no-cache", ? "application/octet-stream"
ContentType: "application/octet-stream", : filename.slice(-5) === ".json"
}); ? "application/json"
await s3Client.send(putObjectCommand); : "text/plain; charset=UTF-8",
} else { cacheControl: "no-cache",
zlib.gzip(content, (err, buffer) => { },
if (err) { });
console.error(err);
}
const putObjectCommand = new PutObjectCommand({
Bucket: functions.config().r2.bucket_name,
Key: `dictionary/${filename}`,
CacheControl: "no-cache",
Body: buffer,
ContentEncoding: "gzip",
ContentType: filename.endsWith(".json")
? "application/json"
: filename.endsWith(".xml")
? "application/xml"
: "text/plain; charset=UTF-8",
});
s3Client.send(putObjectCommand).catch(console.error);
});
}
} }
// async function uploadHunspellToStorage(wordlist: { // async function uploadHunspellToStorage(wordlist: {
@ -296,21 +274,14 @@ async function uploadDictionaryToStorage(dictionary: T.Dictionary) {
} }
function makeSitemap(dictionary: T.Dictionary): string { function makeSitemap(dictionary: T.Dictionary): string {
const pages = [
...["", "about", "settings", "account", "phrase-builder", "new-entries"],
...dictionary.entries.map((x) => `word?id=${x.ts}`),
];
return `<?xml version="1.0" encoding="UTF-8"?> return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages ${dictionary.entries.map(
.map( (entry) =>
(page) => ` <url>
` <loc>https://dictionary.lingdocs.com/word?id=${entry.ts}</loc>
<url>
<loc>https://dictionary.lingdocs.com/${page}</loc>
</url>` </url>`
) )}
.join("")}
</urlset> </urlset>
`; `;
} }

View File

@ -7,7 +7,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2", "@fortawesome/fontawesome-free": "^5.15.2",
"@lingdocs/ps-react": "7.0.8", "@lingdocs/ps-react": "7.0.3",
"@testing-library/jest-dom": "^5.11.4", "@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",

View File

@ -11,7 +11,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default"> <meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Pashto Dictionary"> <meta name="apple-mobile-web-app-title" content="Pashto Dictionary">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="sitemap" type="application/xml" title="Sitemap" href="https://storage.lingdocs.com/dictionary/sitemap.xml" />
<meta name="author" content="lingdocs.com" /> <meta name="author" content="lingdocs.com" />
<link rel="canonical" href="https://dictionary.lingdocs.com/" /> <link rel="canonical" href="https://dictionary.lingdocs.com/" />

View File

@ -1,4 +1,3 @@
# https://www.robotstxt.org/robotstxt.html # https://www.robotstxt.org/robotstxt.html
User-agent: * User-agent: *
Disallow: Disallow:
Sitemap: https://storage.lingdocs.com/dictionary/sitemap.xml

View File

@ -57,10 +57,6 @@ body {
padding-bottom: 60px; */ padding-bottom: 60px; */
} }
dt {
float: left;
}
.p-text { .p-text {
font-size: var(--p-text-size); font-size: var(--p-text-size);
} }

View File

@ -439,13 +439,6 @@ class App extends Component<RouteComponentProps, State> {
action.type === "toggleSearchType" action.type === "toggleSearchType"
) { ) {
if (this.props.location.pathname !== "/new-entries") { if (this.props.location.pathname !== "/new-entries") {
if (
action.type === "toggleSearchType" &&
this.state.options.searchType === "fuzzy" &&
this.props.location.pathname !== "/search"
) {
this.handleSearchValueChange("آ");
}
this.setState((prevState) => ({ this.setState((prevState) => ({
options, options,
page: 1, page: 1,
@ -643,12 +636,12 @@ class App extends Component<RouteComponentProps, State> {
</Link> </Link>
</div> </div>
)} )}
<Link {/* <Link
to="/new-entries" to="/new-entries"
className="plain-link font-weight-light" className="plain-link font-weight-light"
> >
<div className="my-4">New words this month</div> <div className="my-4">New words this month</div>
</Link> </Link> */}
<div className="my-4 pt-3"> <div className="my-4 pt-3">
<Link <Link
to="/phrase-builder" to="/phrase-builder"

View File

@ -36,9 +36,9 @@ function Entry({
data-testid="entry" data-testid="entry"
> >
<div> <div>
<dt className="mr-2"> <strong>
<InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs> <InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs>
</dt> </strong>
{` `} {` `}
<em>{entry.c}</em> <em>{entry.c}</em>
{entry.a && !nonClickable && ( {entry.a && !nonClickable && (
@ -49,9 +49,7 @@ function Entry({
)} )}
</div> </div>
<ExtraEntryInfo entry={entry} textOptions={textOptions} /> <ExtraEntryInfo entry={entry} textOptions={textOptions} />
<dd> <div className="entry-definition">{entry.e}</div>
<div className="entry-definition">{entry.e}</div>
</dd>
</div> </div>
); );
} }

View File

@ -1,32 +0,0 @@
import { Types as T, InlinePs } from "@lingdocs/ps-react";
import { getAudioPath } from "./PlayStorageAudio";
export function EntryAudioDisplay({
entry,
opts,
}: {
entry: T.DictionaryEntry;
opts: T.TextOptions;
}) {
if (!entry.a) {
return null;
}
return (
<figure>
<figcaption className="mb-2 pl-2">
Listen to <InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
</figcaption>
<audio
controls
controlsList="nofullscreen"
src={getAudioPath(entry.ts)}
preload="auto"
>
<a href={getAudioPath(entry.ts)}>
Download audio for{" "}
<InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
</a>
</audio>
</figure>
);
}

View File

@ -1,10 +1,6 @@
export function getAudioPath(ts: number): string {
return `https://storage.lingdocs.com/audio/${ts}.mp3`;
}
export default function playStorageAudio(ts: number, callback: () => void) { export default function playStorageAudio(ts: number, callback: () => void) {
if (!ts) return; if (!ts) return;
let audio = new Audio(getAudioPath(ts)); let audio = new Audio(`https://storage.lingdocs.com/audio/${ts}.mp3`);
audio.addEventListener("ended", () => { audio.addEventListener("ended", () => {
callback(); callback();
audio.remove(); audio.remove();

View File

@ -24,9 +24,9 @@ import { makeAWeeBitFuzzy } from "./wee-bit-fuzzy";
import { getTextOptions } from "./get-text-options"; import { getTextOptions } from "./get-text-options";
import { DictionaryAPI, State } from "../types/dictionary-types"; import { DictionaryAPI, State } from "../types/dictionary-types";
const dictionaryBaseUrl = `https://storage.lingdocs.com/dictionary`; // const dictionaryBaseUrl = "https://storage.googleapis.com/lingdocs/";
const dictionaryUrl = `${dictionaryBaseUrl}/dictionary`; const dictionaryUrl = `https://storage.googleapis.com/lingdocs/dictionary`;
const dictionaryInfoUrl = `${dictionaryBaseUrl}/dictionary-info`; const dictionaryInfoUrl = `https://storage.googleapis.com/lingdocs/dictionary-info`;
const dictionaryInfoLocalStorageKey = "dictionaryInfo5"; const dictionaryInfoLocalStorageKey = "dictionaryInfo5";
const dictionaryCollectionName = "dictionary3"; const dictionaryCollectionName = "dictionary3";

View File

@ -34,7 +34,7 @@ import { Modal } from "react-bootstrap";
import { getTextOptions } from "../lib/get-text-options"; import { getTextOptions } from "../lib/get-text-options";
import { entryFeeder } from "../lib/dictionary"; import { entryFeeder } from "../lib/dictionary";
import { State, DictionaryAPI } from "../types/dictionary-types"; import { State, DictionaryAPI } from "../types/dictionary-types";
import { EntryAudioDisplay } from "../components/EntryAudioDisplay"; import playStorageAudio from "../components/PlayStorageAudio";
function IsolatedEntry({ function IsolatedEntry({
state, state,
@ -46,6 +46,7 @@ function IsolatedEntry({
isolateEntry: (ts: number) => void; isolateEntry: (ts: number) => void;
}) { }) {
const [exploded, setExploded] = useState<boolean>(false); const [exploded, setExploded] = useState<boolean>(false);
const [playing, setPlaying] = useState<boolean>(false);
const [editing, setEditing] = useState<boolean>(false); const [editing, setEditing] = useState<boolean>(false);
const [comment, setComment] = useState<string>(""); const [comment, setComment] = useState<string>("");
const [editSubmitted, setEditSubmitted] = useState<boolean>(false); const [editSubmitted, setEditSubmitted] = useState<boolean>(false);
@ -56,6 +57,7 @@ function IsolatedEntry({
setEditing(false); setEditing(false);
setComment(""); setComment("");
setEditSubmitted(false); setEditSubmitted(false);
setPlaying(false);
}, [state]); }, [state]);
function flashClippedMessage(m: string) { function flashClippedMessage(m: string) {
setShowClipped(m); setShowClipped(m);
@ -152,16 +154,19 @@ function IsolatedEntry({
navigator.clipboard.writeText(JSON.stringify(entry)); navigator.clipboard.writeText(JSON.stringify(entry));
flashClippedMessage("entry data copied to clipboard"); flashClippedMessage("entry data copied to clipboard");
} }
function handlePlayStorageAudio() {
if (!entry) return;
setPlaying(true);
playStorageAudio(entry.ts, () => {
setPlaying(false);
});
}
return ( return (
<div className="wide-width-limiter"> <div className="wide-width-limiter">
<Helmet> <Helmet>
<title>{entry.p} | LingDocs Pashto Dictionary</title> <title>{entry.p} - LingDocs Pashto Dictionary</title>
<link
rel="canonical"
href={`https://dictionary.lingdocs.com/word?id=${entry.ts}`}
/>
</Helmet> </Helmet>
<dl className="row mb-1"> <div className="row">
<div className="col-8"> <div className="col-8">
<Entry <Entry
nonClickable nonClickable
@ -172,6 +177,13 @@ function IsolatedEntry({
</div> </div>
<div className="col-4"> <div className="col-4">
<div className="d-flex flex-row justify-content-end"> <div className="d-flex flex-row justify-content-end">
{entry.a && (
<div className="clickable mr-3" onClick={handlePlayStorageAudio}>
<i
className={`fas fa-lg fa-volume-${playing ? "down" : "off"}`}
/>
</div>
)}
<div <div
className="clickable mr-3" className="clickable mr-3"
onClick={() => setExploded((os) => !os)} onClick={() => setExploded((os) => !os)}
@ -226,8 +238,7 @@ function IsolatedEntry({
)} )}
</div> </div>
</div> </div>
</dl> </div>
<EntryAudioDisplay entry={entry} opts={textOptions} />
{wordlistWord && ( {wordlistWord && (
<> <>
{hasAttachment(wordlistWord, "audio") && ( {hasAttachment(wordlistWord, "audio") && (
@ -358,7 +369,6 @@ function IsolatedEntry({
state={{ ...state, results: relatedEntries }} state={{ ...state, results: relatedEntries }}
isolateEntry={isolateEntry} isolateEntry={isolateEntry}
handleInflectionSearch={() => null} handleInflectionSearch={() => null}
relatedResults
/> />
</> </>
) : ( ) : (

View File

@ -8,278 +8,212 @@
import { useState } from "react"; import { useState } from "react";
import * as FT from "../types/functions-types"; import * as FT from "../types/functions-types";
import { submissionBase, addSubmission } from "../lib/submissions"; import {
submissionBase,
addSubmission,
} from "../lib/submissions";
import { isPashtoScript } from "../lib/is-pashto"; import { isPashtoScript } from "../lib/is-pashto";
import Entry from "../components/Entry"; import Entry from "../components/Entry";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import InflectionFormMatchDisplay from "../components/InflectionFormMatchDisplay"; import InflectionFormMatchDisplay from "../components/InflectionFormMatchDisplay";
import { getTextOptions } from "../lib/get-text-options"; import { getTextOptions } from "../lib/get-text-options";
import { State } from "../types/dictionary-types"; import {
State,
} from "../types/dictionary-types";
export const inflectionSearchIcon = "fas fa-search-plus"; export const inflectionSearchIcon = "fas fa-search-plus";
// TODO: put power results in a prop so we can do it from outside with the keyboard shortcut // TODO: put power results in a prop so we can do it from outside with the keyboard shortcut
function Results({ function Results({ state, isolateEntry, handleInflectionSearch }: {
state, state: State,
isolateEntry, isolateEntry: (ts: number) => void,
handleInflectionSearch, handleInflectionSearch: () => void,
relatedResults,
}: {
state: State;
isolateEntry: (ts: number) => void;
handleInflectionSearch: () => void;
relatedResults?: boolean;
}) { }) {
const [suggestionState, setSuggestionState] = useState< const [suggestionState, setSuggestionState] = useState<"none" | "editing" | "received">("none");
"none" | "editing" | "received" const [comment, setComment] = useState<string>("");
>("none"); const [pashto, setPashto] = useState<string>("");
const [comment, setComment] = useState<string>(""); const [phonetics, setPhonetics] = useState<string>("");
const [pashto, setPashto] = useState<string>(""); const [english, setEnglish] = useState<string>("");
const [phonetics, setPhonetics] = useState<string>(""); const textOptions = getTextOptions(state);
const [english, setEnglish] = useState<string>(""); function startSuggestion() {
const textOptions = getTextOptions(state); const toStart = state.searchValue;
function startSuggestion() { if (isPashtoScript(toStart)) {
const toStart = state.searchValue; setPashto(toStart);
if (isPashtoScript(toStart)) { setPhonetics("");
setPashto(toStart); } else {
setPhonetics(""); setPashto("");
} else { setPhonetics(toStart);
setPashto(""); }
setPhonetics(toStart); setSuggestionState("editing");
} }
setSuggestionState("editing"); function cancelSuggestion() {
} setPashto("");
function cancelSuggestion() { setPhonetics("");
setPashto(""); setSuggestionState("none");
setPhonetics(""); }
setSuggestionState("none"); function submitSuggestion(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
} event.preventDefault();
function submitSuggestion( if (!state.user) return;
event: React.MouseEvent<HTMLButtonElement, MouseEvent> const p = pashto;
) { const f = phonetics;
event.preventDefault(); const e = english;
if (!state.user) return; const newEntry: FT.EntrySuggestion = {
const p = pashto; ...submissionBase(state.user),
const f = phonetics; type: "entry suggestion",
const e = english; entry: { ts: 0, i: 0, p, f, g: "", e },
const newEntry: FT.EntrySuggestion = { comment,
...submissionBase(state.user), };
type: "entry suggestion", addSubmission(newEntry, state.user);
entry: { ts: 0, i: 0, p, f, g: "", e }, setSuggestionState("received");
comment, }
}; const inflectionResults = state.inflectionSearchResults;
addSubmission(newEntry, state.user); return <div className="width-limiter">
setSuggestionState("received");
}
const inflectionResults = state.inflectionSearchResults;
return (
<div className="width-limiter">
{!relatedResults && (
<Helmet> <Helmet>
<title>LingDocs Pashto Dictionary</title> <title>LingDocs Pashto Dictionary</title>
</Helmet> </Helmet>
)} {(state.user && (window.location.pathname !== "/word") && suggestionState === "none" && inflectionResults === undefined) && <button
{state.user &&
window.location.pathname !== "/word" &&
suggestionState === "none" &&
inflectionResults === undefined && (
<button
type="button" type="button"
className={`btn btn-outline-secondary bg-white entry-suggestion-button${ className={`btn btn-outline-secondary bg-white entry-suggestion-button${state.options.searchBarPosition === "bottom" ? " entry-suggestion-button-with-bottom-searchbar" : ""}`}
state.options.searchBarPosition === "bottom"
? " entry-suggestion-button-with-bottom-searchbar"
: ""
}`}
onClick={startSuggestion} onClick={startSuggestion}
> >
<i className="fas fa-plus" style={{ padding: "3px" }} /> <i className="fas fa-plus" style={{ padding: "3px" }} />
</button> </button>}
)} {(inflectionResults === undefined && suggestionState === "none" && window.location.pathname === "/search") && <button
{inflectionResults === undefined &&
suggestionState === "none" &&
window.location.pathname === "/search" && (
<button
type="button" type="button"
className={`btn btn-outline-secondary bg-white conjugation-search-button${ className={`btn btn-outline-secondary bg-white conjugation-search-button${state.options.searchBarPosition === "bottom" ? " conjugation-search-button-with-bottom-searchbar" : ""}`}
state.options.searchBarPosition === "bottom"
? " conjugation-search-button-with-bottom-searchbar"
: ""
}`}
onClick={handleInflectionSearch} onClick={handleInflectionSearch}
> >
<i className={inflectionSearchIcon} style={{ padding: "3px" }} /> <i className={inflectionSearchIcon} style={{ padding: "3px" }} />
</button> </button>}
)} {inflectionResults === "searching" && <div>
{inflectionResults === "searching" && ( <p className="lead mt-1">Searching conjugations/inflections... <i className="fas fa-hourglass-half" /></p>
<div> </div>}
<p className="lead mt-1"> {inflectionResults && inflectionResults !== "searching" && <div>
Searching conjugations/inflections...{" "} <h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4>
<i className="fas fa-hourglass-half" /> {inflectionResults.exact.length === 0 && inflectionResults.fuzzy.length === 0 && <div className="mt-4">
</p> <div>No conjugation/inflection matches found for <strong>{state.searchValue}</strong></div>
</div> </div>}
)} {(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => {
{inflectionResults && inflectionResults !== "searching" && ( return (inflectionResults[t].length !== 0) ? <>
<div> <h5 className="mb-3">{t === "exact" ? "Exact" : "Approximate"} Matches</h5>
<h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4> {inflectionResults[t].map((p) => (
{inflectionResults.exact.length === 0 && <div key={p.entry.ts}>
inflectionResults.fuzzy.length === 0 && ( <Entry
<div className="mt-4"> key={p.entry.i}
<div> entry={p.entry}
No conjugation/inflection matches found for{" "} textOptions={textOptions}
<strong>{state.searchValue}</strong> isolateEntry={isolateEntry}
</div> />
</div> <div className="mb-3 ml-2">
)} {p.forms.map((form, i) => (
{(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => { <InflectionFormMatchDisplay
return inflectionResults[t].length !== 0 ? ( key={`inf-form${i}`}
<> textOptions={textOptions}
<h5 className="mb-3"> form={form}
{t === "exact" ? "Exact" : "Approximate"} Matches entry={p.entry}
</h5> />
{inflectionResults[t].map((p) => ( ))}
<div key={p.entry.ts}> </div>
<Entry </div>
key={p.entry.i} ))}
entry={p.entry} </> : null;
textOptions={textOptions} })}
isolateEntry={isolateEntry} </div>}
/> {inflectionResults === undefined && suggestionState === "none" && state.results.map((entry) => (
<div className="mb-3 ml-2">
{p.forms.map((form, i) => (
<InflectionFormMatchDisplay
key={`inf-form${i}`}
textOptions={textOptions}
form={form}
entry={p.entry}
/>
))}
</div>
</div>
))}
</>
) : null;
})}
</div>
)}
{inflectionResults === undefined && suggestionState === "none" && (
<dl>
{state.results.map((entry) => (
<Entry <Entry
key={entry.i} key={entry.i}
entry={entry} entry={entry}
textOptions={textOptions} textOptions={textOptions}
isolateEntry={isolateEntry} isolateEntry={isolateEntry}
/> />
))} ))}
</dl> {(state.user && (suggestionState === "editing")) && <div className="my-3">
)} <h5 className="mb-3">Suggest an entry for the dictionary:</h5>
{state.user && suggestionState === "editing" && ( <div className="form-group mt-4" style={{ maxWidth: "500px" }}>
<div className="my-3"> <div className="row mb-2">
<h5 className="mb-3">Suggest an entry for the dictionary:</h5> <div className="col">
<div className="form-group mt-4" style={{ maxWidth: "500px" }}> <label htmlFor="suggestionPashto">Pashto:</label>
<div className="row mb-2"> <input
<div className="col"> type="text"
<label htmlFor="suggestionPashto">Pashto:</label> className="form-control"
dir="rtl"
id="suggestionPashto"
data-lpignore="true"
value={pashto}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
onChange={(e) => setPashto(e.target.value)}
/>
</div>
<div className="col">
<label htmlFor="suggestionPhonetics">Phonetics:</label>
<input
type="text"
className="form-control"
dir="ltr"
id="suggestionPhonetics"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
data-lpignore="true"
value={phonetics}
onChange={(e) => setPhonetics(e.target.value)}
/>
</div>
</div>
<label htmlFor="suggestionEnglish">English:</label>
<input <input
type="text" type="text"
className="form-control" className="form-control mb-2"
dir="rtl" id="suggestionEnglish"
id="suggestionPashto" data-lpignore="true"
data-lpignore="true" value={english}
value={pashto} autoComplete="off"
autoComplete="off" onChange={(e) => setEnglish(e.target.value)}
autoCorrect="off"
autoCapitalize="off"
onChange={(e) => setPashto(e.target.value)}
/> />
</div> <label htmlFor="editSuggestionForm">Comments:</label>
<div className="col">
<label htmlFor="suggestionPhonetics">Phonetics:</label>
<input <input
type="text" type="text"
className="form-control" className="form-control"
dir="ltr" id="editSuggestionForm"
id="suggestionPhonetics" data-lpignore="true"
autoComplete="off" value={comment}
autoCorrect="off" onChange={(e) => setComment(e.target.value)}
autoCapitalize="off"
data-lpignore="true"
value={phonetics}
onChange={(e) => setPhonetics(e.target.value)}
/> />
</div>
</div> </div>
<label htmlFor="suggestionEnglish">English:</label> <button
<input type="button"
type="text" className="btn btn-secondary mr-3"
className="form-control mb-2" onClick={submitSuggestion}
id="suggestionEnglish" data-testid="editWordSubmitButton"
data-lpignore="true" >
value={english} Submit
autoComplete="off" </button>
onChange={(e) => setEnglish(e.target.value)} <button
/> type="button"
<label htmlFor="editSuggestionForm">Comments:</label> className="btn btn-outline-secondary"
<input onClick={cancelSuggestion}
type="text" data-testid="editWordCancelButton"
className="form-control" >
id="editSuggestionForm" Cancel
data-lpignore="true" </button>
value={comment} </div>}
onChange={(e) => setComment(e.target.value)} {suggestionState === "received" && <div className="my-3">
/> Thanks for the help!
</div> </div>
<button }
type="button" {(((inflectionResults === undefined) && suggestionState === "none" && state.searchValue && (!state.results.length))) && <div>
className="btn btn-secondary mr-3" <h5 className="mt-2">No Results Found in {state.options.language}</h5>
onClick={submitSuggestion} {state.options.language === "Pashto" && isPashtoScript(state.searchValue) && <p className="mt-3">
data-testid="editWordSubmitButton" Click on the <i className={inflectionSearchIcon} /> to search inflections and conjugations
> </p>}
Submit {state.options.searchType === "alphabetical" && <div className="mt-4 font-weight-light">
</button> <div className="mb-3">You are using alphabetical browsing mode</div>
<button <div>Click on the <span className="fa fa-book" ></span> icon above for smart search <span className="fa fa-bolt" ></span></div>
type="button" </div>}
className="btn btn-outline-secondary" </div>}
onClick={cancelSuggestion} </div>;
data-testid="editWordCancelButton"
>
Cancel
</button>
</div>
)}
{suggestionState === "received" && (
<div className="my-3">Thanks for the help!</div>
)}
{inflectionResults === undefined &&
suggestionState === "none" &&
state.searchValue &&
!state.results.length && (
<div>
<h5 className="mt-2">
No Results Found in {state.options.language}
</h5>
{state.options.language === "Pashto" &&
isPashtoScript(state.searchValue) && (
<p className="mt-3">
Click on the <i className={inflectionSearchIcon} /> to search
inflections and conjugations
</p>
)}
{state.options.searchType === "alphabetical" && (
<div className="mt-4 font-weight-light">
<div className="mb-3">
You are using alphabetical browsing mode
</div>
<div>
Click on the <span className="fa fa-book"></span> icon above
for smart search <span className="fa fa-bolt"></span>
</div>
</div>
)}
</div>
)}
</div>
);
} }
export default Results; export default Results;

View File

@ -2349,10 +2349,10 @@
"@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/sourcemap-codec" "^1.4.10"
"@lingdocs/ps-react@7.0.8": "@lingdocs/ps-react@7.0.3":
version "7.0.8" version "7.0.3"
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.0.8.tgz#4f59cf04237f2fb87054251429695bf44cb7be79" resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.0.3.tgz#9962fca6a7eeec1d9eec7938ca5fbf2c8634c228"
integrity sha512-0TefQ1EBzGeJ+U4NYnXt/DB3QDZ1M8ALY8rOJgX7pj2N275+52yL2RXS6zrjjjKa/Ebr3XVNhK680O+JQh4anQ== integrity sha512-/ASFFGudbqjEaZQ5OSHAwCQaZSdnPPInWv30rLR6ytLA+lS2udXLQHXkW+3ArUQ1UFBQlK1AC2+1tKNxvD2IrQ==
dependencies: dependencies:
"@formkit/auto-animate" "^1.0.0-beta.3" "@formkit/auto-animate" "^1.0.0-beta.3"
classnames "^2.2.6" classnames "^2.2.6"