mirror of
https://github.com/zadam/trilium.git
synced 2025-11-18 03:00:41 +01:00
feat(react/dialogs): port delete_notes
This commit is contained in:
182
apps/client/src/widgets/dialogs/delete_notes.tsx
Normal file
182
apps/client/src/widgets/dialogs/delete_notes.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { useRef, useState } from "preact/hooks";
|
||||
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import FormCheckbox from "../react/FormCheckbox.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import { useEffect } from "react";
|
||||
import type { DeleteNotesPreview } from "@triliumnext/commons";
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import FNote from "../../entities/fnote.js";
|
||||
import link from "../../services/link.js";
|
||||
import Button from "../react/Button.jsx";
|
||||
|
||||
export interface ResolveOptions {
|
||||
proceed: boolean;
|
||||
deleteAllClones?: boolean;
|
||||
eraseNotes?: boolean;
|
||||
}
|
||||
|
||||
interface ShowDeleteNotesDialogOpts {
|
||||
branchIdsToDelete?: string[];
|
||||
callback?: (opts: ResolveOptions) => void;
|
||||
forceDeleteAllClones?: boolean;
|
||||
}
|
||||
|
||||
interface ShowDeleteNotesDialogProps extends ShowDeleteNotesDialogOpts { }
|
||||
|
||||
interface BrokenRelationData {
|
||||
note: string;
|
||||
relation: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
function DeleteNotesDialogComponent({ forceDeleteAllClones, branchIdsToDelete, callback }: ShowDeleteNotesDialogProps) {
|
||||
const [ deleteAllClones, setDeleteAllClones ] = useState(false);
|
||||
const [ eraseNotes, setEraseNotes ] = useState(!!forceDeleteAllClones);
|
||||
const [ brokenRelations, setBrokenRelations ] = useState<DeleteNotesPreview["brokenRelations"]>([]);
|
||||
const [ noteIdsToBeDeleted, setNoteIdsToBeDeleted ] = useState<DeleteNotesPreview["noteIdsToBeDeleted"]>([]);
|
||||
const [ proceed, setProceed ] = useState(false);
|
||||
const okButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!branchIdsToDelete || branchIdsToDelete.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.post<DeleteNotesPreview>("delete-notes-preview", {
|
||||
branchIdsToDelete,
|
||||
deleteAllClones: forceDeleteAllClones || deleteAllClones
|
||||
}).then(response => {
|
||||
setBrokenRelations(response.brokenRelations);
|
||||
setNoteIdsToBeDeleted(response.noteIdsToBeDeleted);
|
||||
});
|
||||
}, [ branchIdsToDelete, deleteAllClones, forceDeleteAllClones ]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="delete-notes-dialog"
|
||||
size="xl"
|
||||
scrollable
|
||||
title={t("delete_notes.delete_notes_preview")}
|
||||
onShown={() => okButtonRef.current?.focus()}
|
||||
onHidden={() => callback?.({ proceed, deleteAllClones, eraseNotes })}
|
||||
footer={<>
|
||||
<Button text={t("delete_notes.cancel")}
|
||||
onClick={() => closeActiveDialog()} />
|
||||
<Button text={t("delete_notes.ok")} primary
|
||||
buttonRef={okButtonRef}
|
||||
onClick={() => {
|
||||
setProceed(true);
|
||||
closeActiveDialog();
|
||||
}} />
|
||||
</>}
|
||||
>
|
||||
<FormCheckbox name="delete-all-clones" label={t("delete_notes.delete_all_clones_description")}
|
||||
currentValue={deleteAllClones} onChange={setDeleteAllClones}
|
||||
/>
|
||||
<FormCheckbox
|
||||
name="erase-notes" label={t("delete_notes.erase_notes_warning")}
|
||||
disabled={forceDeleteAllClones}
|
||||
currentValue={eraseNotes} onChange={setEraseNotes}
|
||||
/>
|
||||
|
||||
<DeletedNotes noteIdsToBeDeleted={noteIdsToBeDeleted} />
|
||||
<BrokenRelations brokenRelations={brokenRelations} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function DeletedNotes({ noteIdsToBeDeleted }: { noteIdsToBeDeleted: DeleteNotesPreview["noteIdsToBeDeleted"] }) {
|
||||
const [ noteLinks, setNoteLinks ] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
froca.getNotes(noteIdsToBeDeleted).then(async (notes: FNote[]) => {
|
||||
const noteLinks = [];
|
||||
|
||||
for (const note of notes) {
|
||||
noteLinks.push((await link.createLink(note.noteId, { showNotePath: true })).html());
|
||||
}
|
||||
|
||||
setNoteLinks(noteLinks);
|
||||
});
|
||||
}, [noteIdsToBeDeleted]);
|
||||
|
||||
return (
|
||||
<div className="delete-notes-list-wrapper">
|
||||
<h4>{t("delete_notes.notes_to_be_deleted", { notesCount: noteIdsToBeDeleted.length })}</h4>
|
||||
|
||||
<ul className="delete-notes-list" style={{ maxHeight: "200px", overflow: "auto" }}>
|
||||
{noteLinks.map((link, index) => (
|
||||
<li key={index} dangerouslySetInnerHTML={{ __html: link }} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPreview["brokenRelations"] }) {
|
||||
const [ notesWithBrokenRelations, setNotesWithBrokenRelations ] = useState<BrokenRelationData[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const noteIds = brokenRelations.map(relation => relation.noteId);
|
||||
froca.getNotes(noteIds).then(async (notes) => {
|
||||
const notesWithBrokenRelations: BrokenRelationData[] = [];
|
||||
for (const attr of brokenRelations) {
|
||||
notesWithBrokenRelations.push({
|
||||
note: (await link.createLink(attr.value)).html(),
|
||||
relation: `<code>${attr.name}</code>`,
|
||||
source: (await link.createLink(attr.noteId)).html()
|
||||
});
|
||||
}
|
||||
setNotesWithBrokenRelations(notesWithBrokenRelations);
|
||||
});
|
||||
}, [brokenRelations]);
|
||||
|
||||
if (brokenRelations.length) {
|
||||
return (
|
||||
<div className="broken-relations-wrapper">
|
||||
<div className="alert alert-danger">
|
||||
<h4>{t("delete_notes.broken_relations_to_be_deleted", { relationCount: brokenRelations.length })}</h4>
|
||||
|
||||
<ul className="broken-relations-list" style={{ maxHeight: "200px", overflow: "auto" }}>
|
||||
{brokenRelations.map((_, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
<span dangerouslySetInnerHTML={{ __html: t("delete_notes.deleted_relation_text", notesWithBrokenRelations[index]) }} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="no-note-to-delete-wrapper alert alert-info">
|
||||
{t("delete_notes.no_note_to_delete")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class DeleteNotesDialog extends ReactBasicWidget {
|
||||
|
||||
private props: ShowDeleteNotesDialogProps = {};
|
||||
|
||||
get component() {
|
||||
return <DeleteNotesDialogComponent {...this.props} />;
|
||||
}
|
||||
|
||||
async showDeleteNotesDialogEvent({ branchIdsToDelete, callback, forceDeleteAllClones }: ShowDeleteNotesDialogOpts) {
|
||||
this.props = {
|
||||
branchIdsToDelete,
|
||||
callback,
|
||||
forceDeleteAllClones
|
||||
};
|
||||
this.doRender();
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user