snow-editor

small markdown and org-mode editor
Log | Files | Refs | README

VersionHistory.jsx (3690B)


      1 import { useCallback, useEffect, useState } from 'react';
      2 import {
      3   fetchDocumentVersions,
      4   friendlyErrorMessage,
      5   restoreDocumentVersion,
      6 } from '../lib/api.js';
      7 import { STR, formatVersionDate } from '../lib/strings.js';
      8 
      9 export default function VersionHistory({
     10   editToken,
     11   clientId,
     12   lockToken,
     13   onRestored,
     14 }) {
     15   const [open, setOpen] = useState(false);
     16   const [versions, setVersions] = useState([]);
     17   const [loading, setLoading] = useState(false);
     18   const [restoringId, setRestoringId] = useState('');
     19   const [error, setError] = useState('');
     20 
     21   const loadVersions = useCallback(async () => {
     22     setLoading(true);
     23     setError('');
     24     try {
     25       const data = await fetchDocumentVersions(editToken, { clientId, lockToken });
     26       setVersions(data.versions ?? []);
     27     } catch (err) {
     28       setError(friendlyErrorMessage(err));
     29     } finally {
     30       setLoading(false);
     31     }
     32   }, [editToken, clientId, lockToken]);
     33 
     34   useEffect(() => {
     35     if (!open) return;
     36     loadVersions();
     37   }, [open, loadVersions]);
     38 
     39   const handleRestore = async (versionId) => {
     40     const confirmed = window.confirm(STR.VERSION_RESTORE_CONFIRM);
     41     if (!confirmed) return;
     42 
     43     setRestoringId(versionId);
     44     setError('');
     45     try {
     46       const data = await restoreDocumentVersion(editToken, versionId, {
     47         clientId,
     48         lockToken,
     49       });
     50       onRestored(data);
     51       await loadVersions();
     52     } catch (err) {
     53       setError(friendlyErrorMessage(err));
     54     } finally {
     55       setRestoringId('');
     56     }
     57   };
     58 
     59   return (
     60     <>
     61       <button
     62         type="button"
     63         className="btn btn-ghost"
     64         onClick={() => setOpen(true)}
     65       >
     66         {STR.VERSION_HISTORY}
     67       </button>
     68 
     69       {open && (
     70         <div className="modal-backdrop" role="presentation" onClick={() => setOpen(false)}>
     71           <div
     72             className="modal share-panel version-panel"
     73             role="dialog"
     74             aria-labelledby="version-history-title"
     75             aria-modal="true"
     76             onClick={(e) => e.stopPropagation()}
     77           >
     78             <h2 id="version-history-title" className="modal__title">
     79               {STR.VERSION_HISTORY}
     80             </h2>
     81 
     82             {loading && <p className="version-panel__status">{STR.VERSION_LOADING}</p>}
     83 
     84             {!loading && versions.length === 0 && (
     85               <p className="version-panel__status">{STR.VERSION_EMPTY}</p>
     86             )}
     87 
     88             {!loading && versions.length > 0 && (
     89               <ul className="version-list">
     90                 {versions.map((version) => (
     91                   <li key={version.id} className="version-list__item">
     92                     <span className="version-list__date">
     93                       {formatVersionDate(version.createdAt)}
     94                     </span>
     95                     <button
     96                       type="button"
     97                       className="btn btn-ghost version-list__restore"
     98                       disabled={restoringId === version.id}
     99                       onClick={() => handleRestore(version.id)}
    100                     >
    101                       {restoringId === version.id
    102                         ? STR.VERSION_RESTORING
    103                         : STR.VERSION_RESTORE}
    104                     </button>
    105                   </li>
    106                 ))}
    107               </ul>
    108             )}
    109 
    110             {error && (
    111               <p className="share-error" role="alert">
    112                 {error}
    113               </p>
    114             )}
    115 
    116             <div className="modal__actions">
    117               <button type="button" className="btn" onClick={() => setOpen(false)}>
    118                 {STR.CLOSE}
    119               </button>
    120             </div>
    121           </div>
    122         </div>
    123       )}
    124     </>
    125   );
    126 }