feat(react/dialogs): port import

This commit is contained in:
Elian Doran
2025-08-07 19:20:35 +03:00
parent 90f9416524
commit 8d27a5aa39
4 changed files with 112 additions and 182 deletions

View File

@@ -1,180 +0,0 @@
import { escapeQuotes } from "../../services/utils.js";
import treeService from "../../services/tree.js";
import importService, { type UploadFilesOptions } from "../../services/import.js";
import options from "../../services/options.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import { Modal, Tooltip } from "bootstrap";
import type { EventData } from "../../components/app_context.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("import.importIntoNote")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("import.close")}"></button>
</div>
<form class="import-form">
<div class="modal-body">
<div class="form-group">
<label for="import-file-upload-input"><strong>${t("import.chooseImportFile")}</strong></label>
<label class="tn-file-input tn-input-field">
<input type="file" class="import-file-upload-input form-control-file" multiple />
</label>
<p>${t("import.importDescription")} <strong class="import-note-title"></strong>.
</div>
<div class="form-group">
<strong>${t("import.options")}:</strong>
<div class="checkbox">
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("import.safeImportTooltip"))}">
<input class="safe-import-checkbox" value="1" type="checkbox" checked>
<span>${t("import.safeImport")}</span>
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("import.explodeArchivesTooltip"))}">
<input class="explode-archives-checkbox" value="1" type="checkbox" checked>
<span>${t("import.explodeArchives")}</span>
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("import.shrinkImagesTooltip"))}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t("import.shrinkImages")}</span>
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox">
<input class="text-imported-as-text-checkbox" value="1" type="checkbox" checked>
${t("import.textImportedAsText")}
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox">
<input class="code-imported-as-code-checkbox" value="1" type="checkbox" checked> ${t("import.codeImportedAsCode")}
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox">
<input class="replace-underscores-with-spaces-checkbox" value="1" type="checkbox" checked>
${t("import.replaceUnderscoresWithSpaces")}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="import-button btn btn-primary">${t("import.import")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class ImportDialog extends BasicWidget {
private parentNoteId: string | null;
private $form!: JQuery<HTMLElement>;
private $noteTitle!: JQuery<HTMLElement>;
private $fileUploadInput!: JQuery<HTMLInputElement>;
private $importButton!: JQuery<HTMLElement>;
private $safeImportCheckbox!: JQuery<HTMLElement>;
private $shrinkImagesCheckbox!: JQuery<HTMLElement>;
private $textImportedAsTextCheckbox!: JQuery<HTMLElement>;
private $codeImportedAsCodeCheckbox!: JQuery<HTMLElement>;
private $explodeArchivesCheckbox!: JQuery<HTMLElement>;
private $replaceUnderscoresWithSpacesCheckbox!: JQuery<HTMLElement>;
constructor() {
super();
this.parentNoteId = null;
}
doRender() {
this.$widget = $(TPL);
Modal.getOrCreateInstance(this.$widget[0]);
this.$form = this.$widget.find(".import-form");
this.$noteTitle = this.$widget.find(".import-note-title");
this.$fileUploadInput = this.$widget.find(".import-file-upload-input");
this.$importButton = this.$widget.find(".import-button");
this.$safeImportCheckbox = this.$widget.find(".safe-import-checkbox");
this.$shrinkImagesCheckbox = this.$widget.find(".shrink-images-checkbox");
this.$textImportedAsTextCheckbox = this.$widget.find(".text-imported-as-text-checkbox");
this.$codeImportedAsCodeCheckbox = this.$widget.find(".code-imported-as-code-checkbox");
this.$explodeArchivesCheckbox = this.$widget.find(".explode-archives-checkbox");
this.$replaceUnderscoresWithSpacesCheckbox = this.$widget.find(".replace-underscores-with-spaces-checkbox");
this.$form.on("submit", () => {
// disabling so that import is not triggered again.
this.$importButton.attr("disabled", "disabled");
if (this.parentNoteId) {
this.importIntoNote(this.parentNoteId);
}
return false;
});
this.$fileUploadInput.on("change", () => {
if (this.$fileUploadInput.val()) {
this.$importButton.removeAttr("disabled");
} else {
this.$importButton.attr("disabled", "disabled");
}
});
let _ = [...this.$widget.find('[data-bs-toggle="tooltip"]')].forEach((element) => {
Tooltip.getOrCreateInstance(element, {
html: true
});
});
}
async showImportDialogEvent({ noteId }: EventData<"showImportDialog">) {
this.parentNoteId = noteId;
this.$fileUploadInput.val("").trigger("change"); // to trigger Import button disabling listener below
this.$safeImportCheckbox.prop("checked", true);
this.$shrinkImagesCheckbox.prop("checked", options.is("compressImages"));
this.$textImportedAsTextCheckbox.prop("checked", true);
this.$codeImportedAsCodeCheckbox.prop("checked", true);
this.$explodeArchivesCheckbox.prop("checked", true);
this.$replaceUnderscoresWithSpacesCheckbox.prop("checked", true);
this.$noteTitle.text(await treeService.getNoteTitle(this.parentNoteId));
openDialog(this.$widget);
}
async importIntoNote(parentNoteId: string) {
const files = Array.from(this.$fileUploadInput[0].files ?? []); // shallow copy since we're resetting the upload button below
const boolToString = ($el: JQuery<HTMLElement>) => ($el.is(":checked") ? "true" : "false");
const options: UploadFilesOptions = {
safeImport: boolToString(this.$safeImportCheckbox),
shrinkImages: boolToString(this.$shrinkImagesCheckbox),
textImportedAsText: boolToString(this.$textImportedAsTextCheckbox),
codeImportedAsCode: boolToString(this.$codeImportedAsCodeCheckbox),
explodeArchives: boolToString(this.$explodeArchivesCheckbox),
replaceUnderscoresWithSpaces: boolToString(this.$replaceUnderscoresWithSpacesCheckbox)
};
this.$widget.modal("hide");
await importService.uploadFiles("notes", parentNoteId, files, options);
}
}

View File

@@ -0,0 +1,109 @@
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 FormCheckbox from "../react/FormCheckbox";
import FormFileUpload from "../react/FormFileUpload";
import FormGroup from "../react/FormGroup";
import Modal from "../react/Modal";
import RawHtml from "../react/RawHtml";
import ReactBasicWidget from "../react/ReactBasicWidget";
import importService, { UploadFilesOptions } from "../../services/import";
interface ImportDialogComponentProps {
parentNoteId?: string;
noteTitle?: string;
}
function ImportDialogComponent({ parentNoteId, noteTitle }: ImportDialogComponentProps) {
const [ files, setFiles ] = useState<FileList | null>(null);
const [ safeImport, setSafeImport ] = useState(true);
const [ explodeArchives, setExplodeArchives ] = useState(true);
const [ shrinkImages, setShrinkImages ] = useState(true);
const [ textImportedAsText, setTextImportedAsText ] = useState(true);
const [ codeImportedAsCode, setCodeImportedAsCode ] = useState(true);
const [ replaceUnderscoresWithSpaces, setReplaceUnderscoresWithSpaces ] = useState(true);
return (parentNoteId &&
<Modal
className="import-dialog"
size="lg"
title={t("import.importIntoNote")}
onSubmit={async () => {
if (!files) {
return;
}
const options: UploadFilesOptions = {
safeImport: boolToString(safeImport),
shrinkImages: boolToString(shrinkImages),
textImportedAsText: boolToString(textImportedAsText),
codeImportedAsCode: boolToString(codeImportedAsCode),
explodeArchives: boolToString(explodeArchives),
replaceUnderscoresWithSpaces: boolToString(replaceUnderscoresWithSpaces)
};
closeActiveDialog();
await importService.uploadFiles("notes", parentNoteId, Array.from(files), options);
}}
footer={<Button text={t("import.import")} primary disabled={!files} />}
>
<FormGroup label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}>
<FormFileUpload multiple onChange={setFiles} />
</FormGroup>
<FormGroup label={t("import.options")}>
<FormCheckbox
name="safe-import" hint={t("import.safeImportTooltip")} label={t("import.safeImport")}
currentValue={safeImport} onChange={setSafeImport}
/>
<FormCheckbox
name="explode-archives" hint={t("import.explodeArchivesTooltip")} label={<RawHtml html={t("import.explodeArchives")} />}
currentValue={explodeArchives} onChange={setExplodeArchives}
/>
<FormCheckbox
name="shrink-images" hint={t("import.shrinkImagesTooltip")} label={t("import.shrinkImages")}
currentValue={shrinkImages} onChange={setShrinkImages}
/>
<FormCheckbox
name="text-imported-as-text" label={t("import.textImportedAsText")}
currentValue={textImportedAsText} onChange={setTextImportedAsText}
/>
<FormCheckbox
name="code-imported-as-code" label={<RawHtml html={t("import.codeImportedAsCode")} />}
currentValue={codeImportedAsCode} onChange={setCodeImportedAsCode}
/>
<FormCheckbox
name="replace-underscores-with-spaces" label={t("import.replaceUnderscoresWithSpaces")}
currentValue={replaceUnderscoresWithSpaces} onChange={setReplaceUnderscoresWithSpaces}
/>
</FormGroup>
</Modal>
);
}
export default class ImportDialog extends ReactBasicWidget {
private props?: ImportDialogComponentProps = {};
get component() {
return <ImportDialogComponent {...this.props} />
}
async showImportDialogEvent({ noteId }: EventData<"showImportDialog">) {
this.props = {
parentNoteId: noteId,
noteTitle: await tree.getNoteTitle(noteId)
}
this.doRender();
openDialog(this.$widget);
}
}
function boolToString(value: boolean) {
return value ? "true" : "false";
}

View File

@@ -1,10 +1,11 @@
import { Tooltip } from "bootstrap";
import { useEffect, useRef } from "preact/compat";
import { escapeQuotes } from "../../services/utils";
import { ComponentChildren } from "preact";
interface FormCheckboxProps {
name: string;
label: string;
label: string | ComponentChildren;
/**
* If set, the checkbox label will be underlined and dotted, indicating a hint. When hovered, it will show the hint text.
*/

View File

@@ -6,7 +6,7 @@ interface FormGroupProps {
title?: string;
className?: string;
children: ComponentChildren;
description?: string;
description?: string | ComponentChildren;
}
export default function FormGroup({ label, title, className, children, description, labelRef }: FormGroupProps) {