better SEO

This commit is contained in:
adueck 2023-12-18 14:57:54 +04:00
parent c67c00ed4a
commit 5b38546043
3 changed files with 253 additions and 186 deletions

View File

@ -13,7 +13,7 @@ export function EntryAudioDisplay({
} }
return ( return (
<figure> <figure>
<figcaption className="mb-1"> <figcaption className="mb-2 pl-2">
Listen to <InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs> Listen to <InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
</figcaption> </figcaption>
<audio <audio

View File

@ -155,13 +155,13 @@ function IsolatedEntry({
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 <link
rel="canonical" rel="canonical"
href={`https://dictionary.lingdocs.com/word?id=${entry.ts}`} href={`https://dictionary.lingdocs.com/word?id=${entry.ts}`}
/> />
</Helmet> </Helmet>
<div className="row"> <dl className="row mb-1">
<div className="col-8"> <div className="col-8">
<Entry <Entry
nonClickable nonClickable
@ -226,7 +226,7 @@ function IsolatedEntry({
)} )}
</div> </div>
</div> </div>
</div> </dl>
<EntryAudioDisplay entry={entry} opts={textOptions} /> <EntryAudioDisplay entry={entry} opts={textOptions} />
{wordlistWord && ( {wordlistWord && (
<> <>
@ -358,6 +358,7 @@ function IsolatedEntry({
state={{ ...state, results: relatedEntries }} state={{ ...state, results: relatedEntries }}
isolateEntry={isolateEntry} isolateEntry={isolateEntry}
handleInflectionSearch={() => null} handleInflectionSearch={() => null}
relatedResults
/> />
</> </>
) : ( ) : (

View File

@ -8,212 +8,278 @@
import { useState } from "react"; import { useState } from "react";
import * as FT from "../types/functions-types"; import * as FT from "../types/functions-types";
import { import { submissionBase, addSubmission } from "../lib/submissions";
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 { import { State } from "../types/dictionary-types";
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({ state, isolateEntry, handleInflectionSearch }: { function Results({
state: State, state,
isolateEntry: (ts: number) => void, isolateEntry,
handleInflectionSearch: () => void, handleInflectionSearch,
relatedResults,
}: {
state: State;
isolateEntry: (ts: number) => void;
handleInflectionSearch: () => void;
relatedResults?: boolean;
}) { }) {
const [suggestionState, setSuggestionState] = useState<"none" | "editing" | "received">("none"); const [suggestionState, setSuggestionState] = useState<
const [comment, setComment] = useState<string>(""); "none" | "editing" | "received"
const [pashto, setPashto] = useState<string>(""); >("none");
const [phonetics, setPhonetics] = useState<string>(""); const [comment, setComment] = useState<string>("");
const [english, setEnglish] = useState<string>(""); const [pashto, setPashto] = useState<string>("");
const textOptions = getTextOptions(state); const [phonetics, setPhonetics] = useState<string>("");
function startSuggestion() { const [english, setEnglish] = useState<string>("");
const toStart = state.searchValue; const textOptions = getTextOptions(state);
if (isPashtoScript(toStart)) { function startSuggestion() {
setPashto(toStart); const toStart = state.searchValue;
setPhonetics(""); if (isPashtoScript(toStart)) {
} else { setPashto(toStart);
setPashto(""); setPhonetics("");
setPhonetics(toStart); } else {
} setPashto("");
setSuggestionState("editing"); setPhonetics(toStart);
} }
function cancelSuggestion() { setSuggestionState("editing");
setPashto(""); }
setPhonetics(""); function cancelSuggestion() {
setSuggestionState("none"); setPashto("");
} setPhonetics("");
function submitSuggestion(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) { setSuggestionState("none");
event.preventDefault(); }
if (!state.user) return; function submitSuggestion(
const p = pashto; event: React.MouseEvent<HTMLButtonElement, MouseEvent>
const f = phonetics; ) {
const e = english; event.preventDefault();
const newEntry: FT.EntrySuggestion = { if (!state.user) return;
...submissionBase(state.user), const p = pashto;
type: "entry suggestion", const f = phonetics;
entry: { ts: 0, i: 0, p, f, g: "", e }, const e = english;
comment, const newEntry: FT.EntrySuggestion = {
}; ...submissionBase(state.user),
addSubmission(newEntry, state.user); type: "entry suggestion",
setSuggestionState("received"); entry: { ts: 0, i: 0, p, f, g: "", e },
} comment,
const inflectionResults = state.inflectionSearchResults; };
return <div className="width-limiter"> addSubmission(newEntry, state.user);
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${state.options.searchBarPosition === "bottom" ? " entry-suggestion-button-with-bottom-searchbar" : ""}`} className={`btn btn-outline-secondary bg-white entry-suggestion-button${
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${state.options.searchBarPosition === "bottom" ? " conjugation-search-button-with-bottom-searchbar" : ""}`} className={`btn btn-outline-secondary bg-white conjugation-search-button${
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> )}
<p className="lead mt-1">Searching conjugations/inflections... <i className="fas fa-hourglass-half" /></p> {inflectionResults === "searching" && (
</div>} <div>
{inflectionResults && inflectionResults !== "searching" && <div> <p className="lead mt-1">
<h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4> Searching conjugations/inflections...{" "}
{inflectionResults.exact.length === 0 && inflectionResults.fuzzy.length === 0 && <div className="mt-4"> <i className="fas fa-hourglass-half" />
<div>No conjugation/inflection matches found for <strong>{state.searchValue}</strong></div> </p>
</div>} </div>
{(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => { )}
return (inflectionResults[t].length !== 0) ? <> {inflectionResults && inflectionResults !== "searching" && (
<h5 className="mb-3">{t === "exact" ? "Exact" : "Approximate"} Matches</h5> <div>
{inflectionResults[t].map((p) => ( <h4 className="mt-1 mb-3">Conjugation/Inflection Results</h4>
<div key={p.entry.ts}> {inflectionResults.exact.length === 0 &&
<Entry inflectionResults.fuzzy.length === 0 && (
key={p.entry.i} <div className="mt-4">
entry={p.entry} <div>
textOptions={textOptions} No conjugation/inflection matches found for{" "}
isolateEntry={isolateEntry} <strong>{state.searchValue}</strong>
/>
<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" && state.results.map((entry) => (
<Entry
key={entry.i}
entry={entry}
textOptions={textOptions}
isolateEntry={isolateEntry}
/>
))}
{(state.user && (suggestionState === "editing")) && <div className="my-3">
<h5 className="mb-3">Suggest an entry for the dictionary:</h5>
<div className="form-group mt-4" style={{ maxWidth: "500px" }}>
<div className="row mb-2">
<div className="col">
<label htmlFor="suggestionPashto">Pashto:</label>
<input
type="text"
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> </div>
<label htmlFor="suggestionEnglish">English:</label> </div>
)}
{(["exact", "fuzzy"] as ("exact" | "fuzzy")[]).map((t) => {
return inflectionResults[t].length !== 0 ? (
<>
<h5 className="mb-3">
{t === "exact" ? "Exact" : "Approximate"} Matches
</h5>
{inflectionResults[t].map((p) => (
<div key={p.entry.ts}>
<Entry
key={p.entry.i}
entry={p.entry}
textOptions={textOptions}
isolateEntry={isolateEntry}
/>
<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
key={entry.i}
entry={entry}
textOptions={textOptions}
isolateEntry={isolateEntry}
/>
))}
</dl>
)}
{state.user && suggestionState === "editing" && (
<div className="my-3">
<h5 className="mb-3">Suggest an entry for the dictionary:</h5>
<div className="form-group mt-4" style={{ maxWidth: "500px" }}>
<div className="row mb-2">
<div className="col">
<label htmlFor="suggestionPashto">Pashto:</label>
<input <input
type="text" type="text"
className="form-control mb-2" className="form-control"
id="suggestionEnglish" dir="rtl"
data-lpignore="true" id="suggestionPashto"
value={english} data-lpignore="true"
autoComplete="off" value={pashto}
onChange={(e) => setEnglish(e.target.value)} autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
onChange={(e) => setPashto(e.target.value)}
/> />
<label htmlFor="editSuggestionForm">Comments:</label> </div>
<div className="col">
<label htmlFor="suggestionPhonetics">Phonetics:</label>
<input <input
type="text" type="text"
className="form-control" className="form-control"
id="editSuggestionForm" dir="ltr"
data-lpignore="true" id="suggestionPhonetics"
value={comment} autoComplete="off"
onChange={(e) => setComment(e.target.value)} autoCorrect="off"
autoCapitalize="off"
data-lpignore="true"
value={phonetics}
onChange={(e) => setPhonetics(e.target.value)}
/> />
</div>
</div> </div>
<button <label htmlFor="suggestionEnglish">English:</label>
type="button" <input
className="btn btn-secondary mr-3" type="text"
onClick={submitSuggestion} className="form-control mb-2"
data-testid="editWordSubmitButton" id="suggestionEnglish"
> data-lpignore="true"
Submit value={english}
</button> autoComplete="off"
<button onChange={(e) => setEnglish(e.target.value)}
type="button" />
className="btn btn-outline-secondary" <label htmlFor="editSuggestionForm">Comments:</label>
onClick={cancelSuggestion} <input
data-testid="editWordCancelButton" type="text"
> className="form-control"
Cancel id="editSuggestionForm"
</button> data-lpignore="true"
</div>} value={comment}
{suggestionState === "received" && <div className="my-3"> onChange={(e) => setComment(e.target.value)}
Thanks for the help! />
</div> </div>
} <button
{(((inflectionResults === undefined) && suggestionState === "none" && state.searchValue && (!state.results.length))) && <div> type="button"
<h5 className="mt-2">No Results Found in {state.options.language}</h5> className="btn btn-secondary mr-3"
{state.options.language === "Pashto" && isPashtoScript(state.searchValue) && <p className="mt-3"> onClick={submitSuggestion}
Click on the <i className={inflectionSearchIcon} /> to search inflections and conjugations data-testid="editWordSubmitButton"
</p>} >
{state.options.searchType === "alphabetical" && <div className="mt-4 font-weight-light"> Submit
<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> <button
</div>} type="button"
</div>} className="btn btn-outline-secondary"
</div>; onClick={cancelSuggestion}
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;