import { createPortal } from "react-dom"; import { useMemo } from "react"; import { useRoamCitekeys } from "Components/RoamCitekeysContext"; import { usePageMenuSettings, useRequestsSettings } from "Components/UserSettings"; import CitekeyMenu from "./CitekeyMenu"; import DNPMenu from "./DNPMenu"; import TagMenu from "./TagMenu"; import { useItems } from "@clients/zotero"; import { categorizeLibraryItems } from "../../../utils"; import { cleanRelatedItem } from "../helpers"; import { SCleanRelatedItem } from "Types/transforms"; import "./_index.sass"; function CitekeyMenuFactory({ menus }: { menus: Element[] }){ const [{ trigger }] = usePageMenuSettings(); const [{ dataRequests }] = useRequestsSettings(); const itemQueries = useItems(dataRequests, { select: (datastore) => datastore.data, notifyOnChangeProps: ["data"] }); const data = useMemo(() => itemQueries.map(q => q.data || []).flat(1), [itemQueries]); const itemList = useMemo(() => categorizeLibraryItems(data), [data]); const citekeyItems = useMemo(() => itemList.items.filter(it => it.has_citekey), [itemList]); const citekeyMenus = useMemo(() => { if (!citekeyItems) { return null; } else { return menus .filter(_menu => { if(trigger.constructor === Boolean){ return trigger; } else { // If trigger isn't a Boolean, set default behavior // i.e citekey menus are always shown return true; } }) .map(menu => { const item = citekeyItems.find(it => it.key == menu.getAttribute("data-citekey")); return { div: menu, item }; }) .map((menu, i) => { if (menu.item) { const { item, div } = menu; return ( createPortal(, div) ); } }); } }, [citekeyItems, itemList, menus, trigger]); return <>{citekeyMenus}; } function DNPMenuFactory({ menus }: { menus: Element[] }){ const [{ trigger }] = usePageMenuSettings(); const [{ dataRequests }] = useRequestsSettings(); const [roamCitekeys/*, updateCitekeys */] = useRoamCitekeys(); const itemQueries = useItems(dataRequests, { notifyOnChangeProps: ["data"], select: (datastore) => datastore.data }); const data = itemQueries.map(q => q.data || []).flat(1); const itemList = useMemo(() => categorizeLibraryItems(data), [data]); const dnpPortals = useMemo(() => { const { items, pdfs, notes } = itemList; if(!items){ return null; } else { return menus .filter(_menu => { if(typeof(trigger) == "boolean"){ return trigger; } else { // If trigger isn't a Boolean, set default behavior // i.e DNP menus are always shown return true; } }) .map(menu => { const title = menu.getAttribute("data-title")!; // e.g. [2023,5,6] is June 6th, 2023 // For the month to be parsed correctly, the array has to be spread - otherwise it will be off by 1 const date_as_array = JSON.parse(menu.getAttribute("data-dnp-date")!) as [number, number, number]; const dnp_date = new Date(...date_as_array).toDateString(); const added = items .filter(it => new Date(it.data.dateAdded).toDateString() == dnp_date) .map(it => cleanRelatedItem(it, { pdfs, notes }, roamCitekeys)); return { div: menu, added, title }; }) .filter(menu => menu.added) .map((menu, i) => { const { added, div, title } = menu; return ( createPortal(, div) ); }); } }, [itemList, menus, roamCitekeys, trigger]); return <>{dnpPortals}; } function TagMenuFactory({ menus }: { menus: Element[] }){ const [{ trigger }] = usePageMenuSettings(); const [{ dataRequests }] = useRequestsSettings(); const [roamCitekeys/*, updateCitekeys */] = useRoamCitekeys(); const itemQueries = useItems(dataRequests, { notifyOnChangeProps: ["data"], select: (datastore) => datastore.data }); const data = itemQueries.map(q => q.data || []).flat(1); const itemList = useMemo(() => categorizeLibraryItems(data), [data]); // Select to reduce dataset size : // - for tag matching, only top-level items that have any tags // - for abstract matching, only items that have an abstract const with_tags_or_abstract = useMemo(() => { return itemList.items .filter(it => it.data.abstractNote || it.data.tags.length > 0) .map(it => { return { itemData: it, abstract: it.data.abstractNote || "", tagList: it.data.tags.map(t => t.tag) }; }); }, [itemList.items]); const tagPortals = useMemo(() => { const { items, pdfs, notes } = itemList; if(!items){ return null; } else { return menus .filter(menu => { if(typeof(trigger) == "boolean"){ return trigger; } else { // If trigger isn't a Boolean, set default behavior // i.e page menus are shown only if the page title is > 5 characters const title = menu.getAttribute("data-title")!; return title.length > 5; } }) .map(menu => { const title = menu.getAttribute("data-title")!; const results = with_tags_or_abstract.reduce<{ with_tags: SCleanRelatedItem[], with_abstract: SCleanRelatedItem[] }>((obj, item) => { if(item.abstract.includes(title)){ obj.with_abstract.push(cleanRelatedItem(item.itemData, { pdfs, notes }, roamCitekeys)); } if(item.tagList.includes(title)){ obj.with_tags.push(cleanRelatedItem(item.itemData, { pdfs, notes }, roamCitekeys)); } return obj; }, { with_tags: [], with_abstract: [] }); return { div: menu, tag: title, ...results }; }) .filter(menu => menu.with_tags.length > 0 || menu.with_abstract.length > 0) .map((menu,i) => { const { with_tags, with_abstract, div, tag } = menu; return ( createPortal(, div) ); }); } }, [itemList, menus, roamCitekeys, trigger, with_tags_or_abstract]); return <>{tagPortals}; } export { CitekeyMenuFactory, DNPMenuFactory, TagMenuFactory };