mirror of
https://github.com/zadam/trilium.git
synced 2025-11-08 23:49:00 +01:00
feat(react/modals): port export dialog
This commit is contained in:
parent
a593ce7c40
commit
90f9416524
16
apps/client/src/widgets/dialogs/export.css
Normal file
16
apps/client/src/widgets/dialogs/export.css
Normal file
@ -0,0 +1,16 @@
|
||||
.export-dialog form .form-check {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.export-dialog form .format-choice {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.export-dialog form .opml-versions {
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
.export-dialog form .form-check-label {
|
||||
padding: 2px;
|
||||
}
|
||||
@ -1,264 +0,0 @@
|
||||
import treeService from "../../services/tree.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import toastService, { type ToastOptions } from "../../services/toast.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import openService from "../../services/open.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { Modal } from "bootstrap";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="export-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<style>
|
||||
.export-dialog .export-form .form-check {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.export-dialog .export-form .format-choice {
|
||||
padding-left: 40px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.export-dialog .export-form .opml-versions {
|
||||
padding-left: 60px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.export-dialog .export-form .form-check-label {
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t("export.export_note_title")} <span class="export-note-title"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("export.close")}"></button>
|
||||
</div>
|
||||
<form class="export-form">
|
||||
<div class="modal-body">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="export-type-subtree form-check-input" type="radio" name="export-type" value="subtree">
|
||||
${t("export.export_type_subtree")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="export-subtree-formats format-choice">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="export-subtree-format" value="html">
|
||||
${t("export.format_html_zip")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="export-subtree-format" value="markdown">
|
||||
${t("export.format_markdown")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="export-subtree-format" value="opml">
|
||||
${t("export.format_opml")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="opml-versions">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="opml-version" value="1.0">
|
||||
${t("export.opml_version_1")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="opml-version" value="2.0">
|
||||
${t("export.opml_version_2")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="export-type" value="single">
|
||||
${t("export.export_type_single")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="export-single-formats format-choice">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="export-single-format" value="html">
|
||||
${t("export.format_html")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label tn-radio">
|
||||
<input class="form-check-input" type="radio" name="export-single-format" value="markdown">
|
||||
${t("export.format_markdown")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="export-button btn btn-primary">${t("export.export")}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class ExportDialog extends BasicWidget {
|
||||
|
||||
private taskId: string;
|
||||
private branchId: string | null;
|
||||
private modal?: Modal;
|
||||
private $form!: JQuery<HTMLElement>;
|
||||
private $noteTitle!: JQuery<HTMLElement>;
|
||||
private $subtreeFormats!: JQuery<HTMLElement>;
|
||||
private $singleFormats!: JQuery<HTMLElement>;
|
||||
private $subtreeType!: JQuery<HTMLElement>;
|
||||
private $singleType!: JQuery<HTMLElement>;
|
||||
private $exportButton!: JQuery<HTMLElement>;
|
||||
private $opmlVersions!: JQuery<HTMLElement>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.taskId = "";
|
||||
this.branchId = null;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
|
||||
this.$form = this.$widget.find(".export-form");
|
||||
this.$noteTitle = this.$widget.find(".export-note-title");
|
||||
this.$subtreeFormats = this.$widget.find(".export-subtree-formats");
|
||||
this.$singleFormats = this.$widget.find(".export-single-formats");
|
||||
this.$subtreeType = this.$widget.find(".export-type-subtree");
|
||||
this.$singleType = this.$widget.find(".export-type-single");
|
||||
this.$exportButton = this.$widget.find(".export-button");
|
||||
this.$opmlVersions = this.$widget.find(".opml-versions");
|
||||
|
||||
this.$form.on("submit", () => {
|
||||
this.modal?.hide();
|
||||
|
||||
const exportType = this.$widget.find("input[name='export-type']:checked").val();
|
||||
|
||||
if (!exportType) {
|
||||
toastService.showError(t("export.choose_export_type"));
|
||||
return;
|
||||
}
|
||||
|
||||
const exportFormat = exportType === "subtree" ? this.$widget.find("input[name=export-subtree-format]:checked").val() : this.$widget.find("input[name=export-single-format]:checked").val();
|
||||
|
||||
const exportVersion = exportFormat === "opml" ? this.$widget.find("input[name='opml-version']:checked").val() : "1.0";
|
||||
|
||||
if (this.branchId) {
|
||||
this.exportBranch(this.branchId, String(exportType), String(exportFormat), String(exportVersion));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$widget.find("input[name=export-type]").on("change", (e) => {
|
||||
if ((e.currentTarget as HTMLInputElement).value === "subtree") {
|
||||
if (this.$widget.find("input[name=export-subtree-format]:checked").length === 0) {
|
||||
this.$widget.find("input[name=export-subtree-format]:first").prop("checked", true);
|
||||
}
|
||||
|
||||
this.$subtreeFormats.slideDown();
|
||||
this.$singleFormats.slideUp();
|
||||
} else {
|
||||
if (this.$widget.find("input[name=export-single-format]:checked").length === 0) {
|
||||
this.$widget.find("input[name=export-single-format]:first").prop("checked", true);
|
||||
}
|
||||
|
||||
this.$subtreeFormats.slideUp();
|
||||
this.$singleFormats.slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
this.$widget.find("input[name=export-subtree-format]").on("change", (e) => {
|
||||
if ((e.currentTarget as HTMLInputElement).value === "opml") {
|
||||
this.$opmlVersions.slideDown();
|
||||
} else {
|
||||
this.$opmlVersions.slideUp();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async showExportDialogEvent({ notePath, defaultType }: EventData<"showExportDialog">) {
|
||||
this.taskId = "";
|
||||
this.$exportButton.removeAttr("disabled");
|
||||
|
||||
if (defaultType === "subtree") {
|
||||
this.$subtreeType.prop("checked", true).trigger("change");
|
||||
|
||||
this.$widget.find("input[name=export-subtree-format]:checked").trigger("change");
|
||||
} else if (defaultType === "single") {
|
||||
this.$singleType.prop("checked", true).trigger("change");
|
||||
} else {
|
||||
throw new Error(`Unrecognized type '${defaultType}'`);
|
||||
}
|
||||
|
||||
this.$widget.find(".opml-v2").prop("checked", true); // setting default
|
||||
|
||||
openDialog(this.$widget);
|
||||
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
|
||||
if (parentNoteId) {
|
||||
this.branchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
}
|
||||
if (noteId) {
|
||||
this.$noteTitle.text(await treeService.getNoteTitle(noteId));
|
||||
}
|
||||
}
|
||||
|
||||
exportBranch(branchId: string, type: string, format: string, version: string) {
|
||||
this.taskId = utils.randomString(10);
|
||||
|
||||
const url = openService.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${this.taskId}`);
|
||||
|
||||
openService.download(url);
|
||||
}
|
||||
}
|
||||
|
||||
ws.subscribeToMessages(async (message) => {
|
||||
function makeToast(id: string, message: string): ToastOptions {
|
||||
return {
|
||||
id: id,
|
||||
title: t("export.export_status"),
|
||||
message: message,
|
||||
icon: "arrow-square-up-right"
|
||||
};
|
||||
}
|
||||
|
||||
if (message.taskType !== "export") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === "taskError") {
|
||||
toastService.closePersistent(message.taskId);
|
||||
toastService.showError(message.message);
|
||||
} else if (message.type === "taskProgressCount") {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
}
|
||||
});
|
||||
163
apps/client/src/widgets/dialogs/export.tsx
Normal file
163
apps/client/src/widgets/dialogs/export.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { EventData } from "../../components/app_context";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
|
||||
import { t } from "../../services/i18n";
|
||||
import tree from "../../services/tree";
|
||||
import Button from "../react/Button";
|
||||
import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import "./export.css";
|
||||
import ws from "../../services/ws";
|
||||
import toastService, { ToastOptions } from "../../services/toast";
|
||||
import utils from "../../services/utils";
|
||||
import open from "../../services/open";
|
||||
import froca from "../../services/froca";
|
||||
|
||||
interface ExportDialogProps {
|
||||
branchId?: string | null;
|
||||
noteTitle?: string;
|
||||
defaultType?: "subtree" | "single";
|
||||
}
|
||||
|
||||
function ExportDialogComponent({ branchId, noteTitle, defaultType }: ExportDialogProps) {
|
||||
const [ exportType, setExportType ] = useState<string>(defaultType ?? "subtree");
|
||||
const [ subtreeFormat, setSubtreeFormat ] = useState<string>("html");
|
||||
const [ singleFormat, setSingleFormat ] = useState<string>("html");
|
||||
const [ opmlVersion, setOpmlVersion ] = useState<string>("2.0");
|
||||
|
||||
return (branchId &&
|
||||
<Modal
|
||||
className="export-dialog"
|
||||
title={`${t("export.export_note_title")} ${noteTitle ?? ""}`}
|
||||
size="lg"
|
||||
onSubmit={() => {
|
||||
const format = (exportType === "subtree" ? subtreeFormat : singleFormat);
|
||||
const version = (format === "opml" ? opmlVersion : "1.0");
|
||||
exportBranch(branchId, exportType, format, version);
|
||||
closeActiveDialog();
|
||||
}}
|
||||
footer={<Button className="export-button" text={t("export.export")} primary />}
|
||||
>
|
||||
|
||||
<FormRadioGroup
|
||||
name="export-type"
|
||||
currentValue={exportType} onChange={setExportType}
|
||||
values={[{
|
||||
value: "subtree",
|
||||
label: t("export.export_type_subtree")
|
||||
}]}
|
||||
/>
|
||||
|
||||
{ exportType === "subtree" &&
|
||||
<div className="export-subtree-formats format-choice">
|
||||
<FormRadioGroup
|
||||
name="export-subtree-format"
|
||||
currentValue={subtreeFormat} onChange={setSubtreeFormat}
|
||||
values={[
|
||||
{ value: "html", label: t("export.format_html_zip") },
|
||||
{ value: "markdown", label: t("export.format_markdown") },
|
||||
{ value: "opml", label: t("export.format_opml") }
|
||||
]}
|
||||
/>
|
||||
|
||||
{ subtreeFormat === "opml" &&
|
||||
<div className="opml-versions">
|
||||
<FormRadioGroup
|
||||
name="opml-version"
|
||||
currentValue={opmlVersion} onChange={setOpmlVersion}
|
||||
values={[
|
||||
{ value: "1.0", label: t("export.opml_version_1") },
|
||||
{ value: "2.0", label: t("export.opml_version_2") }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<FormRadioGroup
|
||||
name="export-type"
|
||||
currentValue={exportType} onChange={setExportType}
|
||||
values={[{
|
||||
value: "single",
|
||||
label: t("export.export_type_single")
|
||||
}]}
|
||||
/>
|
||||
|
||||
{ exportType === "single" &&
|
||||
<div class="export-single-formats format-choice">
|
||||
<FormRadioGroup
|
||||
name="export-single-format"
|
||||
currentValue={singleFormat} onChange={setSingleFormat}
|
||||
values={[
|
||||
{ value: "html", label: t("export.format_html") },
|
||||
{ value: "markdown", label: t("export.format_markdown") }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class ExportDialog extends ReactBasicWidget {
|
||||
|
||||
private props: ExportDialogProps = {};
|
||||
|
||||
get component() {
|
||||
return <ExportDialogComponent {...this.props} />
|
||||
}
|
||||
|
||||
async showExportDialogEvent({ notePath, defaultType }: EventData<"showExportDialog">) {
|
||||
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (!parentNoteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const branchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
|
||||
this.props = {
|
||||
noteTitle: noteId && await tree.getNoteTitle(noteId),
|
||||
defaultType,
|
||||
branchId
|
||||
};
|
||||
this.doRender();
|
||||
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
|
||||
function exportBranch(branchId: string, type: string, format: string, version: string) {
|
||||
const taskId = utils.randomString(10);
|
||||
const url = open.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${taskId}`);
|
||||
open.download(url);
|
||||
}
|
||||
|
||||
ws.subscribeToMessages(async (message) => {
|
||||
function makeToast(id: string, message: string): ToastOptions {
|
||||
return {
|
||||
id: id,
|
||||
title: t("export.export_status"),
|
||||
message: message,
|
||||
icon: "arrow-square-up-right"
|
||||
};
|
||||
}
|
||||
|
||||
if (message.taskType !== "export") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === "taskError") {
|
||||
toastService.closePersistent(message.taskId);
|
||||
toastService.showError(message.message);
|
||||
} else if (message.type === "taskProgressCount") {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
}
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user