Merge pull request #2683 from omnivore-app/feat/web-highlight-colors

Add multiple highlight colours on web
This commit is contained in:
Jackson Harper 2023-08-24 16:38:59 +08:00 committed by GitHub
commit 85d53c6111
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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

View file

@ -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',

View 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>
)
}
}

View file

@ -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>
)
}

View file

@ -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',
},

View file

@ -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)

View file

@ -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'}
/>
</>
)}

View file

@ -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:

View file

@ -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),

View file

@ -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')

View file

@ -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)

View file

@ -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
}

View file

@ -15,6 +15,7 @@ export type CreateHighlightInput = {
suffix?: string
quote?: string
html?: string
color?: string
annotation?: string
patch?: string

View file

@ -40,6 +40,7 @@ export async function mergeHighlightMutation(
prefix
suffix
patch
color
createdAt
updatedAt
annotation

View file

@ -5,6 +5,7 @@ export type UpdateHighlightInput = {
highlightId: string
annotation?: string
sharedAt?: string
color?: string
}
type UpdateHighlightOutput = {

View file

@ -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)'
}

View file

@ -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 {