update ga
This commit is contained in:
parent
ab0916aadd
commit
211d9b3446
|
@ -31,7 +31,7 @@
|
||||||
"react-bootstrap": "^1.5.1",
|
"react-bootstrap": "^1.5.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-dropzone": "^11.3.2",
|
"react-dropzone": "^11.3.2",
|
||||||
"react-ga": "^3.3.0",
|
"react-ga4": "^2.1.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-image-crop": "^8.6.9",
|
"react-image-crop": "^8.6.9",
|
||||||
"react-image-file-resizer": "^0.4.4",
|
"react-image-file-resizer": "^0.4.4",
|
||||||
|
|
|
@ -10,7 +10,12 @@
|
||||||
// sync on initialization and cancel sync on de-initialization
|
// sync on initialization and cancel sync on de-initialization
|
||||||
|
|
||||||
import { Component } from "react";
|
import { Component } from "react";
|
||||||
import { defaultTextOptions, revertSpelling, standardizePashto, Types as T } from "@lingdocs/ps-react";
|
import {
|
||||||
|
defaultTextOptions,
|
||||||
|
revertSpelling,
|
||||||
|
standardizePashto,
|
||||||
|
Types as T,
|
||||||
|
} from "@lingdocs/ps-react";
|
||||||
import { withRouter, Route, RouteComponentProps, Link } from "react-router-dom";
|
import { withRouter, Route, RouteComponentProps, Link } from "react-router-dom";
|
||||||
import Helmet from "react-helmet";
|
import Helmet from "react-helmet";
|
||||||
import BottomNavItem from "./components/BottomNavItem";
|
import BottomNavItem from "./components/BottomNavItem";
|
||||||
|
@ -32,36 +37,23 @@ import {
|
||||||
readUser,
|
readUser,
|
||||||
} from "./lib/local-storage";
|
} from "./lib/local-storage";
|
||||||
import { allEntries, dictionary, pageSize } from "./lib/dictionary";
|
import { allEntries, dictionary, pageSize } from "./lib/dictionary";
|
||||||
import {
|
import { optionsReducer, textOptionsReducer } from "./lib/options-reducer";
|
||||||
optionsReducer,
|
|
||||||
textOptionsReducer,
|
|
||||||
} from "./lib/options-reducer";
|
|
||||||
import hitBottom from "./lib/hitBottom";
|
import hitBottom from "./lib/hitBottom";
|
||||||
import getWordId from "./lib/get-word-id";
|
import getWordId from "./lib/get-word-id";
|
||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import {
|
import { sendSubmissions } from "./lib/submissions";
|
||||||
sendSubmissions,
|
import { getUser } from "./lib/backend-calls";
|
||||||
} from "./lib/submissions";
|
import { getWordlist } from "./lib/wordlist-database";
|
||||||
import {
|
|
||||||
getUser,
|
|
||||||
} from "./lib/backend-calls";
|
|
||||||
import {
|
|
||||||
getWordlist,
|
|
||||||
} from "./lib/wordlist-database";
|
|
||||||
import {
|
import {
|
||||||
startLocalDbs,
|
startLocalDbs,
|
||||||
stopLocalDbs,
|
stopLocalDbs,
|
||||||
getAllDocsLocalDb,
|
getAllDocsLocalDb,
|
||||||
} from "./lib/pouch-dbs";
|
} from "./lib/pouch-dbs";
|
||||||
import {
|
import { forReview } from "./lib/spaced-repetition";
|
||||||
forReview,
|
import { textBadge } from "./lib/badges";
|
||||||
} from "./lib/spaced-repetition";
|
|
||||||
import {
|
|
||||||
textBadge,
|
|
||||||
} from "./lib/badges";
|
|
||||||
import * as AT from "./types/account-types";
|
import * as AT from "./types/account-types";
|
||||||
import ReactGA from "react-ga";
|
import ReactGA from "react-ga4";
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
import "@fortawesome/fontawesome-free/css/all.css";
|
import "@fortawesome/fontawesome-free/css/all.css";
|
||||||
import "./custom-bootstrap.css";
|
import "./custom-bootstrap.css";
|
||||||
|
@ -78,31 +70,35 @@ import {
|
||||||
} from "./types/dictionary-types";
|
} from "./types/dictionary-types";
|
||||||
import PhraseBuilder from "./screens/PhraseBuilder";
|
import PhraseBuilder from "./screens/PhraseBuilder";
|
||||||
import { searchAllInflections } from "./lib/search-all-inflections";
|
import { searchAllInflections } from "./lib/search-all-inflections";
|
||||||
import {
|
import { addToWordlist } from "./lib/wordlist-database";
|
||||||
addToWordlist,
|
|
||||||
} from "./lib/wordlist-database";
|
|
||||||
import ScriptToPhonetics from "./screens/ScriptToPhonetics";
|
import ScriptToPhonetics from "./screens/ScriptToPhonetics";
|
||||||
|
|
||||||
// to allow Moustrap key combos even when input fields are in focus
|
// to allow Moustrap key combos even when input fields are in focus
|
||||||
Mousetrap.prototype.stopCallback = function () {
|
Mousetrap.prototype.stopCallback = function () {
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const prod = document.location.hostname === "dictionary.lingdocs.com";
|
const prod = document.location.hostname === "dictionary.lingdocs.com";
|
||||||
|
|
||||||
if (prod) {
|
if (prod) {
|
||||||
// TODO: migrate to https://www.npmjs.com/package/react-ga4
|
// TODO: migrate to https://www.npmjs.com/package/react-ga4
|
||||||
ReactGA.initialize("UA-196576671-1");
|
ReactGA.initialize("386396674");
|
||||||
ReactGA.set({ anonymizeIp: true });
|
ReactGA.set({ anonymizeIp: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleLandingPages = [
|
const possibleLandingPages = [
|
||||||
"/", "/about", "/settings", "/word", "/account", "/new-entries", "/share-target", "/phrase-builder",
|
"/",
|
||||||
"/privacy", "/script-to-phonetics"
|
"/about",
|
||||||
];
|
"/settings",
|
||||||
const editorOnlyPages = [
|
"/word",
|
||||||
"/edit", "/review-tasks",
|
"/account",
|
||||||
|
"/new-entries",
|
||||||
|
"/share-target",
|
||||||
|
"/phrase-builder",
|
||||||
|
"/privacy",
|
||||||
|
"/script-to-phonetics",
|
||||||
];
|
];
|
||||||
|
const editorOnlyPages = ["/edit", "/review-tasks"];
|
||||||
|
|
||||||
class App extends Component<RouteComponentProps, State> {
|
class App extends Component<RouteComponentProps, State> {
|
||||||
constructor(props: RouteComponentProps) {
|
constructor(props: RouteComponentProps) {
|
||||||
|
@ -112,11 +108,15 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
dictionaryStatus: "loading",
|
dictionaryStatus: "loading",
|
||||||
dictionaryInfo: undefined,
|
dictionaryInfo: undefined,
|
||||||
// TODO: Choose between the saved options and the options in the saved user
|
// TODO: Choose between the saved options and the options in the saved user
|
||||||
options: savedOptions ? savedOptions : {
|
options: savedOptions
|
||||||
|
? savedOptions
|
||||||
|
: {
|
||||||
language: "Pashto",
|
language: "Pashto",
|
||||||
searchType: "fuzzy",
|
searchType: "fuzzy",
|
||||||
searchBarStickyFocus: false,
|
searchBarStickyFocus: false,
|
||||||
theme: (window.matchMedia?.("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
|
theme: window.matchMedia?.("(prefers-color-scheme: dark)").matches
|
||||||
|
? "dark"
|
||||||
|
: "light",
|
||||||
textOptionsRecord: {
|
textOptionsRecord: {
|
||||||
lastModified: Date.now() as AT.TimeStamp,
|
lastModified: Date.now() as AT.TimeStamp,
|
||||||
textOptions: defaultTextOptions,
|
textOptions: defaultTextOptions,
|
||||||
|
@ -133,7 +133,7 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
wordlist: [],
|
wordlist: [],
|
||||||
reviewTasks: [],
|
reviewTasks: [],
|
||||||
user: readUser(),
|
user: readUser(),
|
||||||
inflectionSearchResults: undefined
|
inflectionSearchResults: undefined,
|
||||||
};
|
};
|
||||||
this.handleOptionsUpdate = this.handleOptionsUpdate.bind(this);
|
this.handleOptionsUpdate = this.handleOptionsUpdate.bind(this);
|
||||||
this.handleTextOptionsUpdate = this.handleTextOptionsUpdate.bind(this);
|
this.handleTextOptionsUpdate = this.handleTextOptionsUpdate.bind(this);
|
||||||
|
@ -153,10 +153,15 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
if (!possibleLandingPages.includes(this.props.location.pathname)) {
|
if (!possibleLandingPages.includes(this.props.location.pathname)) {
|
||||||
this.props.history.replace("/");
|
this.props.history.replace("/");
|
||||||
}
|
}
|
||||||
if (prod && (!(this.state.user?.level === "editor"))) {
|
if (prod && !(this.state.user?.level === "editor")) {
|
||||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
ReactGA.send({
|
||||||
|
hitType: "pageview",
|
||||||
|
page: window.location.pathname + window.location.search,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
dictionary.initialize().then((r) => {
|
dictionary
|
||||||
|
.initialize()
|
||||||
|
.then((r) => {
|
||||||
this.cronJob.start();
|
this.cronJob.start();
|
||||||
this.setState({
|
this.setState({
|
||||||
dictionaryStatus: "ready",
|
dictionaryStatus: "ready",
|
||||||
|
@ -165,7 +170,10 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
this.handleLoadUser();
|
this.handleLoadUser();
|
||||||
// incase it took forever and timed out - might need to reinitialize the wordlist here ??
|
// incase it took forever and timed out - might need to reinitialize the wordlist here ??
|
||||||
if (this.state.user) {
|
if (this.state.user) {
|
||||||
startLocalDbs(this.state.user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks });
|
startLocalDbs(this.state.user, {
|
||||||
|
wordlist: this.handleRefreshWordlist,
|
||||||
|
reviewTasks: this.handleRefreshReviewTasks,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (this.props.location.pathname === "/word") {
|
if (this.props.location.pathname === "/word") {
|
||||||
const wordId = getWordId(this.props.location.search);
|
const wordId = getWordId(this.props.location.search);
|
||||||
|
@ -201,20 +209,28 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
if (r.response === "loaded from saved") {
|
if (r.response === "loaded from saved") {
|
||||||
this.handleDictionaryUpdate();
|
this.handleDictionaryUpdate();
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.setState({ dictionaryStatus: "error loading" });
|
this.setState({ dictionaryStatus: "error loading" });
|
||||||
});
|
});
|
||||||
document.documentElement.setAttribute("data-theme", this.state.options.theme);
|
document.documentElement.setAttribute(
|
||||||
|
"data-theme",
|
||||||
|
this.state.options.theme
|
||||||
|
);
|
||||||
/* istanbul ignore if */
|
/* istanbul ignore if */
|
||||||
if (window.matchMedia) {
|
if (window.matchMedia) {
|
||||||
const prefersDarkQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const prefersDarkQuery = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)"
|
||||||
|
);
|
||||||
prefersDarkQuery.addListener((e) => {
|
prefersDarkQuery.addListener((e) => {
|
||||||
if (e.matches) {
|
if (e.matches) {
|
||||||
this.handleOptionsUpdate({ type: "changeTheme", payload: "dark" });
|
this.handleOptionsUpdate({ type: "changeTheme", payload: "dark" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const prefersLightQuery = window.matchMedia("(prefers-color-scheme: light)");
|
const prefersLightQuery = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: light)"
|
||||||
|
);
|
||||||
prefersLightQuery.addListener((e) => {
|
prefersLightQuery.addListener((e) => {
|
||||||
if (e.matches) {
|
if (e.matches) {
|
||||||
this.handleOptionsUpdate({ type: "changeTheme", payload: "light" });
|
this.handleOptionsUpdate({ type: "changeTheme", payload: "light" });
|
||||||
|
@ -235,14 +251,17 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.handleIsolateEntry(toIsolate.ts);
|
this.handleIsolateEntry(toIsolate.ts);
|
||||||
})
|
});
|
||||||
Mousetrap.bind(["ctrl+down", "ctrl+up", "command+down", "command+up"], (e) => {
|
Mousetrap.bind(
|
||||||
|
["ctrl+down", "ctrl+up", "command+down", "command+up"],
|
||||||
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) {
|
if (e.repeat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.handleOptionsUpdate({ type: "toggleLanguage" });
|
this.handleOptionsUpdate({ type: "toggleLanguage" });
|
||||||
});
|
}
|
||||||
|
);
|
||||||
Mousetrap.bind(["ctrl+b", "command+b"], (e) => {
|
Mousetrap.bind(["ctrl+b", "command+b"], (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.repeat) {
|
if (e.repeat) {
|
||||||
|
@ -304,8 +323,11 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: RouteComponentProps) {
|
public componentDidUpdate(prevProps: RouteComponentProps) {
|
||||||
if (this.props.location.pathname !== prevProps.location.pathname) {
|
if (this.props.location.pathname !== prevProps.location.pathname) {
|
||||||
if (prod && (!(this.state.user?.level === "editor"))) {
|
if (prod && !(this.state.user?.level === "editor")) {
|
||||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
ReactGA.send({
|
||||||
|
hitType: "pageview",
|
||||||
|
page: window.location.pathname + window.location.search,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (this.props.location.pathname === "/") {
|
if (this.props.location.pathname === "/") {
|
||||||
this.handleSearchValueChange("");
|
this.handleSearchValueChange("");
|
||||||
|
@ -316,20 +338,29 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (editorOnlyPages.includes(this.props.location.pathname) && !(this.state.user?.level === "editor")) {
|
if (
|
||||||
|
editorOnlyPages.includes(this.props.location.pathname) &&
|
||||||
|
!(this.state.user?.level === "editor")
|
||||||
|
) {
|
||||||
this.props.history.replace("/");
|
this.props.history.replace("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (getWordId(this.props.location.search) !== getWordId(prevProps.location.search)) {
|
if (
|
||||||
if (prod && ((this.state.user?.level !== "editor"))) {
|
getWordId(this.props.location.search) !==
|
||||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
getWordId(prevProps.location.search)
|
||||||
|
) {
|
||||||
|
if (prod && this.state.user?.level !== "editor") {
|
||||||
|
ReactGA.send({
|
||||||
|
type: "pageview",
|
||||||
|
page: window.location.pathname + window.location.search,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const wordId = getWordId(this.props.location.search);
|
const wordId = getWordId(this.props.location.search);
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (wordId) {
|
if (wordId) {
|
||||||
this.handleIsolateEntry(wordId, true);
|
this.handleIsolateEntry(wordId, true);
|
||||||
} else {
|
} else {
|
||||||
this.setState({ isolatedEntry: undefined })
|
this.setState({ isolatedEntry: undefined });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if (!["/wordlist", "/settings", "/review-tasks"].includes(this.props.location.pathname)) {
|
// if (!["/wordlist", "/settings", "/review-tasks"].includes(this.props.location.pathname)) {
|
||||||
|
@ -356,12 +387,17 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!userObjIsEqual(prevUser, user)) {
|
if (!userObjIsEqual(prevUser, user)) {
|
||||||
console.log("setting state user because something is different about the user")
|
console.log(
|
||||||
|
"setting state user because something is different about the user"
|
||||||
|
);
|
||||||
this.setState({ user });
|
this.setState({ user });
|
||||||
saveUser(user);
|
saveUser(user);
|
||||||
}
|
}
|
||||||
if (user) {
|
if (user) {
|
||||||
startLocalDbs(user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks });
|
startLocalDbs(user, {
|
||||||
|
wordlist: this.handleRefreshWordlist,
|
||||||
|
reviewTasks: this.handleRefreshReviewTasks,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
stopLocalDbs();
|
stopLocalDbs();
|
||||||
}
|
}
|
||||||
|
@ -372,9 +408,11 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
|
|
||||||
private handleDictionaryUpdate() {
|
private handleDictionaryUpdate() {
|
||||||
// TODO: fix - what the heck happened and what's going on here
|
// TODO: fix - what the heck happened and what's going on here
|
||||||
dictionary.update(() => {
|
dictionary
|
||||||
|
.update(() => {
|
||||||
// this.setState({ dictionaryStatus: "updating" });
|
// this.setState({ dictionaryStatus: "updating" });
|
||||||
}).then(({ dictionaryInfo }) => {
|
})
|
||||||
|
.then(({ dictionaryInfo }) => {
|
||||||
//if (this.state.dictionaryInfo?.release !== dictionaryInfo?.release) {
|
//if (this.state.dictionaryInfo?.release !== dictionaryInfo?.release) {
|
||||||
// to avoid unnecessary re-rendering that breaks things
|
// to avoid unnecessary re-rendering that breaks things
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -382,7 +420,8 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
dictionaryInfo,
|
dictionaryInfo,
|
||||||
});
|
});
|
||||||
//}
|
//}
|
||||||
}).catch(() => {
|
})
|
||||||
|
.catch(() => {
|
||||||
this.setState({ dictionaryStatus: "error loading" });
|
this.setState({ dictionaryStatus: "error loading" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -394,9 +433,12 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
// 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)
|
// 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);
|
const options = optionsReducer(this.state.options, action);
|
||||||
saveOptions(options);
|
saveOptions(options);
|
||||||
if (action.type === "toggleLanguage" || action.type === "toggleSearchType") {
|
if (
|
||||||
|
action.type === "toggleLanguage" ||
|
||||||
|
action.type === "toggleSearchType"
|
||||||
|
) {
|
||||||
if (this.props.location.pathname !== "/new-entries") {
|
if (this.props.location.pathname !== "/new-entries") {
|
||||||
this.setState(prevState => ({
|
this.setState((prevState) => ({
|
||||||
options,
|
options,
|
||||||
page: 1,
|
page: 1,
|
||||||
results: dictionary.search({ ...prevState, options }),
|
results: dictionary.search({ ...prevState, options }),
|
||||||
|
@ -417,7 +459,10 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
lastModified,
|
lastModified,
|
||||||
textOptions,
|
textOptions,
|
||||||
};
|
};
|
||||||
this.handleOptionsUpdate({ type: "updateTextOptionsRecord", payload: textOptionsRecord });
|
this.handleOptionsUpdate({
|
||||||
|
type: "updateTextOptionsRecord",
|
||||||
|
payload: textOptionsRecord,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleSearchValueChange(searchValue: string) {
|
private handleSearchValueChange(searchValue: string) {
|
||||||
|
@ -427,7 +472,7 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
const lastChar = searchValue[searchValue.length - 1];
|
const lastChar = searchValue[searchValue.length - 1];
|
||||||
// don't let people type in a single digit (to allow for number shortcuts)
|
// don't let people type in a single digit (to allow for number shortcuts)
|
||||||
// but do allow the whole thing to be numbers (to allow for pasting and searching for ts)
|
// but do allow the whole thing to be numbers (to allow for pasting and searching for ts)
|
||||||
if (lastChar >= '0' && lastChar <= '9' && !(/^\d+$/.test(searchValue))) {
|
if (lastChar >= "0" && lastChar <= "9" && !/^\d+$/.test(searchValue)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.state.dictionaryStatus !== "ready") {
|
if (this.state.dictionaryStatus !== "ready") {
|
||||||
|
@ -445,7 +490,7 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState(prevState => ({
|
this.setState((prevState) => ({
|
||||||
searchValue,
|
searchValue,
|
||||||
results: dictionary.search({ ...prevState, searchValue }),
|
results: dictionary.search({ ...prevState, searchValue }),
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -465,7 +510,11 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ isolatedEntry });
|
this.setState({ isolatedEntry });
|
||||||
if (!onlyState && (this.props.location.pathname !== "/word" || (getWordId(this.props.location.search) !== ts))) {
|
if (
|
||||||
|
!onlyState &&
|
||||||
|
(this.props.location.pathname !== "/word" ||
|
||||||
|
getWordId(this.props.location.search) !== ts)
|
||||||
|
) {
|
||||||
this.props.history.push(`/word?id=${isolatedEntry.ts}`);
|
this.props.history.push(`/word?id=${isolatedEntry.ts}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -475,11 +524,15 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
private cronJob = new CronJob("1/10 * * * *", () => {
|
private cronJob = new CronJob("1/10 * * * *", () => {
|
||||||
this.handleDictionaryUpdate();
|
this.handleDictionaryUpdate();
|
||||||
this.handleLoadUser();
|
this.handleLoadUser();
|
||||||
})
|
});
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
private handleScroll() {
|
private handleScroll() {
|
||||||
if (hitBottom() && this.props.location.pathname === "/search" && this.state.results.length >= (pageSize * this.state.page)) {
|
if (
|
||||||
|
hitBottom() &&
|
||||||
|
this.props.location.pathname === "/search" &&
|
||||||
|
this.state.results.length >= pageSize * this.state.page
|
||||||
|
) {
|
||||||
const page = this.state.page + 1;
|
const page = this.state.page + 1;
|
||||||
const moreResults = dictionary.search({ ...this.state, page });
|
const moreResults = dictionary.search({ ...this.state, page });
|
||||||
if (moreResults.length > this.state.results.length) {
|
if (moreResults.length > this.state.results.length) {
|
||||||
|
@ -492,7 +545,10 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleInflectionSearch() {
|
private handleInflectionSearch() {
|
||||||
function prepValueForSearch(searchValue: string, textOptions: T.TextOptions): string {
|
function prepValueForSearch(
|
||||||
|
searchValue: string,
|
||||||
|
textOptions: T.TextOptions
|
||||||
|
): string {
|
||||||
const s = revertSpelling(searchValue, textOptions.spelling);
|
const s = revertSpelling(searchValue, textOptions.spelling);
|
||||||
return standardizePashto(s.trim());
|
return standardizePashto(s.trim());
|
||||||
}
|
}
|
||||||
|
@ -501,7 +557,10 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const inflectionSearchResults = searchAllInflections(
|
const inflectionSearchResults = searchAllInflections(
|
||||||
allEntries(),
|
allEntries(),
|
||||||
prepValueForSearch(this.state.searchValue, this.state.options.textOptionsRecord.textOptions),
|
prepValueForSearch(
|
||||||
|
this.state.searchValue,
|
||||||
|
this.state.options.textOptionsRecord.textOptions
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this.setState({ inflectionSearchResults });
|
this.setState({ inflectionSearchResults });
|
||||||
}, 20);
|
}, 20);
|
||||||
|
@ -525,48 +584,75 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div style={{
|
return (
|
||||||
paddingTop: this.state.options.searchBarPosition === "top" ? "75px" : "7px",
|
<div
|
||||||
|
style={{
|
||||||
|
paddingTop:
|
||||||
|
this.state.options.searchBarPosition === "top" ? "75px" : "7px",
|
||||||
paddingBottom: "60px",
|
paddingBottom: "60px",
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>LingDocs Pashto Dictionary</title>
|
<title>LingDocs Pashto Dictionary</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{this.state.options.searchBarPosition === "top" && <SearchBar
|
{this.state.options.searchBarPosition === "top" && (
|
||||||
|
<SearchBar
|
||||||
state={this.state}
|
state={this.state}
|
||||||
optionsDispatch={this.handleOptionsUpdate}
|
optionsDispatch={this.handleOptionsUpdate}
|
||||||
handleSearchValueChange={this.handleSearchValueChange}
|
handleSearchValueChange={this.handleSearchValueChange}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
<div className="container-fluid" data-testid="body">
|
<div className="container-fluid" data-testid="body">
|
||||||
{this.state.dictionaryStatus !== "ready" ?
|
{this.state.dictionaryStatus !== "ready" ? (
|
||||||
<DictionaryStatusDisplay status={this.state.dictionaryStatus} />
|
<DictionaryStatusDisplay status={this.state.dictionaryStatus} />
|
||||||
:
|
) : (
|
||||||
<>
|
<>
|
||||||
<Route path="/" exact={true}>
|
<Route path="/" exact={true}>
|
||||||
<div className="text-center mt-4">
|
<div className="text-center mt-4">
|
||||||
<h4 className="font-weight-light p-3 mb-4">LingDocs Pashto Dictionary</h4>
|
<h4 className="font-weight-light p-3 mb-4">
|
||||||
|
LingDocs Pashto Dictionary
|
||||||
|
</h4>
|
||||||
<div className="mt-4 font-weight-light">
|
<div className="mt-4 font-weight-light">
|
||||||
<div className="mb-4 small">
|
<div className="mb-4 small">
|
||||||
{this.state.options.searchType === "alphabetical"
|
{this.state.options.searchType === "alphabetical" ? (
|
||||||
? <><span className="fa fa-book mr-2" /> Alphabetical browsing mode</>
|
<>
|
||||||
: <><span className="fa fa-bolt mr-2" /> Approximate search mode</>}
|
<span className="fa fa-book mr-2" /> Alphabetical
|
||||||
|
browsing mode
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="fa fa-bolt mr-2" /> Approximate
|
||||||
|
search mode
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.user?.level === "editor" && <div className="mt-4 font-weight-light">
|
{this.state.user?.level === "editor" && (
|
||||||
|
<div className="mt-4 font-weight-light">
|
||||||
<div className="mb-3">Editor privileges active</div>
|
<div className="mb-3">Editor privileges active</div>
|
||||||
<Link to="/edit">
|
<Link to="/edit">
|
||||||
<button className="btn btn-secondary">New Entry</button>
|
<button className="btn btn-secondary">New Entry</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>}
|
</div>
|
||||||
<Link to="/new-entries" className="plain-link font-weight-light">
|
)}
|
||||||
|
<Link
|
||||||
|
to="/new-entries"
|
||||||
|
className="plain-link font-weight-light"
|
||||||
|
>
|
||||||
<div className="my-4">New words this month</div>
|
<div className="my-4">New words this month</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="mt-4 pt-3">
|
<div className="mt-4 pt-3">
|
||||||
<Link to="/phrase-builder" className="plain-link h5 font-weight-light">
|
<Link
|
||||||
|
to="/phrase-builder"
|
||||||
|
className="plain-link h5 font-weight-light"
|
||||||
|
>
|
||||||
Phrase Builder
|
Phrase Builder
|
||||||
</Link>
|
</Link>
|
||||||
<span className="mx-1"> • </span>
|
<span className="mx-1"> • </span>
|
||||||
<a href="https://grammar.lingdocs.com" className="plain-link h5 font-weight-light">
|
<a
|
||||||
|
href="https://grammar.lingdocs.com"
|
||||||
|
className="plain-link h5 font-weight-light"
|
||||||
|
>
|
||||||
Grammar
|
Grammar
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -601,18 +687,21 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/new-entries">
|
<Route path="/new-entries">
|
||||||
<h4 className="mb-3">New Words This Month</h4>
|
<h4 className="mb-3">New Words This Month</h4>
|
||||||
{this.state.results.length ?
|
{this.state.results.length ? (
|
||||||
<Results
|
<Results
|
||||||
state={this.state}
|
state={this.state}
|
||||||
isolateEntry={this.handleIsolateEntry}
|
isolateEntry={this.handleIsolateEntry}
|
||||||
handleInflectionSearch={this.handleInflectionSearch}
|
handleInflectionSearch={this.handleInflectionSearch}
|
||||||
/>
|
/>
|
||||||
:
|
) : (
|
||||||
<div>No new words added this month 😓</div>
|
<div>No new words added this month 😓</div>
|
||||||
}
|
)}
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/account">
|
<Route path="/account">
|
||||||
<Account user={this.state.user} loadUser={this.handleLoadUser} />
|
<Account
|
||||||
|
user={this.state.user}
|
||||||
|
loadUser={this.handleLoadUser}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/word">
|
<Route path="/word">
|
||||||
<IsolatedEntry
|
<IsolatedEntry
|
||||||
|
@ -634,59 +723,101 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
<Route path="/script-to-phonetics">
|
<Route path="/script-to-phonetics">
|
||||||
<ScriptToPhonetics />
|
<ScriptToPhonetics />
|
||||||
</Route>
|
</Route>
|
||||||
{this.state.user?.level === "editor" && <Route path="/edit">
|
{this.state.user?.level === "editor" && (
|
||||||
|
<Route path="/edit">
|
||||||
<EntryEditor
|
<EntryEditor
|
||||||
isolatedEntry={this.state.isolatedEntry}
|
isolatedEntry={this.state.isolatedEntry}
|
||||||
user={this.state.user}
|
user={this.state.user}
|
||||||
textOptions={getTextOptions(this.state)}
|
textOptions={getTextOptions(this.state)}
|
||||||
dictionary={dictionary}
|
dictionary={dictionary}
|
||||||
searchParams={new URLSearchParams(this.props.history.location.search)}
|
searchParams={
|
||||||
/>
|
new URLSearchParams(this.props.history.location.search)
|
||||||
</Route>}
|
|
||||||
{this.state.user?.level === "editor" && <Route path="/review-tasks">
|
|
||||||
<ReviewTasks state={this.state} />
|
|
||||||
</Route>}
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
)}
|
||||||
|
{this.state.user?.level === "editor" && (
|
||||||
|
<Route path="/review-tasks">
|
||||||
|
<ReviewTasks state={this.state} />
|
||||||
|
</Route>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<footer className={classNames(
|
<footer
|
||||||
|
className={classNames(
|
||||||
"footer",
|
"footer",
|
||||||
{ "bg-white": !["/search", "/word"].includes(this.props.location.pathname) },
|
{
|
||||||
{ "footer-thick": this.state.options.searchBarPosition === "bottom" && !["/search", "/word"].includes(this.props.location.pathname) },
|
"bg-white": !["/search", "/word"].includes(
|
||||||
{ "wee-less-footer": this.state.options.searchBarPosition === "bottom" && ["/search", "/word"].includes(this.props.location.pathname) },
|
this.props.location.pathname
|
||||||
)}>
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"footer-thick":
|
||||||
|
this.state.options.searchBarPosition === "bottom" &&
|
||||||
|
!["/search", "/word"].includes(this.props.location.pathname),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wee-less-footer":
|
||||||
|
this.state.options.searchBarPosition === "bottom" &&
|
||||||
|
["/search", "/word"].includes(this.props.location.pathname),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Route path="/" exact={true}>
|
<Route path="/" exact={true}>
|
||||||
<div className="buttons-footer">
|
<div className="buttons-footer">
|
||||||
<BottomNavItem label="About" icon="info-circle" page="/about" />
|
<BottomNavItem label="About" icon="info-circle" page="/about" />
|
||||||
<BottomNavItem label="Settings" icon="cog" page="/settings" />
|
<BottomNavItem label="Settings" icon="cog" page="/settings" />
|
||||||
<BottomNavItem label={this.state.user ? "Account" : "Sign In"} icon="user" page="/account" />
|
|
||||||
<BottomNavItem
|
<BottomNavItem
|
||||||
label={`Wordlist ${this.state.options.wordlistReviewBadge ? textBadge(forReview(this.state.wordlist).length) : ""}`}
|
label={this.state.user ? "Account" : "Sign In"}
|
||||||
|
icon="user"
|
||||||
|
page="/account"
|
||||||
|
/>
|
||||||
|
<BottomNavItem
|
||||||
|
label={`Wordlist ${
|
||||||
|
this.state.options.wordlistReviewBadge
|
||||||
|
? textBadge(forReview(this.state.wordlist).length)
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
icon="list"
|
icon="list"
|
||||||
page="/wordlist"
|
page="/wordlist"
|
||||||
/>
|
/>
|
||||||
{this.state.user?.level === "editor" &&
|
{this.state.user?.level === "editor" && (
|
||||||
<BottomNavItem
|
<BottomNavItem
|
||||||
label={`Tasks ${textBadge(this.state.reviewTasks.length)}`}
|
label={`Tasks ${textBadge(this.state.reviewTasks.length)}`}
|
||||||
icon="edit"
|
icon="edit"
|
||||||
page="/review-tasks"
|
page="/review-tasks"
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={["/about", "/settings", "/new-entries", "/account", "/wordlist", "/edit", "/review-tasks", "/phrase-builder"]}>
|
<Route
|
||||||
|
path={[
|
||||||
|
"/about",
|
||||||
|
"/settings",
|
||||||
|
"/new-entries",
|
||||||
|
"/account",
|
||||||
|
"/wordlist",
|
||||||
|
"/edit",
|
||||||
|
"/review-tasks",
|
||||||
|
"/phrase-builder",
|
||||||
|
]}
|
||||||
|
>
|
||||||
<div className="buttons-footer">
|
<div className="buttons-footer">
|
||||||
<BottomNavItem label="Home" icon="home" page="/" />
|
<BottomNavItem label="Home" icon="home" page="/" />
|
||||||
</div>
|
</div>
|
||||||
</Route>
|
</Route>
|
||||||
{this.state.options.searchBarPosition === "bottom" && <SearchBar
|
{this.state.options.searchBarPosition === "bottom" && (
|
||||||
|
<SearchBar
|
||||||
state={this.state}
|
state={this.state}
|
||||||
optionsDispatch={this.handleOptionsUpdate}
|
optionsDispatch={this.handleOptionsUpdate}
|
||||||
handleSearchValueChange={this.handleSearchValueChange}
|
handleSearchValueChange={this.handleSearchValueChange}
|
||||||
onBottom={true}
|
onBottom={true}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11335,10 +11335,10 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||||
|
|
||||||
react-ga@^3.3.0:
|
react-ga4@^2.1.0:
|
||||||
version "3.3.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.0.tgz#c91f407198adcb3b49e2bc5c12b3fe460039b3ca"
|
resolved "https://registry.yarnpkg.com/react-ga4/-/react-ga4-2.1.0.tgz#56601f59d95c08466ebd6edfbf8dede55c4678f9"
|
||||||
integrity sha512-o8RScHj6Lb8cwy3GMrVH6NJvL+y0zpJvKtc0+wmH7Bt23rszJmnqEQxRbyrqUzk9DTJIHoP42bfO5rswC9SWBQ==
|
integrity sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==
|
||||||
|
|
||||||
react-helmet@^6.1.0:
|
react-helmet@^6.1.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
|
|
Loading…
Reference in New Issue