bleh
This commit is contained in:
parent
1b52075561
commit
5bffb63028
|
|
@ -35,7 +35,7 @@ export default function MapView({
|
||||||
const [timeFilter, setTimeFilter] = useState(() => {
|
const [timeFilter, setTimeFilter] = useState(() => {
|
||||||
try {
|
try {
|
||||||
const v = localStorage.getItem(TIME_FILTER_KEY);
|
const v = localStorage.getItem(TIME_FILTER_KEY);
|
||||||
const ok = ["NOW","TODAY","RECENT","PAST"].includes(v);
|
const ok = ["NOW", "TODAY", "RECENT", "PAST"].includes(v);
|
||||||
return ok ? v : "TODAY";
|
return ok ? v : "TODAY";
|
||||||
} catch {
|
} catch {
|
||||||
return "TODAY";
|
return "TODAY";
|
||||||
|
|
@ -44,7 +44,9 @@ export default function MapView({
|
||||||
const [subFilter, setSubFilter] = useState("All");
|
const [subFilter, setSubFilter] = useState("All");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try { localStorage.setItem(TIME_FILTER_KEY, timeFilter); } catch {}
|
try {
|
||||||
|
localStorage.setItem(TIME_FILTER_KEY, timeFilter);
|
||||||
|
} catch {}
|
||||||
}, [timeFilter]);
|
}, [timeFilter]);
|
||||||
|
|
||||||
const stageRef = useRef(null);
|
const stageRef = useRef(null);
|
||||||
|
|
@ -52,6 +54,8 @@ export default function MapView({
|
||||||
|
|
||||||
const userPosition = useUserPosition(mapRef, hasLastView);
|
const userPosition = useUserPosition(mapRef, hasLastView);
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({
|
const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } = usePostsEngine({
|
||||||
theme,
|
theme,
|
||||||
mapRef,
|
mapRef,
|
||||||
|
|
@ -59,6 +63,7 @@ export default function MapView({
|
||||||
mainFilter,
|
mainFilter,
|
||||||
subFilter,
|
subFilter,
|
||||||
timeFilter,
|
timeFilter,
|
||||||
|
searchQuery,
|
||||||
markersRef,
|
markersRef,
|
||||||
expandedElRef,
|
expandedElRef,
|
||||||
});
|
});
|
||||||
|
|
@ -72,8 +77,6 @@ export default function MapView({
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [saveError, setSaveError] = useState("");
|
const [saveError, setSaveError] = useState("");
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
|
||||||
|
|
||||||
const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"];
|
const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"];
|
||||||
|
|
||||||
/* SW_SUBCAT_ICONS:BEGIN */
|
/* SW_SUBCAT_ICONS:BEGIN */
|
||||||
|
|
@ -155,7 +158,7 @@ export default function MapView({
|
||||||
}
|
}
|
||||||
}, [selectedPost, mapRef]);
|
}, [selectedPost, mapRef]);
|
||||||
|
|
||||||
// Websocket for new posts
|
// Hide subcats when map isn't visible enough (keeps UI clean)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = stageRef.current;
|
const el = stageRef.current;
|
||||||
if (!el || typeof IntersectionObserver === "undefined") return;
|
if (!el || typeof IntersectionObserver === "undefined") return;
|
||||||
|
|
@ -173,6 +176,7 @@ export default function MapView({
|
||||||
return () => obs.disconnect();
|
return () => obs.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Websocket for new posts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
||||||
const host =
|
const host =
|
||||||
|
|
@ -211,10 +215,13 @@ export default function MapView({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsEmailVerification) {
|
if (needsEmailVerification) {
|
||||||
alert("You must verify your email before posting. Use 'Resend email' in the top bar, then login again.");
|
alert(
|
||||||
|
"You must verify your email before posting. Use 'Resend email' in the top bar, then login again."
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsCreating(true);
|
|
||||||
|
setIsCreating(true);
|
||||||
setDraftTitle("");
|
setDraftTitle("");
|
||||||
setDraftBody("");
|
setDraftBody("");
|
||||||
const main = mainFilter === "All" ? "News" : mainFilter;
|
const main = mainFilter === "All" ? "News" : mainFilter;
|
||||||
|
|
@ -224,11 +231,9 @@ setIsCreating(true);
|
||||||
setDraftCoords(null);
|
setDraftCoords(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Keep submit to allow Enter key, but no popup:
|
||||||
const handleSearchSubmit = (e) => {
|
const handleSearchSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const q = (searchQuery || "").trim();
|
|
||||||
if (!q) return;
|
|
||||||
alert(`🔍 Search: ${q} (coming soon!)`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearSearch = () => {
|
const handleClearSearch = () => {
|
||||||
|
|
@ -247,12 +252,12 @@ setIsCreating(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (needsEmailVerification) {
|
if (needsEmailVerification) {
|
||||||
setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again.");
|
setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const authorName = (username || "").trim() || "anon";
|
|
||||||
|
const authorName = (username || "").trim() || "anon";
|
||||||
|
|
||||||
let baseLng;
|
let baseLng;
|
||||||
let baseLat;
|
let baseLat;
|
||||||
|
|
@ -305,7 +310,9 @@ const authorName = (username || "").trim() || "anon";
|
||||||
|
|
||||||
const code = e && (e.code || "");
|
const code = e && (e.code || "");
|
||||||
const msg = String((e && e.message) || "").toLowerCase();
|
const msg = String((e && e.message) || "").toLowerCase();
|
||||||
const isNotVerified = code === "email_not_verified" || (e && e.status === 403 && (msg.includes("not verified") || msg.includes("verify")));
|
const isNotVerified =
|
||||||
|
code === "email_not_verified" ||
|
||||||
|
(e && e.status === 403 && (msg.includes("not verified") || msg.includes("verify")));
|
||||||
|
|
||||||
if (isNotVerified) {
|
if (isNotVerified) {
|
||||||
setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again.");
|
setSaveError("Verify your email to post. Use 'Resend email' in the top bar, then login again.");
|
||||||
|
|
@ -339,7 +346,12 @@ const authorName = (username || "").trim() || "anon";
|
||||||
{status && <div className="map-status">{status}</div>}
|
{status && <div className="map-status">{status}</div>}
|
||||||
|
|
||||||
<div className="map-overlay map-overlay-top">
|
<div className="map-overlay map-overlay-top">
|
||||||
<form className="sw-searchbar" onSubmit={handleSearchSubmit} role="search" aria-label="Search">
|
<form
|
||||||
|
className="sw-searchbar"
|
||||||
|
onSubmit={handleSearchSubmit}
|
||||||
|
role="search"
|
||||||
|
aria-label="Search"
|
||||||
|
>
|
||||||
<i className="fa-solid fa-magnifying-glass sw-search-icon" />
|
<i className="fa-solid fa-magnifying-glass sw-search-icon" />
|
||||||
<input
|
<input
|
||||||
className="sw-search-input"
|
className="sw-search-input"
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchPosts } from "../../api/client";
|
import { fetchPosts } from "../../api/client";
|
||||||
import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter";
|
import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter";
|
||||||
import { haversineKm } from "./mapGeo";
|
import { haversineKm } from "./mapGeo";
|
||||||
import { createMarkerForPost, clearAllMarkers } from "./markerManager";
|
import { createMarkerForPost } from "./markerManager";
|
||||||
|
|
||||||
|
|
||||||
function getId(p) {
|
function getId(p) {
|
||||||
return p?.id ?? p?._id ?? null;
|
return p?.id ?? p?._id ?? null;
|
||||||
|
|
@ -19,12 +18,48 @@ function sameIdList(a, b) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function asText(v) {
|
||||||
|
if (v == null) return "";
|
||||||
|
return String(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function postHaystack(p) {
|
||||||
|
const title = asText(p?.title || p?.text || p?.body || "");
|
||||||
|
const snippet = asText(p?.snippet || p?.summary || p?.content || "");
|
||||||
|
const author = asText(p?.author || p?.Author || p?.user || p?.username || "");
|
||||||
|
const city = asText(p?.city || p?.location_name || p?.place || "");
|
||||||
|
const cat = asText(p?.category || p?.Category || "");
|
||||||
|
const sub = asText(p?.sub_category || p?.subCategory || "");
|
||||||
|
return `${title} ${snippet} ${author} ${city} ${cat} ${sub}`.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesSearch(p, qRaw) {
|
||||||
|
const q = (qRaw || "").toString().trim().toLowerCase();
|
||||||
|
if (!q) return true;
|
||||||
|
|
||||||
|
const author = asText(p?.author || p?.Author || p?.user || p?.username || "").toLowerCase();
|
||||||
|
const hay = postHaystack(p);
|
||||||
|
|
||||||
|
const terms = q.split(/\s+/).map((t) => t.trim()).filter(Boolean);
|
||||||
|
for (const t of terms) {
|
||||||
|
if (t.startsWith("@")) {
|
||||||
|
const want = t.slice(1).trim();
|
||||||
|
if (!want) continue;
|
||||||
|
if (!author.includes(want)) return false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!hay.includes(t)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function usePostsEngine({
|
export function usePostsEngine({
|
||||||
mapRef,
|
mapRef,
|
||||||
viewParams,
|
viewParams,
|
||||||
mainFilter,
|
mainFilter,
|
||||||
subFilter,
|
subFilter,
|
||||||
timeFilter,
|
timeFilter,
|
||||||
|
searchQuery,
|
||||||
markersRef,
|
markersRef,
|
||||||
expandedElRef,
|
expandedElRef,
|
||||||
theme = "blue",
|
theme = "blue",
|
||||||
|
|
@ -103,10 +138,11 @@ export function usePostsEngine({
|
||||||
}
|
}
|
||||||
if (!matchesSubFilter(p, subFilter)) return false;
|
if (!matchesSubFilter(p, subFilter)) return false;
|
||||||
if (!matchesTimeFilter(p.created_at || p.CreatedAt || p.createdAt, tf)) return false;
|
if (!matchesTimeFilter(p.created_at || p.CreatedAt || p.createdAt, tf)) return false;
|
||||||
|
if (!matchesSearch(p, searchQuery)) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[mainFilter, subFilter]
|
[mainFilter, subFilter, searchQuery]
|
||||||
);
|
);
|
||||||
|
|
||||||
const refreshVisibleAndMarkers = useCallback(
|
const refreshVisibleAndMarkers = useCallback(
|
||||||
|
|
@ -116,7 +152,7 @@ export function usePostsEngine({
|
||||||
// reduce wall blink: don't update if same ids
|
// reduce wall blink: don't update if same ids
|
||||||
setVisiblePosts((prev) => (sameIdList(prev, v) ? prev : v));
|
setVisiblePosts((prev) => (sameIdList(prev, v) ? prev : v));
|
||||||
|
|
||||||
// no annoying "No posts found" bubble
|
// keep status silent (no annoying bubbles)
|
||||||
setStatus("");
|
setStatus("");
|
||||||
|
|
||||||
syncMarkers(v);
|
syncMarkers(v);
|
||||||
|
|
@ -124,10 +160,10 @@ export function usePostsEngine({
|
||||||
[computeVisible, syncMarkers]
|
[computeVisible, syncMarkers]
|
||||||
);
|
);
|
||||||
|
|
||||||
// On filter changes: recompute + sync (NO clear-all rebuild)
|
// On filter/search changes: recompute + sync (NO clear-all rebuild)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshVisibleAndMarkers(timeFilter);
|
refreshVisibleAndMarkers(timeFilter);
|
||||||
}, [timeFilter, mainFilter, subFilter, refreshVisibleAndMarkers]);
|
}, [timeFilter, mainFilter, subFilter, searchQuery, refreshVisibleAndMarkers]);
|
||||||
|
|
||||||
// Fetch on view change (moveend)
|
// Fetch on view change (moveend)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -245,6 +281,7 @@ export function usePostsEngine({
|
||||||
}
|
}
|
||||||
if (!matchesSubFilter(p, subFilter)) return;
|
if (!matchesSubFilter(p, subFilter)) return;
|
||||||
if (!matchesTimeFilter(p.created_at || p.CreatedAt || p.createdAt, timeFilter)) return;
|
if (!matchesTimeFilter(p.created_at || p.CreatedAt || p.createdAt, timeFilter)) return;
|
||||||
|
if (!matchesSearch(p, searchQuery)) return;
|
||||||
|
|
||||||
// add marker if missing
|
// add marker if missing
|
||||||
const id = getId(p);
|
const id = getId(p);
|
||||||
|
|
@ -253,12 +290,9 @@ export function usePostsEngine({
|
||||||
createMarkerForPost(p, mapRef, markersRef, expandedElRef, theme);
|
createMarkerForPost(p, mapRef, markersRef, expandedElRef, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisiblePosts((current) => {
|
setVisiblePosts((current) => [...current, p]);
|
||||||
const next = [...current, p];
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[mainFilter, subFilter, timeFilter, mapRef, markersRef, expandedElRef, theme]
|
[mainFilter, subFilter, timeFilter, searchQuery, mapRef, markersRef, expandedElRef, theme]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost };
|
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue