this repo has no description
1import './drafts.css';
2
3import { useEffect, useMemo, useReducer, useState } from 'react';
4
5import { api } from '../utils/api';
6import db from '../utils/db';
7import niceDateTime from '../utils/nice-date-time';
8import states from '../utils/states';
9import { getCurrentAccountNS } from '../utils/store-utils';
10
11import Icon from './icon';
12import Loader from './loader';
13import MenuConfirm from './menu-confirm';
14
15function Drafts({ onClose }) {
16 const { masto } = api();
17 const [uiState, setUIState] = useState('default');
18 const [drafts, setDrafts] = useState([]);
19 const [reloadCount, reload] = useReducer((c) => c + 1, 0);
20
21 useEffect(() => {
22 setUIState('loading');
23 (async () => {
24 try {
25 const keys = await db.drafts.keys();
26 if (keys.length) {
27 const ns = getCurrentAccountNS();
28 const ownKeys = keys.filter((key) => key.startsWith(ns));
29 if (ownKeys.length) {
30 const drafts = await db.drafts.getMany(ownKeys);
31 drafts.sort(
32 (a, b) =>
33 new Date(b.updatedAt).getTime() -
34 new Date(a.updatedAt).getTime(),
35 );
36 setDrafts(drafts);
37 } else {
38 setDrafts([]);
39 }
40 } else {
41 setDrafts([]);
42 }
43 setUIState('default');
44 } catch (e) {
45 console.error(e);
46 setUIState('error');
47 }
48 })();
49 }, [reloadCount]);
50
51 const hasDrafts = drafts?.length > 0;
52
53 return (
54 <div class="sheet">
55 {!!onClose && (
56 <button type="button" class="sheet-close" onClick={onClose}>
57 <Icon icon="x" />
58 </button>
59 )}
60 <header>
61 <h2>
62 Unsent drafts <Loader abrupt hidden={uiState !== 'loading'} />
63 </h2>
64 {hasDrafts && (
65 <div class="insignificant">
66 Looks like you have unsent drafts. Let's continue where you left
67 off.
68 </div>
69 )}
70 </header>
71 <main>
72 {hasDrafts ? (
73 <>
74 <ul class="drafts-list">
75 {drafts.map((draft) => {
76 const { updatedAt, key, draftStatus, replyTo } = draft;
77 const updatedAtDate = new Date(updatedAt);
78 return (
79 <li key={updatedAt}>
80 <div class="mini-draft-meta">
81 <b>
82 <Icon icon={replyTo ? 'reply' : 'quill'} size="s" />{' '}
83 <time>
84 {!!replyTo && (
85 <>
86 @{replyTo.account.acct}
87 <br />
88 </>
89 )}
90 {niceDateTime(updatedAtDate)}
91 </time>
92 </b>
93 <MenuConfirm
94 confirmLabel={<span>Delete this draft?</span>}
95 menuItemClassName="danger"
96 align="end"
97 disabled={uiState === 'loading'}
98 onClick={() => {
99 (async () => {
100 try {
101 // const yes = confirm('Delete this draft?');
102 // if (yes) {
103 await db.drafts.del(key);
104 reload();
105 // }
106 } catch (e) {
107 alert('Error deleting draft! Please try again.');
108 }
109 })();
110 }}
111 >
112 <button
113 type="button"
114 class="small light"
115 disabled={uiState === 'loading'}
116 >
117 Delete…
118 </button>
119 </MenuConfirm>
120 </div>
121 <button
122 type="button"
123 disabled={uiState === 'loading'}
124 class="draft-item"
125 onClick={async () => {
126 // console.log({ draftStatus });
127 let replyToStatus;
128 if (replyTo) {
129 setUIState('loading');
130 try {
131 replyToStatus = await masto.v1.statuses
132 .$select(replyTo.id)
133 .fetch();
134 } catch (e) {
135 console.error(e);
136 alert('Error fetching reply-to status!');
137 setUIState('default');
138 return;
139 }
140 setUIState('default');
141 }
142 window.__COMPOSE__ = {
143 draftStatus,
144 replyToStatus,
145 };
146 states.showCompose = true;
147 states.showDrafts = false;
148 }}
149 >
150 <MiniDraft draft={draft} />
151 </button>
152 </li>
153 );
154 })}
155 </ul>
156 {drafts.length > 1 && (
157 <p>
158 <MenuConfirm
159 confirmLabel={<span>Delete all drafts?</span>}
160 menuItemClassName="danger"
161 disabled={uiState === 'loading'}
162 onClick={() => {
163 (async () => {
164 // const yes = confirm('Delete all drafts?');
165 // if (yes) {
166 setUIState('loading');
167 try {
168 await db.drafts.delMany(
169 drafts.map((draft) => draft.key),
170 );
171 setUIState('default');
172 reload();
173 } catch (e) {
174 console.error(e);
175 alert('Error deleting drafts! Please try again.');
176 setUIState('error');
177 }
178 // }
179 })();
180 }}
181 >
182 <button
183 type="button"
184 class="light danger"
185 disabled={uiState === 'loading'}
186 >
187 Delete all…
188 </button>
189 </MenuConfirm>
190 </p>
191 )}
192 </>
193 ) : (
194 <p>No drafts found.</p>
195 )}
196 </main>
197 </div>
198 );
199}
200
201function MiniDraft({ draft }) {
202 const { draftStatus, replyTo } = draft;
203 const { status, spoilerText, poll, mediaAttachments } = draftStatus;
204 const hasPoll = poll?.options?.length > 0;
205 const hasMedia = mediaAttachments?.length > 0;
206 const hasPollOrMedia = hasPoll || hasMedia;
207 const firstImageMedia = useMemo(() => {
208 if (!hasMedia) return;
209 const image = mediaAttachments.find((media) => /image/.test(media.type));
210 if (!image) return;
211 const { file } = image;
212 const objectURL = URL.createObjectURL(file);
213 return objectURL;
214 }, [hasMedia, mediaAttachments]);
215 return (
216 <>
217 <div class="mini-draft">
218 {hasPollOrMedia && (
219 <div
220 class={`mini-draft-aside ${firstImageMedia ? 'has-image' : ''}`}
221 style={
222 firstImageMedia
223 ? {
224 '--bg-image': `url(${firstImageMedia})`,
225 }
226 : {}
227 }
228 >
229 {hasPoll && <Icon icon="poll" />}
230 {hasMedia && (
231 <span>
232 <Icon icon="attachment" />{' '}
233 <small>{mediaAttachments?.length}</small>
234 </span>
235 )}
236 </div>
237 )}
238 <div class="mini-draft-main">
239 {!!spoilerText && <div class="mini-draft-spoiler">{spoilerText}</div>}
240 {!!status && <div class="mini-draft-status">{status}</div>}
241 </div>
242 </div>
243 </>
244 );
245}
246
247export default Drafts;