Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 3 additions & 47 deletions src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "leaflet/dist/leaflet.css";
import "./Map.css";
import L from "leaflet";
import { useRouter } from "next/navigation";
import { mapMarkerStoryHref } from "../lib/storyUrls";

const customIcon = new L.Icon({
iconUrl: "/marker.svg",
Expand Down Expand Up @@ -123,53 +124,8 @@ const Map = () => {
);
const router = useRouter();

function handleStoryClick(item: string, category: string) {
if (category === "Huldufólk") {
category = "alfar-og-huldufolk";
}
if (category === "Helgisögur") {
category = "ur-efra-og-nedra-helgisogur";
}
if (category === "Draugar") {
category = "draugar";
}
if (category === "Tröll") {
category = "troll";
}
if (item === "Geirfuglasker") {
item = "geirfugl";
}
if (item === "Loðmundarfjörður") {
item = "a-lodmfj";
}
if (item === "Melstaðarkirkja") {
item = "jonas";
}
if (item === "Skaftafell") {
item = "einar-sk";
}
if (item === "Jórukleif") {
item = "jora";
}
if (item === "Eyvindarmúli") {
item = "gudm-eyv";
}
if (item === "Rafnkelsstaðir") {
item = "flugan";
}
if (item === "Snjóholt") {
item = "setta2";
}
if (item === "Reynisstaðarkirkja") {
item = "reynis";
}
if (item === "Húnavatnssýsla") {
item = "sat-nafn";
}
if (item === "Hruni") {
item = "hruna";
}
router.push(`/stories/${category}/${item}`);
function handleStoryClick(markerTitle: string, mapCategory: string) {
router.push(mapMarkerStoryHref(markerTitle, mapCategory));
}

useEffect(() => {
Expand Down
33 changes: 2 additions & 31 deletions src/components/StoriesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import photo9 from "../../public/resources/hidden people 2.png";
import photo10 from "../../public/resources/MYND5.png";
import photo11 from "../../public/resources/gillitrut.png";
import { useMediaQuery } from "../app/hooks/useMediaQuery";
import { storyHref } from "../lib/storyUrls";

const shuffleArray = <T,>(array: T[]): T[] => {
const shuffledArray = [...array];
Expand All @@ -23,35 +24,6 @@ const shuffleArray = <T,>(array: T[]): T[] => {
return shuffledArray;
};

const storyNavigations: Record<string, string> = {
"Álfadrottning í álögum": "alfa-dr",
"Álfafólkið í Loðmundarfirði": "a-lodmfj",
"Álfakóngurinn í Seley": "seley",
"Ábæjar-Skotta": "skotta3",
"Átján draugar úr Blöndu": "18draug",
"Átján sendingar í senn": "18send",
"Átján Skólabræður": "18skolab",
"Andrarímur og Hallgrímsrímur": "andra",
"Bergþór Bláfellingur": "blafell",
Bakkastaður: "bakka",
"Brytinn í Skálholti": "brytinn",
"Dansinn í Hruna": "hruna",
};

const categoryNavigations: Record<string, string> = {
Allt: "all",
Tröll: "troll",
Draugar: "draugar",
"Álfar og Huldufólk": "alfa",
Helgisögur: "efra",
};

function storyHref(storyLink: string, categorySlug: string): string {
const categorySegment = categoryNavigations[categorySlug] || categorySlug;
const storySlug = storyNavigations[storyLink] || storyLink;
return `/stories/${categorySegment}/${storySlug}`;
}

type StoriesCardType = {
data: {
category: string;
Expand Down Expand Up @@ -91,7 +63,6 @@ export const StoriesCard = ({ data, categoryName, links }: StoriesCardType) => {

useEffect(() => {
if (categoryName === "all" && data) {
console.log(links);
setCategoryStories(data.stories || []);
} else if (!Array.isArray(data) && data.category !== "all") {
const catStories = Object.values(data?.stories || {});
Expand Down Expand Up @@ -131,7 +102,7 @@ export const StoriesCard = ({ data, categoryName, links }: StoriesCardType) => {
height={500}
quality={isMobile ? 50 : 95}
priority={index === 0}
loading={"eager"}
loading={index === 0 ? "eager" : "lazy"}
className="w-full h-auto rounded-lg"
aria-hidden
/>
Expand Down
67 changes: 67 additions & 0 deletions src/lib/storyUrls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Single place for /stories/[category]/[story] path rules used by the feed and the map.
*/

const storyTitleToSlug: Record<string, string> = {
"Álfadrottning í álögum": "alfa-dr",
"Álfafólkið í Loðmundarfirði": "a-lodmfj",
"Álfakóngurinn í Seley": "seley",
"Ábæjar-Skotta": "skotta3",
"Átján draugar úr Blöndu": "18draug",
"Átján sendingar í senn": "18send",
"Átján Skólabræður": "18skolab",
"Andrarímur og Hallgrímsrímur": "andra",
"Bergþór Bláfellingur": "blafell",
Bakkastaður: "bakka",
"Brytinn í Skálholti": "brytinn",
"Dansinn í Hruna": "hruna",
};

/** Icelandic UI labels from category chips → URL segment (when not already a slug). */
const categoryDisplayToSegment: Record<string, string> = {
Allt: "all",
Tröll: "troll",
Draugar: "draugar",
"Álfar og Huldufólk": "alfa",
Helgisögur: "efra",
};

const mapUiCategoryToSegment: Record<string, string> = {
Huldufólk: "alfar-og-huldufolk",
Helgisögur: "ur-efra-og-nedra-helgisogur",
Draugar: "draugar",
Tröll: "troll",
};

const mapMarkerTitleToSlug: Record<string, string> = {
Geirfuglasker: "geirfugl",
Loðmundarfjörður: "a-lodmfj",
Melstaðarkirkja: "jonas",
Skaftafell: "einar-sk",
Jórukleif: "jora",
Eyvindarmúli: "gudm-eyv",
Rafnkelsstaðir: "flugan",
Snjóholt: "setta2",
Reynisstaðarkirkja: "reynis",
Húnavatnssýsla: "sat-nafn",
Hruni: "hruna",
};

export function storyHref(storyLink: string, categoryKey: string): string {
const categorySegment =
categoryDisplayToSegment[categoryKey] || categoryKey;
const storySlug = storyTitleToSlug[storyLink] || storyLink;
return `/stories/${categorySegment}/${storySlug}`;
}

/** Map popup labels → same paths as the stories feed. */
export function mapMarkerStoryHref(
markerTitle: string,
mapUiCategory: string
): string {
const categorySegment =
mapUiCategoryToSegment[mapUiCategory] ?? mapUiCategory;
const storySlug =
mapMarkerTitleToSlug[markerTitle] ?? markerTitle;
return storyHref(storySlug, categorySegment);
}
132 changes: 32 additions & 100 deletions src/pages/api/all.js
Original file line number Diff line number Diff line change
@@ -1,109 +1,39 @@
import fetch from "node-fetch";
import { load } from "cheerio";

/** Source index pages on Netútgáfan (static folklore HTML). */
const THJOD_SOURCE_URLS = [
"https://netutgafan.snerpa.is/thjod/troll.htm",
"https://netutgafan.snerpa.is/thjod/draug.htm",
"https://netutgafan.snerpa.is/thjod/alfa.htm",
"https://netutgafan.snerpa.is/thjod/efra.htm",
];

/** Longer TTL: list changes rarely; reduces cold-load latency from 4 upstream fetches. */
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;

let cachedData = null;
let cacheExpiration = Date.now();
let cacheExpiration = 0;

function extractCategoryPage(html) {
return load(html).extract({
category: { selector: "h1" },
stories: [{ selector: "a" }],
links: [{ selector: "a", value: "href" }],
});
}

export default async function handler(req, res) {
if (cachedData && Date.now() < cacheExpiration) {
return res.status(200).json(cachedData);
}

try {
const [data1, data2, data3, data4] = await Promise.all([
fetch("https://netutgafan.snerpa.is/thjod/troll.htm")
.then((res) => res.text())
.then((html) => {
const json = load(html).extract({
category: {
selector: "h1",
},
stories: [
{
selector: "a",
},
],
links: [
{
selector: "a",

value: "href",
},
],
});

return json;
}),
fetch("https://netutgafan.snerpa.is/thjod/draug.htm")
.then((res) => res.text())
.then((html) => {
const json = load(html).extract({
category: {
selector: "h1",
},
stories: [
{
selector: "a",
},
],
links: [
{
selector: "a",

value: "href",
},
],
});

return json;
}),
fetch("https://netutgafan.snerpa.is/thjod/alfa.htm")
.then((res) => res.text())
.then((html) => {
const json = load(html).extract({
category: {
selector: "h1",
},
stories: [
{
selector: "a",
},
],
links: [
{
selector: "a",

value: "href",
},
],
});

return json;
}),
fetch("https://netutgafan.snerpa.is/thjod/efra.htm")
.then((res) => res.text())
.then((html) => {
const json = load(html).extract({
category: {
selector: "h1",
},
stories: [
{
selector: "a",
},
],
links: [
{
selector: "a",

value: "href",
},
],
});

return json;
}),
]);
const [data1, data2, data3, data4] = await Promise.all(
THJOD_SOURCE_URLS.map((url) =>
fetch(url).then((r) => r.text()).then(extractCategoryPage)
)
);

cachedData = [
{
Expand All @@ -129,11 +59,13 @@ export default async function handler(req, res) {
{ category: "alfar-og-huldufolk", stories: data3 },
{ category: "ur-efra-og-nedra-helgisogur", stories: data4 },
];
cacheExpiration = Date.now() + 1000 * 60 * 5;
console.log("data1", data1);
res.status(200).json(cachedData);
cacheExpiration = Date.now() + CACHE_TTL_MS;
return res.status(200).json(cachedData);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Error fetching data" });
console.error("[api/all]", error);
if (cachedData) {
return res.status(200).json(cachedData);
}
return res.status(500).json({ message: "Error fetching data" });
}
}