more with cleaner demo
This commit is contained in:
parent
1da82dc4da
commit
56b92a912c
|
@ -10,14 +10,47 @@ import {
|
||||||
renderVP,
|
renderVP,
|
||||||
} from "../components/library";
|
} from "../components/library";
|
||||||
|
|
||||||
|
const working = [
|
||||||
|
"limited demo vocab",
|
||||||
|
"phrases with simple verbs",
|
||||||
|
"basic verb tenses",
|
||||||
|
"noun phrases (except participles)",
|
||||||
|
"mini-pronouns for shrunken servants",
|
||||||
|
"grammar error correction",
|
||||||
|
];
|
||||||
|
|
||||||
|
const todo = [
|
||||||
|
"participles",
|
||||||
|
"compound verbs",
|
||||||
|
"adverbial phrases",
|
||||||
|
"relative clauses",
|
||||||
|
"equative verbs",
|
||||||
|
"perfect tenses",
|
||||||
|
"ability verbs",
|
||||||
|
"imperative verbs",
|
||||||
|
"passive verbs",
|
||||||
|
"quantifiers",
|
||||||
|
"demonstrative pronouns",
|
||||||
|
"mini-pronouns for possesives",
|
||||||
|
"approximate spelling",
|
||||||
|
];
|
||||||
|
|
||||||
|
const examples = [
|
||||||
|
"سړي زه ولیدم",
|
||||||
|
"تلم به",
|
||||||
|
"یو به مې ړلې",
|
||||||
|
"د غټې ماشومې زاړه پلار ولیدم",
|
||||||
|
"ستا پخواني ملګري مې ولیدل",
|
||||||
|
"ما ډوډۍ خوړله",
|
||||||
|
];
|
||||||
|
|
||||||
function ParserDemo({ opts }: { opts: T.TextOptions }) {
|
function ParserDemo({ opts }: { opts: T.TextOptions }) {
|
||||||
const [text, setText] = useState<string>("");
|
const [text, setText] = useState<string>("");
|
||||||
const [result, setResult] = useState<
|
const [result, setResult] = useState<
|
||||||
ReturnType<typeof parsePhrase>["success"]
|
ReturnType<typeof parsePhrase>["success"]
|
||||||
>([]);
|
>([]);
|
||||||
const [errors, setErrors] = useState<string[]>([]);
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
function handleInput(value: string) {
|
||||||
const value = e.target.value;
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
setText("");
|
setText("");
|
||||||
setResult([]);
|
setResult([]);
|
||||||
|
@ -31,11 +64,30 @@ function ParserDemo({ opts }: { opts: T.TextOptions }) {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mt-3" style={{ marginBottom: "1000px" }}>
|
<div className="mt-3" style={{ marginBottom: "1000px" }}>
|
||||||
<div>Type a sentence to parse</div>
|
<div className="mb-2">Type a sentence to parse</div>
|
||||||
<div className="small text-muted">
|
<div className="small text-muted mb-2">
|
||||||
(NOT DONE!! limited vocab, and not working for APs, compound verbs, or
|
<div>
|
||||||
grammatically transitive verbs... yet 👷)
|
<strong>NOT DONE:</strong> <em>sort of</em> works with:{` `}
|
||||||
|
{working.map((x) => (
|
||||||
|
<span className="mr-2" key={x}>
|
||||||
|
✅ {x}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{todo.map((x) => (
|
||||||
|
<span className="mr-2" key={x}>
|
||||||
|
❌ {x}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-2 text-right">working examples</div>
|
||||||
|
<ul dir="rtl" className="text-right">
|
||||||
|
{examples.map((ex) => (
|
||||||
|
<li key={ex} className="clickable" onClick={() => handleInput(ex)}>
|
||||||
|
{ex}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
<div className="form-group mb-2">
|
<div className="form-group mb-2">
|
||||||
<input
|
<input
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
|
@ -48,7 +100,7 @@ function ParserDemo({ opts }: { opts: T.TextOptions }) {
|
||||||
}`}
|
}`}
|
||||||
type="text"
|
type="text"
|
||||||
value={text}
|
value={text}
|
||||||
onChange={handleChange}
|
onChange={(e) => handleInput(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.length > 0 && (
|
{errors.length > 0 && (
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
endsWith,
|
endsWith,
|
||||||
trimOffPs,
|
trimOffPs,
|
||||||
undoAaXuPattern,
|
undoAaXuPattern,
|
||||||
prevValNotA,
|
|
||||||
lastVowelNotA,
|
lastVowelNotA,
|
||||||
} from "./p-text-helpers";
|
} from "./p-text-helpers";
|
||||||
import * as T from "../../types";
|
import * as T from "../../types";
|
||||||
|
|
|
@ -42,6 +42,9 @@ export function shouldCheckTpp(s: string): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verbLookup(input: string): T.VerbEntry[] {
|
export function verbLookup(input: string): T.VerbEntry[] {
|
||||||
|
// TODO:
|
||||||
|
// only look up forms if there's an ending
|
||||||
|
// or is third person thing
|
||||||
const s = input.slice(0, -1);
|
const s = input.slice(0, -1);
|
||||||
// IMPORTANT TODO FOR EFFECIANCY!
|
// IMPORTANT TODO FOR EFFECIANCY!
|
||||||
// check endings TODO: ONLY LOOKUP THE VERB POSSIBILITIES IF IT HAS A LEGITIMATE ENDING
|
// check endings TODO: ONLY LOOKUP THE VERB POSSIBILITIES IF IT HAS A LEGITIMATE ENDING
|
||||||
|
@ -127,7 +130,10 @@ export function verbLookup(input: string): T.VerbEntry[] {
|
||||||
) ||
|
) ||
|
||||||
[s, sAddedAa, "و" + s].includes(entry.ssp || "") ||
|
[s, sAddedAa, "و" + s].includes(entry.ssp || "") ||
|
||||||
(entry.separationAtP &&
|
(entry.separationAtP &&
|
||||||
|
// TODO this is super ugly, do check of short and long function
|
||||||
(entry.p.slice(entry.separationAtP) === s ||
|
(entry.p.slice(entry.separationAtP) === s ||
|
||||||
|
entry.p.slice(entry.separationAtP, -1) === s ||
|
||||||
|
(checkTpp && entry.p.slice(entry.separationAtP, -1) === input) ||
|
||||||
entry.psp?.slice(entry.separationAtP) === s ||
|
entry.psp?.slice(entry.separationAtP) === s ||
|
||||||
(entry.prp &&
|
(entry.prp &&
|
||||||
[
|
[
|
||||||
|
|
|
@ -73,18 +73,22 @@ function phMatches(ph: T.ParsedPH | undefined, vb: T.ParsedVBE | undefined) {
|
||||||
if (!ph) {
|
if (!ph) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const v = vb && vb.type === "VB" && vb.info.type === "verb" && vb.info.verb;
|
if (!vb) {
|
||||||
if (!v) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const verbPh = getPhFromVerb(v);
|
if (vb.info.type !== "verb") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const verbPh = getPhFromVerb(vb.info.verb, vb.info.base);
|
||||||
return verbPh === ph.s;
|
return verbPh === ph.s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPhFromVerb(v: T.VerbEntry): string {
|
function getPhFromVerb(v: T.VerbEntry, base: "root" | "stem"): string {
|
||||||
// TODO!! what to do about yo / bo ???
|
// TODO!! what to do about yo / bo ???
|
||||||
if (v.entry.separationAtP) {
|
if (v.entry.separationAtP) {
|
||||||
return v.entry.p.slice(0, v.entry.separationAtP);
|
const p =
|
||||||
|
base === "root" ? v.entry.prp || v.entry.p : v.entry.ssp || v.entry.p;
|
||||||
|
return p.slice(0, v.entry.separationAtP);
|
||||||
}
|
}
|
||||||
// TODO or آ
|
// TODO or آ
|
||||||
if (v.entry.p.startsWith("ا")) {
|
if (v.entry.p.startsWith("ا")) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { verbLookup, wordQuery } from "./lookup";
|
import { verbLookup, wordQuery } from "./lookup";
|
||||||
import { parseVerb } from "./parse-verb";
|
import { parseVerb } from "./parse-verb";
|
||||||
import { tokenizer } from "./tokenizer";
|
import { tokenizer } from "./tokenizer";
|
||||||
import { removeKeys } from "./utils";
|
import { getPeople, removeKeys } from "./utils";
|
||||||
|
|
||||||
const wahul = wordQuery("وهل", "verb");
|
const wahul = wordQuery("وهل", "verb");
|
||||||
const leekul = wordQuery("لیکل", "verb");
|
const leekul = wordQuery("لیکل", "verb");
|
||||||
|
@ -383,6 +383,11 @@ const tests: {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// but not for kedul
|
||||||
|
{
|
||||||
|
input: "کې",
|
||||||
|
output: [],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -729,6 +734,32 @@ const tests: {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "کېناست",
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
ph: "کې",
|
||||||
|
root: {
|
||||||
|
persons: [T.Person.ThirdSingMale],
|
||||||
|
aspects: ["imperfective", "perfective"],
|
||||||
|
},
|
||||||
|
verb: kenaastul,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "ناست",
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
ph: undefined,
|
||||||
|
root: {
|
||||||
|
persons: [T.Person.ThirdSingMale],
|
||||||
|
aspects: ["perfective"],
|
||||||
|
},
|
||||||
|
verb: kenaastul,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: "پرېږدو",
|
input: "پرېږدو",
|
||||||
output: [
|
output: [
|
||||||
|
|
|
@ -73,7 +73,7 @@ function matchVerbs(
|
||||||
return e.psp === base;
|
return e.psp === base;
|
||||||
}
|
}
|
||||||
if (e.c.includes("intrans.")) {
|
if (e.c.includes("intrans.")) {
|
||||||
const miniRoot = e.p.slice(0, -3);
|
const miniRoot = e.p !== "کېدل" && e.p.slice(0, -3);
|
||||||
return miniRoot + "ېږ" === base || miniRoot === base;
|
return miniRoot + "ېږ" === base || miniRoot === base;
|
||||||
} else {
|
} else {
|
||||||
return e.p.slice(0, -1) === base;
|
return e.p.slice(0, -1) === base;
|
||||||
|
@ -168,7 +168,7 @@ function matchVerbs(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} else if (e.c.includes("intrans.")) {
|
} else if (e.c.includes("intrans.")) {
|
||||||
const miniRoot = e.p.slice(0, -3);
|
const miniRoot = e.p !== "کېدل" && e.p.slice(0, -3);
|
||||||
const miniRootEg = miniRoot + "ېږ";
|
const miniRootEg = miniRoot + "ېږ";
|
||||||
if ([miniRoot, miniRootEg].includes(base)) {
|
if ([miniRoot, miniRootEg].includes(base)) {
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import {
|
import {
|
||||||
makeObjectSelectionComplete,
|
makeObjectSelectionComplete,
|
||||||
makeSubjectSelection,
|
|
||||||
makeSubjectSelectionComplete,
|
makeSubjectSelectionComplete,
|
||||||
} from "../phrase-building/blocks-utils";
|
} from "../phrase-building/blocks-utils";
|
||||||
import {
|
import {
|
||||||
|
@ -20,6 +19,8 @@ const sarey = wordQuery("سړی", "noun");
|
||||||
const rasedul = wordQuery("رسېدل", "verb");
|
const rasedul = wordQuery("رسېدل", "verb");
|
||||||
const maashoom = wordQuery("ماشوم", "noun");
|
const maashoom = wordQuery("ماشوم", "noun");
|
||||||
const leedul = wordQuery("لیدل", "verb");
|
const leedul = wordQuery("لیدل", "verb");
|
||||||
|
const kenaastul = wordQuery("کېناستل", "verb");
|
||||||
|
const wurul = wordQuery("وړل", "verb");
|
||||||
|
|
||||||
const tests: {
|
const tests: {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -91,7 +92,7 @@ const tests: {
|
||||||
verb: {
|
verb: {
|
||||||
type: "verb",
|
type: "verb",
|
||||||
verb: tlul,
|
verb: tlul,
|
||||||
transitivity: "transitive",
|
transitivity: "intransitive",
|
||||||
canChangeTransitivity: false,
|
canChangeTransitivity: false,
|
||||||
canChangeStatDyn: false,
|
canChangeStatDyn: false,
|
||||||
negative: false,
|
negative: false,
|
||||||
|
@ -129,7 +130,7 @@ const tests: {
|
||||||
verb: {
|
verb: {
|
||||||
type: "verb",
|
type: "verb",
|
||||||
verb: tlul,
|
verb: tlul,
|
||||||
transitivity: "transitive",
|
transitivity: "intransitive",
|
||||||
canChangeTransitivity: false,
|
canChangeTransitivity: false,
|
||||||
canChangeStatDyn: false,
|
canChangeStatDyn: false,
|
||||||
negative: false,
|
negative: false,
|
||||||
|
@ -168,7 +169,7 @@ const tests: {
|
||||||
verb: {
|
verb: {
|
||||||
type: "verb",
|
type: "verb",
|
||||||
verb: rasedul,
|
verb: rasedul,
|
||||||
transitivity: "transitive",
|
transitivity: "intransitive",
|
||||||
canChangeTransitivity: false,
|
canChangeTransitivity: false,
|
||||||
canChangeStatDyn: false,
|
canChangeStatDyn: false,
|
||||||
negative: false,
|
negative: false,
|
||||||
|
@ -208,7 +209,7 @@ const tests: {
|
||||||
verb: {
|
verb: {
|
||||||
type: "verb",
|
type: "verb",
|
||||||
verb: rasedul,
|
verb: rasedul,
|
||||||
transitivity: "transitive",
|
transitivity: "intransitive",
|
||||||
canChangeTransitivity: false,
|
canChangeTransitivity: false,
|
||||||
canChangeStatDyn: false,
|
canChangeStatDyn: false,
|
||||||
negative: false,
|
negative: false,
|
||||||
|
@ -252,7 +253,7 @@ const tests: {
|
||||||
verb: {
|
verb: {
|
||||||
type: "verb",
|
type: "verb",
|
||||||
verb: tlul,
|
verb: tlul,
|
||||||
transitivity: "transitive",
|
transitivity: "intransitive",
|
||||||
canChangeTransitivity: false,
|
canChangeTransitivity: false,
|
||||||
canChangeStatDyn: false,
|
canChangeStatDyn: false,
|
||||||
negative: false,
|
negative: false,
|
||||||
|
@ -268,6 +269,46 @@ const tests: {
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "کې به ناست",
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
block: makeSubjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(T.Person.ThirdSingMale),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
block: {
|
||||||
|
type: "objectSelection",
|
||||||
|
selection: "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verb: {
|
||||||
|
type: "verb",
|
||||||
|
verb: kenaastul,
|
||||||
|
transitivity: "intransitive",
|
||||||
|
canChangeTransitivity: false,
|
||||||
|
canChangeStatDyn: false,
|
||||||
|
negative: false,
|
||||||
|
tense: "habitualPerfectivePast",
|
||||||
|
canChangeVoice: true,
|
||||||
|
isCompound: false,
|
||||||
|
voice: "active",
|
||||||
|
},
|
||||||
|
externalComplement: undefined,
|
||||||
|
form: {
|
||||||
|
removeKing: true,
|
||||||
|
shrinkServant: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -938,6 +979,129 @@ const tests: {
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "ودې وینم",
|
||||||
|
output: getPeople(2, "sing").flatMap((objectPerson) =>
|
||||||
|
getPeople(1, "sing").map<T.VPSelectionComplete>((subjectPerson) => ({
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
block: makeSubjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(subjectPerson),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
block: makeObjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(objectPerson),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verb: {
|
||||||
|
type: "verb",
|
||||||
|
verb: leedul,
|
||||||
|
transitivity: "transitive",
|
||||||
|
canChangeTransitivity: false,
|
||||||
|
canChangeStatDyn: false,
|
||||||
|
negative: false,
|
||||||
|
tense: "subjunctiveVerb",
|
||||||
|
canChangeVoice: true,
|
||||||
|
isCompound: false,
|
||||||
|
voice: "active",
|
||||||
|
},
|
||||||
|
externalComplement: undefined,
|
||||||
|
form: {
|
||||||
|
removeKing: true,
|
||||||
|
shrinkServant: true,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "وینم به دې",
|
||||||
|
output: getPeople(2, "sing").flatMap((objectPerson) =>
|
||||||
|
getPeople(1, "sing").map<T.VPSelectionComplete>((subjectPerson) => ({
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
block: makeSubjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(subjectPerson),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
block: makeObjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(objectPerson),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verb: {
|
||||||
|
type: "verb",
|
||||||
|
verb: leedul,
|
||||||
|
transitivity: "transitive",
|
||||||
|
canChangeTransitivity: false,
|
||||||
|
canChangeStatDyn: false,
|
||||||
|
negative: false,
|
||||||
|
tense: "imperfectiveFuture",
|
||||||
|
canChangeVoice: true,
|
||||||
|
isCompound: false,
|
||||||
|
voice: "active",
|
||||||
|
},
|
||||||
|
externalComplement: undefined,
|
||||||
|
form: {
|
||||||
|
removeKing: true,
|
||||||
|
shrinkServant: true,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "یو به مې ړلې",
|
||||||
|
output: [...getPeople(2, "sing"), T.Person.ThirdPlurFemale].flatMap(
|
||||||
|
(objectPerson) =>
|
||||||
|
getPeople(1, "sing").map<T.VPSelectionComplete>(
|
||||||
|
(subjectPerson) => ({
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
block: makeSubjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(subjectPerson),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
block: makeObjectSelectionComplete({
|
||||||
|
type: "NP",
|
||||||
|
selection: makePronounSelection(objectPerson),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
verb: {
|
||||||
|
type: "verb",
|
||||||
|
verb: wurul,
|
||||||
|
transitivity: "transitive",
|
||||||
|
canChangeTransitivity: false,
|
||||||
|
canChangeStatDyn: false,
|
||||||
|
negative: false,
|
||||||
|
tense: "habitualPerfectivePast",
|
||||||
|
canChangeVoice: true,
|
||||||
|
isCompound: false,
|
||||||
|
voice: "active",
|
||||||
|
},
|
||||||
|
externalComplement: undefined,
|
||||||
|
form: {
|
||||||
|
removeKing: true,
|
||||||
|
shrinkServant: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { isFirstOrSecondPersPronoun } from "../phrase-building/render-vp";
|
||||||
|
|
||||||
// make impossible subjects like I saw me, error
|
// make impossible subjects like I saw me, error
|
||||||
|
|
||||||
// کې به ناست not working!
|
// TODO: learn how to yank / use plugin for JSON neovim
|
||||||
|
|
||||||
// TODO: transitivity options
|
// TODO: transitivity options
|
||||||
|
|
||||||
|
@ -72,7 +72,9 @@ export function parseVP(
|
||||||
const v: T.VerbSelectionComplete = {
|
const v: T.VerbSelectionComplete = {
|
||||||
type: "verb",
|
type: "verb",
|
||||||
verb: verb.info.verb,
|
verb: verb.info.verb,
|
||||||
transitivity: "transitive",
|
transitivity: verb.info.verb.entry.c.includes("intrans")
|
||||||
|
? "intransitive"
|
||||||
|
: "transitive",
|
||||||
canChangeTransitivity: false,
|
canChangeTransitivity: false,
|
||||||
canChangeStatDyn: false,
|
canChangeStatDyn: false,
|
||||||
negative: false,
|
negative: false,
|
||||||
|
|
Loading…
Reference in New Issue