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