Reintroduce file chooser dialog for every attachment save
This commit is contained in:
parent
1c906e76f9
commit
55eff02872
5 changed files with 74 additions and 39 deletions
|
@ -782,8 +782,8 @@
|
||||||
"message": "A voice message must have only one attachment.",
|
"message": "A voice message must have only one attachment.",
|
||||||
"description": "Shown in toast if tries to record a voice note with any staged attachments"
|
"description": "Shown in toast if tries to record a voice note with any staged attachments"
|
||||||
},
|
},
|
||||||
"attachmentSavedToDownloads": {
|
"attachmentSaved": {
|
||||||
"message": "Attachment saved as \"$name$\" in your Downloads folder. Click to show.",
|
"message": "Attachment saved. Click to show in folder.",
|
||||||
"description": "Shown after user selects to save to downloads",
|
"description": "Shown after user selects to save to downloads",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { app, shell, remote } = require('electron');
|
const { app, dialog, shell, remote } = require('electron');
|
||||||
|
|
||||||
const pify = require('pify');
|
const pify = require('pify');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
|
@ -189,7 +189,16 @@ exports.writeToDownloads = async ({ data, name }) => {
|
||||||
throw new Error('Invalid filename!');
|
throw new Error('Invalid filename!');
|
||||||
}
|
}
|
||||||
|
|
||||||
await fse.writeFile(normalized, Buffer.from(data));
|
writeWithAttributes(normalized, Buffer.from(data));
|
||||||
|
|
||||||
|
return {
|
||||||
|
fullPath: normalized,
|
||||||
|
name: candidateName,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async function writeWithAttributes(target, data) {
|
||||||
|
await fse.writeFile(target, Buffer.from(data));
|
||||||
|
|
||||||
if (process.platform === 'darwin' && xattr) {
|
if (process.platform === 'darwin' && xattr) {
|
||||||
// kLSQuarantineTypeInstantMessageAttachment
|
// kLSQuarantineTypeInstantMessageAttachment
|
||||||
|
@ -204,14 +213,9 @@ exports.writeToDownloads = async ({ data, name }) => {
|
||||||
// https://ilostmynotes.blogspot.com/2012/06/gatekeeper-xprotect-and-quarantine.html
|
// https://ilostmynotes.blogspot.com/2012/06/gatekeeper-xprotect-and-quarantine.html
|
||||||
const attrValue = `${type};${timestamp};${appName};${guid}`;
|
const attrValue = `${type};${timestamp};${appName};${guid}`;
|
||||||
|
|
||||||
await xattr.set(normalized, 'com.apple.quarantine', attrValue);
|
await xattr.set(target, 'com.apple.quarantine', attrValue);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return {
|
|
||||||
fullPath: normalized,
|
|
||||||
name: candidateName,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.openFileInDownloads = async name => {
|
exports.openFileInDownloads = async name => {
|
||||||
const shellToUse = shell || remote.shell;
|
const shellToUse = shell || remote.shell;
|
||||||
|
@ -229,6 +233,37 @@ exports.openFileInDownloads = async name => {
|
||||||
shellToUse.showItemInFolder(normalized);
|
shellToUse.showItemInFolder(normalized);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.saveAttachmentToDisk = async ({ data, name }) => {
|
||||||
|
const dialogToUse = dialog || remote.dialog;
|
||||||
|
const browserWindow = remote.getCurrentWindow();
|
||||||
|
|
||||||
|
const { canceled, filePath } = await dialogToUse.showSaveDialog(
|
||||||
|
browserWindow,
|
||||||
|
{
|
||||||
|
defaultPath: name,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeWithAttributes(filePath, Buffer.from(data));
|
||||||
|
|
||||||
|
const basename = path.basename(filePath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fullPath: filePath,
|
||||||
|
name: basename,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.openFileInFolder = async target => {
|
||||||
|
const shellToUse = shell || remote.shell;
|
||||||
|
|
||||||
|
shellToUse.showItemInFolder(target);
|
||||||
|
};
|
||||||
|
|
||||||
// createWriterForNew :: AttachmentsPath ->
|
// createWriterForNew :: AttachmentsPath ->
|
||||||
// ArrayBuffer ->
|
// ArrayBuffer ->
|
||||||
// IO (Promise RelativePath)
|
// IO (Promise RelativePath)
|
||||||
|
|
|
@ -119,8 +119,8 @@ function initializeMigrations({
|
||||||
getPath,
|
getPath,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
getTempPath,
|
getTempPath,
|
||||||
openFileInDownloads,
|
openFileInFolder,
|
||||||
writeToDownloads,
|
saveAttachmentToDisk,
|
||||||
} = Attachments;
|
} = Attachments;
|
||||||
const {
|
const {
|
||||||
getImageDimensions,
|
getImageDimensions,
|
||||||
|
@ -189,13 +189,13 @@ function initializeMigrations({
|
||||||
loadPreviewData,
|
loadPreviewData,
|
||||||
loadQuoteData,
|
loadQuoteData,
|
||||||
loadStickerData,
|
loadStickerData,
|
||||||
openFileInDownloads,
|
openFileInFolder,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
readDraftData,
|
readDraftData,
|
||||||
readStickerData,
|
readStickerData,
|
||||||
readTempData,
|
readTempData,
|
||||||
run,
|
run,
|
||||||
writeToDownloads,
|
saveAttachmentToDisk,
|
||||||
processNewAttachment: attachment =>
|
processNewAttachment: attachment =>
|
||||||
MessageType.processNewAttachment(attachment, {
|
MessageType.processNewAttachment(attachment, {
|
||||||
writeNewAttachmentData,
|
writeNewAttachmentData,
|
||||||
|
|
|
@ -19,18 +19,18 @@
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
const { Message, MIME, VisualAttachment } = window.Signal.Types;
|
const { Message, MIME, VisualAttachment } = window.Signal.Types;
|
||||||
const {
|
const {
|
||||||
upgradeMessageSchema,
|
|
||||||
getAbsoluteAttachmentPath,
|
|
||||||
getAbsoluteDraftPath,
|
|
||||||
copyIntoTempDirectory,
|
copyIntoTempDirectory,
|
||||||
getAbsoluteTempPath,
|
|
||||||
deleteDraftFile,
|
deleteDraftFile,
|
||||||
deleteTempFile,
|
deleteTempFile,
|
||||||
openFileInDownloads,
|
getAbsoluteAttachmentPath,
|
||||||
|
getAbsoluteDraftPath,
|
||||||
|
getAbsoluteTempPath,
|
||||||
|
openFileInFolder,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
readDraftData,
|
readDraftData,
|
||||||
|
saveAttachmentToDisk,
|
||||||
|
upgradeMessageSchema,
|
||||||
writeNewDraftData,
|
writeNewDraftData,
|
||||||
writeToDownloads,
|
|
||||||
} = window.Signal.Migrations;
|
} = window.Signal.Migrations;
|
||||||
const {
|
const {
|
||||||
getOlderMessagesByConversation,
|
getOlderMessagesByConversation,
|
||||||
|
@ -107,10 +107,10 @@
|
||||||
Whisper.FileSavedToast = Whisper.ToastView.extend({
|
Whisper.FileSavedToast = Whisper.ToastView.extend({
|
||||||
className: 'toast toast-clickable',
|
className: 'toast toast-clickable',
|
||||||
initialize(options) {
|
initialize(options) {
|
||||||
if (!options.name) {
|
if (!options.fullPath) {
|
||||||
throw new Error('FileSavedToast: name option was not provided!');
|
throw new Error('FileSavedToast: name option was not provided!');
|
||||||
}
|
}
|
||||||
this.name = options.name;
|
this.fullPath = options.fullPath;
|
||||||
this.timeout = 10000;
|
this.timeout = 10000;
|
||||||
|
|
||||||
if (window.getInteractionMode() === 'keyboard') {
|
if (window.getInteractionMode() === 'keyboard') {
|
||||||
|
@ -124,7 +124,7 @@
|
||||||
keydown: 'onKeydown',
|
keydown: 'onKeydown',
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
openFileInDownloads(this.name);
|
openFileInFolder(this.fullPath);
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
onKeydown(event) {
|
onKeydown(event) {
|
||||||
|
@ -135,11 +135,11 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
openFileInDownloads(this.name);
|
openFileInFolder(this.fullPath);
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
render_attributes() {
|
render_attributes() {
|
||||||
return { toastMessage: i18n('attachmentSavedToDownloads', this.name) };
|
return { toastMessage: i18n('attachmentSaved') };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1796,13 +1796,13 @@
|
||||||
|
|
||||||
const saveAttachment = async ({ attachment, message } = {}) => {
|
const saveAttachment = async ({ attachment, message } = {}) => {
|
||||||
const timestamp = message.sent_at;
|
const timestamp = message.sent_at;
|
||||||
const name = await Signal.Types.Attachment.save({
|
const fullPath = await Signal.Types.Attachment.save({
|
||||||
attachment,
|
attachment,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
writeToDownloads,
|
saveAttachmentToDisk,
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
this.showToast(Whisper.FileSavedToast, { name });
|
this.showToast(Whisper.FileSavedToast, { fullPath });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onItemClick = async ({ message, attachment, type }) => {
|
const onItemClick = async ({ message, attachment, type }) => {
|
||||||
|
@ -1993,13 +1993,13 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = await Signal.Types.Attachment.save({
|
const fullPath = await Signal.Types.Attachment.save({
|
||||||
attachment,
|
attachment,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
writeToDownloads,
|
saveAttachmentToDisk,
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
this.showToast(Whisper.FileSavedToast, { name });
|
this.showToast(Whisper.FileSavedToast, { fullPath });
|
||||||
},
|
},
|
||||||
|
|
||||||
async displayTapToViewMessage(messageId) {
|
async displayTapToViewMessage(messageId) {
|
||||||
|
@ -2196,14 +2196,14 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSave = async (options = {}) => {
|
const onSave = async (options = {}) => {
|
||||||
const name = await Signal.Types.Attachment.save({
|
const fullPath = await Signal.Types.Attachment.save({
|
||||||
attachment: options.attachment,
|
attachment: options.attachment,
|
||||||
index: options.index + 1,
|
index: options.index + 1,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
writeToDownloads,
|
saveAttachmentToDisk,
|
||||||
timestamp: options.message.get('sent_at'),
|
timestamp: options.message.get('sent_at'),
|
||||||
});
|
});
|
||||||
this.showToast(Whisper.FileSavedToast, { name });
|
this.showToast(Whisper.FileSavedToast, { fullPath });
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
|
|
|
@ -328,13 +328,13 @@ export const save = async ({
|
||||||
attachment,
|
attachment,
|
||||||
index,
|
index,
|
||||||
readAttachmentData,
|
readAttachmentData,
|
||||||
writeToDownloads,
|
saveAttachmentToDisk,
|
||||||
timestamp,
|
timestamp,
|
||||||
}: {
|
}: {
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
index: number;
|
index: number;
|
||||||
readAttachmentData: (relativePath: string) => Promise<ArrayBuffer>;
|
readAttachmentData: (relativePath: string) => Promise<ArrayBuffer>;
|
||||||
writeToDownloads: (options: {
|
saveAttachmentToDisk: (options: {
|
||||||
data: ArrayBuffer;
|
data: ArrayBuffer;
|
||||||
name: string;
|
name: string;
|
||||||
}) => Promise<{ name: string; fullPath: string }>;
|
}) => Promise<{ name: string; fullPath: string }>;
|
||||||
|
@ -349,12 +349,12 @@ export const save = async ({
|
||||||
: attachment.data;
|
: attachment.data;
|
||||||
const name = getSuggestedFilename({ attachment, timestamp, index });
|
const name = getSuggestedFilename({ attachment, timestamp, index });
|
||||||
|
|
||||||
const { name: savedFilename } = await writeToDownloads({
|
const { fullPath } = await saveAttachmentToDisk({
|
||||||
data,
|
data,
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
|
|
||||||
return savedFilename;
|
return fullPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSuggestedFilename = ({
|
export const getSuggestedFilename = ({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue