import { useEffect, useState } from "react"; import { useParams } from "react-router"; import { Link } from "react-router-dom"; import useSWR, { KeyedMutator } from "swr"; import { SWRInfiniteResponse } from "swr/infinite"; import { useScrollToTop, useLocale } from "../../hooks"; import { Button } from "../../ui/atoms/button"; import Container from "../../ui/atoms/container"; import { Loading } from "../../ui/atoms/loading"; import { camelWrap, charSlice, getCategoryByPathname } from "../../utils"; import { FrequentlyViewedItem, Item, useCollection, useItems } from "./api"; import NoteCard from "../../ui/molecules/notecards"; import { DocMetadata } from "../../../../libs/types/document"; import { Authors, LastModified } from "../../document/organisms/article-footer"; import { ArticleActions } from "../../ui/organisms/article-actions"; import { MDN_PLUS_TITLE } from "../../constants"; import "./collection.scss"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import { FrequentlyViewedCollection, useFrequentlyViewed, } from "./frequently-viewed"; import { useGleanClick } from "../../telemetry/glean-context"; import { PLUS_COLLECTIONS } from "../../telemetry/constants"; dayjs.extend(relativeTime); // "swr/infinite" doesn't export InfiniteKeyedMutator directly type InfiniteKeyedMutator = SWRInfiniteResponse< T extends (infer I)[] ? I : T >["mutate"]; export function CollectionComponent() { const { collectionId } = useParams(); const { data: collection, error: collectionError } = useCollection(collectionId); const { data: itemPages, error: itemError, isLoading: itemLoading, size, setSize, atEnd, mutate, } = useItems(collectionId); useScrollToTop(); const name = collection?.name === "Default" ? "Saved Articles" : collection?.name; document.title = `${name || "Collections"} | ${MDN_PLUS_TITLE}`; const description = collection?.name === "Default" ? "The default collection." : collection?.description; return collection ? (
← Back

{name}

{collection.article_count}{" "} {collection.article_count === 1 ? "article" : "articles"} {description &&

{description}

}
{itemPages ?.flat(1) .map((item) => ( )) || (itemLoading && )} {!atEnd && (
)}
) : (
← Back
{collectionError ? (

Error

{collectionError.message}

) : ( )}
); } export function FrequentlyViewedCollectionComponent() { let [size, setSize] = useState(0); let [atEnd, setAtEnd] = useState(false); let frequentlyViewed: FrequentlyViewedCollection = useFrequentlyViewed( 10, size, setAtEnd ); useScrollToTop(); return (
← Back

{frequentlyViewed.name}

{frequentlyViewed.article_count}{" "} {frequentlyViewed.article_count === 1 ? "article" : "articles"}

{frequentlyViewed.description}

{frequentlyViewed.items.map((item) => ( ))} {!atEnd && (
)}
); } function ItemComponent({ addNoteEnabled = true, item, mutate, }: { addNoteEnabled?: boolean; item: Item | FrequentlyViewedItem; mutate?: KeyedMutator | InfiniteKeyedMutator; }) { const [slicedNote, setSlicedNote] = useState(); const [note, setNote] = useState(); const locale = useLocale(); const gleanClick = useGleanClick(); useEffect(() => { const slicedNote = item.notes && charSlice(item.notes, 0, 180); setSlicedNote(slicedNote); setNote(slicedNote); }, [item.notes]); const breadcrumbs = item.parents .slice(0, -1) .map((parent) => camelWrap(parent.title)) .filter( // remove duplicated titles (title, index, titles) => title !== titles[index + 1] ); const openBookmarkMenu: React.MouseEventHandler = (e) => { const article = e.currentTarget.closest("article"); [ article?.querySelector(".article-actions-toggle"), article?.querySelector(".bookmark-button"), ].forEach((button) => { if (button instanceof HTMLElement) { if (getComputedStyle(button).display === "none") return; button.click(); } }); }; const { data: doc } = useSWR( `${item.url}/metadata.json`, async (url) => { const response = await fetch(url); if (!response.ok) { throw Error(response.statusText); } return (await response.json()) as DocMetadata; }, { revalidateIfStale: false, revalidateOnFocus: false, revalidateOnReconnect: false, } ); const category = getCategoryByPathname(item.url); return (

{camelWrap(item.title)}

{breadcrumbs.join(" > ")}
{doc && ( <>

{doc.summary}

)} {note ? (
{doc && ( )}

{note.trimEnd()}

{slicedNote !== item.notes && (note !== item.notes ? ( <> {"… "} ) : ( <> {" "} ))}
) : doc && addNoteEnabled ? ( ) : null}
); }