Skip to content

Instantly share code, notes, and snippets.

@edlaver
Created September 13, 2023 10:00
Show Gist options
  • Save edlaver/0ad518e1a5db5016de6f959e897621d2 to your computer and use it in GitHub Desktop.
Save edlaver/0ad518e1a5db5016de6f959e897621d2 to your computer and use it in GitHub Desktop.
Example service to fetch a Notion record map from a cf-notion-cache worker, and render it in a Polaris container
import React from "react";
import {
Layout,
LegacyCard,
Page,
SkeletonBodyText,
SkeletonDisplayText,
TextContainer,
} from "@shopify/polaris";
import { useQuery } from "@tanstack/react-query";
import { NotionRenderer } from "react-notion-x";
import "react-notion-x/src/styles.css";
import { extractNotionTocHtml } from "@/services/notionService";
//----------------------------------------------------------------//
// Setup some sort of loader to get the Notion record map...
// In this snippet, I'm using React Query, with a function called `notionQuery`
// e.g.
import { fetchNotionPageFullAndTocOmittedRecordMaps } from "@/services/notionService";
const notionQuery = () => ({
queryKey: ["notion"],
queryFn: async () => {
log.debug("Executing: notionQuery...");
// Your Notion page, e.g.
// const docsPageUrl = `https://yournotionsite.notion.site/Docs-4006f958c7a6426ca9f18390444f3111`; <-- Just need the ID part
const docsPageUrl = `4006f958c7a6426ca9f18390444f3111`;
// Load Notion RecordMap from Gadget API
return await fetchNotionPageFullAndTocOmittedRecordMaps(docsPageUrl);
},
});
//----------------------------------------------------------------//
function HelpView() {
const { data: notionData } = useQuery(notionQuery()); // <-- References loader from above, but you'll need to setup something different
const { fullRecordMap, tocOmittedRecordMap } = notionData;
return (
<Layout>
<Layout.Section secondary subdued>
<LegacyCard sectioned>
<div
className="notion-toc-container"
dangerouslySetInnerHTML={{
__html: extractNotionTocHtml(
<NotionRenderer
// Use the full recordMap (i.e. containing the TOC), so we can extract that HTML
recordMap={fullRecordMap}
// Set fullPage to false to render page content only
// this will remove the header, cover image, and footer
fullPage={false}
// TODO: Set based on user's prefs
darkMode={false}
// previewImages={false}
showTableOfContents={true}
/>
),
}}
></div>
</LegacyCard>
</Layout.Section>
<Layout.Section>
<LegacyCard sectioned>
<div>
<NotionRenderer
recordMap={tocOmittedRecordMap}
// Set fullPage to false to render page content only
// this will remove the header, cover image, and footer
fullPage={false}
// TODO: Set based on user's prefs
darkMode={false}
// previewImages={false}
//// Neither of these seem to do anything:
// showTableOfContents={false}
// minTableOfContentsItems={1000}
/>
</div>
</LegacyCard>
</Layout.Section>
</Layout>
);
}
import * as ReactDOMServer from "react-dom/server";
import ky from "ky";
import { cloneDeep, omitBy } from "lodash";
import { parsePageId } from "notion-utils";
// TODO: Move to .env file
const notionCacheBaseUrl = `https://cf-notion-cache.workers.dev/cache/`; // Update with your Cloudflare worker URL
const fetchNotionPageRecordMapByPageId = async (pageId) => {
let recordMap;
let notionCacheUrl = notionCacheBaseUrl + pageId;
if (process.env.NODE_ENV === "development") {
// Force a refresh of the cached version in development
notionCacheUrl += "?refetch=true";
log.debug({ notionCacheUrl });
}
recordMap = await ky.get(notionCacheUrl).json();
// log.debug({ recordMap });
return recordMap;
};
const fetchNotionPageRecordMap = async (pageUrlOrId) => {
const pageId = parsePageId(pageUrlOrId);
const recordMap = await fetchNotionPageRecordMapByPageId(pageId);
return recordMap;
};
const fetchNotionPageFullAndTocOmittedRecordMaps = async (pageUrlOrId) => {
const recordMap = await fetchNotionPageRecordMap(pageUrlOrId);
// log.debug("fetchNotionHelpPageData: recordMap", recordMap); //=> a JSON blob returned by the global action
// Extract table of contents into separate record map
const fullRecordMap = cloneDeep(recordMap);
const tocOmittedRecordMap = cloneDeep(recordMap);
// Exclude table of contents section
tocOmittedRecordMap.block = omitBy(tocOmittedRecordMap.block, (block) => {
return block?.value?.type === "table_of_contents";
});
// TODO: Try to use: notion-utils/src/get-page-table-of-contents.ts
// to get the table of contents structure, and then just render it ourselves?
// log.debug("fetchNotionHelpPageData: returning: ", fullRecordMap, prunedRecordMap);
return { fullRecordMap, tocOmittedRecordMap };
};
// Function to render the TOC from the Notion RecordMap as an HTML string
// Note: Uses browsers DOM Parser, so can only work client side._online_store_editor_live_setting
// TODO: Extract to a .client file if migrated to Remix.
const extractNotionTocHtml = (renderedTocRecordMapElement) => {
// Render the RecordMap element to HTML string
const renderedTocRecordMapStr = ReactDOMServer.renderToStaticMarkup(
renderedTocRecordMapElement
);
// Parse the HTML string so we can get the specific elements using a className selector
const domParser = new DOMParser();
const parsedRenderedTocRecordMap = domParser.parseFromString(
renderedTocRecordMapStr,
"text/html"
);
// Find the specific TOC element using a className selector
const tocElementsCollection =
parsedRenderedTocRecordMap.getElementsByClassName(
"notion-table-of-contents"
);
const tocElement = tocElementsCollection.item(0);
return tocElement?.outerHTML;
};
export {
fetchNotionPageRecordMapByPageId,
fetchNotionPageRecordMap,
fetchNotionPageFullAndTocOmittedRecordMaps,
extractNotionTocHtml,
};
@edlaver
Copy link
Author

edlaver commented Sep 13, 2023

To be used in conjunction with the cf-notion-cache worker at: https://github.com/edlaver/cf-notion-cache

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment