import * as React from 'react' import { ExtendedRecordMap } from 'notion-types' import { wrapNextImage, wrapNextLink } from './next' import { AssetWrapper } from './components/asset-wrapper' import { Header } from './components/header' import { MapPageUrlFn, MapImageUrlFn, SearchNotionFn, NotionComponents } from './types' import { defaultMapPageUrl, defaultMapImageUrl } from './utils' import { Checkbox as DefaultCheckbox } from './components/checkbox' export interface NotionContext { recordMap: ExtendedRecordMap components: NotionComponents mapPageUrl: MapPageUrlFn mapImageUrl: MapImageUrlFn searchNotion?: SearchNotionFn rootPageId?: string rootDomain?: string fullPage: boolean darkMode: boolean previewImages: boolean forceCustomImages: boolean showCollectionViewDropdown: boolean showTableOfContents: boolean minTableOfContentsItems: number linkTableTitleProperties: boolean defaultPageIcon?: string defaultPageCover?: string defaultPageCoverPosition?: number zoom: any } export interface PartialNotionContext { recordMap?: ExtendedRecordMap components?: Partial mapPageUrl?: MapPageUrlFn mapImageUrl?: MapImageUrlFn searchNotion?: SearchNotionFn rootPageId?: string rootDomain?: string fullPage?: boolean darkMode?: boolean previewImages?: boolean forceCustomImages?: boolean showCollectionViewDropdown?: boolean linkTableTitleProperties?: boolean showTableOfContents?: boolean minTableOfContentsItems?: number defaultPageIcon?: string defaultPageCover?: string defaultPageCoverPosition?: number zoom?: any } const DefaultLink: React.FC = (props) => ( ) const DefaultLinkMemo = React.memo(DefaultLink) const DefaultPageLink: React.FC = (props) => const DefaultPageLinkMemo = React.memo(DefaultPageLink) const DefaultEmbed = AssetWrapper const DefaultHeader = Header // eslint-disable-next-line @typescript-eslint/no-unused-vars export const dummyLink = ({ href, rel, target, title, ...rest }) => ( ) const dummyComponent = (name: string) => () => { console.warn( `Warning: using empty component "${name}" (you should override this in NotionRenderer.components)` ) return null } // TODO: should we use React.memo here? // https://reactjs.org/docs/react-api.html#reactmemo const dummyOverrideFn = (_: any, defaultValueFn: () => React.ReactNode) => defaultValueFn() const defaultComponents: NotionComponents = { Image: null, // disable custom images by default Link: DefaultLinkMemo, PageLink: DefaultPageLinkMemo, Checkbox: DefaultCheckbox, Callout: undefined, // use the built-in callout rendering by default Code: dummyComponent('Code'), Equation: dummyComponent('Equation'), Collection: dummyComponent('Collection'), Property: undefined, // use the built-in property rendering by default propertyTextValue: dummyOverrideFn, propertySelectValue: dummyOverrideFn, propertyRelationValue: dummyOverrideFn, propertyFormulaValue: dummyOverrideFn, propertyTitleValue: dummyOverrideFn, propertyPersonValue: dummyOverrideFn, propertyFileValue: dummyOverrideFn, propertyCheckboxValue: dummyOverrideFn, propertyUrlValue: dummyOverrideFn, propertyEmailValue: dummyOverrideFn, propertyPhoneNumberValue: dummyOverrideFn, propertyNumberValue: dummyOverrideFn, propertyLastEditedTimeValue: dummyOverrideFn, propertyCreatedTimeValue: dummyOverrideFn, propertyDateValue: dummyOverrideFn, Pdf: dummyComponent('Pdf'), Tweet: dummyComponent('Tweet'), Modal: dummyComponent('Modal'), Header: DefaultHeader, Embed: DefaultEmbed } const defaultNotionContext: NotionContext = { recordMap: { block: {}, collection: {}, collection_view: {}, collection_query: {}, notion_user: {}, signed_urls: {} }, components: defaultComponents, mapPageUrl: defaultMapPageUrl(), mapImageUrl: defaultMapImageUrl, searchNotion: null, fullPage: false, darkMode: false, previewImages: false, forceCustomImages: false, showCollectionViewDropdown: true, linkTableTitleProperties: true, showTableOfContents: false, minTableOfContentsItems: 3, defaultPageIcon: null, defaultPageCover: null, defaultPageCoverPosition: 0.5, zoom: null } const ctx = React.createContext(defaultNotionContext) export const NotionContextProvider: React.FC = ({ components: themeComponents = {}, children, mapPageUrl, mapImageUrl, rootPageId, ...rest }) => { for (const key of Object.keys(rest)) { if (rest[key] === undefined) { delete rest[key] } } const wrappedThemeComponents = React.useMemo( () => ({ ...themeComponents }), [themeComponents] ) if (wrappedThemeComponents.nextImage) { wrappedThemeComponents.Image = wrapNextImage(themeComponents.nextImage) } if (wrappedThemeComponents.nextLink) { wrappedThemeComponents.nextLink = wrapNextLink(themeComponents.nextLink) } // ensure the user can't override default components with falsy values // since it would result in very difficult-to-debug react errors for (const key of Object.keys(wrappedThemeComponents)) { if (!wrappedThemeComponents[key]) { delete wrappedThemeComponents[key] } } const value = React.useMemo( () => ({ ...defaultNotionContext, ...rest, rootPageId, mapPageUrl: mapPageUrl ?? defaultMapPageUrl(rootPageId), mapImageUrl: mapImageUrl ?? defaultMapImageUrl, components: { ...defaultComponents, ...wrappedThemeComponents } }), [mapImageUrl, mapPageUrl, wrappedThemeComponents, rootPageId, rest] ) return {children} } export const NotionContextConsumer = ctx.Consumer export const useNotionContext = (): NotionContext => { return React.useContext(ctx) }