diff --git a/account/src/lib/couch-db.ts b/account/src/lib/couch-db.ts index 8c3e145..014e016 100644 --- a/account/src/lib/couch-db.ts +++ b/account/src/lib/couch-db.ts @@ -95,7 +95,8 @@ export async function updateLingdocsUser(uuid: T.UUID, toUpdate: level: "student", wordlistDbName: T.WordlistDbName, couchDbPassword: T.UserDbPassword, - } + } | + { userTextOptionsRecord: T.UserTextOptionsRecord } ): Promise { const user = await getLingdocsUser("userId", uuid); if (!user) throw new Error("unable to update - user not found " + uuid); diff --git a/account/src/lib/user-utils.ts b/account/src/lib/user-utils.ts index 6b94d74..f92bdcd 100644 --- a/account/src/lib/user-utils.ts +++ b/account/src/lib/user-utils.ts @@ -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; diff --git a/account/src/routers/api-router.ts b/account/src/routers/api-router.ts index 7a475ae..fe68c3b 100644 --- a/account/src/routers/api-router.ts +++ b/account/src/routers/api-router.ts @@ -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 { diff --git a/website/src/App.tsx b/website/src/App.tsx index f13cf49..68f6878 100644 --- a/website/src/App.tsx +++ b/website/src/App.tsx @@ -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 { 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 { 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 { 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 { } } + 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 { - + diff --git a/website/src/lib/account-types.ts b/website/src/lib/account-types.ts index 94c75cb..90a5854 100644 --- a/website/src/lib/account-types.ts +++ b/website/src/lib/account-types.ts @@ -17,6 +17,13 @@ export type TwitterProfile = WoutRJ & { toke export type ProviderProfile = GoogleProfile | GitHubProfile | TwitterProfile; export type UserLevel = "basic" | "student" | "editor"; +export type UserTextOptions = Omit; + +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, +}; diff --git a/website/src/lib/backend-calls.ts b/website/src/lib/backend-calls.ts index c56309f..5b424e3 100644 --- a/website/src/lib/backend-calls.ts +++ b/website/src/lib/backend-calls.ts @@ -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 { + 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 { const response = await fetch(baseUrl[service] + url, { method, diff --git a/website/src/lib/dictionary.ts b/website/src/lib/dictionary.ts index 58750de..ac9e5ee 100644 --- a/website/src/lib/dictionary.ts +++ b/website/src/lib/dictionary.ts @@ -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 []; diff --git a/website/src/lib/get-text-options.ts b/website/src/lib/get-text-options.ts new file mode 100644 index 0000000..3cc302e --- /dev/null +++ b/website/src/lib/get-text-options.ts @@ -0,0 +1,5 @@ +import { Types as T } from "@lingdocs/pashto-inflector"; + +export function getTextOptions(state: State): T.TextOptions { + return state.options.textOptionsRecord.textOptions; +} \ No newline at end of file diff --git a/website/src/lib/local-storage.ts b/website/src/lib/local-storage.ts index 8d4182f..d5df6d7 100644 --- a/website/src/lib/local-storage.ts +++ b/website/src/lib/local-storage.ts @@ -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); diff --git a/website/src/lib/options-reducer.test.ts b/website/src/lib/options-reducer.test.ts deleted file mode 100644 index 3018fe8..0000000 --- a/website/src/lib/options-reducer.test.ts +++ /dev/null @@ -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"); -}) \ No newline at end of file diff --git a/website/src/lib/options-reducer.ts b/website/src/lib/options-reducer.ts index faa709d..94dfcf5 100644 --- a/website/src/lib/options-reducer.ts +++ b/website/src/lib/options-reducer.ts @@ -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; \ No newline at end of file +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"); +} diff --git a/website/src/screens/EntryEditor.tsx b/website/src/screens/EntryEditor.tsx index d7a89ad..4814ae1 100644 --- a/website/src/screens/EntryEditor.tsx +++ b/website/src/screens/EntryEditor.tsx @@ -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 ? {complement} : "not found") : ""}, + label: <>link {entry.l ? (complement ? {complement} : "not found") : ""}, }; return
Edit - LingDocs Pashto Dictionary - {state.isolatedEntry && null} />} - {suggestedWord && {suggestedWord}} + {state.isolatedEntry && null} />} + {suggestedWord && {suggestedWord}} {comment &&

Comment: "{comment}"

} {submitted ? "Edit submitted/saved" : deleted ? "Entry Deleted" :
@@ -198,7 +200,7 @@ function EntryEditor({ state, dictionary, searchParams }: { {matchingEntries.map((entry) => (
- {entry} + {entry}
))} @@ -330,12 +332,12 @@ function EntryEditor({ state, dictionary, searchParams }: {
} - {inflections && } + {inflections && } {/* TODO: aay tail from state options */}
} ; diff --git a/website/src/screens/IsolatedEntry.tsx b/website/src/screens/IsolatedEntry.tsx index 5fa9882..72850e7 100644 --- a/website/src/screens/IsolatedEntry.tsx +++ b/website/src/screens/IsolatedEntry.tsx @@ -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 }: { @@ -178,12 +180,12 @@ function IsolatedEntry({ state, dictionary, isolateEntry }: { } {editSubmitted &&

Thank you for your help!

} - {inflections && } + {inflections && } {/* TODO: State options for tail type here */} {relatedEntries && <> {relatedEntries.length ? diff --git a/website/src/screens/Options.tsx b/website/src/screens/Options.tsx index 5b21d1e..5393a20 100644 --- a/website/src/screens/Options.tsx +++ b/website/src/screens/Options.tsx @@ -131,10 +131,12 @@ function Options({ options, state, optionsDispatch, + textOptionsDispatch, }: { options: Options, state: State, optionsDispatch: (action: OptionsAction) => void, + textOptionsDispatch: (action: TextOptionsAction) => void, }) { return
@@ -188,22 +190,22 @@ function Options({ optionsDispatch({ type: "changePTextSize", payload: p as PTextSize })} + value={options.textOptionsRecord.textOptions.pTextSize} + handleChange={(p) => textOptionsDispatch({ type: "changePTextSize", payload: p as PTextSize })} />

Diacritics

optionsDispatch({ type: "changeDiacritics", payload: p === "true" })} + value={options.textOptionsRecord.textOptions.diacritics.toString()} + handleChange={(p) => textOptionsDispatch({ type: "changeDiacritics", payload: p === "true" })} />

Pashto Spelling

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 */} {/*

Phonetics

diff --git a/website/src/screens/Results.tsx b/website/src/screens/Results.tsx index 3adfdb0..4d11351 100644 --- a/website/src/screens/Results.tsx +++ b/website/src/screens/Results.tsx @@ -41,6 +41,7 @@ function Results({ state, isolateEntry }: { const [pashto, setPashto] = useState(""); const [phonetics, setPhonetics] = useState(""); const [english, setEnglish] = useState(""); + 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 }: {
{p.results.map((result: InflectionSearchResult, i) => ( @@ -138,7 +139,7 @@ function Results({ state, isolateEntry }: { ))} diff --git a/website/src/screens/ReviewTasks.tsx b/website/src/screens/ReviewTasks.tsx index 00c947a..4de14f2 100644 --- a/website/src/screens/ReviewTasks.tsx +++ b/website/src/screens/ReviewTasks.tsx @@ -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
Review Tasks - LingDocs Pashto Dictionary

Review Tasks

{state.reviewTasks.length ? - state.reviewTasks.map((reviewTask, i) => ) + state.reviewTasks.map((reviewTask, i) => ) :

None

}
; diff --git a/website/src/screens/Wordlist.tsx b/website/src/screens/Wordlist.tsx index 61e4b48..09d441e 100644 --- a/website/src/screens/Wordlist.tsx +++ b/website/src/screens/Wordlist.tsx @@ -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
handleWordClickBrowse(word._id)} /> {hasAttachment(word, "audio") && } @@ -217,14 +220,14 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: {
{state.options.wordlistReviewLanguage === "Pashto" - ? {{ p: word.entry.p, f: word.entry.f }} + ? {{ p: word.entry.p, f: word.entry.f }} : word.entry.e }
{beingQuizzed &&
{state.options.wordlistReviewLanguage === "Pashto" ?
{word.entry.e}
- : + : {{ p: word.entry.p, f: word.entry.f }} } @@ -311,7 +314,7 @@ function Wordlist({ state, isolateEntry, optionsDispatch }: { const { e, ...ps } = nextUp.entry; return
None to review
-

Next word up for review {dayjs().to(nextUp.dueDate)}: +

Next word up for review {dayjs().to(nextUp.dueDate)}: {removeFVariants(ps)}

; diff --git a/website/src/types.d.ts b/website/src/types.d.ts index 7061929..2b67f91 100644 --- a/website/src/types.d.ts +++ b/website/src/types.d.ts @@ -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 = {