mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #2683 from omnivore-app/feat/web-highlight-colors
Add multiple highlight colours on web
This commit is contained in:
commit
85d53c6111
18 changed files with 371 additions and 137 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -246,6 +246,18 @@ export const Button = styled('button', {
|
|||
},
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
highlightBarIcon: {
|
||||
p: '0px',
|
||||
lineHeight: '0px',
|
||||
bg: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
articleActionIcon: {
|
||||
bg: 'transparent',
|
||||
border: 'none',
|
||||
|
|
|
|||
39
packages/web/components/elements/icons/CopyIcon.tsx
Normal file
39
packages/web/components/elements/icons/CopyIcon.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* eslint-disable functional/no-class */
|
||||
/* eslint-disable functional/no-this-expression */
|
||||
import { IconProps } from './IconProps'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export class CopyIcon extends React.Component<IconProps> {
|
||||
render() {
|
||||
const size = (this.props.size || 26).toString()
|
||||
const color = (this.props.color || '#2A2A2A').toString()
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 26 26"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M8.66211 11.241C8.66211 10.6885 8.8816 10.1586 9.2723 9.76791C9.663 9.37721 10.1929 9.15771 10.7454 9.15771H19.0788C19.6313 9.15771 20.1612 9.37721 20.5519 9.76791C20.9426 10.1586 21.1621 10.6885 21.1621 11.241V19.5744C21.1621 20.1269 20.9426 20.6568 20.5519 21.0475C20.1612 21.4382 19.6313 21.6577 19.0788 21.6577H10.7454C10.1929 21.6577 9.663 21.4382 9.2723 21.0475C8.8816 20.6568 8.66211 20.1269 8.66211 19.5744V11.241Z"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.9941 9.15739V7.07406C16.9941 6.52152 16.7746 5.99162 16.3839 5.60092C15.9932 5.21022 15.4633 4.99072 14.9108 4.99072H6.57747C6.02494 4.99072 5.49504 5.21022 5.10433 5.60092C4.71363 5.99162 4.49414 6.52152 4.49414 7.07406V15.4074C4.49414 15.9599 4.71363 16.4898 5.10433 16.8805C5.49504 17.2712 6.02494 17.4907 6.57747 17.4907H8.66081"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,21 @@ 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, Copy } from 'phosphor-react'
|
||||
import {
|
||||
Note,
|
||||
Tag,
|
||||
Trash,
|
||||
Copy,
|
||||
Circle,
|
||||
CopySimple,
|
||||
CheckCircle,
|
||||
} from 'phosphor-react'
|
||||
import { TrashIcon } from '../elements/icons/TrashIcon'
|
||||
import { LabelIcon } from '../elements/icons/LabelIcon'
|
||||
import { NotebookIcon } from '../elements/icons/NotebookIcon'
|
||||
import { highlightColor, highlightColors } from '../../lib/themeUpdater'
|
||||
import { useState } from 'react'
|
||||
import { CopyIcon } from '../elements/icons/CopyIcon'
|
||||
|
||||
type PageCoordinates = {
|
||||
pageX: number
|
||||
|
|
@ -24,27 +36,29 @@ export type HighlightAction =
|
|||
| 'unshare'
|
||||
| 'setHighlightLabels'
|
||||
| 'copy'
|
||||
| 'updateColor'
|
||||
|
||||
type HighlightBarProps = {
|
||||
anchorCoordinates: PageCoordinates
|
||||
isNewHighlight: boolean
|
||||
isSharedToFeed: boolean
|
||||
displayAtBottom: boolean
|
||||
handleButtonClick: (action: HighlightAction) => void
|
||||
highlightColor?: string
|
||||
handleButtonClick: (action: HighlightAction, param?: string) => void
|
||||
}
|
||||
|
||||
export function HighlightBar(props: HighlightBarProps): JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
width: '100%',
|
||||
maxWidth: props.isNewHighlight ? '330px' : '380px',
|
||||
height: '48px',
|
||||
// width: '295px',
|
||||
// height: '50px',
|
||||
position: props.displayAtBottom ? 'fixed' : 'absolute',
|
||||
background: '$grayBg',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid $grayBorder',
|
||||
boxShadow: theme.shadows.cardBoxShadow.toString(),
|
||||
background: '$thBackground2',
|
||||
borderRadius: '5px',
|
||||
border: '1px solid $thHighlightBar',
|
||||
boxShadow: `0px 4px 4px 0px rgba(0, 0, 0, 0.15)`,
|
||||
|
||||
...(props.displayAtBottom && {
|
||||
bottom: 'calc(38px + env(safe-area-inset-bottom, 40px))',
|
||||
}),
|
||||
|
|
@ -65,113 +79,140 @@ export function HighlightBar(props: HighlightBarProps): JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
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: '4px',
|
||||
m: '0px',
|
||||
color: '$readerFont',
|
||||
fontWeight: '400',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</StyledText>
|
||||
</HStack>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
const Separator = styled('div', {
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
mx: '5px',
|
||||
background: '$thHighlightBar',
|
||||
})
|
||||
|
||||
function BarContent(props: HighlightBarProps): JSX.Element {
|
||||
const Separator = styled('div', {
|
||||
width: '1px',
|
||||
maxWidth: '1px',
|
||||
height: '100%',
|
||||
background: '$grayBorder',
|
||||
})
|
||||
const [hovered, setHovered] = useState<string | undefined>(undefined)
|
||||
const size = props.displayAtBottom ? 35 : 25
|
||||
|
||||
return (
|
||||
<HStack
|
||||
distribution="evenly"
|
||||
distribution="start"
|
||||
alignment="center"
|
||||
css={{
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
maxWidth: '100%',
|
||||
width: props.displayAtBottom ? '100%' : 'auto',
|
||||
padding: props.displayAtBottom ? '10px 15px' : '5px 10px',
|
||||
}}
|
||||
>
|
||||
{props.isNewHighlight ? (
|
||||
<BarButton
|
||||
text="Highlight"
|
||||
title="Create Highlight"
|
||||
iconElement={<PenWithColorIcon />}
|
||||
onClick={() => props.handleButtonClick('create')}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<BarButton
|
||||
text="Delete"
|
||||
title="Remove Highlight"
|
||||
iconElement={
|
||||
<TrashIcon
|
||||
size={20}
|
||||
color={theme.colors.omnivoreRed.toString()}
|
||||
{highlightColors.map((color) => {
|
||||
return (
|
||||
<Button
|
||||
key={`color-${color}`}
|
||||
style="highlightBarIcon"
|
||||
title={`Create Highlight (${color})`}
|
||||
onClick={() => {
|
||||
if (!props.isNewHighlight && props.highlightColor != color) {
|
||||
props.handleButtonClick('updateColor', color)
|
||||
} else if (
|
||||
!props.isNewHighlight &&
|
||||
props.highlightColor == color
|
||||
) {
|
||||
props.handleButtonClick('delete')
|
||||
} else {
|
||||
props.handleButtonClick('create', color)
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setHovered(color)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(undefined)
|
||||
}}
|
||||
>
|
||||
{props.isNewHighlight || props.highlightColor != color ? (
|
||||
<Circle
|
||||
key={color}
|
||||
width={size}
|
||||
height={size}
|
||||
color={highlightColor(color)}
|
||||
weight="fill"
|
||||
/>
|
||||
}
|
||||
onClick={() => props.handleButtonClick('delete')}
|
||||
/>
|
||||
<Separator />
|
||||
<BarButton
|
||||
text="Labels"
|
||||
title="Set Labels"
|
||||
iconElement={
|
||||
<LabelIcon size={20} color={theme.colors.readerFont.toString()} />
|
||||
}
|
||||
) : (
|
||||
<CheckCircle
|
||||
key={color}
|
||||
width={size}
|
||||
height={size}
|
||||
color={highlightColor(color)}
|
||||
weight="fill"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
<Separator />
|
||||
{!props.isNewHighlight && (
|
||||
<>
|
||||
<Button
|
||||
title={`Set Labels`}
|
||||
style="highlightBarIcon"
|
||||
onClick={() => props.handleButtonClick('setHighlightLabels')}
|
||||
/>
|
||||
onMouseEnter={() => {
|
||||
setHovered('labels')
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(undefined)
|
||||
}}
|
||||
>
|
||||
<LabelIcon
|
||||
size={size}
|
||||
color={
|
||||
hovered == 'labels'
|
||||
? theme.colors.thTextContrast.toString()
|
||||
: theme.colors.thHighlightBar.toString()
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Separator />
|
||||
<BarButton
|
||||
text="Note"
|
||||
title="Add Note to Highlight"
|
||||
iconElement={
|
||||
<Note size={20} color={theme.colors.readerFont.toString()} />
|
||||
}
|
||||
|
||||
<Button
|
||||
title={props.isNewHighlight ? `Create Highlight w/note` : 'Add Note'}
|
||||
style="highlightBarIcon"
|
||||
onClick={() => props.handleButtonClick('comment')}
|
||||
/>
|
||||
<Separator />
|
||||
<BarButton
|
||||
text="Copy"
|
||||
title="Copy Text to Clipboard"
|
||||
iconElement={
|
||||
<Copy size={20} color={theme.colors.readerFont.toString()} />
|
||||
}
|
||||
onMouseEnter={() => {
|
||||
setHovered('note')
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(undefined)
|
||||
}}
|
||||
>
|
||||
<NotebookIcon
|
||||
size={size}
|
||||
color={
|
||||
hovered == 'note'
|
||||
? theme.colors.thTextContrast.toString()
|
||||
: theme.colors.thHighlightBar.toString()
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
title={`Copy`}
|
||||
style="highlightBarIcon"
|
||||
onClick={() => props.handleButtonClick('copy')}
|
||||
/>
|
||||
onMouseEnter={() => {
|
||||
setHovered('copy')
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovered(undefined)
|
||||
}}
|
||||
>
|
||||
<CopyIcon
|
||||
size={size}
|
||||
color={
|
||||
hovered == 'copy'
|
||||
? theme.colors.thTextContrast.toString()
|
||||
: theme.colors.thHighlightBar.toString()
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ import { styled, theme } from '../tokens/stitches.config'
|
|||
import { HighlightViewNote } from './HighlightNotes'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { isDarkTheme } from '../../lib/themeUpdater'
|
||||
import {
|
||||
highlightColor,
|
||||
highlightColorVar,
|
||||
isDarkTheme,
|
||||
} from '../../lib/themeUpdater'
|
||||
import { ReadableItem } from '../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import {
|
||||
|
|
@ -70,7 +74,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
|
|||
const hover = useHover(context)
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover])
|
||||
const highlightAlpha = isDark ? 0.5 : 0.35
|
||||
const highlightColor = highlightColorVar(props.highlight.color)
|
||||
|
||||
return (
|
||||
<VStack
|
||||
|
|
@ -111,9 +115,8 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
|
|||
css={{
|
||||
'> *': {
|
||||
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})`,
|
||||
padding: '3px',
|
||||
backgroundColor: `rgba(${highlightColor}, var(--colors-highlightBackgroundAlpha))`,
|
||||
boxDecorationBreak: 'clone',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export function HighlightNoteModal(
|
|||
const result = await updateHighlightMutation({
|
||||
highlightId: props.highlight?.id,
|
||||
annotation: noteContent,
|
||||
color: props.highlight?.color,
|
||||
})
|
||||
|
||||
if (result) {
|
||||
|
|
@ -48,6 +49,7 @@ export function HighlightNoteModal(
|
|||
} else {
|
||||
showErrorToast('Error updating your note', { position: 'bottom-right' })
|
||||
}
|
||||
document.dispatchEvent(new Event('highlightsUpdated'))
|
||||
}
|
||||
if (!props.highlight && props.createHighlightForNote) {
|
||||
const result = await props.createHighlightForNote(noteContent)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import 'react-sliding-pane/dist/react-sliding-pane.css'
|
|||
import { NotebookContent } from './Notebook'
|
||||
import { NotebookHeader } from './NotebookHeader'
|
||||
import useGetWindowDimensions from '../../../lib/hooks/useGetWindowDimensions'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
|
||||
type HighlightsLayerProps = {
|
||||
viewer: UserBasicData
|
||||
|
|
@ -88,20 +89,26 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
undefined
|
||||
)
|
||||
|
||||
const [
|
||||
confirmDeleteHighlightWithNoteId,
|
||||
setConfirmDeleteHighlightWithNoteId,
|
||||
] = useState<string | undefined>(undefined)
|
||||
|
||||
const windowDimensions = useGetWindowDimensions()
|
||||
|
||||
const createHighlightFromSelection = useCallback(
|
||||
async (
|
||||
selection: SelectionAttributes,
|
||||
note?: string
|
||||
options: { annotation?: string; color?: string } | undefined
|
||||
): Promise<Highlight | undefined> => {
|
||||
const result = await createHighlight(
|
||||
{
|
||||
selection: selection,
|
||||
articleId: props.articleId,
|
||||
existingHighlights: highlights,
|
||||
color: options?.color,
|
||||
highlightStartEndOffsets: highlightLocations,
|
||||
annotation: note,
|
||||
annotation: options?.annotation,
|
||||
highlightPositionPercent: selectionPercentPos(selection.selection),
|
||||
highlightPositionAnchorIndex: selectionAnchorIndex(
|
||||
selection.selection
|
||||
|
|
@ -190,8 +197,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
setHighlights(highlights.filter(($0) => $0.id !== highlightId))
|
||||
setFocusedHighlight(undefined)
|
||||
document.dispatchEvent(new Event('highlightsUpdated'))
|
||||
showSuccessToast('Highlight removed')
|
||||
} else {
|
||||
console.error('Failed to delete highlight')
|
||||
showErrorToast('Error removing highlight')
|
||||
}
|
||||
},
|
||||
[focusedHighlight, highlights, highlightLocations, props.articleMutations]
|
||||
|
|
@ -206,6 +215,27 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
[highlights, highlightLocations]
|
||||
)
|
||||
|
||||
const updateHighlightColor = useCallback(
|
||||
(highlight: Highlight, color: string) => {
|
||||
const initial = highlight.color
|
||||
highlight.color = color
|
||||
updateHighlightsCallback(highlight)
|
||||
;(async () => {
|
||||
const update = await props.articleMutations.updateHighlightMutation({
|
||||
highlightId: highlight.id,
|
||||
color: color,
|
||||
})
|
||||
if (!update) {
|
||||
highlight.color = initial
|
||||
updateHighlightsCallback(highlight)
|
||||
showErrorToast('Error updating highlight color')
|
||||
}
|
||||
document.dispatchEvent(new Event('highlightsUpdated'))
|
||||
})()
|
||||
},
|
||||
[props, highlights, highlightLocations, updateHighlightsCallback]
|
||||
)
|
||||
|
||||
const openNoteModal = useCallback(
|
||||
(inputs: HighlightActionProps) => {
|
||||
// First try to send a signal to the ios app
|
||||
|
|
@ -227,10 +257,13 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
if (!inputs.selectionData) {
|
||||
return undefined
|
||||
}
|
||||
return await createHighlightFromSelection(inputs.selectionData, note)
|
||||
return await createHighlightFromSelection(inputs.selectionData, {
|
||||
annotation: note,
|
||||
})
|
||||
}
|
||||
setHighlightModalAction(inputs)
|
||||
}
|
||||
document.dispatchEvent(new Event('highlightsUpdated'))
|
||||
},
|
||||
[props.highlightBarDisabled, createHighlightFromSelection]
|
||||
)
|
||||
|
|
@ -271,14 +304,14 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
}
|
||||
|
||||
const createHighlightCallback = useCallback(
|
||||
async (annotation?: string) => {
|
||||
async (options: { annotation?: string; color?: string } | undefined) => {
|
||||
if (!selectionData) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const result = await createHighlightFromSelection(
|
||||
selectionData,
|
||||
annotation
|
||||
options
|
||||
)
|
||||
if (!result) {
|
||||
showErrorToast('Error saving highlight', { position: 'bottom-right' })
|
||||
|
|
@ -436,13 +469,26 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
}, [handleSingleClick, handleDoubleClick])
|
||||
|
||||
const handleAction = useCallback(
|
||||
async (action: HighlightAction) => {
|
||||
async (action: HighlightAction, param?: string) => {
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
await removeHighlightCallback()
|
||||
if (focusedHighlight?.annotation == undefined) {
|
||||
await removeHighlightCallback()
|
||||
} else {
|
||||
setConfirmDeleteHighlightWithNoteId(focusedHighlight?.id)
|
||||
}
|
||||
break
|
||||
case 'create':
|
||||
await createHighlightCallback()
|
||||
await createHighlightCallback({
|
||||
color: param,
|
||||
})
|
||||
break
|
||||
case 'updateColor':
|
||||
if (focusedHighlight && param) {
|
||||
updateHighlightColor(focusedHighlight, param)
|
||||
} else {
|
||||
showErrorToast('Error updating color')
|
||||
}
|
||||
break
|
||||
case 'comment':
|
||||
if (props.highlightBarDisabled || focusedHighlight) {
|
||||
|
|
@ -517,6 +563,8 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
removeHighlightCallback,
|
||||
selectionData,
|
||||
setSelectionData,
|
||||
updateHighlightColor,
|
||||
confirmDeleteHighlightWithNoteId,
|
||||
]
|
||||
)
|
||||
|
||||
|
|
@ -677,12 +725,15 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
dispatchHighlightMessage('noteCreated')
|
||||
} else {
|
||||
try {
|
||||
await createHighlightCallback(event.annotation)
|
||||
await createHighlightCallback({
|
||||
annotation: event.annotation,
|
||||
})
|
||||
dispatchHighlightMessage('noteCreated')
|
||||
} catch (error) {
|
||||
dispatchHighlightError('saveAnnotation', error)
|
||||
}
|
||||
}
|
||||
document.dispatchEvent(new Event('highlightsUpdated'))
|
||||
}
|
||||
|
||||
document.addEventListener('annotate', annotate)
|
||||
|
|
@ -751,6 +802,20 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
onOpenChange={() => setLabelsTarget(undefined)}
|
||||
/>
|
||||
)}
|
||||
{confirmDeleteHighlightWithNoteId && (
|
||||
<ConfirmationModal
|
||||
message="Are you sure you want to delete this highlight? The note associated with it will also be deleted."
|
||||
onAccept={() => {
|
||||
;(async () => {
|
||||
await removeHighlightCallback(confirmDeleteHighlightWithNoteId)
|
||||
setConfirmDeleteHighlightWithNoteId(undefined)
|
||||
})()
|
||||
}}
|
||||
onOpenChange={() => {
|
||||
setConfirmDeleteHighlightWithNoteId(undefined)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* // Display the button bar if we are not in the native app and there // is
|
||||
a focused highlight or selection data */}
|
||||
{!props.highlightBarDisabled && (focusedHighlight || selectionData) && (
|
||||
|
|
@ -761,6 +826,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
handleButtonClick={handleAction}
|
||||
isSharedToFeed={focusedHighlight?.sharedAt != undefined}
|
||||
displayAtBottom={isTouchScreenDevice()}
|
||||
highlightColor={focusedHighlight?.color ?? 'yellow'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,16 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
|||
thProgressFg: '#FFD234',
|
||||
|
||||
thHighContrast: '#3D3D3D',
|
||||
thHighlightBar: '#D9D9D9',
|
||||
|
||||
highlightBackgroundGreen: '85, 198, 137',
|
||||
highlightBackgroundBlue: '106, 177, 255',
|
||||
highlightBackgroundOrange: '254, 181, 109',
|
||||
highlightBackgroundYellow: '255, 210, 52',
|
||||
highlightBackgroundRed: '251, 154, 154',
|
||||
|
||||
highlightBackgroundAlpha: '0.2',
|
||||
highlightUnderlineAlpha: '1.0',
|
||||
},
|
||||
},
|
||||
media: {
|
||||
|
|
@ -298,6 +308,10 @@ const darkThemeSpec = {
|
|||
thProgressFg: '#FFD234',
|
||||
|
||||
thHighContrast: '#D9D9D9',
|
||||
|
||||
thHighlightBar: '#6A6968',
|
||||
highlightUnderlineAlpha: '0.5',
|
||||
highlightBackgroundAlpha: '0.35',
|
||||
},
|
||||
shadows: {
|
||||
cardBoxShadow:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type CreateHighlightInput = {
|
|||
selection: SelectionAttributes
|
||||
articleId: string
|
||||
annotation?: string
|
||||
color?: string
|
||||
existingHighlights: Highlight[]
|
||||
highlightStartEndOffsets: HighlightLocation[]
|
||||
highlightPositionPercent?: number
|
||||
|
|
@ -94,7 +95,8 @@ export async function createHighlight(
|
|||
const highlightAttributes = makeHighlightNodeAttributes(
|
||||
patch,
|
||||
id,
|
||||
annotations.length > 0
|
||||
annotations.length > 0,
|
||||
input.color
|
||||
)
|
||||
|
||||
const newHighlightAttributes = {
|
||||
|
|
@ -102,6 +104,7 @@ export async function createHighlight(
|
|||
shortId: nanoid(8),
|
||||
patch,
|
||||
|
||||
color: input.color,
|
||||
prefix: highlightAttributes.prefix,
|
||||
suffix: highlightAttributes.suffix,
|
||||
quote: htmlToMarkdown(container.innerHTML),
|
||||
|
|
|
|||
|
|
@ -68,9 +68,7 @@ function nodeAttributesFromHighlight(
|
|||
const id = highlight.id
|
||||
const withNote = !!highlight.annotation
|
||||
const tooltip = undefined
|
||||
const customColor = highlight.createdByMe
|
||||
? undefined
|
||||
: 'var(--colors-recommendedHighlightBackground)'
|
||||
const customColor = highlight.color
|
||||
|
||||
return makeHighlightNodeAttributes(patch, id, withNote, customColor, tooltip)
|
||||
}
|
||||
|
|
@ -142,15 +140,17 @@ export function makeHighlightNodeAttributes(
|
|||
}
|
||||
|
||||
const newHighlightSpan = document.createElement('span')
|
||||
newHighlightSpan.className = withNote
|
||||
? highlightWithNoteClassName
|
||||
: highlightClassname
|
||||
newHighlightSpan.className = highlightClassname
|
||||
|
||||
if (withNote) {
|
||||
newHighlightSpan.className = `${newHighlightSpan.className} ${highlightWithNoteClassName}`
|
||||
}
|
||||
|
||||
if (customColor) {
|
||||
newHighlightSpan.className = `${newHighlightSpan.className} highlight_${customColor}`
|
||||
}
|
||||
|
||||
newHighlightSpan.setAttribute(highlightIdAttribute, id)
|
||||
customColor &&
|
||||
newHighlightSpan.setAttribute(
|
||||
'style',
|
||||
`background-color: ${customColor} !important`
|
||||
)
|
||||
tooltip && newHighlightSpan.setAttribute('title', tooltip)
|
||||
newHighlightSpan.appendChild(newTextNode)
|
||||
lastElement = newHighlightSpan
|
||||
|
|
@ -162,7 +162,7 @@ export function makeHighlightNodeAttributes(
|
|||
if (withNote && lastElement) {
|
||||
lastElement.classList.add('last_element')
|
||||
|
||||
const svg = noteImage()
|
||||
const svg = noteImage(customColor)
|
||||
svg.setAttribute(highlightNoteIdAttribute, id)
|
||||
|
||||
const ctr = document.createElement('div')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { highlightColor, highlightColorVar } from '../themeUpdater'
|
||||
|
||||
export type SelectionAttributes = {
|
||||
selection: Selection
|
||||
range: Range
|
||||
|
|
@ -25,7 +27,7 @@ export function getHighlightNoteButton(highlightId: string): Element[] {
|
|||
)
|
||||
}
|
||||
|
||||
export function noteImage(): SVGSVGElement {
|
||||
export function noteImage(color: string | undefined): SVGSVGElement {
|
||||
const svgURI = 'http://www.w3.org/2000/svg'
|
||||
const svg = document.createElementNS(svgURI, 'svg')
|
||||
svg.setAttribute('viewBox', '0 0 14 14')
|
||||
|
|
@ -38,7 +40,7 @@ export function noteImage(): SVGSVGElement {
|
|||
'd',
|
||||
'M1 5.66602C1 3.7804 1 2.83759 1.58579 2.2518C2.17157 1.66602 3.11438 1.66602 5 1.66602H9C10.8856 1.66602 11.8284 1.66602 12.4142 2.2518C13 2.83759 13 3.7804 13 5.66602V7.66601C13 9.55163 13 10.4944 12.4142 11.0802C11.8284 11.666 10.8856 11.666 9 11.666H4.63014C4.49742 11.666 4.43106 11.666 4.36715 11.6701C3.92582 11.6984 3.50632 11.8722 3.17425 12.1642C3.12616 12.2065 3.07924 12.2534 2.98539 12.3473V12.3473C2.75446 12.5782 2.639 12.6937 2.55914 12.7475C1.96522 13.1481 1.15512 12.8125 1.01838 12.1093C1 12.0148 1 11.8515 1 11.5249V5.66602Z'
|
||||
)
|
||||
path.setAttribute('stroke', 'rgba(255, 210, 52, 0.8)')
|
||||
path.setAttribute('stroke', `rgba(${highlightColorVar(color)}, 0.8)`)
|
||||
path.setAttribute('stroke-width', '1.8')
|
||||
path.setAttribute('stroke-linejoin', 'round')
|
||||
svg.appendChild(path)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const highlightFragment = gql`
|
|||
prefix
|
||||
suffix
|
||||
patch
|
||||
color
|
||||
annotation
|
||||
createdByMe
|
||||
createdAt
|
||||
|
|
@ -41,6 +42,7 @@ export type Highlight = {
|
|||
updatedAt: string
|
||||
sharedAt: string
|
||||
labels?: Label[]
|
||||
color?: string
|
||||
highlightPositionPercent?: number
|
||||
highlightPositionAnchorIndex?: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type CreateHighlightInput = {
|
|||
suffix?: string
|
||||
quote?: string
|
||||
html?: string
|
||||
color?: string
|
||||
annotation?: string
|
||||
|
||||
patch?: string
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export async function mergeHighlightMutation(
|
|||
prefix
|
||||
suffix
|
||||
patch
|
||||
color
|
||||
createdAt
|
||||
updatedAt
|
||||
annotation
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export type UpdateHighlightInput = {
|
|||
highlightId: string
|
||||
annotation?: string
|
||||
sharedAt?: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
type UpdateHighlightOutput = {
|
||||
|
|
|
|||
|
|
@ -111,3 +111,37 @@ export function isDarkTheme(): boolean {
|
|||
currentTheme == 'Black'
|
||||
)
|
||||
}
|
||||
|
||||
export const highlightColors = ['yellow', 'red', 'green', 'blue']
|
||||
|
||||
export const highlightColor = (name: string | undefined) => {
|
||||
switch (name) {
|
||||
case 'green':
|
||||
return '#55C689'
|
||||
case 'blue':
|
||||
return '#6AB1FF'
|
||||
case 'yellow':
|
||||
return '#FFD234'
|
||||
case 'orange':
|
||||
return '#FEB56D'
|
||||
case 'red':
|
||||
return '#FB9A9A'
|
||||
}
|
||||
return '#FFD234'
|
||||
}
|
||||
|
||||
export const highlightColorVar = (name: string | undefined) => {
|
||||
switch (name) {
|
||||
case 'green':
|
||||
return 'var(--colors-highlightBackgroundGreen)'
|
||||
case 'blue':
|
||||
return 'var(--colors-highlightBackgroundBlue)'
|
||||
case 'yellow':
|
||||
return 'var(--colors-highlightBackgroundYellow)'
|
||||
case 'orange':
|
||||
return 'var(--colors-highlightBackgroundOrange)'
|
||||
case 'red':
|
||||
return 'var(--colors-highlightBackgroundRed)'
|
||||
}
|
||||
return 'var(--colors-highlightBackground)'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,25 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highlight_with_note {
|
||||
color: var(--colors-highlightText);
|
||||
background-color: rgba(var(--colors-highlightBackground), 0.35);
|
||||
border-bottom: 2px rgb(var(--colors-highlightBackground)) solid;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
.highlight_green {
|
||||
background-color: rgba(var(--colors-highlightBackgroundGreen), var(--colors-highlightBackgroundAlpha));
|
||||
border-bottom: 2px solid rgba(var(--colors-highlightBackgroundGreen), var(--colors-highlightUnderlineAlpha));
|
||||
}
|
||||
|
||||
.highlight_red {
|
||||
background-color: rgba(var(--colors-highlightBackgroundRed), var(--colors-highlightBackgroundAlpha));
|
||||
border-bottom: 2px solid rgba(var(--colors-highlightBackgroundRed), var(--colors-highlightUnderlineAlpha));
|
||||
}
|
||||
|
||||
.highlight_blue {
|
||||
background-color: rgba(var(--colors-highlightBackgroundBlue), var(--colors-highlightBackgroundAlpha));
|
||||
border-bottom: 2px solid rgba(var(--colors-highlightBackgroundBlue), var(--colors-highlightUnderlineAlpha));
|
||||
}
|
||||
|
||||
.highlight_yellow {
|
||||
background-color: rgba(var(--colors-highlightBackgroundYellow), var(--colors-highlightBackgroundAlpha));
|
||||
border-bottom: 2px solid rgba(var(--colors-highlightBackgroundYellow), var(--colors-highlightUnderlineAlpha));
|
||||
}
|
||||
|
||||
.article-inner-css {
|
||||
|
|
|
|||
Loading…
Reference in a new issue