From 5d3edf06c8fec7c413a84801184bff807ce381fc Mon Sep 17 00:00:00 2001 From: joseph Date: Tue, 30 Jul 2024 02:41:03 +0000 Subject: [PATCH] message_edit: Ask users to delete attachments after editing. currently, after a user edits a message and removes an reference to the uploaded file, the uploaded file stays on the storage taking up space. We want to ask the user to possibly delete the removed attachments if they are no longer needed. These changes applies a modal that will appear prompting the user to delete the attachments. Fixes: #25525. Co-authored-by: brijsiyag Co-authored-by: wandrew0 --- tools/test-js-with-node | 1 + web/src/attachments.ts | 4 ++ web/src/attachments_ui.ts | 56 +++++++++++++++++++ web/src/message_edit.ts | 10 +++- .../confirm_delete_detached_attachments.hbs | 22 ++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 web/templates/confirm_dialog/confirm_delete_detached_attachments.hbs diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 921c51259b..2903183220 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -53,6 +53,7 @@ EXEMPT_FILES = make_set( "web/src/alert_words_ui.ts", "web/src/archive.js", "web/src/assets.d.ts", + "web/src/attachments.ts", "web/src/attachments_ui.ts", "web/src/audible_notifications.ts", "web/src/avatar.ts", diff --git a/web/src/attachments.ts b/web/src/attachments.ts index 4c86817768..82b1edabb0 100644 --- a/web/src/attachments.ts +++ b/web/src/attachments.ts @@ -20,3 +20,7 @@ export const attachment_api_response_schema = z.object({ attachments: attachments_schema, upload_space_used: z.number(), }); + +export const detached_uploads_api_response_schema = z.object({ + detached_uploads: attachments_schema, +}); diff --git a/web/src/attachments_ui.ts b/web/src/attachments_ui.ts index bd749bd16b..e9b8776768 100644 --- a/web/src/attachments_ui.ts +++ b/web/src/attachments_ui.ts @@ -2,6 +2,7 @@ import $ from "jquery"; import type {z} from "zod"; import render_confirm_delete_attachment from "../templates/confirm_dialog/confirm_delete_attachment.hbs"; +import render_confirm_delete_detached_attachments_modal from "../templates/confirm_dialog/confirm_delete_detached_attachments.hbs"; import render_settings_upload_space_stats from "../templates/settings/upload_space_stats.hbs"; import render_uploaded_files_list from "../templates/settings/uploaded_files_list.hbs"; @@ -205,3 +206,58 @@ export function set_up_attachments(): void { }, }); } + +export function suggest_delete_detached_attachments(attachments_list: ServerAttachment[]): void { + const html_body = render_confirm_delete_detached_attachments_modal({ + attachments_list, + realm_allow_edit_history: realm.realm_allow_edit_history, + }); + + // Since we want to delete multiple attachments, we want to be + // able to keep track of attachments to delete and which ones to + // retry if it fails. + const attachments_map = new Map(); + for (const attachment of attachments_list) { + attachments_map.set(attachment.id, attachment); + } + + function do_delete_attachments(): void { + dialog_widget.show_dialog_spinner(); + for (const [key, attachment] of attachments_map.entries()) { + const id = Number(key); + void channel.del({ + url: "/json/attachments/" + attachment.id, + success() { + attachments_map.delete(id); + if (attachments_map.size === 0) { + dialog_widget.hide_dialog_spinner(); + dialog_widget.close(); + } + }, + error() { + dialog_widget.hide_dialog_spinner(); + ui_report.error( + $t_html({defaultMessage: "One or more files could not be deleted."}), + undefined, + $("#dialog_error"), + ); + }, + }); + } + // This is to open "Manage uploaded files" link. + $("#confirm_delete_attachments_modal .uploaded_files_settings_link").on("click", (e) => { + e.stopPropagation(); + dialog_widget.close(); + }); + } + + dialog_widget.launch({ + id: "confirm_delete_attachments_modal", + html_heading: $t_html({defaultMessage: "Delete uploaded files?"}), + html_body, + html_submit_button: $t_html({defaultMessage: "Delete"}), + html_exit_button: $t_html({defaultMessage: "Don't delete"}), + loading_spinner: true, + on_click: do_delete_attachments, + }); +} diff --git a/web/src/message_edit.ts b/web/src/message_edit.ts index 9d393f4c35..50a9f21fa3 100644 --- a/web/src/message_edit.ts +++ b/web/src/message_edit.ts @@ -13,6 +13,8 @@ import render_message_moved_widget_body from "../templates/message_moved_widget_ import render_resolve_topic_time_limit_error_modal from "../templates/resolve_topic_time_limit_error_modal.hbs"; import render_topic_edit_form from "../templates/topic_edit_form.hbs"; +import {detached_uploads_api_response_schema} from "./attachments"; +import * as attachments_ui from "./attachments_ui"; import * as blueslip from "./blueslip"; import * as channel from "./channel"; import * as compose_actions from "./compose_actions"; @@ -1093,11 +1095,12 @@ export function save_message_row_edit($row: JQuery): void { void channel.patch({ url: "/json/messages/" + message.id, data: request, - success() { + success(res) { if (edit_locally_echoed) { delete message.local_edit_timestamp; currently_echoing_messages.delete(message_id); } + // Ordinarily, in a code path like this, we'd make // a call to `hide_message_edit_spinner()`. But in // this instance, we want to avoid a momentary flash @@ -1105,6 +1108,11 @@ export function save_message_row_edit($row: JQuery): void { // re-renders. Note that any subsequent editing will // create a fresh Save button, without the spinner // class attached. + + const {detached_uploads} = detached_uploads_api_response_schema.parse(res); + if (detached_uploads.length) { + attachments_ui.suggest_delete_detached_attachments(detached_uploads); + } }, error(xhr) { if (msg_list === message_lists.current) { diff --git a/web/templates/confirm_dialog/confirm_delete_detached_attachments.hbs b/web/templates/confirm_dialog/confirm_delete_detached_attachments.hbs new file mode 100644 index 0000000000..7d4bb48859 --- /dev/null +++ b/web/templates/confirm_dialog/confirm_delete_detached_attachments.hbs @@ -0,0 +1,22 @@ +
+ {{#if realm_allow_edit_history}} + {{#tr}} + The following uploaded files are no longer attached to any messages. They can still be accessed from this message's edit history. Would you like to delete them entirely? + {{#*inline "z-link"}}{{> @partial-block}}{{/inline}} + {{/tr}} + {{else}} + {{#tr}} + The following uploaded files are no longer attached to any messages. Would you like to delete them entirely? + {{#*inline "z-link"}}{{> @partial-block}}{{/inline}} + {{/tr}} + {{/if}} +
+

+

+