From e8ac520f5af227e4b9acf4b566ed414dda6d4ada Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 8 Aug 2023 15:36:16 +0800 Subject: [PATCH] Add ability to export highlights and notes from notebook --- .../templates/article/HighlightsLayer.tsx | 6 ++- .../templates/article/NotebookHeader.tsx | 54 ++++++++++++++----- .../templates/article/NotebookPresenter.tsx | 6 ++- .../templates/article/PdfArticleContainer.tsx | 6 ++- .../templates/homeFeed/HighlightItem.tsx | 35 +++++++++++- 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index 84372b1aa..95b07f762 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -776,7 +776,11 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { }} > <> - + void } export const NotebookHeader = (props: NotebookHeaderProps) => { - const handleClose = useCallback(() => { - props.setShowNotebook(false) - }, [props]) + const { articleData } = useGetArticleQuery({ + slug: props.item.slug, + username: props.viewer.profile.username, + includeFriendsHighlights: false, + }) + + const exportHighlights = useCallback(() => { + if (articleData?.article.article.highlights) { + const markdown = highlightsAsMarkdown( + articleData?.article.article.highlights + ) + ;(async () => { + await navigator.clipboard.writeText(markdown) + showSuccessToast('Highlights and notes copied') + })() + } + }, [articleData]) return ( { title="Delete Article Note" /> */} + + diff --git a/packages/web/components/templates/article/NotebookPresenter.tsx b/packages/web/components/templates/article/NotebookPresenter.tsx index ec81bb501..6626d6815 100644 --- a/packages/web/components/templates/article/NotebookPresenter.tsx +++ b/packages/web/components/templates/article/NotebookPresenter.tsx @@ -34,7 +34,11 @@ export const NotebookPresenter = (props: NotebookPresenterProps) => { }} > <> - + <> - + { + const sorted = (a: number, b: number) => { + if (a < b) { + return -1 + } + if (a > b) { + return 1 + } + return 0 + } + + return (highlights ?? []) + .filter((h) => h.type === 'HIGHLIGHT') + .sort((a: Highlight, b: Highlight) => { + if (a.highlightPositionPercent && b.highlightPositionPercent) { + return sorted(a.highlightPositionPercent, b.highlightPositionPercent) + } + // We do this in a try/catch because it might be an invalid diff + // With PDF it will definitely be an invalid diff. + try { + const aPos = getHighlightLocation(a.patch) + const bPos = getHighlightLocation(b.patch) + if (aPos && bPos) { + return sorted(aPos, bPos) + } + } catch {} + return a.createdAt.localeCompare(b.createdAt) + }) +} + export function highlightAsMarkdown(highlight: Highlight) { let buffer = `> ${highlight.quote}` if (highlight.annotation) { @@ -143,14 +174,14 @@ export function highlightAsMarkdown(highlight: Highlight) { export function highlightsAsMarkdown(highlights: Highlight[]) { const noteMD = highlights.find((h) => h.type == 'NOTE') - const highlightMD = highlights + const highlightMD = sortHighlights(highlights) .filter((h) => h.type == 'HIGHLIGHT') .map((highlight) => { return highlightAsMarkdown(highlight) }) .join('\n\n') - if (noteMD) { + if (noteMD?.annotation) { return `${noteMD.annotation}\n\n${highlightMD}` } return highlightMD