fix: Fixed issue where exporting multiple files with the same name would error
This commit is contained in:
@@ -19,9 +19,9 @@ function zippableFileName(name: string, suffix = '', format = 'txt'): string {
|
|||||||
return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd
|
return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAndCreateZippableFileName(name: string) {
|
export function parseAndCreateZippableFileName(name: string, suffix = '') {
|
||||||
const { name: parsedName, ext } = parseFileName(name)
|
const { name: parsedName, ext } = parseFileName(name)
|
||||||
return zippableFileName(parsedName, '', ext)
|
return zippableFileName(parsedName, suffix, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ZippableData = {
|
type ZippableData = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useState, useEffect, useMemo, useCallback } from 'react'
|
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||||
import { NoteType, Platform, SNNote } from '@standardnotes/snjs'
|
import { NoteType, Platform, SNNote, pluralize } from '@standardnotes/snjs'
|
||||||
import {
|
import {
|
||||||
CHANGE_EDITOR_WIDTH_COMMAND,
|
CHANGE_EDITOR_WIDTH_COMMAND,
|
||||||
OPEN_NOTE_HISTORY_COMMAND,
|
OPEN_NOTE_HISTORY_COMMAND,
|
||||||
@@ -99,6 +99,13 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const downloadSelectedItems = useCallback(async () => {
|
const downloadSelectedItems = useCallback(async () => {
|
||||||
|
if (notes.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const toast = addToast({
|
||||||
|
type: ToastType.Progress,
|
||||||
|
message: `Exporting ${notes.length} ${pluralize(notes.length, 'note', 'notes')}...`,
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
const result = await createNoteExport(application, notes)
|
const result = await createNoteExport(application, notes)
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@@ -113,12 +120,14 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
|||||||
filename: fileName,
|
filename: fileName,
|
||||||
isNativeMobileWeb: application.isNativeMobileWeb(),
|
isNativeMobileWeb: application.isNativeMobileWeb(),
|
||||||
})
|
})
|
||||||
|
dismissToast(toast)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
addToast({
|
addToast({
|
||||||
type: ToastType.Error,
|
type: ToastType.Error,
|
||||||
message: 'Could not export notes',
|
message: 'Could not export notes',
|
||||||
})
|
})
|
||||||
|
dismissToast(toast)
|
||||||
}
|
}
|
||||||
}, [application, notes])
|
}, [application, notes])
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter'
|
import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter'
|
||||||
import { NoteType, PrefKey, SNNote, PrefDefaults, FileItem, PrefValue, pluralize } from '@standardnotes/snjs'
|
import { NoteType, PrefKey, SNNote, PrefDefaults, FileItem, PrefValue } from '@standardnotes/snjs'
|
||||||
import { WebApplicationInterface, parseAndCreateZippableFileName, sanitizeFileName } from '@standardnotes/ui-services'
|
import { WebApplicationInterface, parseAndCreateZippableFileName } from '@standardnotes/ui-services'
|
||||||
import { ZipDirectoryEntry } from '@zip.js/zip.js'
|
import { ZipDirectoryEntry } from '@zip.js/zip.js'
|
||||||
|
// @ts-expect-error Using inline loaders to load CSS as string
|
||||||
|
import superEditorCSS from '!css-loader!sass-loader!../Components/SuperEditor/Lexical/Theme/editor.scss'
|
||||||
|
// @ts-expect-error Using inline loaders to load CSS as string
|
||||||
|
import snColorsCSS from '!css-loader!sass-loader!@standardnotes/styles/src/Styles/_colors.scss'
|
||||||
|
// @ts-expect-error Using inline loaders to load CSS as string
|
||||||
|
import exportOverridesCSS from '!css-loader!sass-loader!../Components/SuperEditor/Lexical/Theme/export-overrides.scss'
|
||||||
|
import { getBase64FromBlob } from './Utils'
|
||||||
|
import { parseFileName } from '@standardnotes/filepicker'
|
||||||
|
|
||||||
export const getNoteFormat = (application: WebApplicationInterface, note: SNNote) => {
|
export const getNoteFormat = (application: WebApplicationInterface, note: SNNote) => {
|
||||||
if (note.noteType === NoteType.Super) {
|
if (note.noteType === NoteType.Super) {
|
||||||
@@ -25,15 +33,6 @@ export const getNoteFileName = (application: WebApplicationInterface, note: SNNo
|
|||||||
|
|
||||||
const headlessSuperConverter = new HeadlessSuperConverter()
|
const headlessSuperConverter = new HeadlessSuperConverter()
|
||||||
|
|
||||||
// @ts-expect-error Using inline loaders to load CSS as string
|
|
||||||
import superEditorCSS from '!css-loader!sass-loader!../Components/SuperEditor/Lexical/Theme/editor.scss'
|
|
||||||
// @ts-expect-error Using inline loaders to load CSS as string
|
|
||||||
import snColorsCSS from '!css-loader!sass-loader!@standardnotes/styles/src/Styles/_colors.scss'
|
|
||||||
// @ts-expect-error Using inline loaders to load CSS as string
|
|
||||||
import exportOverridesCSS from '!css-loader!sass-loader!../Components/SuperEditor/Lexical/Theme/export-overrides.scss'
|
|
||||||
import { getBase64FromBlob } from './Utils'
|
|
||||||
import { ToastType, addToast, dismissToast } from '@standardnotes/toast'
|
|
||||||
|
|
||||||
const superHTML = (note: SNNote, content: string) => `<!DOCTYPE html>
|
const superHTML = (note: SNNote, content: string) => `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -175,11 +174,6 @@ export const createNoteExport = async (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const toast = addToast({
|
|
||||||
type: ToastType.Progress,
|
|
||||||
message: `Exporting ${notes.length} ${pluralize(notes.length, 'note', 'notes')}...`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const superExportFormatPref = application.getPreference(
|
const superExportFormatPref = application.getPreference(
|
||||||
PrefKey.SuperNoteExportFormat,
|
PrefKey.SuperNoteExportFormat,
|
||||||
PrefDefaults[PrefKey.SuperNoteExportFormat],
|
PrefDefaults[PrefKey.SuperNoteExportFormat],
|
||||||
@@ -192,7 +186,6 @@ export const createNoteExport = async (
|
|||||||
if (notes.length === 1 && !noteRequiresFolder(notes[0], superExportFormatPref, superEmbedBehaviorPref)) {
|
if (notes.length === 1 && !noteRequiresFolder(notes[0], superExportFormatPref, superEmbedBehaviorPref)) {
|
||||||
const blob = await getNoteBlob(application, notes[0], superEmbedBehaviorPref)
|
const blob = await getNoteBlob(application, notes[0], superEmbedBehaviorPref)
|
||||||
const fileName = getNoteFileName(application, notes[0])
|
const fileName = getNoteFileName(application, notes[0])
|
||||||
dismissToast(toast)
|
|
||||||
return {
|
return {
|
||||||
blob,
|
blob,
|
||||||
fileName,
|
fileName,
|
||||||
@@ -211,31 +204,37 @@ export const createNoteExport = async (
|
|||||||
await addEmbeddedFilesToFolder(application, notes[0], root)
|
await addEmbeddedFilesToFolder(application, notes[0], root)
|
||||||
|
|
||||||
const zippedBlob = await zipFS.exportBlob()
|
const zippedBlob = await zipFS.exportBlob()
|
||||||
dismissToast(toast)
|
|
||||||
return {
|
return {
|
||||||
blob: zippedBlob,
|
blob: zippedBlob,
|
||||||
fileName: fileName + '.zip',
|
fileName: fileName + '.zip',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filenameCounts: Record<string, number> = {}
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
const blob = await getNoteBlob(application, note, superEmbedBehaviorPref)
|
const blob = await getNoteBlob(application, note, superEmbedBehaviorPref)
|
||||||
const fileName = parseAndCreateZippableFileName(getNoteFileName(application, note))
|
const _name = getNoteFileName(application, note)
|
||||||
|
|
||||||
|
filenameCounts[_name] = filenameCounts[_name] == undefined ? 0 : filenameCounts[_name] + 1
|
||||||
|
|
||||||
|
const currentFileNameIndex = filenameCounts[_name]
|
||||||
|
|
||||||
|
const fileName = parseAndCreateZippableFileName(_name, currentFileNameIndex > 0 ? ` - ${currentFileNameIndex}` : '')
|
||||||
|
|
||||||
if (!noteRequiresFolder(note, superExportFormatPref, superEmbedBehaviorPref)) {
|
if (!noteRequiresFolder(note, superExportFormatPref, superEmbedBehaviorPref)) {
|
||||||
root.addBlob(fileName, blob)
|
root.addBlob(fileName, blob)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = root.addDirectory(sanitizeFileName(note.title))
|
const { name } = parseFileName(fileName)
|
||||||
|
const folder = root.addDirectory(name)
|
||||||
folder.addBlob(fileName, blob)
|
folder.addBlob(fileName, blob)
|
||||||
await addEmbeddedFilesToFolder(application, note, folder)
|
await addEmbeddedFilesToFolder(application, note, folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
const zippedBlob = await zipFS.exportBlob()
|
const zippedBlob = await zipFS.exportBlob()
|
||||||
|
|
||||||
dismissToast(toast)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
blob: zippedBlob,
|
blob: zippedBlob,
|
||||||
fileName: `Standard Notes Export - ${application.archiveService.formattedDateForExports()}.zip`,
|
fileName: `Standard Notes Export - ${application.archiveService.formattedDateForExports()}.zip`,
|
||||||
|
|||||||
Reference in New Issue
Block a user