fix: og image for glossary
This commit is contained in:
@@ -3,7 +3,7 @@ export const dynamic = "force-dynamic";
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getAllGlossaryEntries } from "@/lib/api";
|
import { getAllGlossaryEntries } from "@/lib/api";
|
||||||
import { buildOgMeta } from "@/lib/og";
|
import { buildOgMeta, extractQuoteFromHl } from "@/lib/og";
|
||||||
import { GlossaryClient } from "../glossary-client";
|
import { GlossaryClient } from "../glossary-client";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -11,12 +11,20 @@ import { GlossaryClient } from "../glossary-client";
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const DIACRITIC_TO_BASE: Record<string, string> = {
|
const DIACRITIC_TO_BASE: Record<string, string> = {
|
||||||
Ā: "A", Ä: "A",
|
Ā: "A",
|
||||||
Ī: "I", É: "I",
|
Ä: "A",
|
||||||
Ū: "U", Ü: "U",
|
Ī: "I",
|
||||||
Ṛ: "R", Ṝ: "R",
|
É: "I",
|
||||||
Ś: "S", Ç: "S", Ṣ: "S",
|
Ū: "U",
|
||||||
Ñ: "N", Ṇ: "N", Ṅ: "N",
|
Ü: "U",
|
||||||
|
Ṛ: "R",
|
||||||
|
Ṝ: "R",
|
||||||
|
Ś: "S",
|
||||||
|
Ç: "S",
|
||||||
|
Ṣ: "S",
|
||||||
|
Ñ: "N",
|
||||||
|
Ṇ: "N",
|
||||||
|
Ṅ: "N",
|
||||||
Ṭ: "T",
|
Ṭ: "T",
|
||||||
Ḍ: "D",
|
Ḍ: "D",
|
||||||
Ḥ: "H",
|
Ḥ: "H",
|
||||||
@@ -34,11 +42,53 @@ function getBaseLetter(term: string): string {
|
|||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ letter: string }>;
|
params: Promise<{ letter: string }>;
|
||||||
|
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const { letter } = await params;
|
const { letter } = await params;
|
||||||
|
const sp = await searchParams;
|
||||||
const l = letter.toUpperCase();
|
const l = letter.toUpperCase();
|
||||||
|
|
||||||
|
// If a specific term is referenced via ?term= param, show it in the OG image
|
||||||
|
const termParam = typeof sp.term === "string" ? sp.term : null;
|
||||||
|
if (termParam) {
|
||||||
|
const allEntries = await getAllGlossaryEntries();
|
||||||
|
const entry = allEntries.find(
|
||||||
|
(e) => e.term.toLowerCase() === termParam.toLowerCase(),
|
||||||
|
);
|
||||||
|
if (entry) {
|
||||||
|
// Strip HTML tags from definition for OG quote
|
||||||
|
const plainDef = entry.definitionHtml
|
||||||
|
.replace(/<[^>]*>/g, "")
|
||||||
|
.replace(/&[^;]+;/g, " ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Check for ?hl= highlight param — extract actual selected text from definition
|
||||||
|
const hlQuote = extractQuoteFromHl(sp, [entry.definitionHtml]);
|
||||||
|
|
||||||
|
return buildOgMeta({
|
||||||
|
title: entry.term,
|
||||||
|
detail: "Glossary — Prabhupāda Says",
|
||||||
|
quote:
|
||||||
|
hlQuote ||
|
||||||
|
(plainDef.length > 260 ? plainDef.slice(0, 257) + "..." : plainDef),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for ?hl= without ?term= — try to extract quote from all entries for this letter
|
||||||
|
const hlQuote = extractQuoteFromHl(sp);
|
||||||
|
if (hlQuote) {
|
||||||
|
return buildOgMeta({
|
||||||
|
title: `Glossary — ${l}`,
|
||||||
|
detail: "Sanskrit & Vedic terms",
|
||||||
|
quote: hlQuote,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: letter-level OG
|
||||||
return buildOgMeta({
|
return buildOgMeta({
|
||||||
title: `Glossary — ${l}`,
|
title: `Glossary — ${l}`,
|
||||||
detail: `Sanskrit & Vedic terms starting with ${l}`,
|
detail: `Sanskrit & Vedic terms starting with ${l}`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useMemo, useRef, useEffect, useCallback } from "react";
|
import { useState, useMemo, useRef, useEffect, useCallback } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Search, X, Menu, ChevronLeft, ChevronRight } from "lucide-react";
|
import { Search, X, Menu, ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -289,6 +290,7 @@ export function GlossaryClient({
|
|||||||
letters,
|
letters,
|
||||||
letterCounts,
|
letterCounts,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const router = useRouter();
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [activeTermRaw, setActiveTerm] = useState(entries[0]?.term || "");
|
const [activeTermRaw, setActiveTerm] = useState(entries[0]?.term || "");
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -347,6 +349,17 @@ export function GlossaryClient({
|
|||||||
return idx !== undefined ? entries[idx] : null;
|
return idx !== undefined ? entries[idx] : null;
|
||||||
}, [entries, termIndex, activeTerm]);
|
}, [entries, termIndex, activeTerm]);
|
||||||
|
|
||||||
|
// Update URL with ?term= when active term changes (for OG + shareable links)
|
||||||
|
useEffect(() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
if (activeTerm) {
|
||||||
|
url.searchParams.set("term", activeTerm);
|
||||||
|
} else {
|
||||||
|
url.searchParams.delete("term");
|
||||||
|
}
|
||||||
|
window.history.replaceState({}, "", url.toString());
|
||||||
|
}, [activeTerm]);
|
||||||
|
|
||||||
const handleSelectTerm = useCallback((term: string) => {
|
const handleSelectTerm = useCallback((term: string) => {
|
||||||
setActiveTerm(term);
|
setActiveTerm(term);
|
||||||
contentRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
contentRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
|||||||
+20
-1
@@ -164,8 +164,27 @@ export function getSourceReference(): {
|
|||||||
return { title: parentTitle, detail: chapterTitle, url };
|
return { title: parentTitle, detail: chapterTitle, url };
|
||||||
}
|
}
|
||||||
|
|
||||||
// /glossary — general
|
// /glossary/[letter]?term=...
|
||||||
if (parts[0] === "glossary") {
|
if (parts[0] === "glossary") {
|
||||||
|
const h1 = document.querySelector("h1");
|
||||||
|
const termFromH1 = h1?.textContent?.replace(/^Glossary:\s*/, "").trim();
|
||||||
|
|
||||||
|
// Try to get the term from the URL ?term= param
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const termFromUrl = urlParams.get("term");
|
||||||
|
|
||||||
|
const term = termFromUrl || termFromH1 || null;
|
||||||
|
|
||||||
|
if (term) {
|
||||||
|
return { title: term, detail: "Glossary — Prabhupāda Says", url };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: letter-level
|
||||||
|
const letter = parts[1]?.toUpperCase();
|
||||||
|
if (letter) {
|
||||||
|
return { title: `Glossary — ${letter}`, detail: null, url };
|
||||||
|
}
|
||||||
|
|
||||||
return { title: "Glossary", detail: null, url };
|
return { title: "Glossary", detail: null, url };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user