Add batch export folder/storage in context menu #734
Conversation
9edb7de to
30b0ad1
Compare
Rokt33r
left a comment
There was a problem hiding this comment.
We should do refactoring first. It is too difficult to review this pr. So, please try this.
- Reduce arguments
- Provide proper names for variables and functions(The names should explain what their variables and functions actually do
| const { t } = useTranslation() | ||
| const { storageMap } = useDb() | ||
|
|
||
| const isExportCanceledRef = useRef<boolean>(false) |
| folderName: string | ||
| folderPathname: string | ||
| exportType: string | ||
| isRecursive: boolean |
| folderPathname: string | ||
| exportType: string | ||
| isRecursive: boolean | ||
| isStorageExport: boolean |
| [] | ||
| ) | ||
|
|
||
| const exportCanceled = useCallback(() => { |
| }) | ||
| } | ||
|
|
||
| export function getValidNoteTitle(note: NoteDoc): string { |
There was a problem hiding this comment.
note._id is not a title. Please return Untitled
There was a problem hiding this comment.
I want to return untitled, but if we do that, two notes within the same folder with no title would be named "Untitled.ext", which would overwrite the note contents.
So the solution is some kind of ID for untitled notes, either note._id or some index we could introduce when exporting notes.
There was a problem hiding this comment.
Hmm, how do you think appending note id after the title?
Untitled-XXXXXXXX.md
Untitled-XXXXXXXX.md
There was a problem hiding this comment.
That's great, if note id is something we can let users know of, I think that should be the easiest, safe solution.
| return Math.floor((value / maxValue) * exportProgressMaxValue) | ||
| } | ||
|
|
||
| async function writeNoteData( |
There was a problem hiding this comment.
I don't see any benefits to have this method. It is just writeFile
| async function writeNoteData( | ||
| noteData: Buffer | string, | ||
| location: string, | ||
| exportErrors: string[] |
There was a problem hiding this comment.
Don't provide an array like this. This design makes keeping status difficult
There was a problem hiding this comment.
Method removed!
| } | ||
| } | ||
|
|
||
| export async function batchExport( |
There was a problem hiding this comment.
Will try to refactor it in smaller functions, but a lot of arguments are needed because it calls each export type, for each note, creates directories etc.
I think we could design communication between component and exports.ts in a more clear way, and this could reduce the number of arguments
| return Math.floor((value / maxValue) * exportProgressMaxValue) | ||
| } | ||
|
|
||
| async function writeNoteData( |
There was a problem hiding this comment.
This is just writeFile with some extra. I don't think we need this funciton
| } | ||
| } | ||
|
|
||
| async function getAttachmentData(storage: NoteStorage, src: string) { |
There was a problem hiding this comment.
- Please throw error rather than returning reject
- Rename the function. Its name should explain what it does.
- Reconsider that we really need this function.
There was a problem hiding this comment.
Refactored/removed.
6e68773 to
dab5eff
Compare
| ) | ||
| let exportingNoteCount = calculateNoteCountCondition ? 0 : noteCount | ||
| if (exportSettings.exportingStorage) { | ||
| exportingNoteCount = values(noteMap).length |
There was a problem hiding this comment.
Avoid nested conditional
if (exportSettings.exportingStorage) {
return values(noteMap).length
}
...
There was a problem hiding this comment.
Refactored this altogether, so no longer here!
| if ( | ||
| noteDoc.trashed || | ||
| (exportSettings.recursive && | ||
| !noteDoc.folderPathname.startsWith( |
There was a problem hiding this comment.
I think we cannot use startsWith in this case. For example, when exporting /my-folder, /my-folder2 probably considered as a child folder
There was a problem hiding this comment.
You are right, I added additional "/" to differentiate between startsWith and subfolder which starts with a pathname.
I tested it and it seems to work fine, would that be error prone for all cases?
| for (const [, noteDoc] of entries(noteMap)) { | ||
| if ( | ||
| noteDoc.trashed || | ||
| (exportSettings.recursive && |
There was a problem hiding this comment.
Separate conditions for better readability
if (noteDoc.trashed) {
continue
}
if (exportSettings.recursive && !noteDoc.folderPathname.startsWith) {
continue
}|
|
||
| const [showExportProgress, setShowExportProgress] = useState(false) | ||
| const [exportProgressMaxValue] = useState(100) | ||
| const [exportErrors, setExportErrors] = useState([] as string[]) |
There was a problem hiding this comment.
Prefer generic useState<string[]>([])
There was a problem hiding this comment.
Oh, didn't know about that, adjusted!
| const { storageMap } = useDb() | ||
|
|
||
| const [showExportProgress, setShowExportProgress] = useState(false) | ||
| const [exportProgressMaxValue] = useState(100) |
There was a problem hiding this comment.
This is a const, not a state. Discard this hook and define the value as a const outside of this function component
| const totalNotesToExport = calculateNumberOfExportingNotes(noteMap) | ||
|
|
||
| let exportingNoteIndex = 0 | ||
| for (const [, noteDoc] of entries(noteMap)) { |
There was a problem hiding this comment.
We've already known what to export while executing calculateNumberOfExportingNotes. I think we should refactor calculateNumberOfExportingNotes so exporting doesn't have to check whole notes twice.
There was a problem hiding this comment.
Refactored this, now looks better, no repetition!
| ]) | ||
|
|
||
| useEffectOnce(() => { | ||
| exportDocuments().then(onFinish).catch(onFinish) |
There was a problem hiding this comment.
If we run onFinish either results, please use .finally method. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
There was a problem hiding this comment.
Refactored to finally!
| ] | ||
| ) | ||
|
|
||
| const exportDocuments = useCallback(async () => { |
| [storage.id, note, updateNote] | ||
| ) | ||
|
|
||
| const getAttachmentData = useCallback( |
There was a problem hiding this comment.
Please revoke this change. All attachments must be retrieved from this method.
There was a problem hiding this comment.
If we really need to access the map, then we can introduce a new method to retrieve it. But please avoid introducing the method if possible.
There was a problem hiding this comment.
Umm, I am confused by this comments.
So in essence, we need to have attachments data, this means attachmentMap is an variable wee need access to.
This can be done either via sending it in a method and using it directly, or (as previously coded) using the getAttachmentData method, which was retriving attachment data for a particular src value.
From first comment, you said we should not need this method maybe, so I removed it in this refactor, and used attachmentMap directly.
How would new method to retrieve it be used like? do you mean a method to retrieve attachmentMap, or attachment data? or something else?
I cannot really think of a way to get needed attachments without using attachmentData or some method to retrtieve it for particular found src in content.
Maybe we could somehow preprocess content and fetch attachments needed (similar to how it is done in export now)
and send attachments needed to export? Or in the preprocess step embedd needed changes, thus not needing to send the attachments at all - and send modified content?
| }) | ||
| } | ||
|
|
||
| export function getValidNoteTitle(note: NoteDoc): string { |
There was a problem hiding this comment.
Hmm, how do you think appending note id after the title?
Untitled-XXXXXXXX.md
Untitled-XXXXXXXX.md
624955b to
5096b9a
Compare
5096b9a to
14f3272
Compare
14f3272 to
10cbed4
Compare
10cbed4 to
59f54f1
Compare
Add prepare export functions in exports.ts Add handling of menus for folder exports Add support for non-recursive/recursive exports Add export progress bar Add support for storage export Add component for batch export handling (state, procedure) Refactor folder and storage export to use the component Fix cancelation state logic Add dim background on export procedure Add support for note statistics Add current note exporting vs total number of notes to export beside export note operation Remove getAttachment data helper function Set note title to untitled if note has no title Refactor writeNoteData function Fix PDF export push message to include export type (HTML when set) Update naming of few variables in export Remove unnecessary try/catch in PDF conversion function Refactor attachment linking Add better handling of injecting attachments Removed old code for pdf conversion header/footer Fix attachment rendering in PDF export from HTML tag Add support for attachment rendering from HTML tags for PDF export Add better support for attachment regex: single and double qoutes Add untitled-note_id for notes without title Fix startsWith condition by adding additional '/' to find sub-folders Update code after rebase v0.14.1 Fix progress bar (use new one) Refactor NoteContextMenu Update batch export after rebase v0.14.3 Update batch export to use new exports API Update exports to validate untitled note names
59f54f1 to
05745fc
Compare
Initial batch export for PDF (#491)
Add prepare export functions in exports.ts
Add handling of menus for folder exports
Add support for non-recursive/recursive exports
Add export progress bar
Add support for storage/workspace export
Refactor functions for exports
Add component for batch export handling (state, procedure)
Refactor folder and storage export to use the component
Fix cancelation state logic
Add dim background on export procedure
General functionality:
Users can export folders via the context menu.

By right-clicking on the desired folder the context menu with export options comes up:
A similar context menu dialog is available for workspace/storage navigation item (for exporting whole storage)

Once the exported is selected, the file explorer comes up and offers the user to choose the save location.
When the location is confirmed export process starts and users can see export progress:
First directories are created in the destination (depending on recursive/non-recursive and selected folder/storage)
Then notes are being exported to their appropriate locations:
During export, for now, users can only cancel the export.
This stops the export, and the message of the export state is shown as a push message:
During export, export errors generated from export functions are shown in push messages as well.
In the end, the summary is also generated, as well as any export errors.
Summary for successfully finish - push message:
The whole procedure while exporting can be seen here:

Error handling:
When directory creation fails, the export fails
When one note export fails, the export process continues
Any unknown error happens, it is caught and the user is notified (as well as
console.warn)Add options in progress bar modal
Cancelable promise API instead of useRef
Option to hide export dialog (send to background/status bar)
Status bar rendering of export, re-activate export modal
Handle overwrites (dirs, notes)
Add translations for batch export strings
Better export modal dialog design
Test: