import { FC, memo, useCallback, useMemo } from "react"; import { Classes, Menu, MenuDivider, MenuItem, Tag, useHotkeys } from "@blueprintjs/core"; import DataDrawer from "Components/DataDrawer"; import { ErrorBoundary } from "Components/Errors"; import NotesDrawer from "Components/NotesDrawer"; import { useRoamCitekeys } from "Components/RoamCitekeysContext"; import { formatItemReferenceWithDefault } from "Components/SearchPanel/helpers"; import ShortcutSequence from "Components/ShortcutSequence"; import { useAnnotationsSettings, useCopySettings, useMetadataSettings, useNotesSettings, useShortcutsSettings, useTypemapSettings } from "Components/UserSettings"; import { useBool } from "@hooks"; import { importItemMetadata, importItemNotes, openPageByUID } from "@services/roam"; import { CustomClasses } from "../../constants"; import { validateShortcuts } from "../../setup"; import { copyToClipboard, makeDateFromAgo } from "../../utils"; import { AsBoolean } from "Types/helpers"; import { ZCleanItemTop } from "Types/transforms"; import "./_index.sass"; type ItemCopyFormat = "citation" | "citekey" | "page-reference" | "tag"; const popoverProps = { popoverClassName: CustomClasses.POPOVER }; /** Creates a formatted reference to an item */ const makeItemReference = (citekey: string, format: ItemCopyFormat, item: ZCleanItemTop) => { const pageRef = "[[@" + citekey + "]]"; switch(format){ case "page-reference": return pageRef; case "tag": return "#" + pageRef; case "citation": return "[" + item.authors + " (" + item.year + ")](" + pageRef + ")"; case "citekey": default: return "@" + citekey; } }; function CopyOption(props: CopyButtonsProps & { format: ItemCopyFormat }){ const { citekey, format, item } = props; const [shortcuts] = useShortcutsSettings(); // Only pass valid hotkey combos // TODO: move validation step upstream const sanitizedShortcuts = useMemo(() => validateShortcuts(shortcuts), [shortcuts]); const textOutput = useMemo(() => makeItemReference(citekey, format, item), [citekey, format, item]); const formatCitekey = useCallback(() => copyToClipboard(textOutput), [textOutput]); const label = useMemo(() => { const mapping = { "citation": { action: "copy as citation", command: "copyCitation" }, "citekey": { action: "copy as citekey", command: "copyCitekey" }, "page-reference": { action: "copy as page reference", command: "copyPageRef" }, "tag": { action: "copy as tag", command: "copyTag" } }; if(mapping[format]){ const { action, command } = mapping[format]; if(sanitizedShortcuts[command] !== ""){ return ; } } return null; }, [format, sanitizedShortcuts]); return ; } type CopyButtonsProps = { citekey: string, item: ZCleanItemTop }; function CopyButtons(props: CopyButtonsProps){ const { citekey, item } = props; const [copySettings] = useCopySettings(); const [shortcuts] = useShortcutsSettings(); // Only pass valid hotkey combos // TODO: move validation step upstream const sanitizedShortcuts = useMemo(() => validateShortcuts(shortcuts), [shortcuts]); const defaultCopyText = useMemo(() => { return formatItemReferenceWithDefault(item, copySettings); }, [copySettings, item]); const copyAsDefault = useCallback(() => { copyToClipboard(defaultCopyText); }, [defaultCopyText]); const optionsMenu = useMemo(() => { let standardOptions: ItemCopyFormat[] = ["citation", "citekey", "page-reference", "tag"]; if (copySettings.useAsDefault == "preset") { standardOptions = standardOptions.filter(op => op != copySettings.preset); } return <> Default} onClick={copyAsDefault} text={defaultCopyText} /> {standardOptions.map(op => )} ; }, [citekey, copyAsDefault, copySettings, defaultCopyText, item]); const label = useMemo(() => { const combo = sanitizedShortcuts.copyDefault || ""; return (combo !== "") ? : null; }, [sanitizedShortcuts]); const hotkeys = useMemo(() => { const defaultProps = { allowInInput: false, global: true, preventDefault: true, stopPropagation: true }; const configs = { "copyDefault": { label: "Copy the item in view (default format)", onKeyDown: () => copyAsDefault() }, "copyCitation": { label: "Copy the item in view (as citation)", onKeyDown: () => copyToClipboard(makeItemReference(citekey, "citation", item)) }, "copyCitekey": { label: "Copy the item in view (as citekey)", onKeyDown: () => copyToClipboard(makeItemReference(citekey, "citekey", item)) }, "copyPageRef": { label: "Copy the item in view (as page reference)", onKeyDown: () => copyToClipboard(makeItemReference(citekey, "page-reference", item)) }, "copyTag": { label: "Copy the item in view (as page reference)", onKeyDown: () => copyToClipboard(makeItemReference(citekey, "tag", item)) } }; return Object.keys(configs) .map(cmd => { const combo = sanitizedShortcuts[cmd] || ""; if(combo !== ""){ return { ...defaultProps, ...configs[cmd], combo }; } else { return false; } }).filter(AsBoolean); }, [citekey, copyAsDefault, item, sanitizedShortcuts]); useHotkeys(hotkeys, { showDialogKeyCombo: "shift+Z+R" }); return {optionsMenu} ; } type MetadataProps = { direction?: "col" | "row", label: string } const Metadata: FC = ({ direction = "row", label, children }) => { return
{label}
{children}
; }; type ItemDetailsProps = { closeDialog: () => void, item: ZCleanItemTop }; const ItemDetails = memo(function ItemDetails({ closeDialog, item }) { const { abstract, authors, authorsFull, authorsRoles, children, createdByUser, inGraph, key, publication, raw, tags, title, weblink, year, zotero } = item; const [isNotesDrawerOpen, { toggle: toggleNotes, on: showNotes, off: closeNotes }] = useBool(false); const [isDataDrawerOpen, { on: showData, off: closeData }] = useBool(false); const [annotationsSettings] = useAnnotationsSettings(); const [metadataSettings] = useMetadataSettings(); const [notesSettings] = useNotesSettings(); const [shortcuts] = useShortcutsSettings(); // Only pass valid hotkey combos // TODO: move validation step upstream const sanitizedShortcuts = useMemo(() => validateShortcuts(shortcuts), [shortcuts]); const [typemap] = useTypemapSettings(); const [, updateRoamCitekeys] = useRoamCitekeys(); const importMetadata = useCallback(async() => { const { pdfs = [], notes = [] } = children; const outcome = await importItemMetadata({ item: item.raw, pdfs, notes }, inGraph, metadataSettings, typemap, notesSettings, annotationsSettings); if(outcome.success){ updateRoamCitekeys(); } return outcome; }, [annotationsSettings, children, inGraph, item.raw, metadataSettings, notesSettings, typemap, updateRoamCitekeys]); const importNotes = useCallback(async () => { const outcome = await importItemNotes({ item: item.raw, notes: children.notes }, inGraph, notesSettings, annotationsSettings); if(outcome.success){ updateRoamCitekeys(); } return outcome; }, [annotationsSettings, children.notes, inGraph, item, notesSettings, updateRoamCitekeys]); const navigateToPage = useCallback(() => { if(inGraph != false){ openPageByUID(inGraph); closeDialog(); } }, [closeDialog, inGraph]); const goToPageButton = useMemo(() => { const combo = sanitizedShortcuts.goToItemPage || ""; const label = (combo !== "") ? : null; return inGraph ? : null; }, [inGraph, navigateToPage, sanitizedShortcuts]); const importMetadataButton = useMemo(() => { const combo = sanitizedShortcuts.importMetadata || ""; const label = (combo !== "") ? : null; return ; }, [importMetadata, sanitizedShortcuts]); const pdfs = useMemo(() => { if(children.pdfs.length == 0){ return null; } else { const firstElem = children.pdfs[0]; const libLoc = firstElem.library.type == "group" ? `groups/${firstElem.library.id}` : "library"; return children.pdfs.map(p => { const pdfHref = (["linked_file", "imported_file", "imported_url"].includes(p.data.linkMode)) ? `zotero://open-pdf/${libLoc}/items/${p.data.key}` : p.data.url; return ; }); } }, [children.pdfs]); const notes = useMemo(() => { if(children.notes.length == 0){ return null; } else { const combo = sanitizedShortcuts.toggleNotes || ""; const label = (combo !== "") ? : null; return <> ; } }, [children.notes, closeNotes, isNotesDrawerOpen, sanitizedShortcuts, showNotes]); // * The data drawer here could show additional metadata (formatted ; PDFs ; notes) const rawData = useMemo(() => { return <> ; }, [closeData, item.raw, isDataDrawerOpen, showData]); const hotkeys = useMemo(() => { const defaultProps = { allowInInput: false, global: true, preventDefault: true, stopPropagation: true }; const configs = { "goToItemPage": { disabled: !inGraph, label: "Go to the item's Roam page", onKeyDown: () => navigateToPage() }, "importMetadata": { label: "Import the item's metadata to Roam", onKeyDown: () => importMetadata() }, "toggleNotes": { disabled: children.notes.length == 0, label: "Show the item's linked notes", onKeyDown: () => toggleNotes() } }; return Object.keys(configs) .map(cmd => { const combo = sanitizedShortcuts[cmd] || ""; if(combo !== ""){ return { ...defaultProps, ...configs[cmd], combo }; } else { return false; } }).filter(AsBoolean); }, [children.notes, importMetadata, inGraph, navigateToPage, sanitizedShortcuts, toggleNotes]); useHotkeys(hotkeys, { showDialogKeyCombo: "shift+Z+R" }); return
{title}
{authors + " (" + year + ")"} {publication ? {publication} : null} {weblink ? {weblink.title} : null}

{abstract}

{makeDateFromAgo(raw.data.dateAdded)} {createdByUser ? by {createdByUser} : null} {authorsFull.length > 0 ? {authorsFull.map((aut, i) => {aut}{authorsRoles[i] == "author" ? "" : " (" + authorsRoles[i] + ")"})} : null} {tags.length > 0 ?
{tags.map((tag, i) => #{tag})}
: null}
{navigator.clipboard && } {goToPageButton} {importMetadataButton} {children.notes.length > 0 && } {rawData} {pdfs} {notes}
; }); export default ItemDetails;