feat(export): add JSON format support for export functions and enhance export options

This commit is contained in:
troyeguo committed 2026-09-12 17:43:54 +08:00
1 parent 6e9fc1bc0a
commit d8db403e7f
5 files changed
+193 -99

No files matched your search

+46 -41
View File
@@ -35,27 +35,51 @@ class MoreAction extends React.Component<MoreActionProps, MoreActionState> {
this.state = { exportSubmenu: "" };
}
renderFormatSubmenu(type: "notes" | "highlights") {
renderFormatSubmenu(type: "notes" | "highlights" | "words") {
const isVisible = this.state.exportSubmenu === type;
const isNotes = type === "notes";
const filterFn = isNotes
? (note: any) =>
note.notes && note.notes.length > 0 && note.notes !== "annotation"
: (note: any) => note.notes === "";
const exportFn = isNotes ? exportNotes : exportHighlights;
const isWords = type === "words";
const filterFn = isWords
? () => true
: isNotes
? (note: any) =>
note.notes && note.notes.length > 0 && note.notes !== "annotation"
: (note: any) => note.notes === "";
const exportFn = (
isWords
? exportDictionaryHistory
: isNotes
? exportNotes
: exportHighlights
) as (
records: any[],
books: any[],
format: "csv" | "md" | "txt" | "html" | "pdf" | "json"
) => Promise<"success" | "failed" | "cancel">;
const formats = isWords
? (["csv", "json"] as const)
: (["csv", "md", "txt", "html", "pdf", "json"] as const);
const formatLabels: Record<string, string> = {
csv: "CSV",
md: "Markdown",
txt: "TXT",
html: "HTML",
pdf: "PDF",
json: "JSON",
};
const handleExport = async (
format: "csv" | "md" | "txt" | "html" | "pdf"
format: "csv" | "md" | "txt" | "html" | "pdf" | "json"
) => {
let books = await DatabaseService.getAllRecords("books");
let notes = (
let records = (
await DatabaseService.getRecordsByBookKey(
this.props.currentBook.key,
"notes"
isWords ? "words" : "notes"
)
).filter(filterFn);
if (notes.length > 0) {
const result = await exportFn(notes, books, format);
if (records.length > 0) {
const result = await exportFn(records, books, format);
if (result === "success") {
toast.success(this.props.t("Export successful"), { id: "exporting" });
} else if (result === "failed") {
@@ -93,7 +117,7 @@ class MoreAction extends React.Component<MoreActionProps, MoreActionState> {
195,
mainMenuPos.top + noteOffset * itemHeight,
120,
estimateMenuHeight(5)
estimateMenuHeight(isWords ? 2 : 6)
);
return {
position: "fixed",
@@ -114,24 +138,14 @@ class MoreAction extends React.Component<MoreActionProps, MoreActionState> {
}}
>
<div className="action-dialog-actions-container">
{(["csv", "md", "txt", "html", "pdf"] as const).map((fmt) => (
{formats.map((fmt) => (
<div
key={fmt}
className="action-dialog-edit"
style={{ paddingLeft: "0px" }}
onClick={() => handleExport(fmt)}
>
<p className="action-name">
{fmt === "csv"
? "CSV"
: fmt === "md"
? "Markdown"
: fmt === "txt"
? "TXT"
: fmt === "html"
? "HTML"
: "PDF"}
</p>
<p className="action-name">{formatLabels[fmt]}</p>
</div>
))}
</div>
@@ -271,26 +285,16 @@ class MoreAction extends React.Component<MoreActionProps, MoreActionState> {
<div
className="action-dialog-edit"
style={{ paddingLeft: "0px" }}
onClick={async () => {
let dictHistory = await DatabaseService.getRecordsByBookKey(
this.props.currentBook.key,
"words"
);
let books = await DatabaseService.getAllRecords("books");
if (dictHistory.length > 0) {
const result = await exportDictionaryHistory(dictHistory, books);
if (result === "success") {
toast.success(this.props.t("Export successful"), { id: "exporting" });
} else if (result === "failed") {
toast.error(this.props.t("Failed to export"), { id: "exporting" });
}
} else {
toast(this.props.t("Nothing to export"));
}
onMouseEnter={() => {
this.setState({ exportSubmenu: "words" });
}}
onMouseLeave={() => {
this.setState({ exportSubmenu: "" });
}}
>
<p className="action-name">
<p className="action-name export-action-name">
<Trans>Export dictionary history</Trans>
<span className="icon-dropdown icon-export-all"></span>
</p>
</div>
<div
@@ -434,6 +438,7 @@ class MoreAction extends React.Component<MoreActionProps, MoreActionState> {
</div>
{this.renderFormatSubmenu("notes")}
{this.renderFormatSubmenu("highlights")}
{this.renderFormatSubmenu("words")}
</>
);
}
+94 -45
View File
@@ -213,7 +213,7 @@ class SelectBook extends React.Component<BookListProps, BookListState> {
this.setState({ exportSubmenu: "", isShowExport: false });
}}
>
{(["csv", "md", "txt", "html", "pdf"] as const).map(
{(["csv", "md", "txt", "html", "pdf", "json"] as const).map(
(fmt) => (
<span
key={fmt}
@@ -250,15 +250,16 @@ class SelectBook extends React.Component<BookListProps, BookListState> {
});
}}
>
{fmt === "csv"
? "CSV"
: fmt === "md"
? "Markdown"
: fmt === "txt"
? "TXT"
: fmt === "html"
? "HTML"
: "PDF"}
{
({
csv: "CSV",
md: "Markdown",
txt: "TXT",
html: "HTML",
pdf: "PDF",
json: "JSON",
} as Record<string, string>)[fmt]
}
</span>
)
)}
@@ -297,7 +298,7 @@ class SelectBook extends React.Component<BookListProps, BookListState> {
this.setState({ exportSubmenu: "", isShowExport: false });
}}
>
{(["csv", "md", "txt", "html", "pdf"] as const).map(
{(["csv", "md", "txt", "html", "pdf", "json"] as const).map(
(fmt) => (
<span
key={fmt}
@@ -330,46 +331,94 @@ class SelectBook extends React.Component<BookListProps, BookListState> {
});
}}
>
{fmt === "csv"
? "CSV"
: fmt === "md"
? "Markdown"
: fmt === "txt"
? "TXT"
: fmt === "html"
? "HTML"
: "PDF"}
{
({
csv: "CSV",
md: "Markdown",
txt: "TXT",
html: "HTML",
pdf: "PDF",
json: "JSON",
} as Record<string, string>)[fmt]
}
</span>
)
)}
</div>
</div>
<span
className="book-manage-title select-book-action"
onClick={async () => {
let selectedBooks = await DatabaseService.getRecordsByKeys(
this.props.selectedBooks,
"books"
);
let dictHistory =
await DatabaseService.getRecordsByBookKeys(
this.props.selectedBooks,
"words"
);
if (dictHistory.length > 0) {
const result = await exportDictionaryHistory(dictHistory, selectedBooks);
if (result === "success") {
toast.success(this.props.t("Export successful"), { id: "exporting" });
} else if (result === "failed") {
toast.error(this.props.t("Failed to export"), { id: "exporting" });
}
} else {
toast(this.props.t("Nothing to export"));
<div style={{ position: "relative" }}>
<span
className="book-manage-title select-book-action"
onMouseEnter={() => {
this.setState({
exportSubmenu: "words",
isShowExport: true,
});
}}
onMouseLeave={() => {
this.setState({ exportSubmenu: "" });
}}
>
<Trans>Export dictionary history</Trans>
<span className="icon-dropdown icon-export-all"></span>
</span>
<div
className="select-more-actions select-export-format-submenu"
style={
this.state.exportSubmenu === "words"
? { left: "160px", bottom: "auto", top: "0px" }
: { display: "none" }
}
}}
>
<Trans>Export dictionary history</Trans>
</span>
onMouseEnter={() => {
this.setState({
exportSubmenu: "words",
isShowExport: true,
});
}}
onMouseLeave={() => {
this.setState({ exportSubmenu: "", isShowExport: false });
}}
>
{(["csv", "json"] as const).map((fmt) => (
<span
key={fmt}
className="book-manage-title select-book-action"
onClick={async () => {
let selectedBooks =
await DatabaseService.getRecordsByKeys(
this.props.selectedBooks,
"books"
);
let dictHistory =
await DatabaseService.getRecordsByBookKeys(
this.props.selectedBooks,
"words"
);
if (dictHistory.length > 0) {
const result = await exportDictionaryHistory(
dictHistory,
selectedBooks,
fmt
);
if (result === "success") {
toast.success(this.props.t("Export successful"), { id: "exporting" });
} else if (result === "failed") {
toast.error(this.props.t("Failed to export"), { id: "exporting" });
}
} else {
toast(this.props.t("Nothing to export"));
}
this.setState({
exportSubmenu: "",
isShowExport: false,
});
}}
>
{fmt === "csv" ? "CSV" : "JSON"}
</span>
))}
</div>
</div>
<span
className="book-manage-title select-book-action"
onClick={async () => {