Merge pull request #2414 from 340rahul/2267_feature_copyButtonHighlights

Add copy button to highlight bar
This commit is contained in:
Jackson Harper 2023-06-23 08:40:59 +08:00 committed by GitHub
commit c2e2150f70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 138 deletions

View file

@ -6,7 +6,7 @@ import { StyledText } from '../elements/StyledText'
import { Button } from '../elements/Button'
import { HStack, Box } from '../elements/LayoutPrimitives'
import { PenWithColorIcon } from '../elements/images/PenWithColorIcon'
import { Note, Tag, Trash } from 'phosphor-react'
import { Note, Tag, Trash, Copy } from 'phosphor-react'
type PageCoordinates = {
pageX: number
@ -21,6 +21,7 @@ export type HighlightAction =
| 'post'
| 'unshare'
| 'setHighlightLabels'
| 'copy'
type HighlightBarProps = {
anchorCoordinates: PageCoordinates
@ -31,50 +32,75 @@ type HighlightBarProps = {
}
export function HighlightBar(props: HighlightBarProps): JSX.Element {
if (props.displayAtBottom) {
return (
<Box
css={{
width: '100%',
maxWidth: props.isNewHighlight ? '280px' : '330px',
height: '48px',
position: 'fixed',
background: '$grayBg',
borderRadius: '4px',
border: '1px solid $grayBorder',
boxShadow: theme.shadows.cardBoxShadow.toString(),
return (
<Box
css={{
width: '100%',
maxWidth: props.isNewHighlight ? '330px' : '380px',
height: '48px',
position: props.displayAtBottom ? 'fixed' : 'absolute',
background: '$grayBg',
borderRadius: '4px',
border: '1px solid $grayBorder',
boxShadow: theme.shadows.cardBoxShadow.toString(),
...(props.displayAtBottom && {
bottom: 'calc(38px + env(safe-area-inset-bottom, 40px))',
}),
...(props.displayAtBottom && {
'@smDown': {
maxWidth: '85%',
bottom: `calc(28px + ${
isAndroid() ? 30 : 0
}px + env(safe-area-inset-bottom, 40px))`,
},
}}
>
<BarContent {...props} />
</Box>
)
} else {
return (
<Box
css={{
width: '100%',
maxWidth: props.isNewHighlight ? '280px' : '330px',
height: '48px',
position: 'absolute',
background: '$grayBg',
borderRadius: '4px',
border: '1px solid $grayBorder',
boxShadow: theme.shadows.cardBoxShadow.toString(),
left: props.anchorCoordinates.pageX,
top: props.anchorCoordinates.pageY,
}}
>
<BarContent {...props} />
</Box>
)
}
}),
...(!props.displayAtBottom && { left: props.anchorCoordinates.pageX }),
...(!props.displayAtBottom && { top: props.anchorCoordinates.pageY }),
}}
>
<BarContent {...props} />
</Box>
)
}
type BarButtonProps = {
title: string
onClick: VoidFunction
iconElement: JSX.Element
text: string
}
function BarButton({ text, title, iconElement, onClick }: BarButtonProps) {
return (
<Button
style="plainIcon"
title={title}
onClick={onClick}
css={{
flexDirection: 'column',
height: '100%',
m: 0,
p: 0,
alignItems: 'baseline',
}}
>
<HStack css={{ height: '100%', alignItems: 'center' }}>
{iconElement}
<StyledText
style="body"
css={{
pl: '12px',
m: '0px',
color: '$readerFont',
fontWeight: '400',
fontSize: '16px',
}}
>
{text}
</StyledText>
</HStack>
</Button>
)
}
function BarContent(props: HighlightBarProps): JSX.Element {
@ -96,116 +122,51 @@ function BarContent(props: HighlightBarProps): JSX.Element {
}}
>
{props.isNewHighlight ? (
<Button
style="plainIcon"
<BarButton
text="Highlight"
title="Create Highlight"
iconElement={<PenWithColorIcon />}
onClick={() => props.handleButtonClick('create')}
css={{
flexDirection: 'column',
height: '100%',
m: 0,
p: 0,
alignItems: 'baseline',
}}
>
<HStack css={{ height: '100%', alignItems: 'center' }}>
<PenWithColorIcon />
<StyledText
style="body"
css={{
pl: '12px',
m: '0px',
color: '$readerFont',
fontWeight: '400',
fontSize: '16px',
}}
>
Highlight
</StyledText>
</HStack>
</Button>
/>
) : (
<>
<Button
style="plainIcon"
<BarButton
text="Delete"
title="Remove Highlight"
onClick={() => props.handleButtonClick('delete')}
css={{ color: '$readerFont', height: '100%', m: 0, p: 0 }}
>
<HStack css={{ height: '100%', alignItems: 'center' }}>
iconElement={
<Trash size={24} color={theme.colors.omnivoreRed.toString()} />
<StyledText
style="body"
css={{
pl: '12px',
m: '0px',
color: '$readerFont',
fontWeight: '400',
fontSize: '16px',
}}
>
Delete
</StyledText>
</HStack>
</Button>
}
onClick={() => props.handleButtonClick('delete')}
/>
<Separator />
<Button
style="plainIcon"
<BarButton
text="Labels"
title="Set Labels"
onClick={() => props.handleButtonClick('setHighlightLabels')}
css={{ color: '$readerFont', height: '100%', m: 0, p: 0 }}
>
<HStack css={{ height: '100%', alignItems: 'center' }}>
iconElement={
<Tag size={24} color={theme.colors.readerFont.toString()} />
<StyledText
style="body"
css={{
pl: '12px',
m: '0px',
color: '$readerFont',
fontWeight: '400',
fontSize: '16px',
}}
>
Labels
</StyledText>
</HStack>
</Button>
}
onClick={() => props.handleButtonClick('setHighlightLabels')}
/>
</>
)}
<Separator />
<Button
style="plainIcon"
<BarButton
text="Note"
title="Add Note to Highlight"
onClick={() => props.handleButtonClick('comment')}
css={{ color: '$readerFont', height: '100%', m: 0, p: 0 }}
>
<HStack css={{ height: '100%', alignItems: 'center' }}>
iconElement={
<Note size={24} color={theme.colors.readerFont.toString()} />
<StyledText
style="body"
css={{
pl: '12px',
m: '0px',
color: '$readerFont',
fontWeight: '400',
fontSize: '16px',
}}
>
Note
</StyledText>
</HStack>
</Button>
{/* <Separator />
<Button
style="plainIcon"
title="Share Highlight"
onClick={() => props.handleButtonClick('share')}
css={{ color: '$readerFont', height: '100%', m: 0, p: 0, pt: '6px' }}
>
<ShareIcon size={28} strokeColor={theme.colors.readerFont.toString()} isCompleted={false} />
</Button> */}
}
onClick={() => props.handleButtonClick('comment')}
/>
<Separator />
<BarButton
text="Copy"
title="Copy Text to Clipboard"
iconElement={
<Copy size={24} color={theme.colors.readerFont.toString()} />
}
onClick={() => props.handleButtonClick('copy')}
/>
</HStack>
)
}

