mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 23:29:02 +02:00
feat(react/dialogs): port upload attachments
This commit is contained in:
parent
f196a78728
commit
2a40d6bb7e
@ -310,7 +310,7 @@
|
||||
"upload_attachments_to_note": "上传附件到笔记",
|
||||
"close": "关闭",
|
||||
"choose_files": "选择文件",
|
||||
"files_will_be_uploaded": "文件将作为附件上传到",
|
||||
"files_will_be_uploaded": "文件将作为附件上传到 {{noteTitle}}",
|
||||
"options": "选项",
|
||||
"shrink_images": "缩小图片",
|
||||
"upload": "上传",
|
||||
|
@ -307,7 +307,7 @@
|
||||
"upload_attachments_to_note": "Lade Anhänge zur Notiz hoch",
|
||||
"close": "Schließen",
|
||||
"choose_files": "Wähle Dateien aus",
|
||||
"files_will_be_uploaded": "Dateien werden als Anhänge in hochgeladen",
|
||||
"files_will_be_uploaded": "Dateien werden als Anhänge in hochgeladen {{noteTitle}}",
|
||||
"options": "Optionen",
|
||||
"shrink_images": "Bilder verkleinern",
|
||||
"upload": "Hochladen",
|
||||
|
@ -316,7 +316,7 @@
|
||||
"upload_attachments_to_note": "Upload attachments to note",
|
||||
"close": "Close",
|
||||
"choose_files": "Choose files",
|
||||
"files_will_be_uploaded": "Files will be uploaded as attachments into",
|
||||
"files_will_be_uploaded": "Files will be uploaded as attachments into {{noteTitle}}",
|
||||
"options": "Options",
|
||||
"shrink_images": "Shrink images",
|
||||
"upload": "Upload",
|
||||
|
@ -312,7 +312,7 @@
|
||||
"upload_attachments_to_note": "Cargar archivos adjuntos a nota",
|
||||
"close": "Cerrar",
|
||||
"choose_files": "Elija los archivos",
|
||||
"files_will_be_uploaded": "Los archivos se cargarán como archivos adjuntos en",
|
||||
"files_will_be_uploaded": "Los archivos se cargarán como archivos adjuntos en {{noteTitle}}",
|
||||
"options": "Opciones",
|
||||
"shrink_images": "Reducir imágenes",
|
||||
"upload": "Subir",
|
||||
|
@ -307,7 +307,7 @@
|
||||
"upload_attachments_to_note": "Téléverser des pièces jointes à la note",
|
||||
"close": "Fermer",
|
||||
"choose_files": "Choisir des fichiers",
|
||||
"files_will_be_uploaded": "Les fichiers seront téléversés sous forme de pièces jointes dans",
|
||||
"files_will_be_uploaded": "Les fichiers seront téléversés sous forme de pièces jointes dans {{noteTitle}}",
|
||||
"options": "Options",
|
||||
"shrink_images": "Réduire les images",
|
||||
"upload": "Téléverser",
|
||||
|
@ -1309,7 +1309,7 @@
|
||||
},
|
||||
"upload_attachments": {
|
||||
"choose_files": "Selectați fișierele",
|
||||
"files_will_be_uploaded": "Fișierele vor fi încărcate ca atașamente în",
|
||||
"files_will_be_uploaded": "Fișierele vor fi încărcate ca atașamente în {{noteTitle}}",
|
||||
"options": "Opțuni",
|
||||
"shrink_images": "Micșorează imaginile",
|
||||
"tooltip": "Dacă această opțiune este bifată, Trilium va încerca micșorarea imaginilor încărcate prin scalarea și optimizarea lor, aspect ce va putea afecta calitatea imaginilor. Dacă nu este bifată, imaginile vor fi încărcate fără nicio schimbare.",
|
||||
|
@ -315,7 +315,7 @@
|
||||
"upload_attachments_to_note": "Otpremite priloge uz belešku",
|
||||
"close": "Zatvori",
|
||||
"choose_files": "Izaberite datoteke",
|
||||
"files_will_be_uploaded": "Datoteke će biti otpremljene kao prilozi u",
|
||||
"files_will_be_uploaded": "Datoteke će biti otpremljene kao prilozi u {{noteTitle}}",
|
||||
"options": "Opcije",
|
||||
"shrink_images": "Smanji slike",
|
||||
"upload": "Otpremi",
|
||||
|
@ -281,7 +281,7 @@
|
||||
"upload_attachments": {
|
||||
"upload_attachments_to_note": "上傳附件到筆記",
|
||||
"choose_files": "選擇文件",
|
||||
"files_will_be_uploaded": "文件將作為附件上傳到",
|
||||
"files_will_be_uploaded": "文件將作為附件上傳到 {{noteTitle}}",
|
||||
"options": "選項",
|
||||
"shrink_images": "縮小圖片",
|
||||
"upload": "上傳",
|
||||
|
@ -1,120 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { escapeQuotes } from "../../services/utils.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import importService from "../../services/import.js";
|
||||
import options from "../../services/options.js";
|
||||
import BasicWidget from "../basic_widget.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="upload-attachments-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("upload_attachments.upload_attachments_to_note")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("upload_attachments.close")}"></button>
|
||||
</div>
|
||||
<form class="upload-attachment-form">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="upload-attachment-file-upload-input"><strong>${t("upload_attachments.choose_files")}</strong></label>
|
||||
<label class="tn-file-input tn-input-field">
|
||||
<input type="file" class="upload-attachment-file-upload-input form-control-file" multiple />
|
||||
</label>
|
||||
<p>${t("upload_attachments.files_will_be_uploaded")} <strong class="upload-attachment-note-title"></strong>.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<strong>${t("upload_attachments.options")}:</strong>
|
||||
<div class="checkbox">
|
||||
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("upload_attachments.tooltip"))}">
|
||||
<input class="shrink-images-checkbox form-check-input" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="upload-attachment-button btn btn-primary">${t("upload_attachments.upload")}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class UploadAttachmentsDialog extends BasicWidget {
|
||||
|
||||
private parentNoteId: string | null;
|
||||
private modal!: bootstrap.Modal;
|
||||
private $form!: JQuery<HTMLElement>;
|
||||
private $noteTitle!: JQuery<HTMLElement>;
|
||||
private $fileUploadInput!: JQuery<HTMLInputElement>;
|
||||
private $uploadButton!: JQuery<HTMLElement>;
|
||||
private $shrinkImagesCheckbox!: JQuery<HTMLElement>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.parentNoteId = null;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
|
||||
|
||||
this.$form = this.$widget.find(".upload-attachment-form");
|
||||
this.$noteTitle = this.$widget.find(".upload-attachment-note-title");
|
||||
this.$fileUploadInput = this.$widget.find(".upload-attachment-file-upload-input");
|
||||
this.$uploadButton = this.$widget.find(".upload-attachment-button");
|
||||
this.$shrinkImagesCheckbox = this.$widget.find(".shrink-images-checkbox");
|
||||
|
||||
this.$form.on("submit", () => {
|
||||
// disabling so that import is not triggered again.
|
||||
this.$uploadButton.attr("disabled", "disabled");
|
||||
if (this.parentNoteId) {
|
||||
this.uploadAttachments(this.parentNoteId);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$fileUploadInput.on("change", () => {
|
||||
if (this.$fileUploadInput.val()) {
|
||||
this.$uploadButton.removeAttr("disabled");
|
||||
} else {
|
||||
this.$uploadButton.attr("disabled", "disabled");
|
||||
}
|
||||
});
|
||||
|
||||
Tooltip.getOrCreateInstance(this.$widget.find('[data-bs-toggle="tooltip"]')[0], {
|
||||
html: true
|
||||
});
|
||||
}
|
||||
|
||||
async showUploadAttachmentsDialogEvent({ noteId }: EventData<"showUploadAttachmentsDialog">) {
|
||||
this.parentNoteId = noteId;
|
||||
|
||||
this.$fileUploadInput.val("").trigger("change"); // to trigger upload button disabling listener below
|
||||
this.$shrinkImagesCheckbox.prop("checked", options.is("compressImages"));
|
||||
|
||||
this.$noteTitle.text(await treeService.getNoteTitle(this.parentNoteId));
|
||||
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
async uploadAttachments(parentNoteId: string) {
|
||||
const files = Array.from(this.$fileUploadInput[0].files ?? []); // shallow copy since we're resetting the upload button below
|
||||
|
||||
function boolToString($el: JQuery<HTMLElement>): "true" | "false" {
|
||||
return ($el.is(":checked") ? "true" : "false");
|
||||
}
|
||||
|
||||
const options = {
|
||||
shrinkImages: boolToString(this.$shrinkImagesCheckbox)
|
||||
};
|
||||
|
||||
this.modal.hide();
|
||||
|
||||
await importService.uploadFiles("attachments", parentNoteId, files, options);
|
||||
}
|
||||
}
|
79
apps/client/src/widgets/dialogs/upload_attachments.tsx
Normal file
79
apps/client/src/widgets/dialogs/upload_attachments.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { useEffect, useState } from "preact/compat";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
|
||||
import { t } from "../../services/i18n";
|
||||
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 ReactBasicWidget from "../react/ReactBasicWidget";
|
||||
import options from "../../services/options";
|
||||
import importService from "../../services/import.js";
|
||||
import { EventData } from "../../components/app_context";
|
||||
import tree from "../../services/tree";
|
||||
|
||||
interface UploadAttachmentsDialogProps {
|
||||
parentNoteId?: string;
|
||||
}
|
||||
|
||||
function UploadAttachmentsDialogComponent({ parentNoteId }: UploadAttachmentsDialogProps) {
|
||||
const [ files, setFiles ] = useState<FileList | null>(null);
|
||||
const [ shrinkImages, setShrinkImages ] = useState(options.is("compressImages"));
|
||||
const [ isUploading, setIsUploading ] = useState(false);
|
||||
const [ description, setDescription ] = useState<string | undefined>(undefined);
|
||||
|
||||
if (parentNoteId) {
|
||||
useEffect(() => {
|
||||
tree.getNoteTitle(parentNoteId).then((noteTitle) =>
|
||||
setDescription(t("upload_attachments.files_will_be_uploaded", { noteTitle })));
|
||||
}, [parentNoteId]);
|
||||
}
|
||||
|
||||
return (parentNoteId &&
|
||||
<Modal
|
||||
className="upload-attachments-dialog"
|
||||
size="lg"
|
||||
title={t("upload_attachments.upload_attachments_to_note")}
|
||||
footer={<Button text={t("upload_attachments.upload")} primary disabled={!files || isUploading} />}
|
||||
onSubmit={async () => {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
const filesCopy = Array.from(files);
|
||||
await importService.uploadFiles("attachments", parentNoteId, filesCopy, { shrinkImages });
|
||||
setIsUploading(false);
|
||||
closeActiveDialog();
|
||||
}}
|
||||
>
|
||||
<FormGroup label={t("upload_attachments.choose_files")} description={description}>
|
||||
<FormFileUpload onChange={setFiles} multiple />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup label={t("upload_attachments.options")}>
|
||||
<FormCheckbox
|
||||
name="shrink-images"
|
||||
hint={t("upload_attachments.tooltip")} label={t("upload_attachments.shrink_images")}
|
||||
currentValue={shrinkImages} onChange={setShrinkImages}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default class UploadAttachmentsDialog extends ReactBasicWidget {
|
||||
|
||||
private props: UploadAttachmentsDialogProps = {};
|
||||
|
||||
get component() {
|
||||
return <UploadAttachmentsDialogComponent {...this.props} />;
|
||||
}
|
||||
|
||||
showUploadAttachmentsDialogEvent({ noteId }: EventData<"showUploadAttachmentsDialog">) {
|
||||
this.props = { parentNoteId: noteId };
|
||||
this.doRender();
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
}
|
@ -11,9 +11,10 @@ interface ButtonProps {
|
||||
/** Called when the button is clicked. If not set, the button will submit the form (if any). */
|
||||
onClick?: () => void;
|
||||
primary?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function Button({ buttonRef: _buttonRef, className, text, onClick, keyboardShortcut, icon, primary }: ButtonProps) {
|
||||
export default function Button({ buttonRef: _buttonRef, className, text, onClick, keyboardShortcut, icon, primary, disabled }: ButtonProps) {
|
||||
const classes: string[] = ["btn"];
|
||||
if (primary) {
|
||||
classes.push("btn-primary");
|
||||
@ -33,6 +34,7 @@ export default function Button({ buttonRef: _buttonRef, className, text, onClick
|
||||
type={onClick ? "button" : "submit"}
|
||||
onClick={onClick}
|
||||
ref={buttonRef}
|
||||
disabled={disabled}
|
||||
>
|
||||
{icon && <span className={`bx ${icon}`}></span>}
|
||||
{text} {keyboardShortcut && (
|
||||
|
13
apps/client/src/widgets/react/FormFileUpload.tsx
Normal file
13
apps/client/src/widgets/react/FormFileUpload.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
interface FormFileUploadProps {
|
||||
onChange: (files: FileList | null) => void;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
export default function FormFileUpload({ onChange, multiple }: FormFileUploadProps) {
|
||||
return (
|
||||
<label class="tn-file-input tn-input-field">
|
||||
<input type="file" class="form-control-file" multiple={multiple}
|
||||
onChange={e => onChange((e.target as HTMLInputElement).files)} />
|
||||
</label>
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user