mirror of
https://github.com/cheeaun/phanpy.git
synced 2024-12-25 19:58:15 +03:00
71b50382e9
For now, this only works for unsent unsaved drafts e.g. the browser kill the page without giving the user the chance to discard
240 lines
7.9 KiB
JavaScript
240 lines
7.9 KiB
JavaScript
import './drafts.css';
|
|
|
|
import { useEffect, useMemo, useReducer, useState } from 'react';
|
|
|
|
import db from '../utils/db';
|
|
import states from '../utils/states';
|
|
import { getCurrentAccountNS } from '../utils/store-utils';
|
|
|
|
import Icon from './icon';
|
|
import Loader from './loader';
|
|
|
|
function Drafts() {
|
|
const [uiState, setUIState] = useState('default');
|
|
const [drafts, setDrafts] = useState([]);
|
|
const [reloadCount, reload] = useReducer((c) => c + 1, 0);
|
|
|
|
useEffect(() => {
|
|
setUIState('loading');
|
|
(async () => {
|
|
try {
|
|
const keys = await db.drafts.keys();
|
|
if (keys.length) {
|
|
const ns = getCurrentAccountNS();
|
|
const ownKeys = keys.filter((key) => key.startsWith(ns));
|
|
if (ownKeys.length) {
|
|
const drafts = await db.drafts.getMany(ownKeys);
|
|
drafts.sort(
|
|
(a, b) =>
|
|
new Date(b.updatedAt).getTime() -
|
|
new Date(a.updatedAt).getTime(),
|
|
);
|
|
setDrafts(drafts);
|
|
} else {
|
|
setDrafts([]);
|
|
}
|
|
} else {
|
|
setDrafts([]);
|
|
}
|
|
setUIState('default');
|
|
} catch (e) {
|
|
console.error(e);
|
|
setUIState('error');
|
|
}
|
|
})();
|
|
}, [reloadCount]);
|
|
|
|
const hasDrafts = drafts?.length > 0;
|
|
|
|
return (
|
|
<div class="sheet">
|
|
<header>
|
|
<h2>
|
|
Unsent drafts <Loader abrupt hidden={uiState !== 'loading'} />
|
|
</h2>
|
|
{hasDrafts && (
|
|
<div class="insignificant">
|
|
Looks like you have unsent drafts. Let's continue where you left
|
|
off.
|
|
</div>
|
|
)}
|
|
</header>
|
|
<main>
|
|
{hasDrafts ? (
|
|
<>
|
|
<ul class="drafts-list">
|
|
{drafts.map((draft) => {
|
|
const { updatedAt, key, draftStatus, replyTo } = draft;
|
|
const currentYear = new Date().getFullYear();
|
|
const updatedAtDate = new Date(updatedAt);
|
|
return (
|
|
<li key={updatedAt}>
|
|
<div class="mini-draft-meta">
|
|
<b>
|
|
<Icon icon={replyTo ? 'reply' : 'quill'} size="s" />{' '}
|
|
<time>
|
|
{!!replyTo && (
|
|
<>
|
|
@{replyTo.account.acct}
|
|
<br />
|
|
</>
|
|
)}
|
|
{Intl.DateTimeFormat('en', {
|
|
// Show year if not current year
|
|
year:
|
|
updatedAtDate.getFullYear() === currentYear
|
|
? undefined
|
|
: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
weekday: 'short',
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
}).format(updatedAtDate)}
|
|
</time>
|
|
</b>
|
|
<button
|
|
type="button"
|
|
class="small light"
|
|
disabled={uiState === 'loading'}
|
|
onClick={() => {
|
|
(async () => {
|
|
try {
|
|
const yes = confirm(
|
|
'Are you sure you want to delete this draft?',
|
|
);
|
|
if (yes) {
|
|
await db.drafts.del(key);
|
|
reload();
|
|
}
|
|
} catch (e) {
|
|
alert('Error deleting draft! Please try again.');
|
|
}
|
|
})();
|
|
}}
|
|
>
|
|
Delete…
|
|
</button>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
disabled={uiState === 'loading'}
|
|
class="draft-item"
|
|
onClick={async () => {
|
|
// console.log({ draftStatus });
|
|
let replyToStatus;
|
|
if (replyTo) {
|
|
setUIState('loading');
|
|
try {
|
|
replyToStatus = await masto.v1.statuses.fetch(
|
|
replyTo.id,
|
|
);
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert('Error fetching reply-to status!');
|
|
setUIState('default');
|
|
return;
|
|
}
|
|
setUIState('default');
|
|
}
|
|
window.__COMPOSE__ = {
|
|
draftStatus,
|
|
replyToStatus,
|
|
};
|
|
states.showCompose = true;
|
|
states.showDrafts = false;
|
|
}}
|
|
>
|
|
<MiniDraft draft={draft} />
|
|
</button>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
<p>
|
|
<button
|
|
type="button"
|
|
class="light danger"
|
|
disabled={uiState === 'loading'}
|
|
onClick={() => {
|
|
(async () => {
|
|
const yes = confirm(
|
|
'Are you sure you want to delete all drafts?',
|
|
);
|
|
if (yes) {
|
|
setUIState('loading');
|
|
try {
|
|
await db.drafts.delMany(
|
|
drafts.map((draft) => draft.key),
|
|
);
|
|
setUIState('default');
|
|
reload();
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert('Error deleting drafts! Please try again.');
|
|
setUIState('error');
|
|
}
|
|
}
|
|
})();
|
|
}}
|
|
>
|
|
Delete all drafts…
|
|
</button>
|
|
</p>
|
|
</>
|
|
) : (
|
|
<p>No drafts found.</p>
|
|
)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MiniDraft({ draft }) {
|
|
const { draftStatus, replyTo } = draft;
|
|
const { status, spoilerText, poll, mediaAttachments } = draftStatus;
|
|
const hasPoll = poll?.options?.length > 0;
|
|
const hasMedia = mediaAttachments?.length > 0;
|
|
const hasPollOrMedia = hasPoll || hasMedia;
|
|
const firstImageMedia = useMemo(() => {
|
|
if (!hasMedia) return;
|
|
const image = mediaAttachments.find((media) => /image/.test(media.type));
|
|
if (!image) return;
|
|
const { file } = image;
|
|
const objectURL = URL.createObjectURL(file);
|
|
return objectURL;
|
|
}, [hasMedia, mediaAttachments]);
|
|
return (
|
|
<>
|
|
<div class="mini-draft">
|
|
{hasPollOrMedia && (
|
|
<div
|
|
class={`mini-draft-aside ${firstImageMedia ? 'has-image' : ''}`}
|
|
style={
|
|
firstImageMedia
|
|
? {
|
|
'--bg-image': `url(${firstImageMedia})`,
|
|
}
|
|
: {}
|
|
}
|
|
>
|
|
{hasPoll && <Icon icon="poll" />}
|
|
{hasMedia && (
|
|
<span>
|
|
<Icon icon="attachment" />{' '}
|
|
<small>{mediaAttachments?.length}</small>
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div class="mini-draft-main">
|
|
{!!spoilerText && <div class="mini-draft-spoiler">{spoilerText}</div>}
|
|
{!!status && <div class="mini-draft-status">{status}</div>}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default Drafts;
|