update website

This commit is contained in:
adueck 2022-10-24 13:19:01 +05:00
parent d54b308a88
commit bdcb069208
15 changed files with 743 additions and 277 deletions

View File

@ -11,9 +11,11 @@ Also includes the [Pashto Verb Explorer](https://verbs.lingdocs.com) website.
This library is **published as two libraries**: This library is **published as two libraries**:
- @lingdocs/inflect - @lingdocs/inflect
- `/src/components`
- The core inflection engine with grammatical information and tools for processing LingDocs dictionary entries and Pashto text. - The core inflection engine with grammatical information and tools for processing LingDocs dictionary entries and Pashto text.
- Can be used with Node 16, as CommonJS - Can be used with Node 16, as CommonJS
- @lingdocs/ps-react - @lingdocs/ps-react
- `/src/lib`
- @lingdocs/inflect plus react components for displaying Pashto text, phrase engine UI etc. - @lingdocs/inflect plus react components for displaying Pashto text, phrase engine UI etc.
- Only available as an ES6 Module - Only available as an ES6 Module

View File

@ -5,28 +5,28 @@
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta name="description" content="A playground for exploring Pashto verbs with all their forms and conjugations" /> <meta name="description" content="An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<meta name="keywords" content="Pashto, Verbs, Conjugation, Grammar, Linguistics" /> <meta name="keywords" content="Pashto, Verbs, Conjugation, Grammar, Linguistics" />
<meta name="author" content="lingdocs.com" /> <meta name="author" content="lingdocs.com" />
<link rel="canonical" href="https://verbs.lingdocs.com/" /> <link rel="canonical" href="https://pashto-inflector.lingdocs.com/" />
<meta property="og:site_name" content="Pashto Verb Explorer"/> <meta property="og:site_name" content="Pashto Inflector"/>
<meta property="og:title" content="Pashto Verb Explorer"> <meta property="og:title" content="Pashto Inflector">
<meta property="og:description" content="A playground for exploring Pashto verbs with all their forms and conjugations"> <meta property="og:description" content="An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more">
<meta property="og:image" content="https://verbs.lingdocs.com/android-chrome-512x512.png"> <meta property="og:image" content="https://pashto-inflector.lingdocs.com/android-chrome-512x512.png">
<meta property="og:url" content="https://verbs.lingdocs.com/"> <meta property="og:url" content="https://pashto-inflector.lingdocs.com/">
<meta property="og:author" content="lingdocs.com"> <meta property="og:author" content="lingdocs.com">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta name="twitter:title" content="Pashto Verb Explorer"> <meta name="twitter:title" content="Pashto Inflector">
<meta name="twitter:description" content="A playground for exploring Pashto verbs with all their forms and conjugations"> <meta name="twitter:description" content="An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more">
<meta name="twitter:image" content="https://verbs.lingdocs.com/android-chrome-512x512.png"> <meta name="twitter:image" content="https://pashto-inflector.lingdocs.com/android-chrome-512x512.png">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@lingdocs" /> <meta name="twitter:creator" content="@lingdocs" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Pashto Verb Explorer</title> <title>Pashto Inflector</title>
</head> </head>
<body class="d-flex flex-column h-100" id="root"> <body class="d-flex flex-column h-100" id="root">
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -6,261 +6,97 @@
* *
*/ */
import { useEffect } from "react"; import { useEffect, useState } from "react";
import verbs from "./verbs";
import nounsAdjs from "./nouns-adjs";
import Pashto from "./components/src/Pashto";
import Phonetics from "./components/src/Phonetics";
import { getVerbInfo } from "./lib/src/verb-info";
import ButtonSelect from "./components/src/ButtonSelect"; import ButtonSelect from "./components/src/ButtonSelect";
import {
clamp
} from "./lib/src/p-text-helpers";
import {
randomNumber,
} from "./lib/src/misc-helpers";
import { import {
Modal Modal
} from "react-bootstrap"; } from "react-bootstrap";
import * as T from "./types"; import * as T from "./types";
import { isAdjectiveEntry, isAdverbEntry, isLocativeAdverbEntry, isNounEntry } from "./lib/src/type-predicates";
import defualtTextOptions from "./lib/src/default-text-options"; import defualtTextOptions from "./lib/src/default-text-options";
import PhraseBuilder from "./components/src/vp-explorer/VPExplorer";
import useStickyState from "./components/src/useStickyState"; import useStickyState from "./components/src/useStickyState";
import EPExplorer from "./components/src/ep-explorer/EPExplorer"; import EPExplorer from "./components/src/ep-explorer/EPExplorer";
import VPBuilderDemo from "./demo-components/VPBuilderDemo";
type VerbType = "simple" | "stative compound" | "dynamic compound"; import { entryFeeder } from "./demo-components/entryFeeder";
const verbTypes: VerbType[] = [ import { Hider } from "./components/library";
"simple", import InflectionDemo from "./demo-components/InflectionDemo";
"stative compound", import SpellingDemo from "./demo-components/SpellingDemo";
"dynamic compound",
];
const nouns = nounsAdjs.filter(isNounEntry);
const adjectives = nounsAdjs.filter(isAdjectiveEntry);
const locativeAdverbs = nounsAdjs.filter(isLocativeAdverbEntry);
const adverbs = nounsAdjs.filter(isAdverbEntry);
const entryFeeder: T.EntryFeeder = {
locativeAdverbs,
nouns,
adjectives,
verbs,
adverbs,
};
const transitivities: T.Transitivity[] = [
"transitive",
"intransitive",
"grammatically transitive",
];
const allVerbs = verbs.map((v: { entry: T.DictionaryEntry, complement?: T.DictionaryEntry }) => ({
verb: v,
info: getVerbInfo(v.entry, v.complement),
}));
function App() { function App() {
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing");
const [transitivityShowing, setTransitivityShowing] = useStickyState<T.Transitivity>("intransitive", "transitivityShowing1");
const [showingTextOptions, setShowingTextOptions] = useStickyState<boolean>(false, "showTextOpts1"); const [showingTextOptions, setShowingTextOptions] = useStickyState<boolean>(false, "showTextOpts1");
const [textOptions, setTextOptions] = useStickyState<T.TextOptions>(defualtTextOptions, "textOpts1"); const [textOptions, setTextOptions] = useStickyState<T.TextOptions>(defualtTextOptions, "textOpts1");
const [theme, setTheme] = useStickyState<"light" | "dark">("light", "theme1"); const [theme, setTheme] = useStickyState<"light" | "dark">("light", "theme1");
// const onlyGrammTrans = (arr: Transitivity[]) => ( const [showing, setShowing] = useState<string>("");
// arr.length === 1 && arr[0] === "grammatically transitive" function handleHiderClick(label: string) {
// ); setShowing(os => os === label
// const ensureSimpleVerbTypeSelected = () => { ? ""
// if (!verbTypesShowing.includes["simple"]) { : label);
// setVerbTypesShowing([...verbTypesShowing, "simple"]); }
// }
// }
useEffect(() => { useEffect(() => {
document.documentElement.setAttribute("data-theme", theme); document.documentElement.setAttribute("data-theme", theme);
}, [theme]) }, [theme]);
const handleVerbIndexChange = (e: any) => {
setVerbTs(parseInt(e.target.value));
}
const handleTypeSelection = (e: any) => {
const type = e.target.value as VerbType;
if (type === "dynamic compound") {
setTransitivityShowing("transitive");
}
if (type === "stative compound" && transitivityShowing === "grammatically transitive") {
setTransitivityShowing("transitive");
}
setVerbTypeShowing(type);
}
const handleTransitivitySelection = (e: any) => {
const transitivity = e.target.value as T.Transitivity;
if (transitivity === "grammatically transitive") {
setVerbTypeShowing("simple");
}
if (transitivity === "intransitive" && verbTypeShowing === "dynamic compound") {
setTransitivityShowing("transitive");
return;
}
setTransitivityShowing(e.target.value as T.Transitivity);
}
const verbsAvailable = allVerbs.filter((verb) => (
(
(verb.info.type === "transitive or grammatically transitive simple" && verbTypeShowing === "simple") && (transitivityShowing === "transitive" || transitivityShowing === "grammatically transitive")
) ||
((
verbTypeShowing === verb.info.type ||
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or generative stative compound") ||
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or generative stative compound")
)
&& (
transitivityShowing === verb.info.transitivity
))
)).sort((a, b) => a.verb.entry.p.localeCompare(b.verb.entry.p, "ps"));
const v = (() => {
const vFound = verbsAvailable.find(v => v.verb.entry.ts === verbTs);
if (vFound) return vFound;
if (verbsAvailable.length === 0) return undefined;
const vTopOfList = verbsAvailable[0];
setVerbTs(vTopOfList.verb.entry.ts);
return vTopOfList;
})();
const pickRandomVerb = () => {
let newIndex: number;
do {
newIndex = randomNumber(0, verbsAvailable.length);
} while(verbsAvailable[newIndex].verb.entry.ts === verbTs);
setVerbTs(verbsAvailable[newIndex].verb.entry.ts);
};
const makeVerbLabel = (entry: T.DictionaryEntry): string => (
`${entry.p} - ${clamp(entry.e, 20)}`
);
return <> return <>
<main className="flex-shrink-0 mb-4"> <main className="flex-shrink-0 mb-4">
<div className="container" style={{ maxWidth: "800px" }}> <div className="container" style={{ maxWidth: "800px" }}>
<div style={{ position: "absolute", top: "1.5rem", right: "1.5rem", display: "flex", flexDirection: "row" }}> <div style={{ position: "absolute", top: "1.5rem", right: "1.5rem", display: "flex", flexDirection: "row" }}>
<div <div
className="clickable mr-3" className="clickable mr-3"
onClick={() => setShowingTextOptions(true)}
>
<i className="fa-lg fas fa-cog" />
</div>
<div
className="clickable"
onClick={() => setTheme(theme === "light" ? "dark" : "light")} onClick={() => setTheme(theme === "light" ? "dark" : "light")}
> >
<i className={`fa-lg fas fa-${theme === "light" ? "sun" : "moon"}`} /> <i className={`fa-lg fas fa-${theme === "light" ? "sun" : "moon"}`} />
</div> </div>
<div
className="clickable"
onClick={() => setShowingTextOptions(true)}
>
<i className="fa-lg fas fa-cog" />
</div>
</div> </div>
<div className="text-center" style={{ marginTop: "3rem", marginBottom: "1rem" }}> <div className="text-center mb-4" style={{ marginTop: "3rem", marginBottom: "1rem" }}>
<h1 className="display-4 mt-2">Pashto Verb Explorer</h1> <h1 className="display-4 mt-2"><code>Pashto Inflector</code></h1>
<p>by <a href="https://www.lingdocs.com">LingDocs</a></p> <p className="lead my-3" style={{ maxWidth: "600px", margin: "0 auto" }}>
<p className="lead my-4"> An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more
Each form is made from one simple <samp>formula</samp> which <a href="https://www.lingdocs.com/blog/pashto-verbs-master-chart">works for all verbs</a>. 👨🔬
</p> </p>
<p>Choose a verb 👇, look at its roots and stems 🌳, see how all the forms are made. 🤓</p> <p>Used in the <a href="https://dictionary.lingdocs.com">LingDocs Pashto Dictionary</a> and <a href="https://grammar.lingdocs.com">LingDocs Pashto Grammar</a></p>
<p>by <a href="https://adueck.github.io">Adam Dueck</a> - <a href="https://github.com/lingdocs/pashto-inflector">Source Code</a> on GitHub</p>
</div> </div>
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}> <h2 className="mb-3">Demos:</h2>
<div className="card-body"> <Hider
<div className="row"> label="Verb Conjugation / Verb Phrase Engine"
<div className="col-sm-6"> hLevel={3}
{v ? showing={showing === "verbs"}
<div> handleChange={() => handleHiderClick("verbs")}
<div className="mb-1">Select a verb:</div> >
<div className="input-group"> <VPBuilderDemo opts={textOptions} />
<select className="custom-select" value={verbTs} onChange={handleVerbIndexChange}> </Hider>
{verbsAvailable.length <Hider
? verbsAvailable.map((v, i) => ( label="Equative Phrase Engine"
<option value={v.verb.entry.ts} key={i} dir="ltr"> hLevel={3}
{"\u200E"}{makeVerbLabel(v.verb.entry)} showing={showing === "equatives"}
</option> handleChange={() => handleHiderClick("equatives")}
)) >
: <option>Select a verb type</option> <EPExplorer
}
</select>
<div className="input-group-append">
<button className="btn btn-secondary" onClick={pickRandomVerb}>
<i className="fas fa-random" />
</button>
</div>
</div>
<div className="my-3">
<div>
<strong>
<Pashto opts={textOptions}>{v.verb.entry}</Pashto>
{` `}-{` `}
<Phonetics opts={textOptions}>{v.verb.entry}</Phonetics>
</strong>
{` `}
<em>{v.verb.entry.c}</em>
</div>
<div className="ml-3">{v.verb.entry.e}</div>
</div>
</div>
: <div className="alert alert-warning mb-4" role="alert">
No such verbs available...
</div>
}
</div>
<div className="col-sm-6">
<h6>Verb type:</h6>
<div onChange={handleTypeSelection}>
{verbTypes.map((type) => (
<div key={type} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={verbTypeShowing === type}
value={type}
onChange={() => null}
/>
<label className="form-check-label">
{type}
</label>
</div>
))}
</div>
<h6 className="mt-2">Transitivity:</h6>
<div onChange={handleTransitivitySelection}>
{transitivities.map((transitivity) => (
<div key={transitivity} className="form-check">
<input
className="form-check-input"
type="radio"
name="transitivity"
checked={transitivityShowing === transitivity}
onChange={() => null}
value={transitivity}
/>
<label className="form-check-label">
{transitivity}
</label>
</div>
))}
</div>
</div>
</div>
</div>
</div>
{v?.verb.entry && <div style={{ paddingBottom: "100px" }}>
<PhraseBuilder
handleLinkClick="none"
verb={v.verb as T.VerbEntry}
entryFeeder={entryFeeder}
opts={textOptions} opts={textOptions}
entryFeeder={entryFeeder}
/> />
</div>} </Hider>
<h4>Equative Phrase Builder</h4> <Hider
<EPExplorer label="Inflection Engine"
opts={textOptions} hLevel={3}
entryFeeder={entryFeeder} showing={showing === "inflection"}
/> handleChange={() => handleHiderClick("inflection")}
>
<InflectionDemo opts={textOptions} />
</Hider>
<Hider
label="Spelling Conversion / Diacritics Engine"
hLevel={3}
showing={showing === "spelling"}
handleChange={() => handleHiderClick("spelling")}
>
<SpellingDemo opts={textOptions} onChange={setTextOptions} />
</Hider>
</div> </div>
</main> </main>
<Modal show={showingTextOptions} onHide={() => setShowingTextOptions(false)}> <Modal show={showingTextOptions} onHide={() => setShowingTextOptions(false)}>
@ -308,7 +144,7 @@ function App() {
{ label: "LingDocs", value: "lingdocs" }, { label: "LingDocs", value: "lingdocs" },
{ label: "IPA", value: "ipa" }, { label: "IPA", value: "ipa" },
{ label: "ALAC", value: "alalc" }, { label: "ALAC", value: "alalc" },
{ label: "None", value: "none" }, // { label: "None", value: "none" },
]} ]}
value={textOptions.phonetics} value={textOptions.phonetics}
handleChange={(p) => setTextOptions({ ...textOptions, phonetics: p })} handleChange={(p) => setTextOptions({ ...textOptions, phonetics: p })}
@ -320,11 +156,11 @@ function App() {
</button> </button>
</Modal.Footer> </Modal.Footer>
</Modal> </Modal>
<footer className="footer mt-auto py-3" style={{ backgroundColor: "#f5f5f5" }}> {/* <footer className="footer mt-auto py-3" style={{ backgroundColor: "#f5f5f5" }}>
<div className="container"> <div className="container">
<span className="text-muted">Copyright © 2020 <a href="https://www.lingdocs.com">lingdocs.com</a> all rights reserverd</span> <span className="text-muted">Copyright © 2022 <a href="https://www.lingdocs.com">lingdocs.com</a> - <a href="https://github.com/lingdocs/pashto-inflector/blob/master/LICENSE">MIT License</a></span>
</div> </div>
</footer> </footer> */}
</> </>
} }

