-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat Add DownloadMenu component for exporting markdown files
Fix missing file name
- Loading branch information
Showing
9 changed files
with
225 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { SaveAlt as SaveAltIcon } from "@mui/icons-material"; | ||
import { IconButton, Menu, MenuItem, Paper } from "@mui/material"; | ||
import { MouseEvent, useCallback, useState } from "react"; | ||
import { useFileExport } from "../../hooks/useFileExport"; | ||
|
||
const DownloadMenu = () => { | ||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null); | ||
|
||
const handleClick = (event: MouseEvent<HTMLButtonElement>) => { | ||
setAnchorEl(event.currentTarget); | ||
}; | ||
|
||
const handleClose = () => { | ||
setAnchorEl(null); | ||
}; | ||
|
||
const { exportToPDF, exportToTXT, exportToDOCX } = useFileExport(); | ||
|
||
const handleExportToPDF = useCallback(() => { | ||
exportToPDF(); | ||
handleClose(); | ||
}, [exportToPDF]); | ||
|
||
const handleExportToTXT = useCallback(() => { | ||
exportToTXT(); | ||
handleClose(); | ||
}, [exportToTXT]); | ||
|
||
const handleExportToDOCX = useCallback(() => { | ||
exportToDOCX(); | ||
handleClose(); | ||
}, [exportToDOCX]); | ||
|
||
return ( | ||
<Paper> | ||
<IconButton aria-controls="download-menu" aria-haspopup="true" onClick={handleClick}> | ||
<SaveAltIcon /> | ||
</IconButton> | ||
<Menu | ||
id="download-menu" | ||
anchorEl={anchorEl} | ||
keepMounted | ||
open={Boolean(anchorEl)} | ||
onClose={handleClose} | ||
> | ||
<MenuItem onClick={handleExportToPDF}>Download as PDF</MenuItem> | ||
<MenuItem onClick={handleExportToTXT}>Download as TXT</MenuItem> | ||
<MenuItem onClick={handleExportToDOCX}>Download as DOCX</MenuItem> | ||
</Menu> | ||
</Paper> | ||
); | ||
}; | ||
|
||
export default DownloadMenu; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { MarkdownPreviewRef } from "@uiw/react-markdown-preview"; | ||
import React from "react"; | ||
|
||
export interface PreviewRefContextValue { | ||
previewRef: React.RefObject<MarkdownPreviewRef>; | ||
} | ||
|
||
export const PreviewRefContext = React.createContext<PreviewRefContextValue>({ | ||
previewRef: { current: null }, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { Document, Packer, Paragraph, TextRun } from "docx"; | ||
import { saveAs } from "file-saver"; | ||
import jspdfHtml2canvas from "jspdf-html2canvas"; | ||
import { useSnackbar } from "notistack"; | ||
import { useCallback, useContext } from "react"; | ||
import { useSelector } from "react-redux"; | ||
import { PreviewRefContext } from "../contexts/PreviewRefContext"; | ||
import { selectEditor } from "../store/editorSlice"; | ||
import { documentNameStorage } from "../utils/localStorage"; | ||
|
||
interface useFileExportReturn { | ||
exportToPDF: () => Promise<void>; | ||
exportToTXT: () => void; | ||
exportToDOCX: () => void; | ||
} | ||
|
||
export const useFileExport = (): useFileExportReturn => { | ||
const editorStore = useSelector(selectEditor); | ||
const markdown = editorStore.doc?.getRoot().content?.toString() || ""; | ||
|
||
const { previewRef } = useContext(PreviewRefContext); | ||
|
||
const { enqueueSnackbar } = useSnackbar(); | ||
|
||
const documentName = documentNameStorage.getDocumentName() || 'codepair_document'; | ||
|
||
const exportToPDF = useCallback(async () => { | ||
if (previewRef.current?.mdp && previewRef.current.mdp.current instanceof HTMLDivElement) { | ||
try { | ||
await jspdfHtml2canvas(previewRef.current.mdp.current, { | ||
output: `${documentName}`, | ||
jsPDF: { | ||
format: "a4", | ||
orientation: "portrait", | ||
}, | ||
html2canvas: { | ||
scale: 3, | ||
}, | ||
}); | ||
} catch (error) { | ||
if(previewRef.current.mdp.current) { | ||
enqueueSnackbar("Content is empty", { variant: "error" }); | ||
} else { | ||
enqueueSnackbar("Failed to export PDF", { variant: "error" }); | ||
} | ||
} | ||
} else { | ||
enqueueSnackbar("Please try again", { variant: "error" }); | ||
} | ||
}, [previewRef, enqueueSnackbar, documentName]); | ||
|
||
const exportToTXT = useCallback(() => { | ||
const blob = new Blob([markdown], { type: "text/plain;charset=utf-8" }); | ||
saveAs(blob, documentName); | ||
}, [documentName, markdown]); | ||
|
||
const exportToDOCX = useCallback(() => { | ||
const doc = new Document({ | ||
sections: [ | ||
{ | ||
properties: {}, | ||
children: [ | ||
new Paragraph({ | ||
children: [new TextRun(markdown)], | ||
}), | ||
], | ||
}, | ||
], | ||
}); | ||
|
||
Packer.toBlob(doc).then((blob) => { | ||
saveAs(blob, documentName); | ||
}); | ||
}, [markdown, documentName]); | ||
|
||
return { exportToPDF, exportToTXT, exportToDOCX }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { MarkdownPreviewRef } from "@uiw/react-markdown-preview"; | ||
import { PropsWithChildren, useRef } from "react"; | ||
import { PreviewRefContext } from "../contexts/PreviewRefContext"; | ||
|
||
export default function PreviewRefProvider(props: PropsWithChildren) { | ||
const { children } = props; | ||
const previewRef = useRef<MarkdownPreviewRef | null>(null); | ||
|
||
return ( | ||
<PreviewRefContext.Provider value={{ previewRef }}>{children}</PreviewRefContext.Provider> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const CURRENT_DOCUMENT_NAME = "CURRENT_DOCUMENT_NAME"; | ||
|
||
const getDocumentName = () => { | ||
return localStorage.getItem(CURRENT_DOCUMENT_NAME); | ||
} | ||
|
||
const setDocumentName = (name: string) => { | ||
localStorage.setItem(CURRENT_DOCUMENT_NAME, name); | ||
} | ||
|
||
const deleteDocumentName = () => { | ||
localStorage.removeItem(CURRENT_DOCUMENT_NAME); | ||
} | ||
|
||
const documentNameStorage = { | ||
getDocumentName, | ||
setDocumentName, | ||
deleteDocumentName, | ||
}; | ||
|
||
export { documentNameStorage }; | ||
|