From 6ee2e4e000c9513d09c98554ed0162a97a4f7824 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 29 Sep 2026 08:36:41 -0700 Subject: Discard canceled post drafts --- tools/post-cancel-test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tools/post-cancel-test.js (limited to 'tools/post-cancel-test.js') diff --git a/tools/post-cancel-test.js b/tools/post-cancel-test.js new file mode 100644 index 0000000..5527bf2 --- /dev/null +++ b/tools/post-cancel-test.js @@ -0,0 +1,17 @@ +// Canceling a new post closes the composer and discards every part of its +// saved state, including a late attachment result. +import assert from 'node:assert/strict'; + +const source = await Bun.file('src/features/messages.js').text(); +const start = source.indexOf('function cancelPost()'); +const end = source.indexOf('\n }', start); +const cancel = source.slice(start, end); +assert(start > 0, 'post cancel handler exists'); +assert(cancel.includes('postAttachGeneration++'), 'late uploads are invalidated'); +assert(cancel.includes('ui.profCompose = null'), 'composer is closed'); +assert(cancel.includes('ui.postMedia = []'), 'pending attachment metadata is removed'); +assert(cancel.includes('ui.postPreview = false'), 'preview state is removed'); +assert(cancel.includes('discardDraft(POST_DRAFT)'), 'persisted draft is removed immediately'); +assert(source.includes("onClick: cancelPost }, t('cancel')"), 'Cancel button uses the discard handler'); + +console.log('✓ Cancel closes the new-post form and removes its complete saved draft'); -- cgit v1.2.3 From 62b8e37109464aca875e306269c181faf6c64ae3 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 29 Sep 2026 08:37:25 -0700 Subject: Confirm discarding substantial post drafts --- src/features/messages.js | 26 +++++++++++++++++++++++++- src/i18n.js | 4 ++++ tools/post-cancel-test.js | 7 +++++-- 3 files changed, 34 insertions(+), 3 deletions(-) (limited to 'tools/post-cancel-test.js') diff --git a/src/features/messages.js b/src/features/messages.js index 10773c8..89fe55a 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -7627,12 +7627,35 @@ export function messagesFeature(ctx) { }; function cancelPost() { postAttachGeneration++; + ui.postCancelConfirm = false; ui.profCompose = null; ui.postMedia = []; ui.postPreview = false; discardDraft(POST_DRAFT); render(); } + function requestCancelPost() { + if (composeText().length > 20 || ui.postUploading || (ui.postMedia || []).length) { + ui.postCancelConfirm = true; + render(); + return; + } + cancelPost(); + } + function postCancelWarning() { + if (!ui.postCancelConfirm) return null; + const keep = () => { ui.postCancelConfirm = false; render(); }; + return h('div', { + class: 'confirm-pop-backdrop', + onClick: (event) => { if (event.target === event.currentTarget) keep(); }, + }, + h('div', { class: 'card col confirm-pop', style: 'gap:10px' }, + h('h3', { style: 'margin:0' }, t('postDiscardTitle')), + h('div', { class: 'small muted' }, t('postDiscardBody')), + h('div', { class: 'row gap6' }, + h('button', { class: 'btn-ghost grow', onClick: keep }, t('postKeepEditing')), + h('button', { class: 'btn-danger grow', onClick: cancelPost }, t('postDiscard'))))); + } const CLIP = ''; const EYE = ''; @@ -7703,7 +7726,8 @@ export function messagesFeature(ctx) { type: 'file', id: 'post-file', accept: 'image/*,video/*', style: 'display:none', onChange: async (e) => { const f = e.target.files && e.target.files[0]; e.target.value = ''; await attachMedia(f); }, }), - h('button', { class: 'btn-ghost', onClick: cancelPost }, t('cancel')))); + h('button', { class: 'btn-ghost', onClick: requestCancelPost }, t('cancel'))), + postCancelWarning()); } // ---- the feed view -------------------------------------------------------- diff --git a/src/i18n.js b/src/i18n.js index 475f919..41ccb36 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -365,6 +365,10 @@ const STR = { postBoostedAlready: 'You already boosted that', postQuote: 'Quote', postMore: 'More', + postDiscardTitle: 'Discard this draft?', + postDiscardBody: 'You’ll lose this draft and any attachments.', + postKeepEditing: 'Keep editing', + postDiscard: 'Discard draft', postMute: 'Mute', postUnmute: 'Unmute', postMuted: 'Muted. Their posts are hidden', diff --git a/tools/post-cancel-test.js b/tools/post-cancel-test.js index 5527bf2..217e743 100644 --- a/tools/post-cancel-test.js +++ b/tools/post-cancel-test.js @@ -12,6 +12,9 @@ assert(cancel.includes('ui.profCompose = null'), 'composer is closed'); assert(cancel.includes('ui.postMedia = []'), 'pending attachment metadata is removed'); assert(cancel.includes('ui.postPreview = false'), 'preview state is removed'); assert(cancel.includes('discardDraft(POST_DRAFT)'), 'persisted draft is removed immediately'); -assert(source.includes("onClick: cancelPost }, t('cancel')"), 'Cancel button uses the discard handler'); +assert(source.includes("composeText().length > 20"), 'drafts over 20 characters require confirmation'); +assert(source.includes("ui.postUploading || (ui.postMedia || []).length"), 'attachments require confirmation'); +assert(source.includes("onClick: requestCancelPost }, t('cancel')"), 'Cancel button uses the guarded discard handler'); +assert(source.includes("onClick: cancelPost }, t('postDiscard')"), 'warning can confirm the discard'); -console.log('✓ Cancel closes the new-post form and removes its complete saved draft'); +console.log('✓ Cancel warns for substantial drafts, then closes the form and removes its complete saved draft'); -- cgit v1.2.3