all repos — snow-editor @ main

small and cozy markdown, and orgmode editor

src/components/DraftsMenu.jsx (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
import { useCallback, useEffect, useRef, useState } from 'react';
import { STR, formatVersionDate } from '../lib/strings.js';
import IconButton from './IconButton.jsx';
import { DraftsIcon } from './icons/index.js';

export default function DraftsMenu({
  currentDraftId,
  onListDrafts,
  onSelectDraft,
  onCreateDraft,
  onDeleteDraft,
}) {
  const [open, setOpen] = useState(false);
  const [drafts, setDrafts] = useState([]);
  const rootRef = useRef(null);

  const refresh = useCallback(() => {
    setDrafts(onListDrafts());
  }, [onListDrafts]);

  const toggle = useCallback(() => {
    setOpen((was) => {
      if (!was) refresh();
      return !was;
    });
  }, [refresh]);

  useEffect(() => {
    if (!open) return undefined;

    const onPointerDown = (event) => {
      if (rootRef.current && !rootRef.current.contains(event.target)) {
        setOpen(false);
      }
    };
    const onKeyDown = (event) => {
      if (event.key === 'Escape') setOpen(false);
    };

    document.addEventListener('pointerdown', onPointerDown);
    document.addEventListener('keydown', onKeyDown);
    return () => {
      document.removeEventListener('pointerdown', onPointerDown);
      document.removeEventListener('keydown', onKeyDown);
    };
  }, [open]);

  const handleSelect = (id) => {
    setOpen(false);
    if (id !== currentDraftId) onSelectDraft(id);
  };

  const handleCreate = () => {
    setOpen(false);
    onCreateDraft();
  };

  const handleDelete = (event, draft) => {
    event.stopPropagation();
    const confirmed = window.confirm(
      STR.DELETE_DRAFT_CONFIRM(draft.title || STR.UNTITLED_DOCUMENT),
    );
    if (!confirmed) return;
    onDeleteDraft(draft.id);
    refresh();
  };

  return (
    <div className="drafts-menu" ref={rootRef}>
      <IconButton
        icon={<DraftsIcon />}
        label={STR.DRAFTS}
        aria-expanded={open}
        aria-haspopup="menu"
        onClick={toggle}
      />
      {open && (
        <div className="drafts-menu__popover" role="menu" aria-label={STR.DRAFTS}>
          <button
            type="button"
            className="drafts-menu__new"
            role="menuitem"
            onClick={handleCreate}
          >
            + {STR.NEW_DRAFT}
          </button>
          <ul className="drafts-menu__list">
            {drafts.map((draft) => (
              <li key={draft.id} className="drafts-menu__item">
                <button
                  type="button"
                  role="menuitem"
                  className={`drafts-menu__entry${draft.id === currentDraftId ? ' is-current' : ''}`}
                  onClick={() => handleSelect(draft.id)}
                >
                  <span className="drafts-menu__entry-title">
                    {draft.title || STR.UNTITLED_DOCUMENT}
                  </span>
                  <span className="drafts-menu__entry-meta">
                    {draft.mode === 'org' ? 'Org' : 'Markdown'} ·{' '}
                    {formatVersionDate(draft.updatedAt)}
                  </span>
                </button>
                {drafts.length > 1 && (
                  <button
                    type="button"
                    className="drafts-menu__delete"
                    aria-label={`${STR.DELETE_DRAFT}: ${draft.title || STR.UNTITLED_DOCUMENT}`}
                    title={STR.DELETE_DRAFT}
                    onClick={(event) => handleDelete(event, draft)}
                  >
                    ×
                  </button>
                )}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}