try with context
This commit is contained in:
parent
16066df914
commit
4b820ba231
|
@ -4,7 +4,8 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.15.2",
|
"@fortawesome/fontawesome-free": "^5.15.2",
|
||||||
"@lingdocs/pashto-inflector": "^0.9.3",
|
"@lingdocs/lingdocs-main": "^0.0.4",
|
||||||
|
"@lingdocs/pashto-inflector": "^1.0.5",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
"@types/node": "^14.14.35",
|
"@types/node": "^14.14.35",
|
||||||
"@types/react": "^17.0.3",
|
"@types/react": "^17.0.3",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^17.0.2",
|
||||||
|
"@types/react-router-dom": "^5.1.9",
|
||||||
"bootstrap": "4.5.3",
|
"bootstrap": "4.5.3",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"markdown-to-jsx": "^7.1.3",
|
"markdown-to-jsx": "^7.1.3",
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
import { BrowserRouter as Router, Route, withRouter, Switch } from "react-router-dom";
|
import { BrowserRouter as Router, Route, withRouter, Switch, RouteComponentProps } from "react-router-dom";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import Page404 from "./pages/404";
|
import Page404 from "./pages/404";
|
||||||
import Chapter from "./components/Chapter";
|
import Chapter from "./components/Chapter";
|
||||||
|
@ -17,6 +17,8 @@ import Sidebar from "./components/Sidebar";
|
||||||
import Header from "./components/Header";
|
import Header from "./components/Header";
|
||||||
import TableOfContentsPage from "./pages/TableOfContentsPage";
|
import TableOfContentsPage from "./pages/TableOfContentsPage";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useUser } from "./user-context";
|
||||||
|
import { AT } from "@lingdocs/lingdocs-main";
|
||||||
import ReactGA from "react-ga";
|
import ReactGA from "react-ga";
|
||||||
const chapters = content.reduce((chapters, item) => (
|
const chapters = content.reduce((chapters, item) => (
|
||||||
item.content
|
item.content
|
||||||
|
@ -31,10 +33,21 @@ if (prod) {
|
||||||
ReactGA.set({ anonymizeIp: true });
|
ReactGA.set({ anonymizeIp: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function App(props) {
|
function App(props: RouteComponentProps) {
|
||||||
const [navOpen, setNavOpen] = useState(false);
|
const [navOpen, setNavOpen] = useState(false);
|
||||||
|
const { setUser } = useUser();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.pageview(window.location.pathname);
|
ReactGA.pageview(window.location.pathname);
|
||||||
|
fetch("https://account.lingdocs.com/api/user").then((res) => res.json()).then((res) => {
|
||||||
|
console.log("fetched user info");
|
||||||
|
if (res.user) {
|
||||||
|
const user = res.user as AT.LingdocsUser
|
||||||
|
setUser(user);
|
||||||
|
} else {
|
||||||
|
setUser(undefined);
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scroll(0, 0);
|
window.scroll(0, 0);
|
||||||
|
@ -57,7 +70,7 @@ function App(props) {
|
||||||
<Route path="/" exact>
|
<Route path="/" exact>
|
||||||
<TableOfContentsPage />
|
<TableOfContentsPage />
|
||||||
</Route>
|
</Route>
|
||||||
{chapters.map((chapter) => (
|
{chapters.map((chapter: any) => (
|
||||||
<Route key={chapter.path} path={chapter.path}>
|
<Route key={chapter.path} path={chapter.path}>
|
||||||
<Chapter>{chapter}</Chapter>
|
<Chapter>{chapter}</Chapter>
|
||||||
</Route>
|
</Route>
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from "react";
|
||||||
|
import { AT } from "@lingdocs/lingdocs-main";
|
||||||
|
|
||||||
|
const UserContext = React.createContext<undefined | AT.LingdocsUser>(undefined);
|
||||||
|
|
||||||
|
export default UserContext;
|
|
@ -2,29 +2,31 @@ import React from "react";
|
||||||
import Carousel from "./Carousel";
|
import Carousel from "./Carousel";
|
||||||
import {
|
import {
|
||||||
InlinePs,
|
InlinePs,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
InflectionsTable,
|
InflectionsTable,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
function InflectionCarousel({ items }) {
|
function InflectionCarousel({ items }: any) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Carousel items={items} render={(item) => {
|
<Carousel items={items} render={(item: any) => {
|
||||||
const inf = inflectWord(item.entry);
|
const infOut = inflectWord(item.entry);
|
||||||
if (!inf) {
|
if (!infOut || !infOut.inflections) {
|
||||||
return (
|
return (
|
||||||
<div>Oops! No inflections for <InlinePs opts={opts} ps={item.entry} /></div>
|
// @ts-ignore
|
||||||
|
<div>Oops! No inflections for <InlinePs opts={opts} />{item.entry}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
// @ts-ignore
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <InflectionsTable
|
body: <InflectionsTable
|
||||||
inf={inf}
|
inf={infOut.inflections}
|
||||||
textOptions={opts}
|
textOptions={opts}
|
||||||
/>,
|
/>,
|
||||||
};
|
};
|
|
@ -7,39 +7,66 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable import/no-webpack-loader-syntax */
|
/* eslint-disable import/no-webpack-loader-syntax */
|
||||||
|
// @ts-ignore
|
||||||
import * as intro from "!babel-loader!mdx-loader!./intro.mdx";
|
import * as intro from "!babel-loader!mdx-loader!./intro.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as presentEquative from "!babel-loader!mdx-loader!./equatives/present-equative.mdx"
|
import * as presentEquative from "!babel-loader!mdx-loader!./equatives/present-equative.mdx"
|
||||||
|
// @ts-ignore
|
||||||
import * as subjunctiveHabitualEquative from "!babel-loader!mdx-loader!./equatives/subjunctive-habitual-equative.mdx";
|
import * as subjunctiveHabitualEquative from "!babel-loader!mdx-loader!./equatives/subjunctive-habitual-equative.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as otherEquatives from "!babel-loader!mdx-loader!./equatives/other-equatives.mdx";
|
import * as otherEquatives from "!babel-loader!mdx-loader!./equatives/other-equatives.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as nounsGender from "!babel-loader!mdx-loader!./nouns/nouns-gender.mdx";
|
import * as nounsGender from "!babel-loader!mdx-loader!./nouns/nouns-gender.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as nounsUnisex from "!babel-loader!mdx-loader!./nouns/nouns-unisex.mdx";
|
import * as nounsUnisex from "!babel-loader!mdx-loader!./nouns/nouns-unisex.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as nounsPlural from "!babel-loader!mdx-loader!./nouns/nouns-plural.mdx";
|
import * as nounsPlural from "!babel-loader!mdx-loader!./nouns/nouns-plural.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as arabicPlurals from "!babel-loader!mdx-loader!./nouns/arabic-plurals.mdx";
|
import * as arabicPlurals from "!babel-loader!mdx-loader!./nouns/arabic-plurals.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as bundledPlurals from "!babel-loader!mdx-loader!./nouns/bundled-plurals.mdx";
|
import * as bundledPlurals from "!babel-loader!mdx-loader!./nouns/bundled-plurals.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as verbAspect from "!babel-loader!mdx-loader!./verbs/verb-aspect.mdx";
|
import * as verbAspect from "!babel-loader!mdx-loader!./verbs/verb-aspect.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as verbsIntro from "!babel-loader!mdx-loader!./verbs/verbs-intro.mdx";
|
import * as verbsIntro from "!babel-loader!mdx-loader!./verbs/verbs-intro.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as presentVerbs from "!babel-loader!mdx-loader!./verbs/present-verbs.mdx";
|
import * as presentVerbs from "!babel-loader!mdx-loader!./verbs/present-verbs.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as subjunctiveVerbs from "!babel-loader!mdx-loader!./verbs/subjunctive-verbs.mdx";
|
import * as subjunctiveVerbs from "!babel-loader!mdx-loader!./verbs/subjunctive-verbs.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as futureVerbs from "!babel-loader!mdx-loader!./verbs/future-verbs.mdx";
|
import * as futureVerbs from "!babel-loader!mdx-loader!./verbs/future-verbs.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as imperativeVerbs from "!babel-loader!mdx-loader!./verbs/imperative-verbs.mdx";
|
import * as imperativeVerbs from "!babel-loader!mdx-loader!./verbs/imperative-verbs.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as verbEndings from "!babel-loader!mdx-loader!./verbs/verb-endings.mdx";
|
import * as verbEndings from "!babel-loader!mdx-loader!./verbs/verb-endings.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as rootsAndStems from "!babel-loader!mdx-loader!./verbs/roots-and-stems.mdx";
|
import * as rootsAndStems from "!babel-loader!mdx-loader!./verbs/roots-and-stems.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as sentenceStructure from "!babel-loader!mdx-loader!./verbs/sentence-structure.mdx";
|
import * as sentenceStructure from "!babel-loader!mdx-loader!./verbs/sentence-structure.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as pronounsBasic from "!babel-loader!mdx-loader!./pronouns/pronouns-basic.mdx";
|
import * as pronounsBasic from "!babel-loader!mdx-loader!./pronouns/pronouns-basic.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as pronounsMini from "!babel-loader!mdx-loader!./pronouns/pronouns-mini.mdx";
|
import * as pronounsMini from "!babel-loader!mdx-loader!./pronouns/pronouns-mini.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as directionalPronouns from "!babel-loader!mdx-loader!./pronouns/pronouns-directional.mdx";
|
import * as directionalPronouns from "!babel-loader!mdx-loader!./pronouns/pronouns-directional.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as inflectionIntro from "!babel-loader!mdx-loader!./inflection/inflection-intro.mdx";
|
import * as inflectionIntro from "!babel-loader!mdx-loader!./inflection/inflection-intro.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as inflectionPatterns from "!babel-loader!mdx-loader!./inflection/inflection-patterns.mdx";
|
import * as inflectionPatterns from "!babel-loader!mdx-loader!./inflection/inflection-patterns.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as feminineInflection from "!babel-loader!mdx-loader!./inflection/feminine-inflection.mdx";
|
import * as feminineInflection from "!babel-loader!mdx-loader!./inflection/feminine-inflection.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as sandwiches from "!babel-loader!mdx-loader!./sandwiches/sandwiches.mdx";
|
import * as sandwiches from "!babel-loader!mdx-loader!./sandwiches/sandwiches.mdx";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
import * as theFiveYeys from "!babel-loader!mdx-loader!./writing/the-five-yeys.mdx";
|
import * as theFiveYeys from "!babel-loader!mdx-loader!./writing/the-five-yeys.mdx";
|
||||||
|
// @ts-ignore
|
||||||
import * as typingIssues from "!babel-loader!mdx-loader!./writing/typing-issues.mdx";
|
import * as typingIssues from "!babel-loader!mdx-loader!./writing/typing-issues.mdx";
|
||||||
|
|
||||||
const contentTree = [
|
const contentTree = [
|
||||||
|
@ -196,7 +223,7 @@ const contentTree = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const content = contentTree.map((item) => {
|
export const content = contentTree.map((item) => {
|
||||||
function prepareChapter(chp, subdir) {
|
function prepareChapter(chp: any, subdir?: any) {
|
||||||
return {
|
return {
|
||||||
path: subdir ? `/${subdir}/${chp.slug}/` : `/${chp.slug}/`,
|
path: subdir ? `/${subdir}/${chp.slug}/` : `/${chp.slug}/`,
|
||||||
slug: chp.slug,
|
slug: chp.slug,
|
||||||
|
@ -209,30 +236,34 @@ export const content = contentTree.map((item) => {
|
||||||
? prepareChapter(item)
|
? prepareChapter(item)
|
||||||
: {
|
: {
|
||||||
...item,
|
...item,
|
||||||
chapters: item.chapters.map((c) => {
|
chapters: item.chapters?.map((c) => {
|
||||||
return prepareChapter(c, item.subdirectory);
|
return prepareChapter(c, item.subdirectory);
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}).map((item, i, items) => {
|
}).map((item, i, items) => {
|
||||||
// make the next and previous page information for each chapter
|
// make the next and previous page information for each chapter
|
||||||
function withNextPrev(current, index, arr) {
|
function withNextPrev(current: any, index: any, arr: any) {
|
||||||
function getInfo(x) {
|
function getInfo(x: any) {
|
||||||
return x.content
|
return x.content
|
||||||
? { frontMatter: x.frontMatter, path: x.path }
|
? { frontMatter: x.frontMatter, path: x.path }
|
||||||
: { frontMatter: x.chapters[0].frontMatter, path: x.chapters[0].path }; // TODO: KILL THIS?
|
: { frontMatter: x.chapters[0].frontMatter, path: x.chapters[0].path }; // TODO: KILL THIS?
|
||||||
}
|
}
|
||||||
function getNextOutsideItem() {
|
function getNextOutsideItem() {
|
||||||
|
// @ts-ignore
|
||||||
return items[i+1].content
|
return items[i+1].content
|
||||||
// if it's a single chapter section, get that chapter
|
// if it's a single chapter section, get that chapter
|
||||||
? items[i+1]
|
? items[i+1]
|
||||||
// if it's a section with multiple chapters, get the first chapter
|
// if it's a section with multiple chapters, get the first chapter
|
||||||
|
// @ts-ignore
|
||||||
: items[i+1].chapters[0];
|
: items[i+1].chapters[0];
|
||||||
}
|
}
|
||||||
function getPrevOutsideItem() {
|
function getPrevOutsideItem() {
|
||||||
|
// @ts-ignore
|
||||||
return items[i-1].content
|
return items[i-1].content
|
||||||
// if it's a single chapter section, get that chapter
|
// if it's a single chapter section, get that chapter
|
||||||
? items[i-1]
|
? items[i-1]
|
||||||
// if it's a section with multiple chapters, get the last chapter
|
// if it's a section with multiple chapters, get the last chapter
|
||||||
|
// @ts-ignore
|
||||||
: items[i-1].chapters[items[i-1].chapters.length - 1];
|
: items[i-1].chapters[items[i-1].chapters.length - 1];
|
||||||
}
|
}
|
||||||
const next = index < arr.length - 1
|
const next = index < arr.length - 1
|
||||||
|
@ -259,11 +290,13 @@ export const content = contentTree.map((item) => {
|
||||||
} : {},
|
} : {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
if (item.content) {
|
if (item.content) {
|
||||||
return withNextPrev(item, i, items);
|
return withNextPrev(item, i, items);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
// @ts-ignore
|
||||||
chapters: item.chapters.map((chapter, j, chapters) => (
|
chapters: item.chapters.map((chapter, j, chapters) => (
|
||||||
withNextPrev(chapter, j, chapters)
|
withNextPrev(chapter, j, chapters)
|
||||||
)),
|
)),
|
|
@ -19,7 +19,7 @@ import {
|
||||||
Examples,
|
Examples,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
grammarUnits,
|
grammarUnits,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
InflectionsTable,
|
InflectionsTable,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
|
@ -17,10 +17,6 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
Examples,
|
Examples,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
grammarUnits,
|
|
||||||
removeFVariants,
|
|
||||||
InflectionsTable,
|
|
||||||
inflectWord,
|
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import Carousel from "../../components/Carousel";
|
import Carousel from "../../components/Carousel";
|
||||||
import Table from "../../components/Table";
|
import Table from "../../components/Table";
|
||||||
|
|
|
@ -33,4 +33,4 @@ This is very much a work in progress. 🏗👷♂️ I am slowly adding more
|
||||||
|
|
||||||
> "Part of the task of the grammarian is ... to unravel the complexities of languages, and, as far as possible, simplify them." Frank Palmer - [Grammar](https://www.amazon.com/gp/product/B000S5VSAS)
|
> "Part of the task of the grammarian is ... to unravel the complexities of languages, and, as far as possible, simplify them." Frank Palmer - [Grammar](https://www.amazon.com/gp/product/B000S5VSAS)
|
||||||
|
|
||||||
I hope this grammar helps to show that **Pashto isn't difficult... it's rich and beautiful**.
|
I hope this grammar helps to show that **Pashto isn't difficult... it's rich and beautiful**.
|
|
@ -8,13 +8,15 @@ import {
|
||||||
Examples,
|
Examples,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import genderColors from "../../lib/gender-colors";
|
import genderColors from "../../lib/gender-colors";
|
||||||
import GenderGame from "../../games/GenderGame";
|
|
||||||
import { firstVariation } from "../../lib/text-tools";
|
import { firstVariation } from "../../lib/text-tools";
|
||||||
import GenderTable from "../../components/GenderTable";
|
import GenderTable from "../../components/GenderTable";
|
||||||
import Link from "../../components/Link";
|
import Link from "../../components/Link";
|
||||||
import words from "../../words/nouns-adjs";
|
import words from "../../words/nouns-adjs";
|
||||||
export const femColor = genderColors.f;
|
export const femColor = genderColors.f;
|
||||||
export const mascColor = genderColors.m;
|
export const mascColor = genderColors.m;
|
||||||
|
import nounGenderGame1 from "../../games/games";
|
||||||
|
import nounGenderGame2 from "../../games/games";
|
||||||
|
import GameDisplay from "../../games/GameDisplay";
|
||||||
|
|
||||||
export const femEndingWConsonant = words.filter((w) => w.category === "consonant-fem");
|
export const femEndingWConsonant = words.filter((w) => w.category === "consonant-fem");
|
||||||
|
|
||||||
|
@ -109,7 +111,7 @@ All nouns in Pashto are either <Masc /> or <Fem />. Thankfully, you can pretty m
|
||||||
- Words ending in <InlinePs opts={opts} ps={{p:"و", f:"oo"}} /> can be either <Masc /> or <Fem />.
|
- Words ending in <InlinePs opts={opts} ps={{p:"و", f:"oo"}} /> can be either <Masc /> or <Fem />.
|
||||||
- Words ending in <InlinePs opts={opts} ps={{ p: "ـه", f: "u" }} /> can also be <Fem /> plural, as in <InlinePs opts={opts} ps={{ p: "اوبه", f: "oobu", e: "water" }} />
|
- Words ending in <InlinePs opts={opts} ps={{ p: "ـه", f: "u" }} /> can also be <Fem /> plural, as in <InlinePs opts={opts} ps={{ p: "اوبه", f: "oobu", e: "water" }} />
|
||||||
|
|
||||||
<GenderGame level={1} />
|
<GameDisplay record={nounGenderGame1} />
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
|
@ -164,4 +166,4 @@ Some words are used to describe people who obviously have a gender and they *tot
|
||||||
},
|
},
|
||||||
]} />
|
]} />
|
||||||
|
|
||||||
<GenderGame level={2} />
|
<GameDisplay record={nounGenderGame2} />
|
||||||
|
|
|
@ -10,7 +10,8 @@ import {
|
||||||
import Table from "../../components/Table";
|
import Table from "../../components/Table";
|
||||||
import Link from "../../components/Link";
|
import Link from "../../components/Link";
|
||||||
import GenderTable from "../../components/GenderTable";
|
import GenderTable from "../../components/GenderTable";
|
||||||
import UnisexNounGame from "../../games/UnisexNounGame";
|
import { unisexNounGame } from "../../games/games";
|
||||||
|
import GameDisplay from "../../games/GameDisplay";
|
||||||
|
|
||||||
There are many words for people and animals in Pashto that can be used in both masculine and feminine forms.
|
There are many words for people and animals in Pashto that can be used in both masculine and feminine forms.
|
||||||
|
|
||||||
|
@ -209,7 +210,7 @@ If the accent comes on the end of the word, the femine form is a little differen
|
||||||
}
|
}
|
||||||
]} />
|
]} />
|
||||||
|
|
||||||
<UnisexNounGame />
|
<GameDisplay record={unisexNounGame} />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
Examples,
|
Examples,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
ConjugationViewer,
|
ConjugationViewer,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import cousins from "./cousins.png";
|
import cousins from "./cousins.png";
|
||||||
|
@ -42,7 +42,7 @@ This is used to talk about something happening in the future, while thinking of
|
||||||
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
||||||
return {
|
return {
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <div style={{ textAlign: "left" }}>
|
body: <div style={{ textAlign: "left" }}>
|
||||||
|
@ -74,7 +74,7 @@ This is used to talk about something happening in the future, while thinking of
|
||||||
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
||||||
return {
|
return {
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <div style={{ textAlign: "left" }}>
|
body: <div style={{ textAlign: "left" }}>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
Examples,
|
Examples,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
ConjugationViewer,
|
ConjugationViewer,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import psmd from "../../lib/psmd";
|
import psmd from "../../lib/psmd";
|
||||||
|
@ -54,7 +54,7 @@ The <i class="fas fa-camera" /> **perfective imperative** is used when you want
|
||||||
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
||||||
return {
|
return {
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <div className="text-left">
|
body: <div className="text-left">
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
Examples,
|
Examples,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
ConjugationViewer,
|
ConjugationViewer,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import psmd from "../../lib/psmd";
|
import psmd from "../../lib/psmd";
|
||||||
|
@ -38,7 +38,7 @@ The <Link to="/verbs/verb-endings/">present ending</Link> will change according
|
||||||
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
||||||
return {
|
return {
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <div className="text-left">
|
body: <div className="text-left">
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
grammarUnits,
|
grammarUnits,
|
||||||
RootsAndStems,
|
RootsAndStems,
|
||||||
getVerbInfo,
|
getVerbInfo,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import shuffle from "../../lib/shuffle-array";
|
import shuffle from "../../lib/shuffle-array";
|
||||||
import Carousel from "../../components/Carousel";
|
import Carousel from "../../components/Carousel";
|
||||||
|
@ -44,7 +44,7 @@ export function InfoCarousel({ items, highlighted, hidePastParticiple }) {
|
||||||
: inf;
|
: inf;
|
||||||
return {
|
return {
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <RootsAndStems
|
body: <RootsAndStems
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
Examples,
|
Examples,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
removeFVariants,
|
removeFVarients,
|
||||||
ConjugationViewer,
|
ConjugationViewer,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import cousins from "./cousins.png";
|
import cousins from "./cousins.png";
|
||||||
|
@ -58,7 +58,7 @@ The subjunctive is made the same way as its cousin the <Link to="/verbs/present-
|
||||||
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
<Carousel stickyTitle items={shuffleArray(basicVerbs)} render={(item) => {
|
||||||
return {
|
return {
|
||||||
title: <InlinePs opts={opts} ps={{
|
title: <InlinePs opts={opts} ps={{
|
||||||
...removeFVariants(item.entry),
|
...removeFVarients(item.entry),
|
||||||
e: item.def,
|
e: item.def,
|
||||||
}} />,
|
}} />,
|
||||||
body: <div className="text-left">
|
body: <div className="text-left">
|
||||||
|
|
|
@ -2,14 +2,27 @@ import React, { useState, useRef } from "react";
|
||||||
import { CountdownCircleTimer } from "react-countdown-circle-timer";
|
import { CountdownCircleTimer } from "react-countdown-circle-timer";
|
||||||
import Reward, { RewardElement } from 'react-rewards';
|
import Reward, { RewardElement } from 'react-rewards';
|
||||||
import Link from "../components/Link";
|
import Link from "../components/Link";
|
||||||
|
import { useUser } from "../user-context";
|
||||||
import "./timer.css";
|
import "./timer.css";
|
||||||
import {
|
import {
|
||||||
getPercentageDone,
|
getPercentageDone,
|
||||||
} from "../lib/game-utils";
|
} from "../lib/game-utils";
|
||||||
|
import {
|
||||||
|
Types as T,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
const errorVibration = 200;
|
const errorVibration = 200;
|
||||||
|
|
||||||
function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label }: GameInput<T>) {
|
function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, id }:{
|
||||||
|
id: string,
|
||||||
|
studyLink: string,
|
||||||
|
Instructions: (props: { opts?: T.TextOptions }) => JSX.Element,
|
||||||
|
questions: () => QuestionGenerator<T>,
|
||||||
|
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
||||||
|
timeLimit: number;
|
||||||
|
}) {
|
||||||
|
// TODO: report pass with id to user info
|
||||||
const rewardRef = useRef<RewardElement | null>(null);
|
const rewardRef = useRef<RewardElement | null>(null);
|
||||||
|
const { user } = useUser();
|
||||||
const [finish, setFinish] = useState<null | "pass" | "fail" | "time out">(null);
|
const [finish, setFinish] = useState<null | "pass" | "fail" | "time out">(null);
|
||||||
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
||||||
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
||||||
|
@ -30,10 +43,17 @@ function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label
|
||||||
else setCurrent(next.value);
|
else setCurrent(next.value);
|
||||||
}
|
}
|
||||||
function handleFinish() {
|
function handleFinish() {
|
||||||
// post results
|
if (user) {
|
||||||
|
// TODO: post results
|
||||||
|
console.log(
|
||||||
|
"will post results for",
|
||||||
|
user.userId,
|
||||||
|
"results id",
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
setFinish("pass");
|
setFinish("pass");
|
||||||
rewardRef.current?.rewardMe();
|
rewardRef.current?.rewardMe();
|
||||||
setCurrent(undefined);
|
|
||||||
}
|
}
|
||||||
function handleQuit() {
|
function handleQuit() {
|
||||||
setFinish(null);
|
setFinish(null);
|
||||||
|
@ -66,8 +86,8 @@ function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label
|
||||||
: finish === "fail"
|
: finish === "fail"
|
||||||
? "danger"
|
? "danger"
|
||||||
: "primary";
|
: "primary";
|
||||||
|
console.log("user is", user)
|
||||||
return <div>
|
return <div>
|
||||||
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {label}</h4>
|
|
||||||
<div className="text-center" style={{ minHeight: "200px" }}>
|
<div className="text-center" style={{ minHeight: "200px" }}>
|
||||||
<div className="progress" style={{ height: "5px" }}>
|
<div className="progress" style={{ height: "5px" }}>
|
||||||
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
||||||
|
@ -141,4 +161,4 @@ function failMessage(progress: Progress | undefined, finish: "time out" | "fail"
|
||||||
: `⏳ Time's Up ${face}`;
|
: `⏳ Time's Up ${face}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Game;
|
export default GameCore;
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function GameDisplay({ record: { title, Game } }: { record: GameRecord }) {
|
||||||
|
return <div>
|
||||||
|
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {title}</h4>
|
||||||
|
<Game />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GameDisplay;
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react";
|
||||||
|
import GenderGame from "./sub-cores/GenderGame";
|
||||||
|
import UnisexNounGame from "./sub-cores/UnisexNounGame";
|
||||||
|
|
||||||
|
const unisexNounsId = "unisex-nouns-1";
|
||||||
|
const nounGender1Id = "gender-nouns-1";
|
||||||
|
const nounGender2Id = "gender-nouns-2";
|
||||||
|
|
||||||
|
export const unisexNounGame: GameRecord = {
|
||||||
|
title: "Changing genders on unisex nouns",
|
||||||
|
id: unisexNounsId,
|
||||||
|
Game: function() {
|
||||||
|
// TODO: Why won't this.id word here??!
|
||||||
|
return <UnisexNounGame id={unisexNounsId} />;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nounGenderGame1: GameRecord = {
|
||||||
|
title: "Identify Noun Genders - Level 1",
|
||||||
|
id: nounGender1Id,
|
||||||
|
Game: function() {
|
||||||
|
return <GenderGame id={nounGender1Id} level={1} />;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nounGenderGame2: GameRecord = {
|
||||||
|
title: "Identify Noun Genders - Level 1",
|
||||||
|
id: nounGender2Id,
|
||||||
|
Game: function() {
|
||||||
|
return <GenderGame id={nounGender2Id} level={2} />;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const games: GameRecord[] = [
|
||||||
|
unisexNounGame,
|
||||||
|
nounGenderGame1,
|
||||||
|
nounGenderGame2,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default games;
|
|
@ -2,18 +2,18 @@ import React from "react";
|
||||||
import {
|
import {
|
||||||
getRandomFromList,
|
getRandomFromList,
|
||||||
makeProgress,
|
makeProgress,
|
||||||
} from "../lib/game-utils";
|
} from "../../lib/game-utils";
|
||||||
import genderColors from "../lib/gender-colors";
|
import genderColors from "../../lib/gender-colors";
|
||||||
import Game from "./Game";
|
import GameCore from "../GameCore";
|
||||||
import {
|
import {
|
||||||
Types as T,
|
Types as T,
|
||||||
Examples,
|
Examples,
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import words from "../words/nouns-adjs";
|
import words from "../../words/nouns-adjs";
|
||||||
import {
|
import {
|
||||||
firstVariation,
|
firstVariation,
|
||||||
} from "../lib/text-tools";
|
} from "../../lib/text-tools";
|
||||||
|
|
||||||
const genders: T.Gender[] = ["masc", "fem"];
|
const genders: T.Gender[] = ["masc", "fem"];
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ const exceptions: Record<string, CategorySet> = {
|
||||||
|
|
||||||
const amount = 40;
|
const amount = 40;
|
||||||
|
|
||||||
export default function({level}: { level: 1 | 2 }) {
|
export default function({level, id}: { level: 1 | 2, id: string}) {
|
||||||
function* questions () {
|
function* questions () {
|
||||||
const wordPool = {...types};
|
const wordPool = {...types};
|
||||||
const exceptionsPool = {...exceptions};
|
const exceptionsPool = {...exceptions};
|
||||||
|
@ -100,10 +100,10 @@ export default function({level}: { level: 1 | 2 }) {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Game
|
return <GameCore
|
||||||
label={level === 1 ? "Choose the right gender - Level 1" : "Choose the right gender - Level 2"}
|
|
||||||
studyLink={level === 1 ? "/nouns/nouns-gender#gender-by-ending" : "/nouns/nouns-gender#exceptions"}
|
studyLink={level === 1 ? "/nouns/nouns-gender#gender-by-ending" : "/nouns/nouns-gender#exceptions"}
|
||||||
questions={questions}
|
questions={questions}
|
||||||
|
id={id}
|
||||||
Display={Display}
|
Display={Display}
|
||||||
timeLimit={level === 1 ? 65 : 85}
|
timeLimit={level === 1 ? 65 : 85}
|
||||||
Instructions={Instructions}
|
Instructions={Instructions}
|
|
@ -3,9 +3,9 @@ import {
|
||||||
getRandomFromList,
|
getRandomFromList,
|
||||||
makeProgress,
|
makeProgress,
|
||||||
compareF,
|
compareF,
|
||||||
} from "../lib/game-utils";
|
} from "../../lib/game-utils";
|
||||||
import genderColors from "../lib/gender-colors";
|
import genderColors from "../../lib/gender-colors";
|
||||||
import Game from "./Game";
|
import GameCore from "../GameCore";
|
||||||
import {
|
import {
|
||||||
Types as T,
|
Types as T,
|
||||||
Examples,
|
Examples,
|
||||||
|
@ -14,10 +14,10 @@ import {
|
||||||
standardizePashto,
|
standardizePashto,
|
||||||
// pashtoConsonants,
|
// pashtoConsonants,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import words from "../words/nouns-adjs";
|
import words from "../../words/nouns-adjs";
|
||||||
import {
|
import {
|
||||||
firstVariation,
|
firstVariation,
|
||||||
} from "../lib/text-tools";
|
} from "../../lib/text-tools";
|
||||||
|
|
||||||
const nouns = words.filter((w) => w.category === "nouns-unisex").map(x => x.entry);
|
const nouns = words.filter((w) => w.category === "nouns-unisex").map(x => x.entry);
|
||||||
// type NType = "consonant" | "eyUnstressed" | "eyStressed" | "pashtun" | "withu"
|
// type NType = "consonant" | "eyUnstressed" | "eyStressed" | "pashtun" | "withu"
|
||||||
|
@ -34,7 +34,7 @@ const amount = 20;
|
||||||
|
|
||||||
type Question = { entry: T.DictionaryEntry, gender: T.Gender };
|
type Question = { entry: T.DictionaryEntry, gender: T.Gender };
|
||||||
|
|
||||||
export default function() {
|
export default function({ id }: { id: string }) {
|
||||||
function* questions (): Generator<Current<Question>> {
|
function* questions (): Generator<Current<Question>> {
|
||||||
let pool = [...nouns];
|
let pool = [...nouns];
|
||||||
for (let i = 0; i < amount; i++) {
|
for (let i = 0; i < amount; i++) {
|
||||||
|
@ -61,19 +61,26 @@ export default function() {
|
||||||
return g === "masc" ? "fem" : "masc";
|
return g === "masc" ? "fem" : "masc";
|
||||||
}
|
}
|
||||||
const [answer, setAnswer] = useState<string>("");
|
const [answer, setAnswer] = useState<string>("");
|
||||||
const inflected = inflectWord(question.entry) as T.UnisexInflections;
|
const infOut = inflectWord(question.entry);
|
||||||
|
if (!infOut) return <div>WORD ERROR</div>;
|
||||||
|
const { inflections } = infOut;
|
||||||
|
if (!inflections) return <div>WORD ERROR</div>;
|
||||||
const givenGender = question.gender === "masc" ? "masculine" : "feminine";
|
const givenGender = question.gender === "masc" ? "masculine" : "feminine";
|
||||||
const requiredGender = question.gender === "fem" ? "masculine" : "feminine";
|
const requiredGender = question.gender === "fem" ? "masculine" : "feminine";
|
||||||
if (!inflected || !inflected.masc || !inflected.fem) {
|
if (!("masc" in inflections ) || !("fem" in inflections)) {
|
||||||
return <div>WORD ERROR</div>;
|
return <div>WORD ERROR</div>;
|
||||||
}
|
}
|
||||||
function handleInput({ target: { value }}: React.ChangeEvent<HTMLInputElement>) {
|
if (!inflections.masc || !inflections.fem) {
|
||||||
|
return <div>WORD ERROR</div>;
|
||||||
|
}
|
||||||
|
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setAnswer(value);
|
setAnswer(value);
|
||||||
}
|
}
|
||||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const given = standardizePashto(answer.trim());
|
const given = standardizePashto(answer.trim());
|
||||||
const correct = inflected[flipGender(question.gender)][0].some((ps) => (
|
// @ts-ignore
|
||||||
|
const correct = inflections[flipGender(question.gender)][0].some((ps: T.PsString) => (
|
||||||
(given === ps.p) || compareF(given, ps.f)
|
(given === ps.p) || compareF(given, ps.f)
|
||||||
));
|
));
|
||||||
if (correct) {
|
if (correct) {
|
||||||
|
@ -86,7 +93,7 @@ export default function() {
|
||||||
<div className="pt-2 pb-1 mb-2" style={{ maxWidth: "300px", margin: "0 auto", backgroundColor: genderColors[question.gender === "masc" ? "m" : "f"]}}>
|
<div className="pt-2 pb-1 mb-2" style={{ maxWidth: "300px", margin: "0 auto", backgroundColor: genderColors[question.gender === "masc" ? "m" : "f"]}}>
|
||||||
<Examples opts={opts}>{[
|
<Examples opts={opts}>{[
|
||||||
{
|
{
|
||||||
...inflected[question.gender][0][0],
|
...inflections[question.gender][0][0],
|
||||||
e: firstVariation(question.entry.e),
|
e: firstVariation(question.entry.e),
|
||||||
}
|
}
|
||||||
]}</Examples>
|
]}</Examples>
|
||||||
|
@ -119,10 +126,10 @@ export default function() {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Game
|
return <GameCore
|
||||||
label="Changing genders on unisex nouns"
|
|
||||||
studyLink="/nouns/nouns-unisex#"
|
studyLink="/nouns/nouns-unisex#"
|
||||||
questions={questions}
|
questions={questions}
|
||||||
|
id={id}
|
||||||
Display={Display}
|
Display={Display}
|
||||||
timeLimit={130}
|
timeLimit={130}
|
||||||
Instructions={Instructions}
|
Instructions={Instructions}
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { BrowserRouter as Router } from "react-router-dom";
|
import { BrowserRouter as Router } from "react-router-dom";
|
||||||
|
import { UserProvider} from "./user-context";
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
import "@fortawesome/fontawesome-free/css/all.css";
|
import "@fortawesome/fontawesome-free/css/all.css";
|
||||||
|
@ -17,7 +18,9 @@ import "@fortawesome/fontawesome-free/css/all.css";
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Router>
|
<Router>
|
||||||
<App />
|
<UserProvider>
|
||||||
|
<App />
|
||||||
|
</UserProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
|
|
|
@ -15,11 +15,8 @@ type QuestionDisplayProps<T> = {
|
||||||
callback: (correct: boolean) => void,
|
callback: (correct: boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
type GameInput<T> = {
|
type GameRecord = {
|
||||||
label: string,
|
title: string,
|
||||||
studyLink: string,
|
id: string,
|
||||||
Instructions: (props: { opts?: import("@lingdocs/pashto-inflector").Types.TextOptions }) => JSX.Element,
|
Game: () => JSX.Element,
|
||||||
questions: () => QuestionGenerator<T>,
|
};
|
||||||
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
|
||||||
timeLimit: number;
|
|
||||||
}
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React, { useState, createContext } from "react"
|
||||||
|
import { AT } from "@lingdocs/lingdocs-main";
|
||||||
|
|
||||||
|
const UserContext = createContext<
|
||||||
|
{ user: AT.LingdocsUser | undefined, setUser: React.Dispatch<React.SetStateAction<AT.LingdocsUser | undefined>> }
|
||||||
|
| undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
function UserProvider({ children }: any) {
|
||||||
|
const [user, setUser] = useState<AT.LingdocsUser | undefined>(undefined);
|
||||||
|
return <UserContext.Provider value={{ user, setUser }}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useUser() {
|
||||||
|
const context = React.useContext(UserContext)
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useCount must be used within a CountProvider')
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { UserProvider, useUser };
|
130
yarn.lock
130
yarn.lock
|
@ -1566,10 +1566,19 @@
|
||||||
"@types/yargs" "^15.0.0"
|
"@types/yargs" "^15.0.0"
|
||||||
chalk "^4.0.0"
|
chalk "^4.0.0"
|
||||||
|
|
||||||
"@lingdocs/pashto-inflector@^0.9.3":
|
"@lingdocs/lingdocs-main@^0.0.4":
|
||||||
version "0.9.6"
|
version "0.0.4"
|
||||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-0.9.6.tgz#c7b5fe2d0c253cdae48151f667b0248600603819"
|
resolved "https://npm.lingdocs.com/@lingdocs%2flingdocs-main/-/lingdocs-main-0.0.4.tgz#7ca25f48e934f070d3c6048be1787dd8fb3133af"
|
||||||
integrity sha512-/fLCHUdRqCLO9bDDve59yQlRXNd+7gXOeD6aO+IKOBz2KhIzSk7KRctSdM1jfv2E356QU/JmVL/+o0EfdWI33Q==
|
integrity sha512-6tegCbI7eeq43GIAspoAfGhkCXNbXgH/9immS+WljJ3188LIXFduMikwFbJZYVI7h3eUrOz1CYCB0qbdbLd0Vw==
|
||||||
|
dependencies:
|
||||||
|
passport-github2 "^0.1.12"
|
||||||
|
passport-google-oauth "^2.0.0"
|
||||||
|
passport-twitter "^1.0.4"
|
||||||
|
|
||||||
|
"@lingdocs/pashto-inflector@^1.0.5":
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-1.0.6.tgz#b61262c04916442a1023002bd7426ee39d77635f"
|
||||||
|
integrity sha512-yhijpCx1nBHnwykydOPOWzZlA388EioLr/SftNcYgRilxOjHVzXzEHL9dMNxciAiCNKFcNic2UVp+Tha4WEbSA==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.6"
|
classnames "^2.2.6"
|
||||||
pbf "^3.2.1"
|
pbf "^3.2.1"
|
||||||
|
@ -1906,6 +1915,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@types/unist" "*"
|
||||||
|
|
||||||
|
"@types/history@*":
|
||||||
|
version "4.7.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724"
|
||||||
|
integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==
|
||||||
|
|
||||||
"@types/invariant@^2.2.29":
|
"@types/invariant@^2.2.29":
|
||||||
version "2.2.35"
|
version "2.2.35"
|
||||||
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be"
|
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be"
|
||||||
|
@ -2022,6 +2036,23 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-router-dom@^5.1.9":
|
||||||
|
version "5.1.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.9.tgz#e8a8f687351ecc8c68bb4161d7e4b9df4994416e"
|
||||||
|
integrity sha512-Go0vxZSigXTyXx8xPkGiBrrc3YbBs82KE14WENMLS6TSUKcRFSmYVbL19zFOnNFqJhqrPqEs2h5eUpJhSRrwZw==
|
||||||
|
dependencies:
|
||||||
|
"@types/history" "*"
|
||||||
|
"@types/react" "*"
|
||||||
|
"@types/react-router" "*"
|
||||||
|
|
||||||
|
"@types/react-router@*":
|
||||||
|
version "5.1.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.16.tgz#f3ba045fb96634e38b21531c482f9aeb37608a99"
|
||||||
|
integrity sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==
|
||||||
|
dependencies:
|
||||||
|
"@types/history" "*"
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-transition-group@^4.4.1":
|
"@types/react-transition-group@^4.4.1":
|
||||||
version "4.4.1"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1"
|
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1"
|
||||||
|
@ -2883,6 +2914,11 @@ base64-js@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||||
|
|
||||||
|
base64url@3.x.x:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||||
|
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||||
|
|
||||||
base@^0.11.1:
|
base@^0.11.1:
|
||||||
version "0.11.2"
|
version "0.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||||
|
@ -8106,6 +8142,11 @@ oauth-sign@~0.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||||
|
|
||||||
|
oauth@0.9.x:
|
||||||
|
version "0.9.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
|
||||||
|
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
|
||||||
|
|
||||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
@ -8521,6 +8562,68 @@ pascalcase@^0.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
|
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
|
||||||
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
|
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
|
||||||
|
|
||||||
|
passport-github2@^0.1.12:
|
||||||
|
version "0.1.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-github2/-/passport-github2-0.1.12.tgz#a72ebff4fa52a35bc2c71122dcf470d1116f772c"
|
||||||
|
integrity sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==
|
||||||
|
dependencies:
|
||||||
|
passport-oauth2 "1.x.x"
|
||||||
|
|
||||||
|
passport-google-oauth1@1.x.x:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
|
||||||
|
integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw=
|
||||||
|
dependencies:
|
||||||
|
passport-oauth1 "1.x.x"
|
||||||
|
|
||||||
|
passport-google-oauth20@2.x.x:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
|
||||||
|
integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==
|
||||||
|
dependencies:
|
||||||
|
passport-oauth2 "1.x.x"
|
||||||
|
|
||||||
|
passport-google-oauth@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae"
|
||||||
|
integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==
|
||||||
|
dependencies:
|
||||||
|
passport-google-oauth1 "1.x.x"
|
||||||
|
passport-google-oauth20 "2.x.x"
|
||||||
|
|
||||||
|
passport-oauth1@1.x.x:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.2.0.tgz#5229d431781bf5b265bec86ce9a9cce58a756cf9"
|
||||||
|
integrity sha512-Sv2YWodC6jN12M/OXwmR4BIXeeIHjjbwYTQw4kS6tHK4zYzSEpxBgSJJnknBjICA5cj0ju3FSnG1XmHgIhYnLg==
|
||||||
|
dependencies:
|
||||||
|
oauth "0.9.x"
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
utils-merge "1.x.x"
|
||||||
|
|
||||||
|
passport-oauth2@1.x.x:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.0.tgz#5f599735e0ea40ea3027643785f81a3a9b4feb50"
|
||||||
|
integrity sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw==
|
||||||
|
dependencies:
|
||||||
|
base64url "3.x.x"
|
||||||
|
oauth "0.9.x"
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
uid2 "0.0.x"
|
||||||
|
utils-merge "1.x.x"
|
||||||
|
|
||||||
|
passport-strategy@1.x.x:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
||||||
|
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
|
||||||
|
|
||||||
|
passport-twitter@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-twitter/-/passport-twitter-1.0.4.tgz#01a799e1f760bf2de49f2ba5fba32282f18932d7"
|
||||||
|
integrity sha1-AaeZ4fdgvy3knyul+6MigvGJMtc=
|
||||||
|
dependencies:
|
||||||
|
passport-oauth1 "1.x.x"
|
||||||
|
xtraverse "0.1.x"
|
||||||
|
|
||||||
path-browserify@0.0.1:
|
path-browserify@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
|
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
|
||||||
|
@ -11792,6 +11895,11 @@ typographic-quotes@^1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
typographic-quotes-l10n-db "^1.0.0"
|
typographic-quotes-l10n-db "^1.0.0"
|
||||||
|
|
||||||
|
uid2@0.0.x:
|
||||||
|
version "0.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44"
|
||||||
|
integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==
|
||||||
|
|
||||||
uncontrollable@^7.0.0, uncontrollable@^7.2.1:
|
uncontrollable@^7.0.0, uncontrollable@^7.2.1:
|
||||||
version "7.2.1"
|
version "7.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738"
|
resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738"
|
||||||
|
@ -12123,7 +12231,7 @@ utila@^0.4.0, utila@~0.4:
|
||||||
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
|
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
|
||||||
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
|
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
|
||||||
|
|
||||||
utils-merge@1.0.1:
|
utils-merge@1.0.1, utils-merge@1.x.x:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
@ -12674,6 +12782,11 @@ xmlchars@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||||
|
|
||||||
|
xmldom@0.1.x:
|
||||||
|
version "0.1.31"
|
||||||
|
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
|
||||||
|
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
|
||||||
|
|
||||||
xregexp@^4.3.0:
|
xregexp@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
|
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
|
||||||
|
@ -12686,6 +12799,13 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
|
xtraverse@0.1.x:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/xtraverse/-/xtraverse-0.1.0.tgz#b741bad018ef78d8a9d2e83ade007b3f7959c732"
|
||||||
|
integrity sha1-t0G60BjveNip0ug63gB7P3lZxzI=
|
||||||
|
dependencies:
|
||||||
|
xmldom "0.1.x"
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||||
|
|
Loading…
Reference in New Issue