all repos — snow-editor @ 13d855e25e8510a57608b0ce77f62cbeed372d0e

small and cozy markdown, and orgmode editor

src/components/VersionHistory.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
 123
 124
 125
 126
import { useCallback, useEffect, useState } from 'react';
import {
  fetchDocumentVersions,
  friendlyErrorMessage,
  restoreDocumentVersion,
} from '../lib/api.js';
import { STR, formatVersionDate } from '../lib/strings.js';

export default function VersionHistory({
  editToken,
  clientId,
  lockToken,
  onRestored,
}) {
  const [open, setOpen] = useState(false);
  const [versions, setVersions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [restoringId, setRestoringId] = useState('');
  const [error, setError] = useState('');

  const loadVersions = useCallback(async () => {
    setLoading(true);
    setError('');
    try {
      const data = await fetchDocumentVersions(editToken, { clientId, lockToken });
      setVersions(data.versions ?? []);
    } catch (err) {
      setError(friendlyErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }, [editToken, clientId, lockToken]);

  useEffect(() => {
    if (!open) return;
    loadVersions();
  }, [open, loadVersions]);

  const handleRestore = async (versionId) => {
    const confirmed = window.confirm(STR.VERSION_RESTORE_CONFIRM);
    if (!confirmed) return;

    setRestoringId(versionId);
    setError('');
    try {
      const data = await restoreDocumentVersion(editToken, versionId, {
        clientId,
        lockToken,
      });
      onRestored(data);
      await loadVersions();
    } catch (err) {
      setError(friendlyErrorMessage(err));
    } finally {
      setRestoringId('');
    }
  };

  return (
    <>
      <button
        type="button"
        className="btn btn-ghost"
        onClick={() => setOpen(true)}
      >
        {STR.VERSION_HISTORY}
      </button>

      {open && (
        <div className="modal-backdrop" role="presentation" onClick={() => setOpen(false)}>
          <div
            className="modal share-panel version-panel"
            role="dialog"
            aria-labelledby="version-history-title"
            aria-modal="true"
            onClick={(e) => e.stopPropagation()}
          >
            <h2 id="version-history-title" className="modal__title">
              {STR.VERSION_HISTORY}
            </h2>

            {loading && <p className="version-panel__status">{STR.VERSION_LOADING}</p>}

            {!loading && versions.length === 0 && (
              <p className="version-panel__status">{STR.VERSION_EMPTY}</p>
            )}

            {!loading && versions.length > 0 && (
              <ul className="version-list">
                {versions.map((version) => (
                  <li key={version.id} className="version-list__item">
                    <span className="version-list__date">
                      {formatVersionDate(version.createdAt)}
                    </span>
                    <button
                      type="button"
                      className="btn btn-ghost version-list__restore"
                      disabled={restoringId === version.id}
                      onClick={() => handleRestore(version.id)}
                    >
                      {restoringId === version.id
                        ? STR.VERSION_RESTORING
                        : STR.VERSION_RESTORE}
                    </button>
                  </li>
                ))}
              </ul>
            )}

            {error && (
              <p className="share-error" role="alert">
                {error}
              </p>
            )}

            <div className="modal__actions">
              <button type="button" className="btn" onClick={() => setOpen(false)}>
                {STR.CLOSE}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}