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
This commit is contained in:
joseph 2024-07-30 02:41:03 +00:00 committed by Tim Abbott
parent 5133f34a05
commit 5d3edf06c8
5 changed files with 92 additions and 1 deletions

View File

@ -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",

View File

@ -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,
});

View File

@ -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<number, ServerAttachment>();
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,
});
}

View File

@ -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) {

View File

@ -0,0 +1,22 @@
<div>
{{#if realm_allow_edit_history}}
{{#tr}}
The following <z-link>uploaded files</z-link> 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"}}<a class="uploaded_files_settings_link" href="/#settings/uploaded-files">{{> @partial-block}}</a>{{/inline}}
{{/tr}}
{{else}}
{{#tr}}
The following <z-link>uploaded files</z-link> are no longer attached to any messages. Would you like to delete them entirely?
{{#*inline "z-link"}}<a class="uploaded_files_settings_link" href="/#settings/uploaded-files">{{> @partial-block}}</a>{{/inline}}
{{/tr}}
{{/if}}
</div>
<p>
<ul>
{{#each attachments_list}}
<li>
<a href="/user_uploads/{{this.path_id}}" rel="noopener noreferrer" target="_blank">{{this.name}}</a>
</li>
{{/each}}
</ul>
</p>