diff --git a/packages/web/components/elements/icons/ExportIcon.tsx b/packages/web/components/elements/icons/ExportIcon.tsx new file mode 100644 index 000000000..97d775b86 --- /dev/null +++ b/packages/web/components/elements/icons/ExportIcon.tsx @@ -0,0 +1,46 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class ExportIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + + + ) + } +} diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 361f6c131..b258cbe88 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-children-prop */ -import { useMemo, useState } from 'react' +import { useState } from 'react' import type { Highlight } from '../../lib/networking/fragments/highlightFragment' import { LabelChip } from '../elements/LabelChip' import { @@ -14,7 +14,6 @@ import { HighlightViewNote } from './HighlightNotes' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import { isDarkTheme } from '../../lib/themeUpdater' -import { HighlightsMenu } from '../templates/homeFeed/HighlightItem' import { ReadableItem } from '../../lib/networking/queries/useGetLibraryItemsQuery' import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery' import { @@ -25,7 +24,6 @@ import { useHover, useInteractions, } from '@floating-ui/react' -import { LibraryHoverActions } from './LibraryCards/LibraryHoverActions' import { HighlightHoverActions } from './HighlightHoverActions' type HighlightViewProps = { @@ -54,7 +52,6 @@ const StyledQuote = styled(Blockquote, { export function HighlightView(props: HighlightViewProps): JSX.Element { const isDark = isDarkTheme() const [noteMode, setNoteMode] = useState<'preview' | 'edit'>('preview') - const [isHovered, setIsHovered] = useState(false) const [isOpen, setIsOpen] = useState(false) const { refs, floatingStyles, context } = useFloating({ @@ -73,7 +70,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { const hover = useHover(context) const { getReferenceProps, getFloatingProps } = useInteractions([hover]) - const highlightAlpha = isDark ? 1.0 : 0.35 + const highlightAlpha = isDark ? 0.5 : 0.35 return ( *': { + display: 'inline', + padding: '2px', backgroundColor: `rgba(var(--colors-highlightBackground), ${highlightAlpha})`, boxShadow: `3px 0 0 rgba(var(--colors-highlightBackground), ${highlightAlpha}), -3px 0 0 rgba(var(--colors-highlightBackground), ${highlightAlpha})`, boxDecorationBreak: 'clone', borderRadius: '2px', }, + '> ul': { + display: 'block', + boxShadow: 'unset', + backgroundColor: 'unset', + }, fontSize: '15px', lineHeight: 1.5, color: '$thTextSubtle2', 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 { }} > <> - + ) } - -type SectionTitleProps = { - title: string - selected: boolean - setSelected: (set: boolean) => void -} - -function SectionTitle(props: SectionTitleProps): JSX.Element { - return ( - <> - - - ) -} diff --git a/packages/web/components/templates/article/NotebookHeader.tsx b/packages/web/components/templates/article/NotebookHeader.tsx index 149e08a93..284bd8cb3 100644 --- a/packages/web/components/templates/article/NotebookHeader.tsx +++ b/packages/web/components/templates/article/NotebookHeader.tsx @@ -1,28 +1,47 @@ -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 { showErrorToast, 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 + ) + if (markdown.length > 1) { + ;(async () => { + await navigator.clipboard.writeText(markdown) + showSuccessToast('Highlights and notes copied') + })() + } else { + showSuccessToast('Nothing to export') + } + } else { + showErrorToast('Could not copy highlights') + } + }, [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 diff --git a/packages/web/components/tokens/stitches.config.ts b/packages/web/components/tokens/stitches.config.ts index 6e57310c7..74a105863 100644 --- a/packages/web/components/tokens/stitches.config.ts +++ b/packages/web/components/tokens/stitches.config.ts @@ -280,8 +280,8 @@ const darkThemeSpec = { thLibrarySelectionColor: '#3D3D3D', thNotebookSubtle: '#898989', - thNotebookBorder: '#898989', - thNotebookBackground: '#3B3938', + thNotebookBorder: '#3D3D3D', + thNotebookBackground: '#2F2F2F', thNotebookTextBackground: '#3D3D3D', thNotebookHighContrast: '#2A2A2A',