View File

@ -101,7 +101,7 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
<Select <Select
styles={customStyles} styles={customStyles}
isSearchable={true} isSearchable={true}
value={value} value={value || null}
// @ts-ignore - gets messed up when using customStyles // @ts-ignore - gets messed up when using customStyles
onChange={onChange} onChange={onChange}
className="mb-2" className="mb-2"

View File

@ -6,9 +6,10 @@
* *
*/ */
import { createElement } from "react"; import { createElement, useEffect, useRef } from "react";
import classNames from "classnames"; import classNames from "classnames";
import * as T from "../../types"; import * as T from "../../types";
import autoAnimate from "@formkit/auto-animate";
const caretRight = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-caret-right-fill" viewBox="0 0 16 16"> const caretRight = <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-caret-right-fill" viewBox="0 0 16 16">
<path d="M12.14 8.753l-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/> <path d="M12.14 8.753l-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/>
@ -29,6 +30,10 @@ function Hider(props: {
hLevel?: number, hLevel?: number,
ignore?: boolean, ignore?: boolean,
}) { }) {
const parent = useRef<HTMLDivElement>(null);
useEffect(() => {
parent.current && autoAnimate(parent.current);
}, [parent]);
const hLev = Math.min((props.hLevel ? props.hLevel : defaultLevel), 6); const hLev = Math.min((props.hLevel ? props.hLevel : defaultLevel), 6);
const extraMargin = (props.hLevel && (props.hLevel > indentAfterLevel)) const extraMargin = (props.hLevel && (props.hLevel > indentAfterLevel))
? `ml-${(props.hLevel - indentAfterLevel) + 1}` ? `ml-${(props.hLevel - indentAfterLevel) + 1}`
@ -38,7 +43,7 @@ function Hider(props: {
{props.children} {props.children}
</>; </>;
} }
return <div className="mb-3"> return <div className="mb-3" ref={parent}>
{createElement( {createElement(
`h${hLev}`, `h${hLev}`,
{ {

View File

@ -6,53 +6,56 @@
* *
*/ */
import { useState } from "react"; // TODO: REMOVING THE EXPLANATION MODAL BC IT'S BUGGY NOW SAYNG "ABOUT ARABIC PLURAL" AND IT'S
import Pashto from "./Pashto"; // PROB BETTER JUST TO LINK TO THE GRAMMAR
import { Modal } from "react-bootstrap";
// import { useState } from "react";
// import Pashto from "./Pashto";
// import { Modal } from "react-bootstrap";
import TableCell from "./TableCell"; import TableCell from "./TableCell";
import * as T from "../../types"; import * as T from "../../types";
import { isPluralInflections } from "../../lib/src/p-text-helpers"; import { isPluralInflections } from "../../lib/src/p-text-helpers";
const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => { // const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => {
const isPluralInfs = isPluralInflections(inf); // const isPluralInfs = isPluralInflections(inf);
const w = "masc" in inf // const w = "masc" in inf
? inf.masc[0][0] // ? inf.masc[0][0]
: inf.fem[0][0]; // : inf.fem[0][0];
return isPluralInfs ? null : <> // return isPluralInfs ? null : <>
<p>In Pashto, <strong>nouns, pronouns, and adjectives</strong> get inflected when they are either:</p> // <p>In Pashto, <strong>nouns, pronouns, and adjectives</strong> get inflected when they are either:</p>
<ul> // <ul>
<li>Plural</li> // <li>Plural</li>
<li>Sandwiched with a preposition/postposition (oblique), or</li> // <li>Sandwiched with a preposition/postposition (oblique), or</li>
<li>The subject of a transitive past tense verb</li> // <li>The subject of a transitive past tense verb</li>
</ul> // </ul>
<p>Whatever the reason, the inflection looks the same.</p> // <p>Whatever the reason, the inflection looks the same.</p>
<h5>Double Inflection</h5> // <h5>Double Inflection</h5>
<p>If there are <em>2 reasons</em> to inflect (ie. if a noun is plural <em>and</em> the subject of a transitive past tense verb) then you use the <strong>second inflection</strong>.</p> // <p>If there are <em>2 reasons</em> to inflect (ie. if a noun is plural <em>and</em> the subject of a transitive past tense verb) then you use the <strong>second inflection</strong>.</p>
<h5>Notes:</h5> // <h5>Notes:</h5>
<p><small>Pronouns don't get inflected for being plural. Instead, they have a seperate plural form.</small></p> // <p><small>Pronouns don't get inflected for being plural. Instead, they have a seperate plural form.</small></p>
<p><small>Not all nouns, pronouns, and adjectives can inflect. But if you're seeing this table here, it means that <Pashto opts={textOptions}>{w}</Pashto> does inflect.</small></p> // <p><small>Not all nouns, pronouns, and adjectives can inflect. But if you're seeing this table here, it means that <Pashto opts={textOptions}>{w}</Pashto> does inflect.</small></p>
<p><small>Irregular nouns like پښتون or مېلمه often only take the 1st inflection when they're plural, and not for the other two reasons, depending on dialect. When there are two reasons to inflect, these will always take the double inflection.</small></p> // <p><small>Irregular nouns like پښتون or مېلمه often only take the 1st inflection when they're plural, and not for the other two reasons, depending on dialect. When there are two reasons to inflect, these will always take the double inflection.</small></p>
<p><small>For prepositional/postpositional sandwiches of location like په ... کې and په ... باندې the first inflection of nouns (not of adjectives/pronouns) often doesn't happen. The second one always will though.</small></p> // <p><small>For prepositional/postpositional sandwiches of location like په ... کې and په ... باندې the first inflection of nouns (not of adjectives/pronouns) often doesn't happen. The second one always will though.</small></p>
</>; // </>;
} // }
const InflectionTable = ({ inf, textOptions, hideTitle }: { const InflectionTable = ({ inf, textOptions, hideTitle }: {
inf: T.Inflections | T.PluralInflections, inf: T.Inflections | T.PluralInflections,
textOptions: T.TextOptions, textOptions: T.TextOptions,
hideTitle?: boolean, hideTitle?: boolean,
}) => { }) => {
const [showingExplanation, setShowingExplanation] = useState(false); // const [showingExplanation, setShowingExplanation] = useState(false);
/* istanbul ignore next */ // Insanely can't see the modal to close it /* istanbul ignore next */ // Insanely can't see the modal to close it
const handleCloseExplanation = () => setShowingExplanation(false); // const handleCloseExplanation = () => setShowingExplanation(false);
const handleShowExplanation = () => setShowingExplanation(true); // const handleShowExplanation = () => setShowingExplanation(true);
const isPluralInfs = isPluralInflections(inf); const isPluralInfs = isPluralInflections(inf);
return ( return (
<div className={!hideTitle ? "" : "mt-4"}> <div className={!hideTitle ? "" : "mt-4"}>
{!hideTitle && <div style={{ display: "flex", justifyContent: !isPluralInfs ? "space-between" : "left" }}> {!hideTitle && <div style={{ display: "flex", justifyContent: !isPluralInfs ? "space-between" : "left" }}>
{!isPluralInfs && <h5>Inflections</h5>} {!isPluralInfs && <h5>Inflections</h5>}
{!isPluralInfs && <div className="clickable mr-2" onClick={handleShowExplanation} data-testid="help-button"> {/* {!isPluralInfs && <div className="clickable mr-2" onClick={handleShowExplanation} data-testid="help-button">
<i className={`fa fa-question-circle`}></i> <i className={`fa fa-question-circle`}></i>
</div>} </div>} */}
</div>} </div>}
<table className="table" style={{ tableLayout: "fixed" }}> <table className="table" style={{ tableLayout: "fixed" }}>
<thead> <thead>
@ -72,7 +75,7 @@ const InflectionTable = ({ inf, textOptions, hideTitle }: {
))} ))}
</tbody> </tbody>
</table> </table>
{(!hideTitle && !isPluralInfs) && <Modal show={showingExplanation} onHide={handleCloseExplanation}> {/* {(!hideTitle && !isPluralInfs) && <Modal show={showingExplanation} onHide={handleCloseExplanation}>
<Modal.Header closeButton> <Modal.Header closeButton>
<Modal.Title>About {isPluralInfs ? "Inflections" : "Arabic Plural"}</Modal.Title> <Modal.Title>About {isPluralInfs ? "Inflections" : "Arabic Plural"}</Modal.Title>
</Modal.Header> </Modal.Header>
@ -82,7 +85,7 @@ const InflectionTable = ({ inf, textOptions, hideTitle }: {
Close Close
</button> </button>
</Modal.Footer> </Modal.Footer>
</Modal>} </Modal>} */}
</div> </div>
); );
}; };

View File

@ -0,0 +1,201 @@
import React, { useState } from "react";
import * as T from "../types";
import InlinePs from "../components/src/InlinePs";
import EntrySelect from "../components/src/EntrySelect";
import * as tp from "../lib/src/type-predicates";
import nounsAdjsUnsafe from "../nouns-adjs";
import { inflectWord } from "../lib/src/pashto-inflector";
import { getInflectionPattern } from "../lib/src/inflection-pattern";
import InflectionsTable from "../components/src/InflectionsTable";
import HumanReadableInflectionPattern from "../components/src/HumanReadableInflectionPattern";
import leftChevron from "./chevron_left-24px.svg";
import rightChevron from "./chevron_right-24px.svg";
const chevStyle = {
height: "2rem",
width: "2rem",
color: "#ccc",
};
const nounsAdjs = nounsAdjsUnsafe
.filter(x => tp.isNounEntry(x) || tp.isAdjectiveEntry(x)) as (T.NounEntry | T.AdjectiveEntry)[];
function InflectionDemo({ opts }: {
opts: T.TextOptions,
}) {
const [pattern, setPattern] = useState<T.InflectionPattern | "all">("all");
const [word, setWord] = useState<T.NounEntry | T.AdjectiveEntry | undefined>(undefined)
const patterns: {
value: T.InflectionPattern | "all",
label: JSX.Element | string,
}[] = [
{
value: "all",
label: "all types",
},
{
value: 0,
label: "no inflection",
}, {
value: 1,
label: "basic",
}, {
value: 2,
label: <>unstressed <InlinePs opts={opts}>{{ p: "ی", f: "ey" }}</InlinePs></>,
}, {
value: 3,
label: <>stressed <InlinePs opts={opts}>{{ p: "ی", f: "éy" }}</InlinePs></>,
}, {
value: 4,
label: '"Pashtoon" pattern',
}, {
value: 5,
label: "short squish",
}, {
value: 6,
label: <>fem. inan. <InlinePs opts={opts}>{{ p: "ي", f: "ee" }}</InlinePs></>,
},
];
const entries = (nounsAdjs as (T.NounEntry | T.AdjectiveEntry)[]).filter(tp.isPattern(pattern))
function handlePatternChange(e: React.ChangeEvent<HTMLInputElement>) {
const v = e.target.value;
const value = v === "all" ? v : (Number(v) as T.InflectionPattern);
setPattern(value);
console.log({ word });
if (word && !tp.isPattern(value)(word)) {
setWord(undefined);
}
}
function wordForward() {
if (!word) {
setWord(entries[0]);
return;
}
const oldIndex = entries.findIndex(e => e.ts === word.ts);
const newIndex = entries.length === (oldIndex + 1)
? 0 : (oldIndex + 1);
setWord(entries[newIndex]);
}
function wordBack() {
if (!word) {
setWord(entries[entries.length - 1]);
return;
}
const oldIndex = entries.findIndex(e => e.ts === word.ts);
const newIndex = oldIndex === 0
? (entries.length - 1) : (oldIndex + 1);
setWord(entries[newIndex]);
}
const inf = ((): T.InflectorOutput | false => {
if (!word) return false;
try {
return inflectWord(word);
} catch (e) {
console.error("error inflecting entry", word);
return false;
}
})();
return <div>
<p>
Produces the inflections and plural forms (Pashto and Arabic plurals where applicable) for words
</p>
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}>
<div className="card-body">
<div className="row">
<div className="col-sm-6">
<div>
<h6 className="my-2">Filter words by <a href="https://grammar.lingdocs.com/inflection/inflection-patterns/">inflection pattern</a>:</h6>
<div>
{patterns.map(({ value, label}) => (
<div key={value} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={pattern === value}
value={value}
onChange={handlePatternChange}
/>
<label className="form-check-label">
{label}
</label>
</div>
))}
</div>
</div>
</div>
<div className="col-sm-6">
<h6 className="my-2">Select word:</h6>
<EntrySelect
entryFeeder={entries}
value={word}
onChange={setWord}
opts={opts}
name="word-select"
// @ts-ignore
style={{ width: "18rem" }}
/>
<div
className="d-flex flex-row justify-content-between align-items-center"
style={{ width: "18rem" }}
>
<img
src={leftChevron}
className="clickable"
style={chevStyle}
alt={"previous"}
onClick={wordBack}
/>
<img
src={rightChevron}
className="clickable"
style={chevStyle}
onClick={wordForward}
alt={"next"}
/>
</div>
</div>
</div>
</div>
</div>
{inf ? <div className="mt-3">
{inf.inflections && word && (() => {
const pattern = getInflectionPattern(word);
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, opts)}
</div>
</a>
<InflectionsTable inf={inf.inflections} textOptions={opts} />
</div>;
})()}
{"plural" in inf && inf.plural !== undefined && <div>
<h5>Plural</h5>
<InflectionsTable inf={inf.plural} textOptions={opts} />
</div>}
{"arabicPlural" in inf && inf.arabicPlural !== undefined && <div>
<h5>Arabic Plural</h5>
<InflectionsTable inf={inf.arabicPlural} textOptions={opts} />
</div>}
</div> : <div>
</div>}
</div>;
}
function inflectionSubUrl(pattern: T.InflectionPattern): string {
return pattern === 0
? ""
: pattern === 1
? "#1-basic"
: pattern === 2
? "#2-words-ending-in-an-unstressed-ی---ey"
: pattern === 3
? "#3-words-ending-in-a-stressed-ی---éy"
: pattern === 4
? "#4-words-with-the-pashtoon-pattern"
: pattern === 5
? "#5-shorter-words-that-squish"
// : pattern === 6
: "#6-inanimate-feminine-nouns-ending-in-ي---ee"
}
export default InflectionDemo;

View File

@ -0,0 +1,131 @@
import Examples from "../components/src/Examples";
import ButtonSelect from "../components/src/ButtonSelect";
import * as T from "../types";
const spellingOptions: {
value: T.TextOptions["spelling"],
label: string,
}[] = [
{
value: "Afghan",
label: "Afghan",
},
{
value: "Pakistani ي",
label: "Pakistani ي",
},
{
value: "Pakistani ی",
label: "Pakistani ی",
},
];
const phoneticsOptions: {
value: T.TextOptions["phonetics"],
label: string,
}[] = [
{
value: "lingdocs",
label: "LingDocs",
},
{
value: "ipa",
label: "IPA",
},
{
value: "alalc",
label: "ALALC",
},
];
function SpellingDemo({ opts, onChange }: {
opts: T.TextOptions,
onChange: (opts: T.TextOptions) => void,
}) {
return <div>
<ul>
<li>Converts text between Afghan and Pakistani spelling conventions</li>
<li>Generates diacritics for Pashto script when given phonetic script along with Pashto script</li>
</ul>
<div className="d-block mx-auto card mb-3" style={{ maxWidth: "700px", background: "var(--closer)"}}>
<div className="card-body">
<div className="row">
<div className="col-sm-6 mb-2">
<h6>Pashto Spelling Convention:</h6>
<div>
{spellingOptions.map(({ value, label }) => (
<div key={value} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={opts.spelling === value}
value={value}
onChange={() => {
onChange({
...opts,
spelling: value,
});
}}
/>
<label className="form-check-label">
{label}
</label>
</div>
))}
</div>
</div>
<div className="col-sm-6 mb-2">
<h6>Latin Phonetic System:</h6>
<div>
{phoneticsOptions.map(({ value, label }) => (
<div key={value} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={opts.phonetics === value}
value={value}
onChange={() => {
onChange({
...opts,
phonetics: value,
});
}}
/>
<label className="form-check-label">
{label}
</label>
</div>
))}
</div>
</div>
<div className="col-sm-6">
<h6>Diacritics</h6>
<ButtonSelect
options={[
{ label: "On", value: "true" },
{ label: "Off", value: "false" },
]}
value={opts.diacritics.toString()}
handleChange={(p) => onChange({ ...opts, diacritics: p === "true" })}
/>
</div>
</div>
</div>
</div>
<Examples opts={opts}>{[
{
p: "زما زوی مکتب ته ځي",
f: "zmaa zooy maktab ta dzee",
},
{
p: "دا ښه سړی دی",
f: "daa xu saRey dey",
},
]}</Examples>
</div>;
}
export default SpellingDemo;

View File

@ -0,0 +1,203 @@
import PhraseBuilder from "../components/src/vp-explorer/VPExplorer";
import * as T from "../types";
import Pashto from "../components/src/Pashto";
import Phonetics from "../components/src/Phonetics";
import { getVerbInfo } from "../lib/src/verb-info";
import verbs from "../verbs";
import { useStickyState } from "../components/library";
import {
clamp
} from "../lib/src/p-text-helpers";
import {
randomNumber,
} from "../lib/src/misc-helpers";
import { entryFeeder } from "./entryFeeder";
const transitivities: T.Transitivity[] = [
"transitive",
"intransitive",
"grammatically transitive",
];
const allVerbs = verbs.map((v: { entry: T.DictionaryEntry, complement?: T.DictionaryEntry }) => ({
verb: v,
info: getVerbInfo(v.entry, v.complement),
}));
type VerbType = "simple" | "stative compound" | "dynamic compound";
const verbTypes: VerbType[] = [
"simple",
"stative compound",
"dynamic compound",
];
function VPBuilderDemo({ opts }: {
opts: T.TextOptions,
}) {
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing");
const [transitivityShowing, setTransitivityShowing] = useStickyState<T.Transitivity>("intransitive", "transitivityShowing1");
// const onlyGrammTrans = (arr: Transitivity[]) => (
// arr.length === 1 && arr[0] === "grammatically transitive"
// );
// const ensureSimpleVerbTypeSelected = () => {
// if (!verbTypesShowing.includes["simple"]) {
// setVerbTypesShowing([...verbTypesShowing, "simple"]);
// }
// }
const handleVerbIndexChange = (e: any) => {
setVerbTs(parseInt(e.target.value));
}
const handleTypeSelection = (e: any) => {
const type = e.target.value as VerbType;
if (type === "dynamic compound") {
setTransitivityShowing("transitive");
}
if (type === "stative compound" && transitivityShowing === "grammatically transitive") {
setTransitivityShowing("transitive");
}
setVerbTypeShowing(type);
}
const handleTransitivitySelection = (e: any) => {
const transitivity = e.target.value as T.Transitivity;
if (transitivity === "grammatically transitive") {
setVerbTypeShowing("simple");
}
if (transitivity === "intransitive" && verbTypeShowing === "dynamic compound") {
setTransitivityShowing("transitive");
return;
}
setTransitivityShowing(e.target.value as T.Transitivity);
}
const verbsAvailable = allVerbs.filter((verb) => (
(
(verb.info.type === "transitive or grammatically transitive simple" && verbTypeShowing === "simple") && (transitivityShowing === "transitive" || transitivityShowing === "grammatically transitive")
) ||
((
verbTypeShowing === verb.info.type ||
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or stative compound") ||
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or generative stative compound") ||
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or generative stative compound")
)
&& (
transitivityShowing === verb.info.transitivity
))
)).sort((a, b) => a.verb.entry.p.localeCompare(b.verb.entry.p, "ps"));
const v = (() => {
const vFound = verbsAvailable.find(v => v.verb.entry.ts === verbTs);
if (vFound) return vFound;
if (verbsAvailable.length === 0) return undefined;
const vTopOfList = verbsAvailable[0];
setVerbTs(vTopOfList.verb.entry.ts);
return vTopOfList;
})();
const pickRandomVerb = () => {
let newIndex: number;
do {
newIndex = randomNumber(0, verbsAvailable.length);
} while(verbsAvailable[newIndex].verb.entry.ts === verbTs);
setVerbTs(verbsAvailable[newIndex].verb.entry.ts);
};
const makeVerbLabel = (entry: T.DictionaryEntry): string => (
`${entry.p} - ${clamp(entry.e, 20)}`
);
return <>
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}>
<div className="card-body">
<div className="row">
<div className="col-sm-6">
{v ?
<div>
<div className="mb-1">Select a verb:</div>
<div className="input-group">
<select className="custom-select" value={verbTs} onChange={handleVerbIndexChange}>
{verbsAvailable.length
? verbsAvailable.map((v, i) => (
<option value={v.verb.entry.ts} key={i} dir="ltr">
{"\u200E"}{makeVerbLabel(v.verb.entry)}
</option>
))
: <option>Select a verb type</option>
}
</select>
<div className="input-group-append">
<button className="btn btn-secondary" onClick={pickRandomVerb}>
<i className="fas fa-random" />
</button>
</div>
</div>
<div className="my-3">
<div>
<strong>
<Pashto opts={opts}>{v.verb.entry}</Pashto>
{` `}-{` `}
<Phonetics opts={opts}>{v.verb.entry}</Phonetics>
</strong>
{` `}
<em>{v.verb.entry.c}</em>
</div>
<div className="ml-3">{v.verb.entry.e}</div>
</div>
</div>
: <div className="alert alert-warning mb-4" role="alert">
No such verbs available...
</div>
}
</div>
<div className="col-sm-6">
<h6>Verb type:</h6>
<div onChange={handleTypeSelection}>
{verbTypes.map((type) => (
<div key={type} className="form-check">
<input
className="form-check-input"
type="radio"
name="verb-type"
checked={verbTypeShowing === type}
value={type}
onChange={() => null}
/>
<label className="form-check-label">
{type}
</label>
</div>
))}
</div>
<h6 className="mt-2">Transitivity:</h6>
<div onChange={handleTransitivitySelection}>
{transitivities.map((transitivity) => (
<div key={transitivity} className="form-check">
<input
className="form-check-input"
type="radio"
name="transitivity"
checked={transitivityShowing === transitivity}
onChange={() => null}
value={transitivity}
/>
<label className="form-check-label">
{transitivity}
</label>
</div>
))}
</div>
</div>
</div>
</div>
</div>
{v?.verb.entry && <div style={{ paddingBottom: "100px" }}>
<PhraseBuilder
handleLinkClick="none"
verb={v.verb as T.VerbEntry}
entryFeeder={entryFeeder}
opts={opts}
/>
</div>}
</>;
}
export default VPBuilderDemo;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>

After

Width:  |  Height:  |  Size: 183 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,21 @@
import verbs from "../verbs";
import nounsAdjs from "../nouns-adjs";
import {
isAdjectiveEntry,
isAdverbEntry,
isLocativeAdverbEntry,
isNounEntry,
} from "../lib/src/type-predicates";
import * as T from "../types";
const nouns = nounsAdjs.filter(isNounEntry);
const adjectives = nounsAdjs.filter(isAdjectiveEntry);
const locativeAdverbs = nounsAdjs.filter(isLocativeAdverbEntry);
const adverbs = nounsAdjs.filter(isAdverbEntry);
export const entryFeeder: T.EntryFeeder = {
locativeAdverbs,
nouns,
adjectives,
verbs,
adverbs,
};

View File

@ -0,0 +1,31 @@
import * as T from "../../../types";
function renderVerb(vs: T.VerbSelectionComplete): {
verbBlocks: VerbBlocks,
hasBa: boolean,
} {
const base = chooseRootOrStem(vs.tense, vs.verb);
const b = concatPsString(base, grammarUnits.presentEndings[0][0][0]);
return {
verbBlocks: [{
type: "verb",
block: {
...vs,
hasBa: false,
ps: [],
person: 0,
complementWelded: undefined,
}
}],
hasBa: false,
}
}
function chooseRootOrStem(tense: T.VerbFormName, entry: T.VerbEntry): T.FullForm<T.PsString> {
const info = getVerbInfo(entry.entry, entry.complement)
if ("stative" in info || "transitive" in info) {
throw new Error("multiple verb types not supported yet");
}
return info.stem.imperfective
}

View File

@ -35,6 +35,8 @@ import { renderAPSelection } from "./render-ap";
import { findPossesivesToShrink, orderKids, getMiniPronounPs } from "./render-common"; import { findPossesivesToShrink, orderKids, getMiniPronounPs } from "./render-common";
import { renderComplementSelection } from "./render-complement"; import { renderComplementSelection } from "./render-complement";
import { makeNounSelection } from "./make-selections"; import { makeNounSelection } from "./make-selections";
// import { getVerbInfo } from "../verb-info";
// import { grammarUnits } from "../../library";
export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
const subject = getSubjectSelection(VP.blocks).selection; const subject = getSubjectSelection(VP.blocks).selection;
@ -353,6 +355,7 @@ type VerbBlocks =
| [T.PerfectParticipleBlock, T.PerfectEquativeBlock] // perfect verb | [T.PerfectParticipleBlock, T.PerfectEquativeBlock] // perfect verb
| [T.ModalVerbBlock, T.ModalVerbKedulPart] // modal verb | [T.ModalVerbBlock, T.ModalVerbKedulPart] // modal verb
function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, complementPerson: T.Person | undefined, externalComplement: T.ComplementSelection | T.UnselectedComplementSelection | undefined): { function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, complementPerson: T.Person | undefined, externalComplement: T.ComplementSelection | T.UnselectedComplementSelection | undefined): {
verbBlocks: VerbBlocks verbBlocks: VerbBlocks
hasBa: boolean, hasBa: boolean,

View File

@ -68,6 +68,34 @@ export function isAdjOrUnisexNounEntry(e: T.Entry): e is (T.AdjectiveEntry | T.U
); );
} }
export function isPattern(p: T.InflectionPattern | "all"): (entry: T.NounEntry | T.AdjectiveEntry) => boolean {
if (p === 0) {
return (e: T.NounEntry | T.AdjectiveEntry) => (
!isPattern1Entry(e) && !isPattern2Entry(e) && !isPattern3Entry(e)
&& !isPattern4Entry(e) && !isPattern5Entry(e) && !isPattern6FemEntry(e)
)
}
if (p === 1) {
return isPattern1Entry;
}
if (p === 2) {
return isPattern2Entry;
}
if (p === 3) {
return isPattern3Entry;
}
if (p === 4) {
return isPattern4Entry;
}
if (p === 5) {
return isPattern5Entry;
}
if (p === 6) {
return isPattern6FemEntry;
}
return () => true;
}
/** /**
* shows if a noun/adjective has the basic (consonant / ه) inflection pattern * shows if a noun/adjective has the basic (consonant / ه) inflection pattern
* *
@ -153,7 +181,7 @@ export function isPattern5Entry<T extends (T.NounEntry | T.AdjectiveEntry)>(e: T
); );
} }
export function isPattern6FemEntry(e: T.FemNounEntry): e is T.Pattern6FemEntry<T.FemNounEntry> { export function isPattern6FemEntry(e: T.NounEntry | T.AdjectiveEntry): e is T.Pattern6FemEntry<T.FemNounEntry> {
if (!isFemNounEntry(e)) return false; if (!isFemNounEntry(e)) return false;
if (e.c.includes("anim.")) return false; if (e.c.includes("anim.")) return false;
return e.p.slice(-1) === "ي"; return e.p.slice(-1) === "ي";