/* ==========================================================================
BROWSE: /wissen (curated knowledge base) and /forum (community feed).
========================================================================== */
const PAGE = 24;
/** Shared fetching + pagination for both views. */
function usePosts(filters) {
const [state, setState] = useState({ items: [], total: 0, loading: true });
const [offset, setOffset] = useState(0);
const key = JSON.stringify(filters);
useEffect(() => { setOffset(0); }, [key]);
useEffect(() => {
let alive = true;
setState((s) => ({ ...s, loading: true }));
BB.get("/posts" + BB.qs(Object.assign({}, filters, { limit: PAGE, offset })))
.then((d) => {
if (!alive) return;
setState((s) => ({
items: offset === 0 ? d.items : s.items.concat(d.items),
total: d.total,
loading: false,
}));
})
.catch(() => { if (alive) setState({ items: [], total: 0, loading: false }); });
return () => { alive = false; };
}, [key, offset]);
const more = state.items.length < state.total;
return { ...state, more, loadMore: () => setOffset((o) => o + PAGE) };
}
/* --- category tree ------------------------------------------------------- */
function TreeNode({ cat, cats, active, onPick, depth }) {
const kids = BB.children(cats, cat.id);
const inPath = useMemo(
() => active === cat.id || BB.ancestors(cats, active).some((a) => a.id === cat.id),
[active, cat.id, cats]
);
const [open, setOpen] = useState(inPath);
useEffect(() => { if (inPath) setOpen(true); }, [inPath]);
return (
{open && kids.length > 0 && (
{kids.map((k) => (
))}
)}
);
}
function CategoryTree({ cats, active, onPick }) {
return (
);
}
/* --- /wissen ------------------------------------------------------------- */
function Wissen({ categories, params }) {
const { go } = useRouter();
const { user } = useAuth();
const cat = params.get("cat") || "";
const q = params.get("q") || "";
const onlyUnread = params.get("unread") === "1";
const [draft, setDraft] = useState(q);
useEffect(() => { setDraft(q); }, [q]);
const setParam = useCallback((patch, opts) => {
const next = new URLSearchParams(params);
Object.entries(patch).forEach(([k, v]) => (v ? next.set(k, v) : next.delete(k)));
const s = next.toString();
go("/wissen/alle" + (s ? "?" + s : ""), opts);
}, [go, params]);
// Die Liste folgt dem Tippen mit kurzer Pause, damit nicht jeder einzelne
// Tastendruck einen Request ausloest. Replace haelt die Browser-Historie bei
// einem Suchbegriff sauber, waehrend Kategoriewechsel weiter zurueckgehen.
useEffect(() => {
const wert = draft.trim();
if (wert === q) return undefined;
const timer = setTimeout(() => {
setParam({ q: wert }, { keepScroll: true, replace: true });
}, 250);
return () => clearTimeout(timer);
}, [draft, q, setParam]);
const { items, total, loading, more, loadMore } = usePosts({
collection: "wiki", category: cat, q, sort: q ? "new" : "title",
unread: onlyUnread ? 1 : "",
});
const current = BB.catById(categories, cat);
const trail = cat ? BB.ancestors(categories, cat) : [];
const forumTarget = current && current.forumOnly
? "/forum?kind=recipe" + (cat !== "rezepte" ? "&category=" + encodeURIComponent(cat) : "")
: "";
useEffect(() => {
if (forumTarget) go(forumTarget);
}, [forumTarget, go]);
// progress for the selected branch, or for the whole knowledge base
const progress = useMemo(() => {
const readableTotal = (entry) => BB.isAdmin(user)
? (entry.count || 0)
: (entry.readyCount !== undefined
? entry.readyCount
: (entry.publishedCount !== undefined ? entry.publishedCount : (entry.count || 0)));
if (current) return { read: current.readCount || 0, total: readableTotal(current) };
return BB.children(categories, null).filter((c) => !c.forumOnly).reduce(
(acc, c) => ({ read: acc.read + (c.readCount || 0), total: acc.total + readableTotal(c) }),
{ read: 0, total: 0 }
);
}, [categories, current, user]);
if (forumTarget) return
;
return (
{BB.t("Wissensbasis")}
{trail.map((a) => (
/
{a.label}
))}
{current ? current.label : BB.t("Alles Wissen")}
{current && current.blurb
? current.blurb
: BB.t("Redaktionell gepflegte Einträge: Mechanismus, Kennzahlen, Nebenwirkungen und Quellen.")}
{user && progress.total > 0 && (
)}
{user && (
setParam({ unread: v === "unread" ? "1" : "" })}
options={[{ value: "all", label: BB.t("Alle") }, { value: "unread", label: BB.t("Ungelesen") }]}
/>
)}
{BB.num(total)} {BB.t("Einträge")}
{loading && items.length === 0 ? (
) : items.length === 0 ? (
{BB.t("Für diese Kombination aus Kategorie und Suche gibt es keine Einträge.")}
) : (
)}
{more && (
)}
);
}
/* --- /forum -------------------------------------------------------------- */
const SORTS = [
{ value: "new", label: "Neu" },
{ value: "top", label: "Top" },
{ value: "discussed", label: "Diskutiert" },
];
function Forum({ params }) {
const { go } = useRouter();
const { user } = useAuth();
const kind = params.get("kind") || "";
const category = params.get("category") || "";
const tag = params.get("tag") || "";
const sort = params.get("sort") || "new";
const q = params.get("q") || "";
const [draft, setDraft] = useState(q);
useEffect(() => { setDraft(q); }, [q]);
const setParam = (patch) => {
const next = new URLSearchParams(params);
Object.entries(patch).forEach(([k, v]) => (v ? next.set(k, v) : next.delete(k)));
const s = next.toString();
go("/forum" + (s ? "?" + s : ""));
};
const { items, total, loading, more, loadMore } = usePosts({
collection: "forum", kind, category, tag, sort, q,
});
return (
{BB.t("Forum")}
{BB.t("Was die Community teilt")}
+ {BB.t("Beitrag schreiben")}
{BB.t("Artikel, Erfahrungsberichte, Stack-Protokolle, Rezepte und Transformationen aus Community und Redaktion.")}
setParam({ kind: v })}
options={[{ value: "", label: "Alle" }].concat(
BB.USER_KINDS.map((k) => ({ value: k, label: BB.KINDS[k].label }))
)}
/>
setParam({ sort: v })} options={SORTS} />
{tag && (
{BB.t("Gefiltert nach")}
)}
{loading && items.length === 0 ? (
) : items.length === 0 ? (
{BB.t("Ersten Beitrag schreiben")}
: {BB.t("Profil erstellen")}
}
>
{BB.t("Hier ist noch nichts. Mit deinem Beitrag fängt es an.")}
) : (
<>
{BB.num(total)} {BB.t("Beiträge")}
>
)}
{more && (
)}
);
}
Object.assign(window, { Wissen, Forum, CategoryTree, usePosts });