feat(entries): offer to save the editor content as a new entry when the row is gone
A 404 on save meant the work had to be copied out by hand. The POST mints a fresh uuid, so this is a new entry rather than a resurrection and cannot fight the tombstone the delete left behind. Gated on status 404 AND the server message: the HTTP layer answers 404 to any request it refuses, which the status alone would misread.
This commit is contained in:
@@ -5072,11 +5072,49 @@ async function soSaveInner() {
|
||||
toast('Saved');
|
||||
}
|
||||
} else {
|
||||
await api('/entries/' + soState.id, {
|
||||
method: 'PUT',
|
||||
headers: authHeaders({ 'Content-Type': 'application/json' }),
|
||||
body,
|
||||
});
|
||||
try {
|
||||
await api('/entries/' + soState.id, {
|
||||
method: 'PUT',
|
||||
headers: authHeaders({ 'Content-Type': 'application/json' }),
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
// The row is gone — deleted by a sync, another device, or a
|
||||
// purge, after the editor opened it. Offer to keep the work as
|
||||
// a NEW entry rather than making the user copy it out by hand.
|
||||
// A fresh uuid is minted, so this is a new entry and not a
|
||||
// resurrection: it can't fight the tombstone the delete left.
|
||||
// Both checks matter: the HTTP layer also answers 404 to any
|
||||
// request it refuses (token lockdown), so the status alone
|
||||
// would misread a rejected request as a deleted entry.
|
||||
if (err.status !== 404 || !/no longer exists/i.test(err.message || ''))
|
||||
throw err;
|
||||
const again = await confirmDialog({
|
||||
title: 'Entry no longer exists',
|
||||
message: 'It was deleted after you opened it — by a sync, ' +
|
||||
'an import, or another device. Your changes are ' +
|
||||
'still here and can be saved as a new entry.',
|
||||
okText: 'Save as new entry',
|
||||
});
|
||||
if (!again) return;
|
||||
const created = await api('/entries', {
|
||||
method: 'POST',
|
||||
headers: authHeaders({ 'Content-Type': 'application/json' }),
|
||||
body,
|
||||
});
|
||||
const newId = created && created.id;
|
||||
toast('Saved as a new entry');
|
||||
await loadEntries();
|
||||
if (typeof healthCache !== 'undefined') healthCache = null;
|
||||
// Cleared before reopening for the same reason as the normal
|
||||
// save path: a stale soState would trigger the discard prompt.
|
||||
soState = null;
|
||||
if (newId) openSlideOver(newId, { noFocus: true });
|
||||
else closeSlideOver();
|
||||
render();
|
||||
if (newId) flashEntry(newId);
|
||||
return;
|
||||
}
|
||||
toast('Saved');
|
||||
}
|
||||
await loadEntries();
|
||||
|
||||
Reference in New Issue
Block a user