feat: export as zip if multiple notes are selected (#926)

This commit is contained in:
Aman Harwara
2022-03-15 15:38:22 +05:30
committed by GitHub
parent 6f41577ec9
commit a2a4b6b180
4 changed files with 75 additions and 16 deletions

View File

@@ -11,6 +11,7 @@ import { ChangeEditorOption } from './ChangeEditorOption';
import { BYTES_IN_ONE_MEGABYTE } from '@/constants';
import { ListedActionsOption } from './ListedActionsOption';
import { AddTagOption } from './AddTagOption';
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit';
export type NotesOptionsProps = {
application: WebApplication;
@@ -236,18 +237,39 @@ export const NotesOptions = observer(
};
}, [application]);
const downloadSelectedItems = () => {
notes.forEach((note) => {
const editor = application.componentManager.editorForNote(note);
const format = editor?.package_info?.file_type || 'txt';
const downloadAnchor = document.createElement('a');
downloadAnchor.setAttribute(
'href',
'data:text/plain;charset=utf-8,' + encodeURIComponent(note.text)
const getNoteFileName = (note: SNNote): string => {
const editor = application.componentManager.editorForNote(note);
const format = editor?.package_info?.file_type || 'txt';
return `${note.title}.${format}`;
};
const downloadSelectedItems = async () => {
if (notes.length === 1) {
application
.getArchiveService()
.downloadData(new Blob([notes[0].text]), getNoteFileName(notes[0]));
return;
}
if (notes.length > 1) {
const loadingToastId = addToast({
type: ToastType.Loading,
message: `Exporting ${notes.length} notes...`,
});
await application.getArchiveService().downloadDataAsZip(
notes.map((note) => {
return {
filename: getNoteFileName(note),
content: new Blob([note.text]),
};
})
);
downloadAnchor.setAttribute('download', `${note.title}.${format}`);
downloadAnchor.click();
});
dismissToast(loadingToastId);
addToast({
type: ToastType.Success,
message: `Exported ${notes.length} notes`,
});
}
};
const duplicateSelectedItems = () => {

View File

@@ -1,4 +1,5 @@
import { WebApplication } from '@/ui_models/application';
import { parseFileName } from '@standardnotes/filepicker';
import {
EncryptionIntent,
ContentType,
@@ -11,13 +12,18 @@ function sanitizeFileName(name: string): string {
return name.trim().replace(/[.\\/:"?*|<>]/g, '_');
}
function zippableTxtName(name: string, suffix = ''): string {
function zippableFileName(name: string, suffix = '', format = 'txt'): string {
const sanitizedName = sanitizeFileName(name);
const nameEnd = suffix + '.txt';
const nameEnd = suffix + '.' + format;
const maxFileNameLength = 100;
return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd;
}
type ZippableData = {
filename: string;
content: Blob;
}[];
export class ArchiveManager {
private readonly application: WebApplication;
private textFile?: string;
@@ -89,7 +95,7 @@ export class ArchiveManager {
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'text/plain',
});
const fileName = zippableTxtName(
const fileName = zippableFileName(
'Standard Notes Backup and Import File'
);
zipWriter.add(fileName, new this.zip.BlobReader(blob), resolve);
@@ -113,7 +119,7 @@ export class ArchiveManager {
const blob = new Blob([contents], { type: 'text/plain' });
const fileName =
`Items/${sanitizeFileName(item.content_type)}/` +
zippableTxtName(name, `-${item.uuid.split('-')[0]}`);
zippableFileName(name, `-${item.uuid.split('-')[0]}`);
zipWriter.add(fileName, new this.zip.BlobReader(blob), () => {
index++;
if (index < items.length) {
@@ -135,6 +141,31 @@ export class ArchiveManager {
);
}
async zipData(data: ZippableData): Promise<Blob> {
const zip = await import('@zip.js/zip.js');
const writer = new zip.ZipWriter(new zip.BlobWriter('application/zip'));
for (let i = 0; i < data.length; i++) {
const { name, ext } = parseFileName(data[i].filename);
await writer.add(
zippableFileName(name, '', ext),
new zip.BlobReader(data[i].content)
);
}
const zipFileAsBlob = await writer.close();
return zipFileAsBlob;
}
async downloadDataAsZip(data: ZippableData) {
const zipFileAsBlob = await this.zipData(data);
this.downloadData(
zipFileAsBlob,
`Standard Notes Export - ${this.formattedDate()}.zip`
);
}
private hrefForData(data: Blob) {
// If we are replacing a previously generated file we need to
// manually revoke the object URL to avoid memory leaks.
@@ -146,7 +177,7 @@ export class ArchiveManager {
return this.textFile;
}
private downloadData(data: Blob, fileName: string) {
downloadData(data: Blob, fileName: string) {
const link = document.createElement('a');
link.setAttribute('download', fileName);
link.href = this.hrefForData(data);