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

@ -10,10 +10,12 @@ Also includes the [Pashto Verb Explorer](https://verbs.lingdocs.com) website.
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.
- Can be used with Node 16, as CommonJS
- @lingdocs/ps-react
- `/src/lib`
- @lingdocs/inflect plus react components for displaying Pashto text, phrase engine UI etc.
- Only available as an ES6 Module

View File

@ -5,28 +5,28 @@
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<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" />
<meta name="keywords" content="Pashto, Verbs, Conjugation, Grammar, Linguistics" />
<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:title" content="Pashto Verb Explorer">
<meta property="og:description" content="A playground for exploring Pashto verbs with all their forms and conjugations">
<meta property="og:image" content="https://verbs.lingdocs.com/android-chrome-512x512.png">
<meta property="og:url" content="https://verbs.lingdocs.com/">
<meta property="og:site_name" content="Pashto Inflector"/>
<meta property="og:title" content="Pashto Inflector">
<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://pashto-inflector.lingdocs.com/android-chrome-512x512.png">
<meta property="og:url" content="https://pashto-inflector.lingdocs.com/">
<meta property="og:author" content="lingdocs.com">
<meta property="og:type" content="website">
<meta name="twitter:title" content="Pashto Verb Explorer">
<meta name="twitter:description" content="A playground for exploring Pashto verbs with all their forms and conjugations">
<meta name="twitter:image" content="https://verbs.lingdocs.com/android-chrome-512x512.png">
<meta name="twitter:title" content="Pashto Inflector">
<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://pashto-inflector.lingdocs.com/android-chrome-512x512.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@lingdocs" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Pashto Verb Explorer</title>
<title>Pashto Inflector</title>
</head>
<body class="d-flex flex-column h-100" id="root">
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -6,261 +6,97 @@
*
*/
import { useEffect } 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 { useEffect, useState } from "react";
import ButtonSelect from "./components/src/ButtonSelect";
import {
clamp
} from "./lib/src/p-text-helpers";
import {
randomNumber,
} from "./lib/src/misc-helpers";
import {
Modal
} from "react-bootstrap";
import * as T from "./types";
import { isAdjectiveEntry, isAdverbEntry, isLocativeAdverbEntry, isNounEntry } from "./lib/src/type-predicates";
import defualtTextOptions from "./lib/src/default-text-options";
import PhraseBuilder from "./components/src/vp-explorer/VPExplorer";
import useStickyState from "./components/src/useStickyState";
import EPExplorer from "./components/src/ep-explorer/EPExplorer";
type VerbType = "simple" | "stative compound" | "dynamic compound";
const verbTypes: VerbType[] = [
"simple",
"stative compound",
"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),
}));
import VPBuilderDemo from "./demo-components/VPBuilderDemo";
import { entryFeeder } from "./demo-components/entryFeeder";
import { Hider } from "./components/library";
import InflectionDemo from "./demo-components/InflectionDemo";
import SpellingDemo from "./demo-components/SpellingDemo";
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 [textOptions, setTextOptions] = useStickyState<T.TextOptions>(defualtTextOptions, "textOpts1");
const [theme, setTheme] = useStickyState<"light" | "dark">("light", "theme1");
// const onlyGrammTrans = (arr: Transitivity[]) => (
// arr.length === 1 && arr[0] === "grammatically transitive"
// );
// const ensureSimpleVerbTypeSelected = () => {
// if (!verbTypesShowing.includes["simple"]) {
// setVerbTypesShowing([...verbTypesShowing, "simple"]);
// }
// }
const [showing, setShowing] = useState<string>("");
function handleHiderClick(label: string) {
setShowing(os => os === label
? ""
: label);
}
useEffect(() => {
document.documentElement.setAttribute("data-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)}`
);
}, [theme]);
return <>
<main className="flex-shrink-0 mb-4">
<div className="container" style={{ maxWidth: "800px" }}>
<div style={{ position: "absolute", top: "1.5rem", right: "1.5rem", display: "flex", flexDirection: "row" }}>
<div
className="clickable mr-3"
onClick={() => setShowingTextOptions(true)}
>
<i className="fa-lg fas fa-cog" />
</div>
<div
className="clickable"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<i className={`fa-lg fas fa-${theme === "light" ? "sun" : "moon"}`} />
</div>
<div
className="clickable"
onClick={() => setShowingTextOptions(true)}
>
<i className="fa-lg fas fa-cog" />
</div>
</div>
<div className="text-center" style={{ marginTop: "3rem", marginBottom: "1rem" }}>
<h1 className="display-4 mt-2">Pashto Verb Explorer</h1>
<p>by <a href="https://www.lingdocs.com">LingDocs</a></p>
<p className="lead my-4">
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>. 👨🔬
<div className="text-center mb-4" style={{ marginTop: "3rem", marginBottom: "1rem" }}>
<h1 className="display-4 mt-2"><code>Pashto Inflector</code></h1>
<p className="lead my-3" style={{ maxWidth: "600px", margin: "0 auto" }}>
An open source TypeScript/React library for Pashto inflection, verb conjugation, phrase generation, text conversion, and more
</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 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={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}
<h2 className="mb-3">Demos:</h2>
<Hider
label="Verb Conjugation / Verb Phrase Engine"
hLevel={3}
showing={showing === "verbs"}
handleChange={() => handleHiderClick("verbs")}
>
<VPBuilderDemo opts={textOptions} />
</Hider>
<Hider
label="Equative Phrase Engine"
hLevel={3}
showing={showing === "equatives"}
handleChange={() => handleHiderClick("equatives")}
>
<EPExplorer
opts={textOptions}
entryFeeder={entryFeeder}
/>
</div>}
<h4>Equative Phrase Builder</h4>
<EPExplorer
opts={textOptions}
entryFeeder={entryFeeder}
/>
</Hider>
<Hider
label="Inflection Engine"
hLevel={3}
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>
</main>
<Modal show={showingTextOptions} onHide={() => setShowingTextOptions(false)}>
@ -308,7 +144,7 @@ function App() {
{ label: "LingDocs", value: "lingdocs" },
{ label: "IPA", value: "ipa" },
{ label: "ALAC", value: "alalc" },
{ label: "None", value: "none" },
// { label: "None", value: "none" },
]}
value={textOptions.phonetics}
handleChange={(p) => setTextOptions({ ...textOptions, phonetics: p })}
@ -320,11 +156,11 @@ function App() {
</button>
</Modal.Footer>
</Modal>
<footer className="footer mt-auto py-3" style={{ backgroundColor: "#f5f5f5" }}>
{/* <footer className="footer mt-auto py-3" style={{ backgroundColor: "#f5f5f5" }}>
<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>
</footer>
</footer> */}
</>
}

