Compare commits
No commits in common. "5a90557ebb818f160b7f4b8345cf088df1217a37" and "35a093c14d2295f90c6a6d3add876069bcad37d1" have entirely different histories.
5a90557ebb
...
35a093c14d
|
@ -9,7 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lingdocs/inflect": "7.0.8",
|
"@lingdocs/inflect": "7.0.3",
|
||||||
"base64url": "^3.0.1",
|
"base64url": "^3.0.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-redis": "^6.0.0",
|
"connect-redis": "^6.0.0",
|
||||||
|
@ -126,9 +126,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lingdocs/inflect": {
|
"node_modules/@lingdocs/inflect": {
|
||||||
"version": "7.0.8",
|
"version": "7.0.3",
|
||||||
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.8.tgz",
|
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
|
||||||
"integrity": "sha512-+RuZ2Tcw4gGjWwNOpZb/C7kwgWBAILY4ZRSzZBrG3KN+jEDrW++wgB5LfaKkzHLIurI73L5rNc4FKYUw8zXn3A==",
|
"integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fp-ts": "^2.16.0",
|
"fp-ts": "^2.16.0",
|
||||||
"pbf": "^3.2.1",
|
"pbf": "^3.2.1",
|
||||||
|
@ -3109,9 +3109,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@lingdocs/inflect": {
|
"@lingdocs/inflect": {
|
||||||
"version": "7.0.8",
|
"version": "7.0.3",
|
||||||
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.8.tgz",
|
"resolved": "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz",
|
||||||
"integrity": "sha512-+RuZ2Tcw4gGjWwNOpZb/C7kwgWBAILY4ZRSzZBrG3KN+jEDrW++wgB5LfaKkzHLIurI73L5rNc4FKYUw8zXn3A==",
|
"integrity": "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fp-ts": "^2.16.0",
|
"fp-ts": "^2.16.0",
|
||||||
"pbf": "^3.2.1",
|
"pbf": "^3.2.1",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lingdocs/inflect": "7.0.8",
|
"@lingdocs/inflect": "7.0.3",
|
||||||
"base64url": "^3.0.1",
|
"base64url": "^3.0.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"connect-redis": "^6.0.0",
|
"connect-redis": "^6.0.0",
|
||||||
|
|
|
@ -45,10 +45,10 @@
|
||||||
"@jridgewell/resolve-uri" "^3.0.3"
|
"@jridgewell/resolve-uri" "^3.0.3"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||||
|
|
||||||
"@lingdocs/inflect@7.0.8":
|
"@lingdocs/inflect@7.0.3":
|
||||||
"integrity" "sha512-+RuZ2Tcw4gGjWwNOpZb/C7kwgWBAILY4ZRSzZBrG3KN+jEDrW++wgB5LfaKkzHLIurI73L5rNc4FKYUw8zXn3A=="
|
"integrity" "sha512-Ia4upjGHRUf6zuJ626V4g1AcaQQ5uKm8aHRn5qWYw1xEfBtSPHTvLd8Y6TXUjglv7HZ8udUXoP2LIGPWlD2aYA=="
|
||||||
"resolved" "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.8.tgz"
|
"resolved" "https://npm.lingdocs.com/@lingdocs/inflect/-/inflect-7.0.3.tgz"
|
||||||
"version" "7.0.8"
|
"version" "7.0.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"fp-ts" "^2.16.0"
|
"fp-ts" "^2.16.0"
|
||||||
"pbf" "^3.2.1"
|
"pbf" "^3.2.1"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,8 +14,8 @@
|
||||||
},
|
},
|
||||||
"main": "lib/functions/src/index.js",
|
"main": "lib/functions/src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.474.0",
|
"@google-cloud/storage": "^5.8.1",
|
||||||
"@lingdocs/inflect": "7.0.8",
|
"@lingdocs/inflect": "7.0.3",
|
||||||
"@types/cors": "^2.8.10",
|
"@types/cors": "^2.8.10",
|
||||||
"@types/google-spreadsheet": "^3.0.2",
|
"@types/google-spreadsheet": "^3.0.2",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.0.21",
|
||||||
|
|
|
@ -13,20 +13,15 @@ import {
|
||||||
} from "@lingdocs/inflect";
|
} from "@lingdocs/inflect";
|
||||||
import { getWordList } from "./word-list-maker";
|
import { getWordList } from "./word-list-maker";
|
||||||
import { PublishDictionaryResponse } from "../../website/src/types/functions-types";
|
import { PublishDictionaryResponse } from "../../website/src/types/functions-types";
|
||||||
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
import { Storage } from "@google-cloud/storage";
|
||||||
import zlib from "zlib";
|
const storage = new Storage({
|
||||||
const s3Client = new S3Client({
|
projectId: "lingdocs",
|
||||||
region: "auto",
|
|
||||||
endpoint: functions.config().r2.endpoint,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: functions.config().r2.access_key_id,
|
|
||||||
secretAccessKey: functions.config().r2.secret_access_key,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = "LingDocs Pashto Dictionary";
|
const title = "LingDocs Pashto Dictionary";
|
||||||
const license = `Copyright © ${new Date().getFullYear()} lingdocs.com All Rights Reserved - Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - https://creativecommons.org/licenses/by-nc-sa/4.0/`;
|
const license = `Copyright © ${new Date().getFullYear()} lingdocs.com All Rights Reserved - Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - https://creativecommons.org/licenses/by-nc-sa/4.0/`;
|
||||||
const baseUrl = `https://storage.lingdocs.com/dictionary/`;
|
const bucketName = "lingdocs";
|
||||||
|
const baseUrl = `https://storage.googleapis.com/${bucketName}/`;
|
||||||
const dictionaryFilename = "dictionary";
|
const dictionaryFilename = "dictionary";
|
||||||
const dictionaryInfoFilename = "dictionary-info";
|
const dictionaryInfoFilename = "dictionary-info";
|
||||||
// const hunspellAffFileFilename = "ps_AFF.aff";
|
// const hunspellAffFileFilename = "ps_AFF.aff";
|
||||||
|
@ -225,36 +220,19 @@ function checkForErrors(
|
||||||
|
|
||||||
async function upload(content: Buffer | string, filename: string) {
|
async function upload(content: Buffer | string, filename: string) {
|
||||||
const isBuffer = typeof content !== "string";
|
const isBuffer = typeof content !== "string";
|
||||||
// upload to r2 (new destination)
|
const file = storage.bucket(bucketName).file(filename);
|
||||||
if (isBuffer) {
|
await file.save(content, {
|
||||||
const putObjectCommand = new PutObjectCommand({
|
gzip: isBuffer ? false : true,
|
||||||
Bucket: functions.config().r2.bucket_name,
|
predefinedAcl: "publicRead",
|
||||||
Key: `dictionary/${filename}`,
|
metadata: {
|
||||||
Body: content,
|
contentType: isBuffer
|
||||||
CacheControl: "no-cache",
|
? "application/octet-stream"
|
||||||
ContentType: "application/octet-stream",
|
: filename.slice(-5) === ".json"
|
||||||
});
|
? "application/json"
|
||||||
await s3Client.send(putObjectCommand);
|
: "text/plain; charset=UTF-8",
|
||||||
} else {
|
cacheControl: "no-cache",
|
||||||
zlib.gzip(content, (err, buffer) => {
|
},
|
||||||
if (err) {
|
});
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
const putObjectCommand = new PutObjectCommand({
|
|
||||||
Bucket: functions.config().r2.bucket_name,
|
|
||||||
Key: `dictionary/${filename}`,
|
|
||||||
CacheControl: "no-cache",
|
|
||||||
Body: buffer,
|
|
||||||
ContentEncoding: "gzip",
|
|
||||||
ContentType: filename.endsWith(".json")
|
|
||||||
? "application/json"
|
|
||||||
: filename.endsWith(".xml")
|
|
||||||
? "application/xml"
|
|
||||||
: "text/plain; charset=UTF-8",
|
|
||||||
});
|
|
||||||
s3Client.send(putObjectCommand).catch(console.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function uploadHunspellToStorage(wordlist: {
|
// async function uploadHunspellToStorage(wordlist: {
|
||||||
|
@ -296,21 +274,14 @@ async function uploadDictionaryToStorage(dictionary: T.Dictionary) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSitemap(dictionary: T.Dictionary): string {
|
function makeSitemap(dictionary: T.Dictionary): string {
|
||||||
const pages = [
|
|
||||||
...["", "about", "settings", "account", "phrase-builder", "new-entries"],
|
|
||||||
...dictionary.entries.map((x) => `word?id=${x.ts}`),
|
|
||||||
];
|
|
||||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
${pages
|
${dictionary.entries.map(
|
||||||
.map(
|
(entry) =>
|
||||||
(page) =>
|
` <url>
|
||||||
`
|
<loc>https://dictionary.lingdocs.com/word?id=${entry.ts}</loc>
|
||||||
<url>
|
|
||||||
<loc>https://dictionary.lingdocs.com/${page}</loc>
|
|
||||||
</url>`
|
</url>`
|
||||||
)
|
)}
|
||||||
.join("")}
|
|
||||||
</urlset>
|
</urlset>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.15.2",
|
"@fortawesome/fontawesome-free": "^5.15.2",
|
||||||
"@lingdocs/ps-react": "7.0.8",
|
"@lingdocs/ps-react": "7.0.3",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
<meta name="apple-mobile-web-app-title" content="Pashto Dictionary">
|
<meta name="apple-mobile-web-app-title" content="Pashto Dictionary">
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<link rel="sitemap" type="application/xml" title="Sitemap" href="https://storage.lingdocs.com/dictionary/sitemap.xml" />
|
|
||||||
|
|
||||||
<meta name="author" content="lingdocs.com" />
|
<meta name="author" content="lingdocs.com" />
|
||||||
<link rel="canonical" href="https://dictionary.lingdocs.com/" />
|
<link rel="canonical" href="https://dictionary.lingdocs.com/" />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# https://www.robotstxt.org/robotstxt.html
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Disallow:
|
||||||
Sitemap: https://storage.lingdocs.com/dictionary/sitemap.xml
|
|
||||||
|
|
|
@ -57,10 +57,6 @@ body {
|
||||||
padding-bottom: 60px; */
|
padding-bottom: 60px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
dt {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-text {
|
.p-text {
|
||||||
font-size: var(--p-text-size);
|
font-size: var(--p-text-size);
|
||||||
}
|
}
|
||||||
|
|
|
@ -439,13 +439,6 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
action.type === "toggleSearchType"
|
action.type === "toggleSearchType"
|
||||||
) {
|
) {
|
||||||
if (this.props.location.pathname !== "/new-entries") {
|
if (this.props.location.pathname !== "/new-entries") {
|
||||||
if (
|
|
||||||
action.type === "toggleSearchType" &&
|
|
||||||
this.state.options.searchType === "fuzzy" &&
|
|
||||||
this.props.location.pathname !== "/search"
|
|
||||||
) {
|
|
||||||
this.handleSearchValueChange("آ");
|
|
||||||
}
|
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
options,
|
options,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -643,12 +636,12 @@ class App extends Component<RouteComponentProps, State> {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Link
|
{/* <Link
|
||||||
to="/new-entries"
|
to="/new-entries"
|
||||||
className="plain-link font-weight-light"
|
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="my-4 pt-3">
|
<div className="my-4 pt-3">
|
||||||
<Link
|
<Link
|
||||||
to="/phrase-builder"
|
to="/phrase-builder"
|
||||||
|
|
|
@ -36,9 +36,9 @@ function Entry({
|
||||||
data-testid="entry"
|
data-testid="entry"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<dt className="mr-2">
|
<strong>
|
||||||
<InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
<InlinePs opts={textOptions}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
||||||
</dt>
|
</strong>
|
||||||
{` `}
|
{` `}
|
||||||
<em>{entry.c}</em>
|
<em>{entry.c}</em>
|
||||||
{entry.a && !nonClickable && (
|
{entry.a && !nonClickable && (
|
||||||
|
@ -49,9 +49,7 @@ function Entry({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ExtraEntryInfo entry={entry} textOptions={textOptions} />
|
<ExtraEntryInfo entry={entry} textOptions={textOptions} />
|
||||||
<dd>
|
<div className="entry-definition">{entry.e}</div>
|
||||||
<div className="entry-definition">{entry.e}</div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { Types as T, InlinePs } from "@lingdocs/ps-react";
|
|
||||||
import { getAudioPath } from "./PlayStorageAudio";
|
|
||||||
|
|
||||||
export function EntryAudioDisplay({
|
|
||||||
entry,
|
|
||||||
opts,
|
|
||||||
}: {
|
|
||||||
entry: T.DictionaryEntry;
|
|
||||||
opts: T.TextOptions;
|
|
||||||
}) {
|
|
||||||
if (!entry.a) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<figure>
|
|
||||||
<figcaption className="mb-2 pl-2">
|
|
||||||
Listen to <InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
|
||||||
</figcaption>
|
|
||||||
<audio
|
|
||||||
controls
|
|
||||||
controlsList="nofullscreen"
|
|
||||||
src={getAudioPath(entry.ts)}
|
|
||||||
preload="auto"
|
|
||||||
>
|
|
||||||
<a href={getAudioPath(entry.ts)}>
|
|
||||||
Download audio for{" "}
|
|
||||||
<InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
|
||||||
</a>
|
|
||||||
</audio>
|
|
||||||
</figure>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,10 +1,6 @@
|
||||||
export function getAudioPath(ts: number): string {
|
|
||||||
return `https://storage.lingdocs.com/audio/${ts}.mp3`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function playStorageAudio(ts: number, callback: () => void) {
|
export default function playStorageAudio(ts: number, callback: () => void) {
|
||||||
if (!ts) return;
|
if (!ts) return;
|
||||||
let audio = new Audio(getAudioPath(ts));
|
let audio = new Audio(`https://storage.lingdocs.com/audio/${ts}.mp3`);
|
||||||
audio.addEventListener("ended", () => {
|
audio.addEventListener("ended", () => {
|
||||||
callback();
|
callback();
|
||||||
audio.remove();
|
audio.remove();
|
||||||
|
|
|
@ -24,9 +24,9 @@ import { makeAWeeBitFuzzy } from "./wee-bit-fuzzy";
|
||||||
import { getTextOptions } from "./get-text-options";
|
import { getTextOptions } from "./get-text-options";
|
||||||
import { DictionaryAPI, State } from "../types/dictionary-types";
|
import { DictionaryAPI, State } from "../types/dictionary-types";
|
||||||
|
|
||||||
const dictionaryBaseUrl = `https://storage.lingdocs.com/dictionary`;
|
// const dictionaryBaseUrl = "https://storage.googleapis.com/lingdocs/";
|
||||||
const dictionaryUrl = `${dictionaryBaseUrl}/dictionary`;
|
const dictionaryUrl = `https://storage.googleapis.com/lingdocs/dictionary`;
|
||||||
const dictionaryInfoUrl = `${dictionaryBaseUrl}/dictionary-info`;
|
const dictionaryInfoUrl = `https://storage.googleapis.com/lingdocs/dictionary-info`;
|
||||||
|
|
||||||
const dictionaryInfoLocalStorageKey = "dictionaryInfo5";
|
const dictionaryInfoLocalStorageKey = "dictionaryInfo5";
|
||||||
const dictionaryCollectionName = "dictionary3";
|
const dictionaryCollectionName = "dictionary3";
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { Modal } from "react-bootstrap";
|
||||||
import { getTextOptions } from "../lib/get-text-options";
|
import { getTextOptions } from "../lib/get-text-options";
|
||||||
import { entryFeeder } from "../lib/dictionary";
|
import { entryFeeder } from "../lib/dictionary";
|
||||||
import { State, DictionaryAPI } from "../types/dictionary-types";
|
import { State, DictionaryAPI } from "../types/dictionary-types";
|
||||||
import { EntryAudioDisplay } from "../components/EntryAudioDisplay";
|
import playStorageAudio from "../components/PlayStorageAudio";
|
||||||
|
|
||||||
function IsolatedEntry({
|
function IsolatedEntry({
|
||||||
state,
|
state,
|
||||||
|
@ -46,6 +46,7 @@ function IsolatedEntry({
|
||||||
isolateEntry: (ts: number) => void;
|
isolateEntry: (ts: number) => void;
|
||||||
}) {
|
}) {
|
||||||
const [exploded, setExploded] = useState<boolean>(false);
|
const [exploded, setExploded] = useState<boolean>(false);
|
||||||
|
const [playing, setPlaying] = useState<boolean>(false);
|
||||||
const [editing, setEditing] = useState<boolean>(false);
|
const [editing, setEditing] = useState<boolean>(false);
|
||||||
const [comment, setComment] = useState<string>("");
|
const [comment, setComment] = useState<string>("");
|
||||||
const [editSubmitted, setEditSubmitted] = useState<boolean>(false);
|
const [editSubmitted, setEditSubmitted] = useState<boolean>(false);
|
||||||
|
@ -56,6 +57,7 @@ function IsolatedEntry({
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
setComment("");
|
setComment("");
|
||||||
setEditSubmitted(false);
|
setEditSubmitted(false);
|
||||||
|
setPlaying(false);
|
||||||
}, [state]);
|
}, [state]);
|
||||||
function flashClippedMessage(m: string) {
|
function flashClippedMessage(m: string) {
|
||||||
setShowClipped(m);
|
setShowClipped(m);
|
||||||
|
@ -152,16 +154,19 @@ function IsolatedEntry({
|
||||||
navigator.clipboard.writeText(JSON.stringify(entry));
|
navigator.clipboard.writeText(JSON.stringify(entry));
|
||||||
flashClippedMessage("entry data copied to clipboard");
|
flashClippedMessage("entry data copied to clipboard");
|
||||||
}
|
}
|
||||||
|
function handlePlayStorageAudio() {
|
||||||
|
if (!entry) return;
|
||||||
|
setPlaying(true);
|
||||||
|
playStorageAudio(entry.ts, () => {
|
||||||
|
setPlaying(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="wide-width-limiter">
|
<div className="wide-width-limiter">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{entry.p} | LingDocs Pashto Dictionary</title>
|
<title>{entry.p} - LingDocs Pashto Dictionary</title>
|
||||||
<link
|
|
||||||
rel="canonical"
|
|
||||||
href={`https://dictionary.lingdocs.com/word?id=${entry.ts}`}
|
|
||||||
/>
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<dl className="row mb-1">
|
<div className="row">
|
||||||
<div className="col-8">
|
<div className="col-8">
|
||||||
<Entry
|
<Entry
|
||||||
nonClickable
|
nonClickable
|
||||||
|
@ -172,6 +177,13 @@ function IsolatedEntry({
|
||||||
</div>
|
</div>
|
||||||
<div className="col-4">
|
<div className="col-4">
|
||||||
<div className="d-flex flex-row justify-content-end">
|
<div className="d-flex flex-row justify-content-end">
|
||||||
|
{entry.a && (
|
||||||
|
<div className="clickable mr-3" onClick={handlePlayStorageAudio}>
|
||||||
|
<i
|
||||||
|
className={`fas fa-lg fa-volume-${playing ? "down" : "off"}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className="clickable mr-3"
|
className="clickable mr-3"
|
||||||
onClick={() => setExploded((os) => !os)}
|
onClick={() => setExploded((os) => !os)}
|
||||||
|
@ -226,8 +238,7 @@ function IsolatedEntry({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</div>
|
||||||
<EntryAudioDisplay entry={entry} opts={textOptions} />
|
|
||||||
{wordlistWord && (
|
{wordlistWord && (
|
||||||
<>
|
<>
|
||||||
{hasAttachment(wordlistWord, "audio") && (
|
{hasAttachment(wordlistWord, "audio") && (
|
||||||
|
@ -358,7 +369,6 @@ function IsolatedEntry({
|
||||||
state={{ ...state, results: relatedEntries }}
|
state={{ ...state, results: relatedEntries }}
|
||||||
isolateEntry={isolateEntry}
|
isolateEntry={isolateEntry}
|
||||||
handleInflectionSearch={() => null}
|
handleInflectionSearch={() => null}
|
||||||
relatedResults
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -8,278 +8,212 @@
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import * as FT from "../types/functions-types";
|
import * as FT from "../types/functions-types";
|
||||||
import { submissionBase, addSubmission } from "../lib/submissions";
|
import {
|
||||||
|
submissionBase,
|
||||||
|
addSubmission,
|
||||||
|
} from "../lib/submissions";
|
||||||
import { isPashtoScript } from "../lib/is-pashto";
|
import { isPashtoScript } from "../lib/is-pashto";
|
||||||
import Entry from "../components/Entry";
|
import Entry from "../components/Entry";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import InflectionFormMatchDisplay from "../components/InflectionFormMatchDisplay";
|
import InflectionFormMatchDisplay from "../components/InflectionFormMatchDisplay";
|
||||||
import { getTextOptions } from "../lib/get-text-options";
|
import { getTextOptions } from "../lib/get-text-options";
|
||||||
import { State } from "../types/dictionary-types";
|
import {
|
||||||
|
State,
|
||||||
|
} from "../types/dictionary-types";
|
||||||
|
|
||||||
export const inflectionSearchIcon = "fas fa-search-plus";
|
export const inflectionSearchIcon = "fas fa-search-plus";
|
||||||
|
|
||||||
// TODO: put power results in a prop so we can do it from outside with the keyboard shortcut
|
// TODO: put power results in a prop so we can do it from outside with the keyboard shortcut
|
||||||
function Results({
|
function Results({ state, isolateEntry, handleInflectionSearch }: {
|
||||||
state,
|
state: State,
|
||||||
isolateEntry,
|
isolateEntry: (ts: number) => void,
|
||||||
handleInflectionSearch,
|
handleInflectionSearch: () => void,
|
||||||
relatedResults,
|
|
||||||
}: {
|
|
||||||
state: State;
|
|
||||||
isolateEntry: (ts: number) => void;
|
|
||||||
handleInflectionSearch: () => void;
|
|
||||||
relatedResults?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
const [suggestionState, setSuggestionState] = useState<
|
const [suggestionState, setSuggestionState] = useState<"none" | "editing" | "received">("none");
|
||||||
"none" | "editing" | "received"
|
const [comment, setComment] = useState<string>("");
|
||||||
>("none");
|
const [pashto, setPashto] = useState<string>("");
|
||||||
const [comment, setComment] = useState<string>("");
|
const [phonetics, setPhonetics] = useState<string>("");
|
||||||
const [pashto, setPashto] = useState<string>("");
|
const [english, setEnglish] = useState<string>("");
|
||||||
const [phonetics, setPhonetics] = useState<string>("");
|
const textOptions = getTextOptions(state);
|
||||||
const [english, setEnglish] = useState<string>("");
|
function startSuggestion() {
|
||||||
const textOptions = getTextOptions(state);
|
const toStart = state.searchValue;
|
||||||
function startSuggestion() {
|
if (isPashtoScript(toStart)) {
|
||||||
const toStart = state.searchValue;
|
setPashto(toStart);
|
||||||
if (isPashtoScript(toStart)) {
|
setPhonetics("");
|
||||||
setPashto(toStart);
|
} else {
|
||||||
setPhonetics("");
|
setPashto("");
|
||||||
} else {
|
setPhonetics(toStart);
|
||||||
setPashto("");
|
}
|
||||||
setPhonetics(toStart);
|
setSuggestionState("editing");
|
||||||
}
|
}
|
||||||
setSuggestionState("editing");
|
function cancelSuggestion() {
|
||||||
}
|
setPashto("");
|
||||||
function cancelSuggestion() {
|
setPhonetics("");
|
||||||
setPashto("");
|
setSuggestionState("none");
|
||||||
setPhonetics("");
|
}
|
||||||
setSuggestionState("none");
|
function submitSuggestion(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||||
}
|
event.preventDefault();
|
||||||
function submitSuggestion(
|
if (!state.user) return;
|
||||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
const p = pashto;
|
||||||
) {
|
const f = phonetics;
|
||||||
event.preventDefault();
|
const e = english;
|
||||||
if (!state.user) return;
|
const newEntry: FT.EntrySuggestion = {
|
||||||
const p = pashto;
|
...submissionBase(state.user),
|
||||||
const f = phonetics;
|
type: "entry suggestion",
|
||||||
const e = english;
|
entry: { ts: 0, i: 0, p, f, g: "", e },
|
||||||
const newEntry: FT.EntrySuggestion = {
|
comment,
|
||||||
...submissionBase(state.user),
|
};
|
||||||
type: "entry suggestion",
|
addSubmission(newEntry, state.user);
|
||||||
entry: { ts: 0, i: 0, p, f, g: "", e },
|
setSuggestionState("received");
|
||||||
comment,
|
}
|
||||||
};
|
const inflectionResults = state.inflectionSearchResults;
|
||||||
addSubmission(newEntry, state.user);
|
return <div className="width-limiter">
|
||||||
setSuggestionState("received");
|
|
||||||
}
|
|
||||||
const inflectionResults = state.inflectionSearchResults;
|
|
||||||
return (
|
|
||||||
<div className="width-limiter">
|
|
||||||
{!relatedResults && (
|
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>LingDocs Pashto Dictionary</title>
|
<title>LingDocs Pashto Dictionary</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
)}
|
{(state.user && (window.location.pathname !== "/word") && suggestionState === "none" && inflectionResults === undefined) && <button
|
||||||
{state.user &&
|
|
||||||
window.location.pathname !== "/word" &&
|
|
||||||
suggestionState === "none" &&
|
|
||||||
inflectionResults === undefined && (
|
|
||||||
<button
|
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-outline-secondary bg-white entry-suggestion-button${
|
className={`btn btn-outline-secondary bg-white entry-suggestion-button${state.options.searchBarPosition === "bottom" ? " entry-suggestion-button-with-bottom-searchbar" : ""}`}
|
||||||
state.options.searchBarPosition === "bottom"
|
|
||||||
? " entry-suggestion-button-with-bottom-searchbar"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
onClick={startSuggestion}
|
onClick={startSuggestion}
|
||||||
>
|
>
|
||||||
<i className="fas fa-plus" style={{ padding: "3px" }} />
|
<i className="fas fa-plus" style={{ padding: "3px" }} />
|
||||||
</button>
|
</button>}
|
||||||
)}
|
{(inflectionResults === undefined && suggestionState === "none" && window.location.pathname === "/search") && <button
|
||||||
{inflectionResults === undefined &&
|
|
||||||
suggestionState === "none" &&
|
|
||||||
window.location.pathname === "/search" && (
|
|
||||||
<button
|
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-outline-secondary bg-white conjugation-search-button${
|
className={`btn btn-outline-secondary bg-white conjugation-search-button${state.options.searchBarPosition === "bottom" ? " conjugation-search-button-with-bottom-searchbar" : ""}`}
|
||||||
state.options.searchBarPosition === "bottom"
|
|
||||||
? " conjugation-search-button-with-bottom-searchbar"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
onClick={handleInflectionSearch}
|
onClick={handleInflectionSearch}
|
||||||
>
|
>
|
||||||
<i className={inflectionSearchIcon} style={{ padding: "3px" }} />
|
<i className={inflectionSearchIcon} style={{ padding: "3px" }} />
|
||||||
</button>
|
</button>}
|
||||||
)}
|
{inflectionResults === "searching" && <div>
|
||||||
{inflectionResults === "searching" && (
|
<p className="lead mt-1">Searching conjugations/inflections... <i className="fas fa-hourglass-half" /></p>
|
||||||
<div>
|
</div>}
|
||||||
<p className="lead mt-1">
|
{inflectionResults && inflectionResults !== "searching" && <div>
|
||||||
Searching conjugations/inflections...{" "}
|
<h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4>
|
||||||
<i className="fas fa-hourglass-half" />
|
{inflectionResults.exact.length === 0 && inflectionResults.fuzzy.length === 0 && <div className="mt-4">
|
||||||
</p>
|
<div>No conjugation/inflection matches found for <strong>{state.searchValue}</strong></div>
|
||||||
</div>
|
</div>}
|
||||||
)}
|
{(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => {
|
||||||
{inflectionResults && inflectionResults !== "searching" && (
|
return (inflectionResults[t].length !== 0) ? <>
|
||||||
<div>
|
<h5 className="mb-3">{t === "exact" ? "Exact" : "Approximate"} Matches</h5>
|
||||||
<h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4>
|
{inflectionResults[t].map((p) => (
|
||||||
{inflectionResults.exact.length === 0 &&
|
<div key={p.entry.ts}>
|
||||||
inflectionResults.fuzzy.length === 0 && (
|
<Entry
|
||||||
<div className="mt-4">
|
key={p.entry.i}
|
||||||
<div>
|
entry={p.entry}
|
||||||
No conjugation/inflection matches found for{" "}
|
textOptions={textOptions}
|
||||||
<strong>{state.searchValue}</strong>
|
isolateEntry={isolateEntry}
|
||||||
</div>
|
/>
|
||||||
</div>
|
<div className="mb-3 ml-2">
|
||||||
)}
|
{p.forms.map((form, i) => (
|
||||||
{(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => {
|
<InflectionFormMatchDisplay
|
||||||
return inflectionResults[t].length !== 0 ? (
|
key={`inf-form${i}`}
|
||||||
<>
|
textOptions={textOptions}
|
||||||
<h5 className="mb-3">
|
form={form}
|
||||||
{t === "exact" ? "Exact" : "Approximate"} Matches
|
entry={p.entry}
|
||||||
</h5>
|
/>
|
||||||
{inflectionResults[t].map((p) => (
|
))}
|
||||||
<div key={p.entry.ts}>
|
</div>
|
||||||
<Entry
|
</div>
|
||||||
key={p.entry.i}
|
))}
|
||||||
entry={p.entry}
|
</> : null;
|
||||||
textOptions={textOptions}
|
})}
|
||||||
isolateEntry={isolateEntry}
|
</div>}
|
||||||
/>
|
{inflectionResults === undefined && suggestionState === "none" && state.results.map((entry) => (
|
||||||
<div className="mb-3 ml-2">
|
|
||||||
{p.forms.map((form, i) => (
|
|
||||||
<InflectionFormMatchDisplay
|
|
||||||
key={`inf-form${i}`}
|
|
||||||
textOptions={textOptions}
|
|
||||||
form={form}
|
|
||||||
entry={p.entry}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
) : null;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{inflectionResults === undefined && suggestionState === "none" && (
|
|
||||||
<dl>
|
|
||||||
{state.results.map((entry) => (
|
|
||||||
<Entry
|
<Entry
|
||||||
key={entry.i}
|
key={entry.i}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
textOptions={textOptions}
|
textOptions={textOptions}
|
||||||
isolateEntry={isolateEntry}
|
isolateEntry={isolateEntry}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</dl>
|
{(state.user && (suggestionState === "editing")) && <div className="my-3">
|
||||||
)}
|
<h5 className="mb-3">Suggest an entry for the dictionary:</h5>
|
||||||
{state.user && suggestionState === "editing" && (
|
<div className="form-group mt-4" style={{ maxWidth: "500px" }}>
|
||||||
<div className="my-3">
|
<div className="row mb-2">
|
||||||
<h5 className="mb-3">Suggest an entry for the dictionary:</h5>
|
<div className="col">
|
||||||
<div className="form-group mt-4" style={{ maxWidth: "500px" }}>
|
<label htmlFor="suggestionPashto">Pashto:</label>
|
||||||
<div className="row mb-2">
|
<input
|
||||||
<div className="col">
|
type="text"
|
||||||
<label htmlFor="suggestionPashto">Pashto:</label>
|
className="form-control"
|
||||||
|
dir="rtl"
|
||||||
|
id="suggestionPashto"
|
||||||
|
data-lpignore="true"
|
||||||
|
value={pashto}
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
onChange={(e) => setPashto(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<label htmlFor="suggestionPhonetics">Phonetics:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
dir="ltr"
|
||||||
|
id="suggestionPhonetics"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
data-lpignore="true"
|
||||||
|
value={phonetics}
|
||||||
|
onChange={(e) => setPhonetics(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label htmlFor="suggestionEnglish">English:</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control mb-2"
|
||||||
dir="rtl"
|
id="suggestionEnglish"
|
||||||
id="suggestionPashto"
|
data-lpignore="true"
|
||||||
data-lpignore="true"
|
value={english}
|
||||||
value={pashto}
|
autoComplete="off"
|
||||||
autoComplete="off"
|
onChange={(e) => setEnglish(e.target.value)}
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
onChange={(e) => setPashto(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<label htmlFor="editSuggestionForm">Comments:</label>
|
||||||
<div className="col">
|
|
||||||
<label htmlFor="suggestionPhonetics">Phonetics:</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
dir="ltr"
|
id="editSuggestionForm"
|
||||||
id="suggestionPhonetics"
|
data-lpignore="true"
|
||||||
autoComplete="off"
|
value={comment}
|
||||||
autoCorrect="off"
|
onChange={(e) => setComment(e.target.value)}
|
||||||
autoCapitalize="off"
|
|
||||||
data-lpignore="true"
|
|
||||||
value={phonetics}
|
|
||||||
onChange={(e) => setPhonetics(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<label htmlFor="suggestionEnglish">English:</label>
|
<button
|
||||||
<input
|
type="button"
|
||||||
type="text"
|
className="btn btn-secondary mr-3"
|
||||||
className="form-control mb-2"
|
onClick={submitSuggestion}
|
||||||
id="suggestionEnglish"
|
data-testid="editWordSubmitButton"
|
||||||
data-lpignore="true"
|
>
|
||||||
value={english}
|
Submit
|
||||||
autoComplete="off"
|
</button>
|
||||||
onChange={(e) => setEnglish(e.target.value)}
|
<button
|
||||||
/>
|
type="button"
|
||||||
<label htmlFor="editSuggestionForm">Comments:</label>
|
className="btn btn-outline-secondary"
|
||||||
<input
|
onClick={cancelSuggestion}
|
||||||
type="text"
|
data-testid="editWordCancelButton"
|
||||||
className="form-control"
|
>
|
||||||
id="editSuggestionForm"
|
Cancel
|
||||||
data-lpignore="true"
|
</button>
|
||||||
value={comment}
|
</div>}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
{suggestionState === "received" && <div className="my-3">
|
||||||
/>
|
Thanks for the help!
|
||||||
</div>
|
</div>
|
||||||
<button
|
}
|
||||||
type="button"
|
{(((inflectionResults === undefined) && suggestionState === "none" && state.searchValue && (!state.results.length))) && <div>
|
||||||
className="btn btn-secondary mr-3"
|
<h5 className="mt-2">No Results Found in {state.options.language}</h5>
|
||||||
onClick={submitSuggestion}
|
{state.options.language === "Pashto" && isPashtoScript(state.searchValue) && <p className="mt-3">
|
||||||
data-testid="editWordSubmitButton"
|
Click on the <i className={inflectionSearchIcon} /> to search inflections and conjugations
|
||||||
>
|
</p>}
|
||||||
Submit
|
{state.options.searchType === "alphabetical" && <div className="mt-4 font-weight-light">
|
||||||
</button>
|
<div className="mb-3">You are using alphabetical browsing mode</div>
|
||||||
<button
|
<div>Click on the <span className="fa fa-book" ></span> icon above for smart search <span className="fa fa-bolt" ></span></div>
|
||||||
type="button"
|
</div>}
|
||||||
className="btn btn-outline-secondary"
|
</div>}
|
||||||
onClick={cancelSuggestion}
|
</div>;
|
||||||
data-testid="editWordCancelButton"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{suggestionState === "received" && (
|
|
||||||
<div className="my-3">Thanks for the help!</div>
|
|
||||||
)}
|
|
||||||
{inflectionResults === undefined &&
|
|
||||||
suggestionState === "none" &&
|
|
||||||
state.searchValue &&
|
|
||||||
!state.results.length && (
|
|
||||||
<div>
|
|
||||||
<h5 className="mt-2">
|
|
||||||
No Results Found in {state.options.language}
|
|
||||||
</h5>
|
|
||||||
{state.options.language === "Pashto" &&
|
|
||||||
isPashtoScript(state.searchValue) && (
|
|
||||||
<p className="mt-3">
|
|
||||||
Click on the <i className={inflectionSearchIcon} /> to search
|
|
||||||
inflections and conjugations
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{state.options.searchType === "alphabetical" && (
|
|
||||||
<div className="mt-4 font-weight-light">
|
|
||||||
<div className="mb-3">
|
|
||||||
You are using alphabetical browsing mode
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Click on the <span className="fa fa-book"></span> icon above
|
|
||||||
for smart search <span className="fa fa-bolt"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Results;
|
export default Results;
|
|
@ -2349,10 +2349,10 @@
|
||||||
"@jridgewell/resolve-uri" "^3.0.3"
|
"@jridgewell/resolve-uri" "^3.0.3"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||||
|
|
||||||
"@lingdocs/ps-react@7.0.8":
|
"@lingdocs/ps-react@7.0.3":
|
||||||
version "7.0.8"
|
version "7.0.3"
|
||||||
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.0.8.tgz#4f59cf04237f2fb87054251429695bf44cb7be79"
|
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.0.3.tgz#9962fca6a7eeec1d9eec7938ca5fbf2c8634c228"
|
||||||
integrity sha512-0TefQ1EBzGeJ+U4NYnXt/DB3QDZ1M8ALY8rOJgX7pj2N275+52yL2RXS6zrjjjKa/Ebr3XVNhK680O+JQh4anQ==
|
integrity sha512-/ASFFGudbqjEaZQ5OSHAwCQaZSdnPPInWv30rLR6ytLA+lS2udXLQHXkW+3ArUQ1UFBQlK1AC2+1tKNxvD2IrQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formkit/auto-animate" "^1.0.0-beta.3"
|
"@formkit/auto-animate" "^1.0.0-beta.3"
|
||||||
classnames "^2.2.6"
|
classnames "^2.2.6"
|
||||||
|
|
Loading…
Reference in New Issue