View file

@ -10,6 +10,7 @@ import type { HighlightLocation } from '../../../lib/highlights/highlightGenerat
import { useSelection } from '../../../lib/highlights/useSelection'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import {
getHighlightElements,
highlightIdAttribute,
highlightNoteIdAttribute,
SelectionAttributes,
@ -19,7 +20,7 @@ import { removeHighlights } from '../../../lib/highlights/deleteHighlight'
import { createHighlight } from '../../../lib/highlights/createHighlight'
import { HighlightNoteModal } from './HighlightNoteModal'
import { NotebookModal } from './NotebookModal'
import { showErrorToast } from '../../../lib/toastHelpers'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { ArticleMutations } from '../../../lib/articleActions'
import { isTouchScreenDevice } from '../../../lib/deviceType'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
@ -263,7 +264,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
}
const createHighlightCallback = useCallback(
async (successAction: HighlightModalAction, annotation?: string) => {
async (annotation?: string) => {
if (!selectionData) {
return
}
@ -395,7 +396,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
)
const handleCloseNotebook = useCallback(
(updatedHighlights: Highlight[], deletedHighlights: Highlight[]) => {
(updatedHighlights: Highlight[]) => {
props.setShowHighlightsModal(false)
// Remove all the existing highlights, then set the new ones
@ -454,6 +455,41 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
})
}
break
case 'copy': {
const selection = window.getSelection()
if (selection === null) return
const userSelectionText = selection.toString()
let textToCopy = ''
if (focusedHighlight) {
const highlightedElements = getHighlightElements(
focusedHighlight.id
)
highlightedElements.forEach(
(element) => (textToCopy += element.textContent)
)
} else if (userSelectionText) {
textToCopy = userSelectionText
}
if (textToCopy) {
try {
await navigator.clipboard.writeText(textToCopy)
showSuccessToast('Highlight copied', {
position: 'bottom-right',
})
} catch (error) {
showErrorToast('Error copying highlight, permission denied.', {
position: 'bottom-right',
})
}
}
selection.empty()
setSelectionData(null)
break
}
case 'setHighlightLabels':
if (props.isAppleAppEmbed) {
window?.webkit?.messageHandlers.highlightAction?.postMessage({
@ -474,6 +510,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
props.isAppleAppEmbed,
removeHighlightCallback,
selectionData,
setSelectionData,
]
)
@ -615,7 +652,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
dispatchHighlightMessage('noteCreated')
} else {
try {
await createHighlightCallback('none', event.annotation)
await createHighlightCallback('none')
dispatchHighlightMessage('noteCreated')
} catch (error) {
dispatchHighlightError('saveAnnotation', error)