View File

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

View File

@ -6,9 +6,10 @@
*
*/
import { createElement } from "react";
import { createElement, useEffect, useRef } from "react";
import classNames from "classnames";
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">
<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,
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 extraMargin = (props.hLevel && (props.hLevel > indentAfterLevel))
? `ml-${(props.hLevel - indentAfterLevel) + 1}`
@ -38,7 +43,7 @@ function Hider(props: {
{props.children}
</>;
}
return <div className="mb-3">
return <div className="mb-3" ref={parent}>
{createElement(
`h${hLev}`,
{

View File

@ -6,53 +6,56 @@
*
*/
import { useState } from "react";
import Pashto from "./Pashto";
import { Modal } from "react-bootstrap";
// TODO: REMOVING THE EXPLANATION MODAL BC IT'S BUGGY NOW SAYNG "ABOUT ARABIC PLURAL" AND IT'S
// PROB BETTER JUST TO LINK TO THE GRAMMAR
// import { useState } from "react";
// import Pashto from "./Pashto";
// import { Modal } from "react-bootstrap";
import TableCell from "./TableCell";
import * as T from "../../types";
import { isPluralInflections } from "../../lib/src/p-text-helpers";
const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => {
const isPluralInfs = isPluralInflections(inf);
const w = "masc" in inf
? inf.masc[0][0]
: inf.fem[0][0];
return isPluralInfs ? null : <>
<p>In Pashto, <strong>nouns, pronouns, and adjectives</strong> get inflected when they are either:</p>
<ul>
<li>Plural</li>
<li>Sandwiched with a preposition/postposition (oblique), or</li>
<li>The subject of a transitive past tense verb</li>
</ul>
<p>Whatever the reason, the inflection looks the same.</p>
<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>
<h5>Notes:</h5>
<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>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>
</>;
}
// const explanation = (inf: T.Inflections | T.PluralInflections, textOptions: T.TextOptions) => {
// const isPluralInfs = isPluralInflections(inf);
// const w = "masc" in inf
// ? inf.masc[0][0]
// : inf.fem[0][0];
// return isPluralInfs ? null : <>
// <p>In Pashto, <strong>nouns, pronouns, and adjectives</strong> get inflected when they are either:</p>
// <ul>
// <li>Plural</li>
// <li>Sandwiched with a preposition/postposition (oblique), or</li>
// <li>The subject of a transitive past tense verb</li>
// </ul>
// <p>Whatever the reason, the inflection looks the same.</p>
// <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>
// <h5>Notes:</h5>
// <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>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>
// </>;
// }
const InflectionTable = ({ inf, textOptions, hideTitle }: {
inf: T.Inflections | T.PluralInflections,
textOptions: T.TextOptions,
hideTitle?: boolean,
}) => {
const [showingExplanation, setShowingExplanation] = useState(false);
// const [showingExplanation, setShowingExplanation] = useState(false);
/* istanbul ignore next */ // Insanely can't see the modal to close it
const handleCloseExplanation = () => setShowingExplanation(false);
const handleShowExplanation = () => setShowingExplanation(true);
// const handleCloseExplanation = () => setShowingExplanation(false);
// const handleShowExplanation = () => setShowingExplanation(true);
const isPluralInfs = isPluralInflections(inf);
return (
<div className={!hideTitle ? "" : "mt-4"}>
{!hideTitle && <div style={{ display: "flex", justifyContent: !isPluralInfs ? "space-between" : "left" }}>
{!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>
</div>}
</div>} */}
</div>}
<table className="table" style={{ tableLayout: "fixed" }}>
<thead>
@ -72,7 +75,7 @@ const InflectionTable = ({ inf, textOptions, hideTitle }: {
))}
</tbody>
</table>
{(!hideTitle && !isPluralInfs) && <Modal show={showingExplanation} onHide={handleCloseExplanation}>
{/* {(!hideTitle && !isPluralInfs) && <Modal show={showingExplanation} onHide={handleCloseExplanation}>
<Modal.Header closeButton>
<Modal.Title>About {isPluralInfs ? "Inflections" : "Arabic Plural"}</Modal.Title>
</Modal.Header>
@ -82,7 +85,7 @@ const InflectionTable = ({ inf, textOptions, hideTitle }: {
Close
</button>
</Modal.Footer>
</Modal>}
</Modal>} */}
</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 { renderComplementSelection } from "./render-complement";
import { makeNounSelection } from "./make-selections";
// import { getVerbInfo } from "../verb-info";
// import { grammarUnits } from "../../library";
export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
const subject = getSubjectSelection(VP.blocks).selection;
@ -353,6 +355,7 @@ type VerbBlocks =
| [T.PerfectParticipleBlock, T.PerfectEquativeBlock] // perfect 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): {
verbBlocks: VerbBlocks
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
*
@ -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 (e.c.includes("anim.")) return false;
return e.p.slice(-1) === "ي";