Add ability to export highlights and notes from notebook
This commit is contained in:
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user