Skip to content
Snippets Groups Projects
NotesButton.tsx 4.89 KiB
Newer Older
  • Learn to ignore specific revisions
  • import React, { createRef, useState, useEffect } from 'react';
    
    import ReactMarkdown from 'react-markdown';
    
    import remarkGfm from 'remark-gfm';
    
    James Long's avatar
    James Long committed
    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';
    
    James Long's avatar
    James Long committed
    
    
    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({
    
    James Long's avatar
    James Long committed
      defaultNotes,
      position = 'bottom-left',
    
    }: NotesTooltipProps) {
      let [notes, setNotes] = useState<string>(defaultNotes);
      let inputRef = createRef<HTMLTextAreaElement>();
    
    James Long's avatar
    James Long committed
    
      useEffect(() => {
    
        if (editable) {
          inputRef.current.focus();
        }
      }, [inputRef, editable]);
    
    James Long's avatar
    James Long committed
    
      return (
        <Tooltip position={position} onClose={() => onClose(notes)}>
    
          {editable ? (
            <textarea
              ref={inputRef}
              {...css({
                border: '1px solid ' + colors.border,
                padding: 7,
    
                minWidth: 350,
    
              })}
              value={notes || ''}
              onChange={e => setNotes(e.target.value)}
    
              placeholder="Notes (markdown supported)"
    
            <Text {...markdownStyles}>
              <ReactMarkdown
                remarkPlugins={remarkPlugins}
                linkTarget="_blank"
                children={notes}
              />
    
    James Long's avatar
    James Long committed
        </Tooltip>
      );
    }
    
    
    type NotesButtonProps = {
      id: string;
      width?: number;
      height?: number;
      defaultColor?: string;
      tooltipPosition?: string;
      style?: CSSProperties;
    };
    
    James Long's avatar
    James Long committed
    export default function NotesButton({
      id,
      width = 12,
      height = 12,
      defaultColor = colors.n8,
      tooltipPosition,
    
    }: NotesButtonProps) {
    
      let [hover, setHover] = useState(false);
    
    James Long's avatar
    James Long committed
      let tooltip = useTooltip();
    
      let data = useLiveQuery(() => q('notes').filter({ id }).select('*'), [id]);
    
    James Long's avatar
    James Long committed
      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);
    
    
    James Long's avatar
    James Long committed
      return (
        <View
    
          onMouseEnter={handleMouseEnter}
          onMouseLeave={handleMouseLeave}
    
    James Long's avatar
    James Long committed
        >
          <Button
    
            className={!hasNotes && !tooltipOpen ? 'hover-visible' : ''}
    
            style={{
              color: defaultColor,
              ...style,
              ...(hasNotes && { display: 'flex !important' }),
              ...(tooltipOpen && { color: colors.n1 }),
            }}
    
    James Long's avatar
    James Long committed
            {...tooltip.getOpenEvents()}
          >
    
            <CustomNotesPaper style={{ width, height }} />
    
    James Long's avatar
    James Long committed
          </Button>
    
    James Long's avatar
    James Long committed
            <NotesTooltip
    
              editable={tooltip.isOpen}
    
    James Long's avatar
    James Long committed
              defaultNotes={note}
              position={tooltipPosition}
              onClose={onClose}
            />
          )}
        </View>
      );
    }