Add ability to export highlights and notes from notebook

This commit is contained in:
Jackson Harper
2023-08-08 15:36:16 +08:00
parent dd139345b0
commit e8ac520f5a
5 changed files with 88 additions and 19 deletions

View File

@ -776,7 +776,11 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
}}
>
<>
<NotebookHeader setShowNotebook={props.setShowHighlightsModal} />
<NotebookHeader
viewer={props.viewer}
item={props.item}
setShowNotebook={props.setShowHighlightsModal}
/>
<NotebookContent
viewer={props.viewer}
item={props.item}

View File

@ -1,28 +1,41 @@
import { useCallback } from 'react'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import {
UserBasicData,
useGetViewerQuery,
} from '../../../lib/networking/queries/useGetViewerQuery'
import { CloseButton } from '../../elements/CloseButton'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { HStack } from '../../elements/LayoutPrimitives'
import { MenuTrigger } from '../../elements/MenuTrigger'
import { StyledText } from '../../elements/StyledText'
import { NotebookModal } from './NotebookModal'
import { Sidebar } from 'phosphor-react'
import { theme } from '../../tokens/stitches.config'
import { Button } from '../../elements/Button'
import { ExportIcon } from '../../elements/icons/ExportIcon'
import { useCallback, useMemo } from 'react'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { highlightsAsMarkdown } from '../homeFeed/HighlightItem'
import { showSuccessToast } from '../../../lib/toastHelpers'
type NotebookHeaderProps = {
viewer: UserBasicData
item: ReadableItem
setShowNotebook: (set: boolean) => 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 (
<HStack
@ -68,6 +81,19 @@ export const NotebookHeader = (props: NotebookHeaderProps) => {
title="Delete Article Note"
/>
</Dropdown> */}
<Button
style="plainIcon"
onClick={(event) => {
exportHighlights()
event.preventDefault()
}}
>
<ExportIcon
size={25}
color={theme.colors.thNotebookSubtle.toString()}
/>
</Button>
<Button style="plainIcon" onClick={() => props.setShowNotebook(false)}>
<Sidebar size={25} color={theme.colors.thNotebookSubtle.toString()} />
</Button>

View File

@ -34,7 +34,11 @@ export const NotebookPresenter = (props: NotebookPresenterProps) => {
}}
>
<>
<NotebookHeader setShowNotebook={props.setOpen} />
<NotebookHeader
viewer={props.viewer}
item={props.item}
setShowNotebook={props.setOpen}
/>
<NotebookContent
viewer={props.viewer}
item={props.item}

View File

@ -508,7 +508,11 @@ export default function PdfArticleContainer(
}}
>
<>
<NotebookHeader setShowNotebook={props.setShowHighlightsModal} />
<NotebookHeader
viewer={props.viewer}
item={props.article}
setShowNotebook={props.setShowHighlightsModal}
/>
<NotebookContent
viewer={props.viewer}
item={props.article}

View File

@ -13,6 +13,7 @@ import {
import { Box, VStack } from '../../elements/LayoutPrimitives'
import { styled, theme } from '../../tokens/stitches.config'
import { getHighlightLocation } from '../article/Notebook'
type HighlightsMenuProps = {
viewer: UserBasicData
@ -131,6 +132,36 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
)
}
const sortHighlights = (highlights: Highlight[]) => {
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