half functional textOptions sync

This commit is contained in:
lingdocs 2021-08-24 23:08:45 +04:00
parent 5031d64139
commit c63ab30f1e
18 changed files with 192 additions and 201 deletions

View File

@ -95,7 +95,8 @@ export async function updateLingdocsUser(uuid: T.UUID, toUpdate:
level: "student",
wordlistDbName: T.WordlistDbName,
couchDbPassword: T.UserDbPassword,
}
} |
{ userTextOptionsRecord: T.UserTextOptionsRecord }
): Promise<T.LingdocsUser> {
const user = await getLingdocsUser("userId", uuid);
if (!user) throw new Error("unable to update - user not found " + uuid);

View File

@ -74,6 +74,7 @@ export async function createNewUser(input: {
tests: [],
lastLogin: now,
lastActive: now,
textOptions: undefined,
};
const user = await insertLingdocsUser(newUser);
sendVerificationEmail(user, email.token).catch(console.error);
@ -91,6 +92,7 @@ export async function createNewUser(input: {
tests: [],
lastLogin: now,
lastActive: now,
textOptions: undefined,
};
const user = await insertLingdocsUser(newUser);
return user;
@ -111,6 +113,7 @@ export async function createNewUser(input: {
tests: [],
lastActive: now,
level: "basic",
textOptions: undefined,
}
const user = await insertLingdocsUser(newUser);
sendVerificationEmail(user, em.token);
@ -127,6 +130,7 @@ export async function createNewUser(input: {
tests: [],
lastActive: now,
level: "basic",
textOptions: undefined,
}
const user = await insertLingdocsUser(newUser);
return user;

View File

@ -93,6 +93,18 @@ apiRouter.put("/email-verification", async (req, res, next) => {
}
});
apiRouter.put("/user/userTextOptionsRecord", async (req, res, next) => {
if (!req.user) throw new Error("user not found");
try {
const { userTextOptionsRecord } = req.body as T.UpdateUserTextOptionsRecordBody;
const user = await updateLingdocsUser(req.user.userId, { userTextOptionsRecord });
const toSend: T.UpdateUserTextOptionsRecordResponse = { ok: true, message: "updated userTextOptionsRecord", user };
res.send(toSend);
} catch (e) {
next(e);
}
})
apiRouter.put("/user/upgrade", async (req, res, next) => {
if (!req.user) throw new Error("user not found");
try {

View File

@ -32,7 +32,10 @@ import {
readUser,
} from "./lib/local-storage";
import { dictionary, pageSize } from "./lib/dictionary";
import optionsReducer from "./lib/options-reducer";
import {
optionsReducer,
textOptionsReducer,
} from "./lib/options-reducer";
import hitBottom from "./lib/hitBottom";
import getWordId from "./lib/get-word-id";
import { CronJob } from "cron";
@ -42,6 +45,7 @@ import {
} from "./lib/submissions";
import {
getUser,
updateUserTextOptionsRecord,
} from "./lib/backend-calls";
import {
getWordlist,
@ -57,6 +61,7 @@ import {
import {
textBadge,
} from "./lib/badges";
import * as AT from "./lib/account-types";
import ReactGA from "react-ga";
// tslint:disable-next-line
import "@fortawesome/fontawesome-free/css/all.css";
@ -91,12 +96,16 @@ class App extends Component<RouteComponentProps, State> {
this.state = {
dictionaryStatus: "loading",
dictionaryInfo: undefined,
// TODO: Choose between the saved options and the options in the saved user
options: savedOptions ? savedOptions : {
language: "Pashto",
searchType: "fuzzy",
theme: /* istanbul ignore next */ (window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
textOptions: defaultTextOptions,
textOptionsRecord: {
lastModified: Date.now() as AT.TimeStamp,
textOptions: defaultTextOptions,
},
wordlistMode: "browse",
wordlistReviewLanguage: "Pashto",
wordlistReviewBadge: true,
@ -254,6 +263,7 @@ class App extends Component<RouteComponentProps, State> {
if (user === "offline") return;
if (user) sendSubmissions();
this.setState({ user });
// TODO: LOAD AND RESOLVE THE USER'S TEXT OPTIONS ETC.
saveUser(user);
if (user) {
startLocalDbs(user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks });
@ -282,6 +292,7 @@ class App extends Component<RouteComponentProps, State> {
if (action.type === "changeTheme") {
document.documentElement.setAttribute("data-theme", action.payload);
}
// TODO: use a seperate reducer for changing text options (otherwise you could just be updating the saved text options instead of the user text options that the program is going off of)
const options = optionsReducer(this.state.options, action);
saveOptions(options);
if (action.type === "toggleLanguage" || action.type === "toggleSearchType") {
@ -300,6 +311,25 @@ class App extends Component<RouteComponentProps, State> {
}
}
private handleTextOptionsUpdate(action: TextOptionsAction) {
const textOptions = textOptionsReducer(this.state.options.textOptionsRecord.textOptions, action);
const lastModified = Date.now() as AT.TimeStamp;
const textOptionsRecord: TextOptionsRecord = {
lastModified,
textOptions,
};
this.handleOptionsUpdate({ type: "updateTextOptionsRecord", payload: textOptionsRecord });
// try to save the new text options to the user
if (this.state.user) {
const { pTextSize, ...userTextOptions } = textOptions;
const userTextOptionsRecord = {
userTextOptions,
lastModified,
};
updateUserTextOptionsRecord(userTextOptionsRecord);
}
}
private handleSearchValueChange(searchValue: string) {
if (this.state.dictionaryStatus !== "ready") return;
if (searchValue === "") {
@ -416,7 +446,12 @@ class App extends Component<RouteComponentProps, State> {
<About state={this.state} />
</Route>
<Route path="/settings">
<Options state={this.state} options={this.state.options} optionsDispatch={this.handleOptionsUpdate} />
<Options
state={this.state}
options={this.state.options}
optionsDispatch={this.handleOptionsUpdate}
textOptionsDispatch={this.handleTextOptionsUpdate}
/>
</Route>
<Route path="/search">
<Results state={this.state} isolateEntry={this.handleIsolateEntry} />

View File

@ -17,6 +17,13 @@ export type TwitterProfile = WoutRJ<import("passport-twitter").Profile> & { toke
export type ProviderProfile = GoogleProfile | GitHubProfile | TwitterProfile;
export type UserLevel = "basic" | "student" | "editor";
export type UserTextOptions = Omit<import("@lingdocs/pashto-inflector").Types.TextOptions, "pTextSize">;
export type UserTextOptionsRecord = {
lastModified: TimeStamp,
userTextOptions: UserTextOptions,
};
// TODO: TYPE GUARDING SO WE NEVER HAVE A USER WITH NO Id or Password
export type LingdocsUser = {
userId: UUID,
@ -34,8 +41,9 @@ export type LingdocsUser = {
tests: [],
lastLogin: TimeStamp,
lastActive: TimeStamp,
userTextOptionsRecord: undefined | UserTextOptionsRecord,
} & (
{ level: "basic"} |
{ level: "basic" } |
{
level: "student" | "editor",
couchDbPassword: UserDbPassword,
@ -58,3 +66,11 @@ export type UpgradeUserResponse = {
message: "user already upgraded" | "user upgraded to student",
user: LingdocsUser,
};
export type UpdateUserTextOptionsRecordBody = { userTextOptionsRecord: UserTextOptionsRecord };
export type UpdateUserTextOptionsRecordResponse = {
ok: true,
message: "updated userTextOptionsRecord",
user: LingdocsUser,
};

View File

@ -1,5 +1,6 @@
import * as FT from "./functions-types";
import * as AT from "./account-types";
import { Types as IT } from "@lingdocs/pashto-inflector";
type Service = "account" | "functions";
@ -23,6 +24,11 @@ export async function upgradeAccount(password: string): Promise<AT.UpgradeUserRe
return response as AT.UpgradeUserResponse;
}
export async function updateUserTextOptionsRecord(userTextOptionsRecord: AT.UserTextOptionsRecord): Promise<AT.UpdateUserTextOptionsRecordResponse> {
const response = await myFetch("account", "user/userTextOptionsRecord", "PUT", { userTextOptionsRecord }) as AT.UpdateUserTextOptionsRecordResponse;
return response;
}
export async function signOut() {
try {
await myFetch("account", "sign-out", "POST");
@ -48,7 +54,7 @@ async function myFetch(
service: Service,
url: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
body?: FT.SubmissionsRequest | { password: string },
body?: FT.SubmissionsRequest | { password: string } | AT.UpdateUserTextOptionsRecordBody,
): Promise<AT.APIResponse> {
const response = await fetch(baseUrl[service] + url, {
method,

View File

@ -19,6 +19,7 @@ import { fuzzifyPashto } from "./fuzzify-pashto/fuzzify-pashto";
// @ts-ignore
import relevancy from "relevancy";
import { makeAWeeBitFuzzy } from "./wee-bit-fuzzy";
import { getTextOptions } from "./get-text-options";
// const dictionaryBaseUrl = "https://storage.googleapis.com/lingdocs/";
const dictionaryUrl = `https://storage.googleapis.com/lingdocs/dictionary`;
@ -353,7 +354,7 @@ export const dictionary: DictionaryAPI = {
search: function(state: State): Types.DictionaryEntry[] {
const searchString = convertSpelling(
state.searchValue,
state.options.textOptions.spelling,
getTextOptions(state).spelling,
);
if (state.searchValue === "") {
return [];

View File

@ -0,0 +1,5 @@
import { Types as T } from "@lingdocs/pashto-inflector";
export function getTextOptions(state: State): T.TextOptions {
return state.options.textOptionsRecord.textOptions;
}

View File

@ -8,27 +8,20 @@
import * as AT from "./account-types";
export const optionsLocalStorageName = "options2";
export const optionsLocalStorageName = "options3";
export const userLocalStorageName = "user1";
export function saveOptions(options: Options): void {
localStorage.setItem(optionsLocalStorageName, JSON.stringify(options));
};
export const readOptions = (): Options | undefined => {
export const readOptions = (): undefined | Options => {
const optionsRaw = localStorage.getItem(optionsLocalStorageName);
if (!optionsRaw) {
return undefined;
}
try {
const options = JSON.parse(optionsRaw) as Options;
// check for new options here
if (options.wordlistReviewBadge === undefined) {
options.wordlistReviewBadge = true;
}
if (options.searchBarPosition === undefined) {
options.searchBarPosition = "top";
}
return options;
} catch (e) {
console.error("error parsing saved state JSON", e);

View File

@ -1,99 +0,0 @@
import optionsReducer from "./options-reducer";
import { defaultTextOptions } from "@lingdocs/pashto-inflector";
const options: Options = {
textOptions: defaultTextOptions,
language: "Pashto",
searchType: "fuzzy",
theme: "light",
wordlistMode: "browse",
wordlistReviewLanguage: "Pashto",
wordlistReviewBadge: true,
searchBarPosition: "top",
};
test("options reducer should work", () => {
expect(optionsReducer(options, { type: "toggleLanguage" }))
.toEqual({
...options,
language: "English",
});
expect(optionsReducer({ ...options, language: "English" }, { type: "toggleLanguage" }))
.toEqual(options);
expect(optionsReducer(options, { type: "toggleSearchType" }))
.toEqual({
...options,
searchType: "alphabetical",
});
expect(optionsReducer({ ...options, searchType: "alphabetical" }, { type: "toggleSearchType" }))
.toEqual(options);
expect(optionsReducer(options, { type: "changeTheme", payload: "dark" }))
.toEqual({
...options,
theme: "dark",
});
expect(optionsReducer(options, { type: "changeWordlistMode", payload: "review" }))
.toEqual({
...options,
wordlistMode: "review",
});
expect(optionsReducer(options, { type: "changeWordlistReviewLanguage", payload: "English" }))
.toEqual({
...options,
wordlistReviewLanguage: "English",
});
expect(optionsReducer(options, { type: "changeWordlistReviewBadge", payload: false }))
.toEqual({
...options,
wordlistReviewBadge: false,
});
expect(optionsReducer(options, { type: "changeSearchBarPosition", payload: "bottom" }))
.toEqual({
...options,
searchBarPosition: "bottom",
});
expect(optionsReducer(options, { type: "changePTextSize", payload: "largest" }))
.toEqual({
...options,
textOptions: {
...defaultTextOptions,
pTextSize: "largest",
},
});
expect(optionsReducer(options, { type: "changeSpelling", payload: "Pakistani ی" }))
.toEqual({
...options,
textOptions: {
...defaultTextOptions,
spelling: "Pakistani ی",
},
});
expect(optionsReducer(options, { type: "changePhonetics", payload: "ipa" }))
.toEqual({
...options,
textOptions: {
...defaultTextOptions,
phonetics: "ipa",
},
});
expect(optionsReducer(options, { type: "changeDialect", payload: "southern" }))
.toEqual({
...options,
textOptions: {
...defaultTextOptions,
dialect: "southern",
},
});
expect(optionsReducer(options, { type: "changeDiacritics", payload: true }))
.toEqual({
...options,
textOptions: {
...defaultTextOptions,
diacritics: true,
},
});
expect(() => {
// @ts-ignore
optionsReducer(options, { type: "non existent action" });
}).toThrow("action type not recognized in reducer");
})

View File

@ -1,4 +1,6 @@
function optionsReducer(options: Options, action: OptionsAction): Options {
import { Types as IT } from "@lingdocs/pashto-inflector";
export function optionsReducer(options: Options, action: OptionsAction): Options {
if (action.type === "toggleLanguage") {
return {
...options,
@ -41,52 +43,45 @@ function optionsReducer(options: Options, action: OptionsAction): Options {
wordlistReviewLanguage: action.payload,
};
}
if (action.type === "changePTextSize") {
if (action.type === "updateTextOptionsRecord") {
return {
...options,
textOptions: {
...options.textOptions,
pTextSize: action.payload,
},
textOptionsRecord: action.payload,
};
}
if (action.type === "changeSpelling") {
return {
...options,
textOptions: {
...options.textOptions,
spelling: action.payload,
}
};
}
if (action.type === "changePhonetics") {
return {
...options,
textOptions: {
...options.textOptions,
phonetics: action.payload,
}
};
}
if (action.type === "changeDialect") {
return {
...options,
textOptions: {
...options.textOptions,
dialect: action.payload,
}
};
}
if (action.type === "changeDiacritics") {
return {
...options,
textOptions: {
...options.textOptions,
diacritics: action.payload,
}
};
}
throw new Error("action type not recognized in reducer");
}
throw new Error("action type not recognized in options reducer");
}
export default optionsReducer;
export function textOptionsReducer(textOptions: IT.TextOptions, action: TextOptionsAction): IT.TextOptions {
if (action.type === "changePTextSize") {
return {
...textOptions,
pTextSize: action.payload,
};
}
if (action.type === "changeSpelling") {
return {
...textOptions,
spelling: action.payload,
};
}
if (action.type === "changePhonetics") {
return {
...textOptions,
phonetics: action.payload,
};
}
if (action.type === "changeDialect") {
return {
...textOptions,
dialect: action.payload,
};
}
if (action.type === "changeDiacritics") {
return {
...textOptions,
diacritics: action.payload,
};
}
throw new Error("action type not recognized in text options reducer");
}

View File

@ -23,6 +23,7 @@ import {
submissionBase,
addSubmission,
} from "../lib/submissions";
import { getTextOptions } from "../lib/get-text-options";
import { Helmet } from "react-helmet";
const textFields: {field: T.DictionaryEntryTextField, label: string}[] = [
@ -116,6 +117,7 @@ function EntryEditor({ state, dictionary, searchParams }: {
setMatchingEntries(state.isolatedEntry ? searchForMatchingEntries(state.isolatedEntry.p) : []);
// eslint-disable-next-line
}, [state]);
const textOptions = getTextOptions(state);
function searchForMatchingEntries(s: string): T.DictionaryEntry[] {
return dictionary.exactPashtoSearch(s)
.filter((w) => w.ts !== state.isolatedEntry?.ts);
@ -181,15 +183,15 @@ function EntryEditor({ state, dictionary, searchParams }: {
})();
const linkField: { field: "l", label: string | JSX.Element } = {
field: "l",
label: <>link {entry.l ? (complement ? <InlinePs opts={state.options.textOptions}>{complement}</InlinePs> : "not found") : ""}</>,
label: <>link {entry.l ? (complement ? <InlinePs opts={textOptions}>{complement}</InlinePs> : "not found") : ""}</>,
};
return <div className="width-limiter" style={{ marginBottom: "70px" }}>
<Helmet>
<link rel="canonical" href="https://dictionary.lingdocs.com/edit" />
<title>Edit - LingDocs Pashto Dictionary</title>
</Helmet>
{state.isolatedEntry && <Entry nonClickable entry={state.isolatedEntry} textOptions={state.options.textOptions} isolateEntry={() => null} />}
{suggestedWord && <InlinePs opts={state.options.textOptions}>{suggestedWord}</InlinePs>}
{state.isolatedEntry && <Entry nonClickable entry={state.isolatedEntry} textOptions={textOptions} isolateEntry={() => null} />}
{suggestedWord && <InlinePs opts={textOptions}>{suggestedWord}</InlinePs>}
{comment && <p>Comment: "{comment}"</p>}
{submitted ? "Edit submitted/saved" : deleted ? "Entry Deleted" :
<div>
@ -198,7 +200,7 @@ function EntryEditor({ state, dictionary, searchParams }: {
{matchingEntries.map((entry) => (
<div key={entry.ts}>
<Link to={`/edit?id=${entry.ts}`} className="plain-link">
<InlinePs opts={state.options.textOptions}>{entry}</InlinePs>
<InlinePs opts={textOptions}>{entry}</InlinePs>
</Link>
</div>
))}
@ -330,12 +332,12 @@ function EntryEditor({ state, dictionary, searchParams }: {
</ul>
</div>}
</form>
{inflections && <InflectionsTable inf={inflections} textOptions={state.options.textOptions} />}
{inflections && <InflectionsTable inf={inflections} textOptions={textOptions} />}
{/* TODO: aay tail from state options */}
<ConjugationViewer
entry={entry}
complement={complement}
textOptions={state.options.textOptions}
textOptions={textOptions}
/>
</div>}
</div>;

View File

@ -31,6 +31,7 @@ import { wordlistEnabled } from "../lib/level-management";
import AudioPlayButton from "../components/AudioPlayButton";
import { Helmet } from "react-helmet";
import { Modal } from "react-bootstrap";
import { getTextOptions } from "../lib/get-text-options";
function IsolatedEntry({ state, dictionary, isolateEntry }: {
state: State,
@ -47,6 +48,7 @@ function IsolatedEntry({ state, dictionary, isolateEntry }: {
setEditSubmitted(false);
}, [state]);
const wordlistWord = state.wordlist.find((w) => w.entry.ts === state.isolatedEntry?.ts);
const textOptions = getTextOptions(state);
function submitEdit() {
if (!state.isolatedEntry) return;
if (!state.user) return;
@ -102,7 +104,7 @@ function IsolatedEntry({ state, dictionary, isolateEntry }: {
<Entry
nonClickable
entry={entry}
textOptions={state.options.textOptions}
textOptions={textOptions}
isolateEntry={isolateEntry}
/>
</div>
@ -178,12 +180,12 @@ function IsolatedEntry({ state, dictionary, isolateEntry }: {
</div>
}
{editSubmitted && <p>Thank you for your help!</p>}
{inflections && <InflectionsTable inf={inflections} textOptions={state.options.textOptions} />}
{inflections && <InflectionsTable inf={inflections} textOptions={textOptions} />}
{/* TODO: State options for tail type here */}
<ConjugationViewer
entry={entry}
complement={complement}
textOptions={state.options.textOptions}
textOptions={textOptions}
/>
{relatedEntries && <>
{relatedEntries.length ?

View File

@ -131,10 +131,12 @@ function Options({
options,
state,
optionsDispatch,
textOptionsDispatch,
}: {
options: Options,
state: State,
optionsDispatch: (action: OptionsAction) => void,
textOptionsDispatch: (action: TextOptionsAction) => void,
}) {
return <div style={{ maxWidth: "700px", marginBottom: "150px" }}>
<Helmet>
@ -188,22 +190,22 @@ function Options({
<ButtonSelect
small
options={fontSizeOptions}
value={options.textOptions.pTextSize}
handleChange={(p) => optionsDispatch({ type: "changePTextSize", payload: p as PTextSize })}
value={options.textOptionsRecord.textOptions.pTextSize}
handleChange={(p) => textOptionsDispatch({ type: "changePTextSize", payload: p as PTextSize })}
/>
<h4 className="mt-3">Diacritics</h4>
<ButtonSelect
small
options={booleanOptions}
value={options.textOptions.diacritics.toString()}
handleChange={(p) => optionsDispatch({ type: "changeDiacritics", payload: p === "true" })}
value={options.textOptionsRecord.textOptions.diacritics.toString()}
handleChange={(p) => textOptionsDispatch({ type: "changeDiacritics", payload: p === "true" })}
/>
<h4 className="mt-3">Pashto Spelling</h4>
<ButtonSelect
small
options={spellingOptions}
value={options.textOptions.spelling}
handleChange={(p) => optionsDispatch({ type: "changeSpelling", payload: p as T.Spelling })}
value={options.textOptionsRecord.textOptions.spelling}
handleChange={(p) => textOptionsDispatch({ type: "changeSpelling", payload: p as T.Spelling })}
/>
{/* NEED TO UPDATE THE PHONETICS DIALECT OPTION THING */}
{/* <h4 className="mt-3">Phonetics</h4>

View File

@ -41,6 +41,7 @@ function Results({ state, isolateEntry }: {
const [pashto, setPashto] = useState<string>("");
const [phonetics, setPhonetics] = useState<string>("");
const [english, setEnglish] = useState<string>("");
const textOptions = state.user?.textOptions ? state.user.textOptions : state.options.textOptions;
useEffect(() => {
setPowerResults(undefined);
}, [state.searchValue])
@ -82,7 +83,7 @@ function Results({ state, isolateEntry }: {
const allDocs = allEntries();
const results = searchAllInflections(
allDocs,
prepValueForSearch(state.searchValue, state.options.textOptions),
prepValueForSearch(state.searchValue, textOptions),
);
setPowerResults(results);
}, 20);
@ -118,14 +119,14 @@ function Results({ state, isolateEntry }: {
<Entry
key={p.entry.i}
entry={p.entry}
textOptions={state.options.textOptions}
textOptions={textOptions}
isolateEntry={isolateEntry}
/>
<div className="mb-3 ml-2">
{p.results.map((result: InflectionSearchResult, i) => (
<InflectionSearchResult
key={"inf-result" + i}
textOptions={state.options.textOptions}
textOptions={textOptions}
result={result}
entry={p.entry}
/>
@ -138,7 +139,7 @@ function Results({ state, isolateEntry }: {
<Entry
key={entry.i}
entry={entry}
textOptions={state.options.textOptions}
textOptions={textOptions}
isolateEntry={isolateEntry}
/>
))}

View File

@ -8,6 +8,7 @@ import {
Types as T,
} from "@lingdocs/pashto-inflector";
import { Helmet } from "react-helmet";
import { getTextOptions } from "../lib/get-text-options";
function ReviewTask({ reviewTask, textOptions }: { reviewTask: FT.ReviewTask, textOptions: T.TextOptions }) {
function handleDelete() {
@ -50,13 +51,14 @@ function ReviewTask({ reviewTask, textOptions }: { reviewTask: FT.ReviewTask, te
}
export default function ReviewTasks({ state }: { state: State }) {
const textOptions = getTextOptions(state);
return <div className="width-limiter" style={{ marginBottom: "70px" }}>
<Helmet>
<title>Review Tasks - LingDocs Pashto Dictionary</title>
</Helmet>
<h3 className="mb-4">Review Tasks</h3>
{state.reviewTasks.length ?
state.reviewTasks.map((reviewTask, i) => <ReviewTask key={i} reviewTask={reviewTask} textOptions={state.options.textOptions} />)
state.reviewTasks.map((reviewTask, i) => <ReviewTask key={i} reviewTask={reviewTask} textOptions={textOptions} />)
: <p>None</p>
}
</div>;

View File

@ -44,6 +44,8 @@ import AudioPlayButton from "../components/AudioPlayButton";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime.js";
import hitBottom from "../lib/hitBottom";
import { getTextOptions } from "../lib/get-text-options";
const cleanupIcon = "broom";
dayjs.extend(relativeTime);
@ -94,6 +96,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
// eslint-disable-next-line
}, []);
const toReview = forReview(state.wordlist);
const textOptions = getTextOptions(state);
function handleScroll() {
// TODO: DON'T HAVE ENDLESS PAGE INCREASING
if (hitBottom() && state.options.wordlistMode === "browse") {
@ -116,7 +119,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
}
function handleSearchValueChange(value: string) {
setWordlistSearchValue(value);
const results = value ? searchWordlist(value, state.wordlist, state.options.textOptions) : [];
const results = value ? searchWordlist(value, state.wordlist, textOptions) : [];
setFilteredWords(results);
}
async function handleGetWordlistCSV() {
@ -152,7 +155,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
return <div className="mb-4">
<Entry
entry={word.entry}
textOptions={state.options.textOptions}
textOptions={textOptions}
isolateEntry={() => handleWordClickBrowse(word._id)}
/>
{hasAttachment(word, "audio") && <AudioPlayButton word={word} />}
@ -217,14 +220,14 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
<div className="card-body">
<h6 className="card-title text-center">
{state.options.wordlistReviewLanguage === "Pashto"
? <InlinePs opts={state.options.textOptions}>{{ p: word.entry.p, f: word.entry.f }}</InlinePs>
? <InlinePs opts={textOptions}>{{ p: word.entry.p, f: word.entry.f }}</InlinePs>
: word.entry.e
}
</h6>
{beingQuizzed && <div className="card-text text-center">
{state.options.wordlistReviewLanguage === "Pashto"
? <div>{word.entry.e}</div>
: <InlinePs opts={state.options.textOptions}>
: <InlinePs opts={textOptions}>
{{ p: word.entry.p, f: word.entry.f }}
</InlinePs>
}
@ -311,7 +314,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
const { e, ...ps } = nextUp.entry;
return <div>
<div className="lead my-3">None to review</div>
<p>Next word up for review <strong>{dayjs().to(nextUp.dueDate)}</strong>: <InlinePs opts={state.options.textOptions}>
<p>Next word up for review <strong>{dayjs().to(nextUp.dueDate)}</strong>: <InlinePs opts={textOptions}>
{removeFVariants(ps)}
</InlinePs></p>
</div>;

View File

@ -17,11 +17,16 @@ type SearchBarPosition = "top" | "bottom";
type WordlistMode = "browse" | "review";
type TextOptionsRecord = {
lastModified: import("./lib/account-types").TimeStamp,
textOptions: import("@lingdocs/pashto-inflector").Types.TextOptions,
};
type Options = {
language: Language,
searchType: SearchType,
theme: Theme,
textOptions: import("@lingdocs/pashto-inflector").Types.TextOptions,
textOptionsRecord: TextOptionsRecord,
wordlistMode: WordlistMode,
wordlistReviewLanguage: Language,
wordlistReviewBadge: boolean,
@ -47,27 +52,12 @@ type OptionsAction = {
type: "toggleSearchType",
} | {
type: "toggleLanguage",
} | {
type: "changePTextSize",
payload: PTextSize,
} | {
type: "changeTheme",
payload: Theme,
} | {
type: "changeSearchBarPosition",
payload: SearchBarPosition,
} | {
type: "changeSpelling",
payload: import("@lingdocs/pashto-inflector").Types.Spelling,
} | {
type: "changePhonetics",
payload: import("@lingdocs/pashto-inflector").Types.Phonetics,
} | {
type: "changeDialect",
payload: import("@lingdocs/pashto-inflector").Types.Dialect,
} | {
type: "changeDiacritics",
payload: boolean,
} | {
type: "changeUserLevel",
payload: UserLevel,
@ -80,6 +70,26 @@ type OptionsAction = {
} | {
type: "changeWordlistReviewBadge",
payload: boolean,
} | {
type: "updateTextOptionsRecord",
payload: TextOptionsRecord,
};
type TextOptionsAction = {
type: "changePTextSize",
payload: PTextSize,
} | {
type: "changeSpelling",
payload: import("@lingdocs/pashto-inflector").Types.Spelling,
} | {
type: "changePhonetics",
payload: import("@lingdocs/pashto-inflector").Types.Phonetics,
} | {
type: "changeDialect",
payload: import("@lingdocs/pashto-inflector").Types.Dialect,
} | {
type: "changeDiacritics",
payload: boolean,
};
type DictionaryAPI = {