import React, { createRef, useState, useEffect } from 'react'; import ReactMarkdown from 'react-markdown'; import { css } from 'glamor'; import remarkGfm from 'remark-gfm'; import q from 'loot-core/src/client/query-helpers'; import { useLiveQuery } from 'loot-core/src/client/query-hooks'; import { send } from 'loot-core/src/platform/client/fetch'; import CustomNotesPaper from '../icons/v2/CustomNotesPaper'; import { type CSSProperties, colors } from '../style'; import { remarkBreaks, sequentialNewlinesPlugin } from '../util/markdown'; import Button from './common/Button'; import Text from './common/Text'; import View from './common/View'; import { Tooltip, useTooltip } from './tooltips'; const remarkPlugins = [sequentialNewlinesPlugin, remarkGfm, remarkBreaks]; const markdownStyles = css({ display: 'block', maxWidth: 350, padding: 8, overflowWrap: 'break-word', '& p': { margin: 0, ':not(:first-child)': { marginTop: '0.25rem', }, }, '& ul, & ol': { listStylePosition: 'inside', margin: 0, paddingLeft: 0, }, '&>* ul, &>* ol': { marginLeft: '1.5rem', }, '& li>p': { display: 'contents', }, '& blockquote': { paddingLeft: '0.75rem', borderLeft: '3px solid ' + colors.p6, margin: 0, }, '& hr': { borderTop: 'none', borderLeft: 'none', borderRight: 'none', borderBottom: '1px solid ' + colors.p9, }, '& code': { backgroundColor: colors.p10, padding: '0.1rem 0.5rem', borderRadius: '0.25rem', }, '& pre': { padding: '0.5rem', backgroundColor: colors.p10, borderRadius: '0.5rem', margin: 0, ':not(:first-child)': { marginTop: '0.25rem', }, '& code': { background: 'inherit', padding: 0, borderRadius: 0, }, }, '& table, & th, & td': { border: '1px solid ' + colors.p9, }, '& table': { borderCollapse: 'collapse', wordBreak: 'break-word', }, '& td': { padding: '0.25rem 0.75rem', }, }); type NotesTooltipProps = { editable?: boolean; defaultNotes?: string; position?: string; onClose?: (notes: string) => void; }; function NotesTooltip({ editable, defaultNotes, position = 'bottom-left', onClose, }: NotesTooltipProps) { let [notes, setNotes] = useState<string>(defaultNotes); let inputRef = createRef<HTMLTextAreaElement>(); useEffect(() => { if (editable) { inputRef.current.focus(); } }, [inputRef, editable]); return ( <Tooltip position={position} onClose={() => onClose(notes)}> {editable ? ( <textarea ref={inputRef} {...css({ border: '1px solid ' + colors.border, padding: 7, minWidth: 350, minHeight: 120, outline: 'none', })} value={notes || ''} onChange={e => setNotes(e.target.value)} placeholder="Notes (markdown supported)" /> ) : ( <Text {...markdownStyles}> <ReactMarkdown remarkPlugins={remarkPlugins} linkTarget="_blank" children={notes} /> </Text> )} </Tooltip> ); } type NotesButtonProps = { id: string; width?: number; height?: number; defaultColor?: string; tooltipPosition?: string; style?: CSSProperties; }; export default function NotesButton({ id, width = 12, height = 12, defaultColor = colors.n8, tooltipPosition, style, }: NotesButtonProps) { let [hover, setHover] = useState(false); let tooltip = useTooltip(); let data = useLiveQuery(() => q('notes').filter({ id }).select('*'), [id]); let note = data && data.length > 0 ? data[0].note : null; let hasNotes = note && note !== ''; function onClose(notes) { send('notes-save', { id, note: notes }); tooltip.close(); } const [delayHandler, setDelayHandler] = useState(null); const handleMouseEnter = () => { setDelayHandler( setTimeout(() => { setHover(true); }, 300), ); }; const handleMouseLeave = () => { clearTimeout(delayHandler); setHover(false); }; // This account for both the tooltip hover, and editing tooltip const tooltipOpen = tooltip.isOpen || (hasNotes && hover); return ( <View style={{ flexShrink: 0 }} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > <Button type="bare" className={!hasNotes && !tooltipOpen ? 'hover-visible' : ''} style={{ color: defaultColor, ...style, ...(hasNotes && { display: 'flex !important' }), ...(tooltipOpen && { color: colors.n1 }), }} {...tooltip.getOpenEvents()} > <CustomNotesPaper style={{ width, height }} /> </Button> {tooltipOpen && ( <NotesTooltip editable={tooltip.isOpen} defaultNotes={note} position={tooltipPosition} onClose={onClose} /> )} </View> ); }