Compare commits
No commits in common. "77018ef252b442804caeeb03873bb1cd6608f5e7" and "49b405331e7da053d9c77edc1b708bcb3637e240" have entirely different histories.
77018ef252
...
49b405331e
|
@ -9,7 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lingdocs/inflect": "7.0.3",
|
"@lingdocs/inflect": "6.0.12",
|
||||||
"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.3",
|
"version": "6.0.12",
|
||||||
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
|
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-6.0.12.tgz",
|
||||||
"integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
|
"integrity": "sha512-ljOlZ8DuhZuUs86LFioQLSNs7HlGpvPRUHNF4EWOroEfLvNoTAHRdlgoVX1ovQS3JBUN7fFWSkovG/HWYPJZOQ==",
|
||||||
"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.3",
|
"version": "6.0.12",
|
||||||
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
|
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-6.0.12.tgz",
|
||||||
"integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
|
"integrity": "sha512-ljOlZ8DuhZuUs86LFioQLSNs7HlGpvPRUHNF4EWOroEfLvNoTAHRdlgoVX1ovQS3JBUN7fFWSkovG/HWYPJZOQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fp-ts": "^2.16.0",
|
"fp-ts": "^2.16.0",
|
||||||
"pbf": "^3.2.1",
|
"pbf": "^3.2.1",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lingdocs/inflect": "7.0.3",
|
"@lingdocs/inflect": "6.0.12",
|
||||||
"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",
|
||||||
|
|
|
@ -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.3":
|
"@lingdocs/inflect@6.0.12":
|
||||||
"integrity" "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA=="
|
"integrity" "sha512-ljOlZ8DuhZuUs86LFioQLSNs7HlGpvPRUHNF4EWOroEfLvNoTAHRdlgoVX1ovQS3JBUN7fFWSkovG/HWYPJZOQ=="
|
||||||
"resolved" "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz"
|
"resolved" "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-6.0.12.tgz"
|
||||||
"version" "7.0.3"
|
"version" "6.0.12"
|
||||||
dependencies:
|
dependencies:
|
||||||
"fp-ts" "^2.16.0"
|
"fp-ts" "^2.16.0"
|
||||||
"pbf" "^3.2.1"
|
"pbf" "^3.2.1"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"name": "functions",
|
"name": "functions",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/storage": "^5.8.1",
|
"@google-cloud/storage": "^5.8.1",
|
||||||
"@lingdocs/inflect": "7.0.3",
|
"@lingdocs/inflect": "6.0.12",
|
||||||
"@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",
|
||||||
|
@ -1468,9 +1468,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lingdocs/inflect": {
|
"node_modules/@lingdocs/inflect": {
|
||||||
"version": "7.0.3",
|
"version": "6.0.12",
|
||||||
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
|
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-6.0.12.tgz",
|
||||||
"integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
|
"integrity": "sha512-ljOlZ8DuhZuUs86LFioQLSNs7HlGpvPRUHNF4EWOroEfLvNoTAHRdlgoVX1ovQS3JBUN7fFWSkovG/HWYPJZOQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fp-ts": "^2.16.0",
|
"fp-ts": "^2.16.0",
|
||||||
"pbf": "^3.2.1",
|
"pbf": "^3.2.1",
|
||||||
|
@ -8055,9 +8055,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@lingdocs/inflect": {
|
"@lingdocs/inflect": {
|
||||||
"version": "7.0.3",
|
"version": "6.0.12",
|
||||||
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
|
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-6.0.12.tgz",
|
||||||
"integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
|
"integrity": "sha512-ljOlZ8DuhZuUs86LFioQLSNs7HlGpvPRUHNF4EWOroEfLvNoTAHRdlgoVX1ovQS3JBUN7fFWSkovG/HWYPJZOQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fp-ts": "^2.16.0",
|
"fp-ts": "^2.16.0",
|
||||||
"pbf": "^3.2.1",
|
"pbf": "^3.2.1",
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"main": "lib/functions/src/index.js",
|
"main": "lib/functions/src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/storage": "^5.8.1",
|
"@google-cloud/storage": "^5.8.1",
|
||||||
"@lingdocs/inflect": "7.0.3",
|
"@lingdocs/inflect": "6.0.12",
|
||||||
"@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",
|
||||||
|
|
|
@ -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.3",
|
"@lingdocs/ps-react": "6.0.12",
|
||||||
"@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",
|
||||||
|
|
|
@ -7,39 +7,37 @@
|
||||||
*/
|
*/
|
||||||
import ExtraEntryInfo from "../components/ExtraEntryInfo";
|
import ExtraEntryInfo from "../components/ExtraEntryInfo";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Types as T, InlinePs } from "@lingdocs/ps-react";
|
import {
|
||||||
|
Types as T,
|
||||||
|
InlinePs,
|
||||||
|
} from "@lingdocs/ps-react";
|
||||||
|
|
||||||
function Entry({
|
function Entry({ entry, textOptions, nonClickable, isolateEntry }: {
|
||||||
entry,
|
entry: T.DictionaryEntry,
|
||||||
textOptions,
|
textOptions: T.TextOptions,
|
||||||
nonClickable,
|
nonClickable?: boolean,
|
||||||
isolateEntry,
|
isolateEntry?: (ts: number) => void,
|
||||||
}: {
|
|
||||||
entry: T.DictionaryEntry;
|
|
||||||
textOptions: T.TextOptions;
|
|
||||||
nonClickable?: boolean;
|
|
||||||
isolateEntry?: (ts: number) => void;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames("entry", { clickable: !nonClickable })}
|
className={classNames("entry", { clickable: !nonClickable })}
|
||||||
onClick={
|
onClick={(!nonClickable && isolateEntry) ? () => isolateEntry(entry.ts) : undefined}
|
||||||
!nonClickable && isolateEntry ? () => isolateEntry(entry.ts) : undefined
|
data-testid="entry"
|
||||||
}
|
>
|
||||||
data-testid="entry"
|
<div>
|
||||||
>
|
<strong>
|
||||||
<div>
|
<InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
||||||
<strong>
|
</strong>
|
||||||
<InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
{` `}
|
||||||
</strong>
|
<em>{entry.c}</em>
|
||||||
{` `}
|
</div>
|
||||||
<em>{entry.c}</em>
|
<ExtraEntryInfo
|
||||||
{entry.a && !nonClickable && <i className="ml-2 fas fa-volume-down" />}
|
entry={entry}
|
||||||
</div>
|
textOptions={textOptions}
|
||||||
<ExtraEntryInfo entry={entry} textOptions={textOptions} />
|
/>
|
||||||
<div className="entry-definition">{entry.e}</div>
|
<div className="entry-definition">{entry.e}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Entry;
|
export default Entry;
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
export default function playStorageAudio(ts: number, callback: () => void) {
|
|
||||||
if (!ts) return;
|
|
||||||
let audio = new Audio(`https://storage.lingdocs.com/${ts}.mp3`);
|
|
||||||
audio.addEventListener("ended", () => {
|
|
||||||
callback();
|
|
||||||
audio.remove();
|
|
||||||
audio.srcObject = null;
|
|
||||||
});
|
|
||||||
audio.play().catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
alert("Error playing audio - Connect to the internet and try again");
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -8,429 +8,334 @@
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
VPExplorer,
|
VPExplorer,
|
||||||
InflectionsTable,
|
InflectionsTable,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
Types as T,
|
Types as T,
|
||||||
typePredicates as tp,
|
typePredicates as tp,
|
||||||
getInflectionPattern,
|
getInflectionPattern,
|
||||||
HumanReadableInflectionPattern,
|
HumanReadableInflectionPattern,
|
||||||
} from "@lingdocs/ps-react";
|
} from "@lingdocs/ps-react";
|
||||||
import { submissionBase, addSubmission } from "../lib/submissions";
|
import {
|
||||||
|
submissionBase,
|
||||||
|
addSubmission,
|
||||||
|
} from "../lib/submissions";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Entry from "../components/Entry";
|
import Entry from "../components/Entry";
|
||||||
import Results from "../screens/Results";
|
import Results from "../screens/Results";
|
||||||
import WordlistWordEditor from "../components/WordlistWordEditor";
|
import WordlistWordEditor from "../components/WordlistWordEditor";
|
||||||
import {
|
import {
|
||||||
addToWordlist,
|
addToWordlist,
|
||||||
deleteWordFromWordlist,
|
deleteWordFromWordlist,
|
||||||
hasAttachment,
|
hasAttachment,
|
||||||
} from "../lib/wordlist-database";
|
} from "../lib/wordlist-database";
|
||||||
import { wordlistEnabled } from "../lib/level-management";
|
import { wordlistEnabled } from "../lib/level-management";
|
||||||
import AudioPlayButton from "../components/AudioPlayButton";
|
import AudioPlayButton from "../components/AudioPlayButton";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { Modal } from "react-bootstrap";
|
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 {
|
||||||
import { State, DictionaryAPI } from "../types/dictionary-types";
|
entryFeeder,
|
||||||
import playStorageAudio from "../components/PlayStorageAudio";
|
} from "../lib/dictionary";
|
||||||
|
import {
|
||||||
|
State,
|
||||||
|
DictionaryAPI,
|
||||||
|
} from "../types/dictionary-types";
|
||||||
|
|
||||||
function IsolatedEntry({
|
function IsolatedEntry({ state, dictionary, isolateEntry }: {
|
||||||
state,
|
state: State,
|
||||||
dictionary,
|
dictionary: DictionaryAPI,
|
||||||
isolateEntry,
|
isolateEntry: (ts: number) => void,
|
||||||
}: {
|
|
||||||
state: State;
|
|
||||||
dictionary: DictionaryAPI;
|
|
||||||
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);
|
const [showingDeleteWarning, setShowingDeleteWarning] = useState<boolean>(false);
|
||||||
const [showingDeleteWarning, setShowingDeleteWarning] =
|
const [showClipped, setShowClipped] = useState<string>("");
|
||||||
useState<boolean>(false);
|
useEffect(() => {
|
||||||
const [showClipped, setShowClipped] = useState<string>("");
|
setEditing(false);
|
||||||
useEffect(() => {
|
setComment("");
|
||||||
setEditing(false);
|
setEditSubmitted(false);
|
||||||
setComment("");
|
}, [state]);
|
||||||
setEditSubmitted(false);
|
function flashClippedMessage(m: string) {
|
||||||
setPlaying(false);
|
setShowClipped(m);
|
||||||
}, [state]);
|
setTimeout(() => {
|
||||||
function flashClippedMessage(m: string) {
|
setShowClipped("");
|
||||||
setShowClipped(m);
|
}, 1250);
|
||||||
setTimeout(() => {
|
|
||||||
setShowClipped("");
|
|
||||||
}, 1250);
|
|
||||||
}
|
|
||||||
const wordlistWord = state.wordlist.find(
|
|
||||||
(w) => w.entry.ts === state.isolatedEntry?.ts
|
|
||||||
);
|
|
||||||
const textOptions = getTextOptions(state);
|
|
||||||
function submitEdit() {
|
|
||||||
if (!state.isolatedEntry) return;
|
|
||||||
if (!state.user) return;
|
|
||||||
addSubmission(
|
|
||||||
{
|
|
||||||
...submissionBase(state.user),
|
|
||||||
type: "edit suggestion",
|
|
||||||
entry: state.isolatedEntry,
|
|
||||||
comment,
|
|
||||||
},
|
|
||||||
state.user
|
|
||||||
);
|
|
||||||
setEditing(false);
|
|
||||||
setComment("");
|
|
||||||
setEditSubmitted(true);
|
|
||||||
}
|
|
||||||
function handleAddToWordlist() {
|
|
||||||
if (!state.isolatedEntry) return;
|
|
||||||
const toAdd = {
|
|
||||||
entry: state.isolatedEntry,
|
|
||||||
notes: "",
|
|
||||||
};
|
|
||||||
addToWordlist(toAdd);
|
|
||||||
}
|
|
||||||
function handleDeleteFromWordlist() {
|
|
||||||
if (!state.isolatedEntry) return;
|
|
||||||
if (!wordlistWord) return;
|
|
||||||
setShowingDeleteWarning(false);
|
|
||||||
deleteWordFromWordlist(wordlistWord._id);
|
|
||||||
}
|
|
||||||
const entry = state.isolatedEntry;
|
|
||||||
if (!entry) {
|
|
||||||
return (
|
|
||||||
<div className="text-center">
|
|
||||||
<h4 className="mb-4 mt-4">Word not found</h4>
|
|
||||||
<h5>
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const complement = entry.l ? dictionary.findOneByTs(entry.l) : undefined;
|
|
||||||
const relatedEntries = dictionary.findRelatedEntries(entry);
|
|
||||||
const inf = ((): T.InflectorOutput | false => {
|
|
||||||
try {
|
|
||||||
return inflectWord(entry);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("error inflecting entry", entry);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
})();
|
const wordlistWord = state.wordlist.find((w) => w.entry.ts === state.isolatedEntry?.ts);
|
||||||
const isVerbEntry = tp.isVerbEntry({ entry, complement });
|
const textOptions = getTextOptions(state);
|
||||||
function DisplayVPExplorer(props: {
|
function submitEdit() {
|
||||||
entry: T.DictionaryEntry;
|
if (!state.isolatedEntry) return;
|
||||||
complement: T.DictionaryEntry | undefined;
|
if (!state.user) return;
|
||||||
}) {
|
addSubmission({
|
||||||
try {
|
...submissionBase(state.user),
|
||||||
return (
|
type: "edit suggestion",
|
||||||
<VPExplorer
|
entry: state.isolatedEntry,
|
||||||
verb={{
|
comment,
|
||||||
// TODO: CLEAN THIS UP!
|
}, state.user);
|
||||||
// @ts-ignore
|
setEditing(false);
|
||||||
entry: props.entry,
|
setComment("");
|
||||||
complement: props.complement,
|
setEditSubmitted(true);
|
||||||
}}
|
|
||||||
opts={textOptions}
|
|
||||||
entryFeeder={entryFeeder}
|
|
||||||
handleLinkClick={isolateEntry}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("error rendering VPExplorer", e);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
function handleAddToWordlist() {
|
||||||
function handleClipId() {
|
if (!state.isolatedEntry) return;
|
||||||
if (!entry) return;
|
const toAdd = {
|
||||||
navigator.clipboard.writeText(entry.ts.toString());
|
entry: state.isolatedEntry,
|
||||||
flashClippedMessage("word id copied to clipboard");
|
notes: "",
|
||||||
}
|
};
|
||||||
function handleClipEntry() {
|
addToWordlist(toAdd);
|
||||||
if (!entry) return;
|
}
|
||||||
navigator.clipboard.writeText(JSON.stringify(entry));
|
function handleDeleteFromWordlist() {
|
||||||
flashClippedMessage("entry data copied to clipboard");
|
if (!state.isolatedEntry) return;
|
||||||
}
|
if (!wordlistWord) return;
|
||||||
function handlePlayStorageAudio() {
|
setShowingDeleteWarning(false);
|
||||||
if (!entry) return;
|
deleteWordFromWordlist(wordlistWord._id);
|
||||||
setPlaying(true);
|
}
|
||||||
playStorageAudio(entry.ts, () => {
|
const entry = state.isolatedEntry;
|
||||||
setPlaying(false);
|
if (!entry) {
|
||||||
});
|
return <div className="text-center">
|
||||||
}
|
<h4 className="mb-4 mt-4">Word not found</h4>
|
||||||
return (
|
<h5><Link to="/">Home</Link></h5>
|
||||||
<div className="wide-width-limiter">
|
</div>;
|
||||||
<Helmet>
|
}
|
||||||
<title>{entry.p} - LingDocs Pashto Dictionary</title>
|
const complement = entry.l
|
||||||
</Helmet>
|
? dictionary.findOneByTs(entry.l)
|
||||||
<div className="row">
|
: undefined;
|
||||||
<div className="col-8">
|
const relatedEntries = dictionary.findRelatedEntries(entry);
|
||||||
<Entry
|
const inf = ((): T.InflectorOutput | false => {
|
||||||
nonClickable
|
try {
|
||||||
entry={exploded ? explodeEntry(entry) : entry}
|
return inflectWord(entry);
|
||||||
textOptions={textOptions}
|
} catch (e) {
|
||||||
isolateEntry={isolateEntry}
|
console.error("error inflecting entry", entry);
|
||||||
/>
|
return false;
|
||||||
</div>
|
}
|
||||||
<div className="col-4">
|
})();
|
||||||
<div className="d-flex flex-row justify-content-end">
|
const isVerbEntry = tp.isVerbEntry({ entry, complement });
|
||||||
{entry.a && (
|
function DisplayVPExplorer(props: {
|
||||||
<div className="clickable mr-3" onClick={handlePlayStorageAudio}>
|
entry: T.DictionaryEntry,
|
||||||
<i
|
complement: T.DictionaryEntry | undefined,
|
||||||
className={`fas fa-lg fa-volume-${playing ? "down" : "off"}`}
|
}) {
|
||||||
/>
|
try {
|
||||||
</div>
|
return <VPExplorer
|
||||||
)}
|
verb={{
|
||||||
<div
|
// TODO: CLEAN THIS UP!
|
||||||
className="clickable mr-3"
|
// @ts-ignore
|
||||||
onClick={() => setExploded((os) => !os)}
|
entry: props.entry,
|
||||||
>
|
complement: props.complement,
|
||||||
<i className={`fas fa-${exploded ? "compress" : "expand"}-alt`} />
|
}}
|
||||||
</div>
|
opts={textOptions}
|
||||||
<div className="clickable mr-3" onClick={handleClipId}>
|
entryFeeder={entryFeeder}
|
||||||
<i className="fas fa-tag"></i>
|
handleLinkClick={isolateEntry}
|
||||||
</div>
|
|
||||||
{state.user && state.user.level === "editor" && (
|
|
||||||
<>
|
|
||||||
<div className="clickable mr-3" onClick={handleClipEntry}>
|
|
||||||
<i className="fas fa-code"></i>
|
|
||||||
</div>
|
|
||||||
<Link to={`/edit?id=${entry.ts}`} className="plain-link">
|
|
||||||
<div
|
|
||||||
className="clickable mr-3"
|
|
||||||
data-testid="finalEditEntryButton"
|
|
||||||
>
|
|
||||||
<i className="fa fa-gavel" />
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{state.user && (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className="clickable mr-3"
|
|
||||||
data-testid="editEntryButton"
|
|
||||||
onClick={() => setEditing((os) => !os)}
|
|
||||||
>
|
|
||||||
<i className="fa fa-pen" />
|
|
||||||
</div>
|
|
||||||
{wordlistEnabled(state.user) && (
|
|
||||||
<div
|
|
||||||
className="clickable"
|
|
||||||
data-testid={
|
|
||||||
wordlistWord ? "fullStarButton" : "emptyStarButton"
|
|
||||||
}
|
|
||||||
onClick={
|
|
||||||
wordlistWord
|
|
||||||
? () => setShowingDeleteWarning(true)
|
|
||||||
: () => handleAddToWordlist()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className={`fa${wordlistWord ? "s" : "r"} fa-star fa-lg`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{wordlistWord && (
|
|
||||||
<>
|
|
||||||
{hasAttachment(wordlistWord, "audio") && (
|
|
||||||
<AudioPlayButton word={wordlistWord} />
|
|
||||||
)}
|
|
||||||
<WordlistWordEditor word={wordlistWord} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{editing && (
|
|
||||||
<div className="mb-3">
|
|
||||||
<div className="form-group" style={{ maxWidth: "500px" }}>
|
|
||||||
<label htmlFor="editSuggestionForm">Suggest correction/edit:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
id="editSuggestionForm"
|
|
||||||
data-lpignore="true"
|
|
||||||
value={comment}
|
|
||||||
onChange={(e) => setComment(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
} catch (e) {
|
||||||
<div className="btn-group">
|
console.error("error rendering VPExplorer", e);
|
||||||
<button
|
return null;
|
||||||
type="button"
|
}
|
||||||
className="btn btn-outline-secondary"
|
}
|
||||||
onClick={comment ? submitEdit : () => null}
|
function handleClipId() {
|
||||||
data-testid="editWordSubmitButton"
|
if (!entry) return
|
||||||
>
|
navigator.clipboard.writeText(entry.ts.toString());
|
||||||
Submit
|
flashClippedMessage("word id copied to clipboard");
|
||||||
</button>
|
}
|
||||||
<button
|
function handleClipEntry() {
|
||||||
type="button"
|
if (!entry) return
|
||||||
className="btn btn-outline-secondary"
|
navigator.clipboard.writeText(JSON.stringify(entry));
|
||||||
onClick={() => {
|
flashClippedMessage("entry data copied to clipboard");
|
||||||
setEditing(false);
|
}
|
||||||
setComment("");
|
return <div className="wide-width-limiter">
|
||||||
}}
|
<Helmet>
|
||||||
data-testid="editWordCancelButton"
|
<title>{entry.p} - LingDocs Pashto Dictionary</title>
|
||||||
>
|
</Helmet>
|
||||||
Cancel
|
<div className="row">
|
||||||
</button>
|
<div className="col-8">
|
||||||
</div>
|
<Entry
|
||||||
</div>
|
nonClickable
|
||||||
)}
|
entry={exploded ? explodeEntry(entry) : entry}
|
||||||
{editSubmitted && <p>Thank you for your help!</p>}
|
|
||||||
{inf && (
|
|
||||||
<>
|
|
||||||
{inf.inflections &&
|
|
||||||
(() => {
|
|
||||||
const pattern = getInflectionPattern(
|
|
||||||
// @ts-ignore
|
|
||||||
entry
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href={`https://grammar.lingdocs.com/inflection/inflection-patterns/${inflectionSubUrl(
|
|
||||||
pattern
|
|
||||||
)}`}
|
|
||||||
rel="noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<div className="badge bg-light mb-2">
|
|
||||||
Inflection pattern{" "}
|
|
||||||
{HumanReadableInflectionPattern(pattern, textOptions)}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<InflectionsTable
|
|
||||||
inf={inf.inflections}
|
|
||||||
textOptions={textOptions}
|
textOptions={textOptions}
|
||||||
/>
|
isolateEntry={isolateEntry}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-4">
|
||||||
|
<div className="d-flex flex-row justify-content-end">
|
||||||
|
<div
|
||||||
|
className="clickable mr-3"
|
||||||
|
onClick={() => setExploded(os => !os)}
|
||||||
|
>
|
||||||
|
<i className={`fas fa-${exploded ? "compress" : "expand"}-alt`} />
|
||||||
|
</div>
|
||||||
|
<div className="clickable mr-3" onClick={handleClipId}>
|
||||||
|
<i className="fas fa-tag"></i>
|
||||||
|
</div>
|
||||||
|
{state.user && state.user.level === "editor" && <>
|
||||||
|
<div className="clickable mr-3" onClick={handleClipEntry}>
|
||||||
|
<i className="fas fa-code"></i>
|
||||||
|
</div>
|
||||||
|
<Link to={`/edit?id=${entry.ts}`} className="plain-link">
|
||||||
|
<div
|
||||||
|
className="clickable mr-3"
|
||||||
|
data-testid="finalEditEntryButton"
|
||||||
|
>
|
||||||
|
<i className="fa fa-gavel" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</>}
|
||||||
|
{state.user && <>
|
||||||
|
<div
|
||||||
|
className="clickable mr-3"
|
||||||
|
data-testid="editEntryButton"
|
||||||
|
onClick={() => setEditing(os => !os)}
|
||||||
|
>
|
||||||
|
<i className="fa fa-pen" />
|
||||||
|
</div>
|
||||||
|
{wordlistEnabled(state.user) && <div
|
||||||
|
className="clickable"
|
||||||
|
data-testid={wordlistWord ? "fullStarButton" : "emptyStarButton"}
|
||||||
|
onClick={wordlistWord
|
||||||
|
? () => setShowingDeleteWarning(true)
|
||||||
|
: () => handleAddToWordlist()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className={`fa${wordlistWord ? "s" : "r"} fa-star fa-lg`}/>
|
||||||
|
</div>}
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})()}
|
|
||||||
{"plural" in inf && inf.plural !== undefined && (
|
|
||||||
<div>
|
|
||||||
<h5>Plural</h5>
|
|
||||||
<InflectionsTable inf={inf.plural} textOptions={textOptions} />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{"bundledPlural" in inf && inf.bundledPlural !== undefined && (
|
|
||||||
<div>
|
|
||||||
<h5>Bundled Plural</h5>
|
|
||||||
<InflectionsTable
|
|
||||||
inf={inf.bundledPlural}
|
|
||||||
textOptions={textOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{"arabicPlural" in inf && inf.arabicPlural !== undefined && (
|
|
||||||
<div>
|
|
||||||
<h5>Arabic Plural</h5>
|
|
||||||
<InflectionsTable
|
|
||||||
inf={inf.arabicPlural}
|
|
||||||
textOptions={textOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{isVerbEntry && (
|
|
||||||
<div className="pb-4">
|
|
||||||
<DisplayVPExplorer entry={entry} complement={complement} />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{wordlistWord && <>
|
||||||
{showClipped && (
|
{hasAttachment(wordlistWord, "audio") && <AudioPlayButton word={wordlistWord} />}
|
||||||
<div
|
<WordlistWordEditor word={wordlistWord} />
|
||||||
className="alert alert-primary text-center"
|
</>}
|
||||||
role="alert"
|
{editing &&
|
||||||
style={{
|
<div className="mb-3">
|
||||||
|
<div className="form-group" style={{ maxWidth: "500px" }}>
|
||||||
|
<label htmlFor="editSuggestionForm">Suggest correction/edit:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="editSuggestionForm"
|
||||||
|
data-lpignore="true"
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => setComment(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="btn-group">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-outline-secondary"
|
||||||
|
onClick={comment ? submitEdit : () => null}
|
||||||
|
data-testid="editWordSubmitButton"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-outline-secondary"
|
||||||
|
onClick={() => { setEditing(false); setComment("") }}
|
||||||
|
data-testid="editWordCancelButton"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{editSubmitted && <p>Thank you for your help!</p>}
|
||||||
|
{inf && <>
|
||||||
|
{inf.inflections && (() => {
|
||||||
|
const pattern = getInflectionPattern(
|
||||||
|
// @ts-ignore
|
||||||
|
entry
|
||||||
|
);
|
||||||
|
return <div>
|
||||||
|
<a href={`https://grammar.lingdocs.com/inflection/inflection-patterns/${inflectionSubUrl(pattern)}`} rel="noreferrer" target="_blank">
|
||||||
|
<div className="badge bg-light mb-2">Inflection pattern {HumanReadableInflectionPattern(pattern, textOptions)}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<InflectionsTable inf={inf.inflections} textOptions={textOptions} />
|
||||||
|
</div>;
|
||||||
|
})()}
|
||||||
|
{"plural" in inf && inf.plural !== undefined && <div>
|
||||||
|
<h5>Plural</h5>
|
||||||
|
<InflectionsTable inf={inf.plural} textOptions={textOptions} />
|
||||||
|
</div>}
|
||||||
|
{"bundledPlural" in inf && inf.bundledPlural !== undefined && <div>
|
||||||
|
<h5>Bundled Plural</h5>
|
||||||
|
<InflectionsTable inf={inf.bundledPlural} textOptions={textOptions} />
|
||||||
|
</div>}
|
||||||
|
{"arabicPlural" in inf && inf.arabicPlural !== undefined && <div>
|
||||||
|
<h5>Arabic Plural</h5>
|
||||||
|
<InflectionsTable inf={inf.arabicPlural} textOptions={textOptions} />
|
||||||
|
</div>}
|
||||||
|
</>}
|
||||||
|
{isVerbEntry && <div className="pb-4">
|
||||||
|
<DisplayVPExplorer entry={entry} complement={complement} />
|
||||||
|
</div>}
|
||||||
|
{showClipped && <div className="alert alert-primary text-center" role="alert" style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: "30%",
|
top: "30%",
|
||||||
left: "50%",
|
left: "50%",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
zIndex: 9999999999999,
|
zIndex: 9999999999999,
|
||||||
}}
|
}}>
|
||||||
>
|
{showClipped}
|
||||||
{showClipped}
|
</div>}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!(relatedEntries && relatedEntries.length) ? (
|
{!!(relatedEntries && relatedEntries.length) ? <>
|
||||||
<>
|
<h4 style={{ marginTop: isVerbEntry ? "10rem" : "5rem" }}>Related Words</h4>
|
||||||
<h4 style={{ marginTop: isVerbEntry ? "10rem" : "5rem" }}>
|
<Results
|
||||||
Related Words
|
state={{ ...state, results: relatedEntries }}
|
||||||
</h4>
|
isolateEntry={isolateEntry}
|
||||||
<Results
|
handleInflectionSearch={() => null}
|
||||||
state={{ ...state, results: relatedEntries }}
|
/>
|
||||||
isolateEntry={isolateEntry}
|
</> : <div style={{ height: "500px" }} />}
|
||||||
handleInflectionSearch={() => null}
|
<Modal
|
||||||
/>
|
show={showingDeleteWarning}
|
||||||
</>
|
onHide={() => setShowingDeleteWarning(false)}
|
||||||
) : (
|
animation={false}
|
||||||
<div style={{ height: "500px" }} />
|
>
|
||||||
)}
|
<Modal.Header closeButton>
|
||||||
<Modal
|
<Modal.Title>Delete from wordlist?</Modal.Title>
|
||||||
show={showingDeleteWarning}
|
</Modal.Header>
|
||||||
onHide={() => setShowingDeleteWarning(false)}
|
<Modal.Body>Delete <InlinePs
|
||||||
animation={false}
|
opts={textOptions}
|
||||||
>
|
>{{ p: entry.p, f: entry.f }}</InlinePs> from your wordlist?
|
||||||
<Modal.Header closeButton>
|
</Modal.Body>
|
||||||
<Modal.Title>Delete from wordlist?</Modal.Title>
|
<Modal.Footer>
|
||||||
</Modal.Header>
|
<button type="button" className="btn btn-secorndary clb" onClick={() => setShowingDeleteWarning(false)}>
|
||||||
<Modal.Body>
|
Cancel
|
||||||
Delete{" "}
|
</button>
|
||||||
<InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs>{" "}
|
<button type="button" data-testid="confirmDeleteFromWordlist" className="btn btn-primary clb" onClick={handleDeleteFromWordlist}>
|
||||||
from your wordlist?
|
Delete
|
||||||
</Modal.Body>
|
</button>
|
||||||
<Modal.Footer>
|
</Modal.Footer>
|
||||||
<button
|
</Modal>
|
||||||
type="button"
|
</div>;
|
||||||
className="btn btn-secorndary clb"
|
|
||||||
onClick={() => setShowingDeleteWarning(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-testid="confirmDeleteFromWordlist"
|
|
||||||
className="btn btn-primary clb"
|
|
||||||
onClick={handleDeleteFromWordlist}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function explodeEntry(entry: T.DictionaryEntry): T.DictionaryEntry {
|
function explodeEntry(entry: T.DictionaryEntry): T.DictionaryEntry {
|
||||||
return {
|
return {
|
||||||
...entry,
|
...entry,
|
||||||
p: entry.p.split("").join(" "),
|
p: entry.p.split("").join(" "),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function inflectionSubUrl(pattern: T.InflectionPattern): string {
|
function inflectionSubUrl(pattern: T.InflectionPattern): string {
|
||||||
return pattern === 0
|
return pattern === 0
|
||||||
? ""
|
? ""
|
||||||
: pattern === 1
|
: pattern === 1
|
||||||
? "#1-basic"
|
? "#1-basic"
|
||||||
: pattern === 2
|
: pattern === 2
|
||||||
? "#2-words-ending-in-an-unstressed-ی---ey"
|
? "#2-words-ending-in-an-unstressed-ی---ey"
|
||||||
: pattern === 3
|
: pattern === 3
|
||||||
? "#3-words-ending-in-a-stressed-ی---éy"
|
? "#3-words-ending-in-a-stressed-ی---éy"
|
||||||
: pattern === 4
|
: pattern === 4
|
||||||
? "#4-words-with-the-pashtoon-pattern"
|
? "#4-words-with-the-pashtoon-pattern"
|
||||||
: pattern === 5
|
: pattern === 5
|
||||||
? "#5-shorter-words-that-squish"
|
? "#5-shorter-words-that-squish"
|
||||||
: // : pattern === 6
|
// : pattern === 6
|
||||||
"#6-inanimate-feminine-nouns-ending-in-ي---ee";
|
: "#6-inanimate-feminine-nouns-ending-in-ي---ee"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IsolatedEntry;
|
export default IsolatedEntry;
|
|
@ -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.3":
|
"@lingdocs/ps-react@6.0.12":
|
||||||
version "7.0.3"
|
version "6.0.12"
|
||||||
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.0.3.tgz#9962fca6a7eeec1d9eec7938ca5fbf2c8634c228"
|
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-6.0.12.tgz#526a962c06ec59bc86884936e8c5bbea43f64461"
|
||||||
integrity sha512-/ASFFGudbqjEaZQ5OSHAwCQaZSdnPPInWv30rLR6ytLA+lS2udXLQHXkW+3ArUQ1UFBQlK1AC2+1tKNxvD2IrQ==
|
integrity sha512-ncOccEYU40woDK7ChyBwtdyiIKrWqAVIlTiIeVGkKa1w6mq14P02SZFCvbqnjJ54pBIK8r0VXRj0LcYkmZn4rw==
|
||||||
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"
|
||||||
|
|
Loading…
Reference in New Issue