mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 18:05:55 +01:00 
			
		
		
		
	
		
			
	
	
		
			143 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			143 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import { useEffect, useState } from "preact/hooks"; | ||
|  | import { EventData } from "../../components/app_context"; | ||
|  | import { closeActiveDialog, openDialog } from "../../services/dialog"; | ||
|  | import { t } from "../../services/i18n"; | ||
|  | import Modal from "../react/Modal"; | ||
|  | import ReactBasicWidget from "../react/ReactBasicWidget"; | ||
|  | import "./bulk_actions.css"; | ||
|  | import { BulkActionAffectedNotes } from "@triliumnext/commons"; | ||
|  | import server from "../../services/server"; | ||
|  | import FormCheckbox from "../react/FormCheckbox"; | ||
|  | import Button from "../react/Button"; | ||
|  | import bulk_action from "../../services/bulk_action"; | ||
|  | import toast from "../../services/toast"; | ||
|  | import RenameNoteBulkAction from "../bulk_actions/note/rename_note"; | ||
|  | import { RawHtmlBlock } from "../react/RawHtml"; | ||
|  | import FNote from "../../entities/fnote"; | ||
|  | import froca from "../../services/froca"; | ||
|  | 
 | ||
|  | interface BulkActionProps { | ||
|  |     bulkActionNote?: FNote | null; | ||
|  |     selectedOrActiveNoteIds?: string[]; | ||
|  | } | ||
|  | 
 | ||
|  | function BulkActionComponent({ selectedOrActiveNoteIds, bulkActionNote }: BulkActionProps) { | ||
|  |     const [ includeDescendants, setIncludeDescendants ] = useState(false); | ||
|  |     const [ affectedNoteCount, setAffectedNoteCount ] = useState(0); | ||
|  |     const [ existingActions, setExistingActions ] = useState<RenameNoteBulkAction[]>([]); | ||
|  | 
 | ||
|  |     if (!selectedOrActiveNoteIds || !bulkActionNote) { | ||
|  |         return; | ||
|  |     } | ||
|  | 
 | ||
|  |     useEffect(() => { | ||
|  |         server.post<BulkActionAffectedNotes>("bulk-action/affected-notes", { | ||
|  |             noteIds: selectedOrActiveNoteIds, | ||
|  |             includeDescendants | ||
|  |         }).then(({ affectedNoteCount }) => setAffectedNoteCount(affectedNoteCount)); | ||
|  |     }, [ selectedOrActiveNoteIds, includeDescendants ]); | ||
|  | 
 | ||
|  |     // Refresh is forced by the entities reloaded event outside React.
 | ||
|  |     useEffect(() => {         | ||
|  |         setExistingActions(bulk_action.parseActions(bulkActionNote)); | ||
|  |     }, []); | ||
|  | 
 | ||
|  |     return ( selectedOrActiveNoteIds && | ||
|  |         <Modal | ||
|  |             className="bulk-actions-dialog" | ||
|  |             size="xl" | ||
|  |             title={t("bulk_actions.bulk_actions")} | ||
|  |             footer={<Button text={t("bulk_actions.execute_bulk_actions")} primary />} | ||
|  |             onSubmit={async () => { | ||
|  |                 await server.post("bulk-action/execute", { | ||
|  |                     noteIds: selectedOrActiveNoteIds, | ||
|  |                     includeDescendants | ||
|  |                 }); | ||
|  | 
 | ||
|  |                 toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000); | ||
|  |                 closeActiveDialog(); | ||
|  |             }} | ||
|  |         > | ||
|  |             <h4>{t("bulk_actions.affected_notes")}: <span>{affectedNoteCount}</span></h4> | ||
|  |             <FormCheckbox  | ||
|  |                 name="include-descendants" label={t("bulk_actions.include_descendants")} | ||
|  |                 currentValue={includeDescendants} onChange={setIncludeDescendants} | ||
|  |             /> | ||
|  | 
 | ||
|  |             <h4>{t("bulk_actions.available_actions")}</h4> | ||
|  |             <AvailableActionsList /> | ||
|  | 
 | ||
|  |             <h4>{t("bulk_actions.chosen_actions")}</h4> | ||
|  |             <ExistingActionsList existingActions={existingActions} /> | ||
|  |         </Modal> | ||
|  |     ) | ||
|  | } | ||
|  | 
 | ||
|  | function AvailableActionsList() { | ||
|  |     return <table class="bulk-available-action-list"> | ||
|  |         {bulk_action.ACTION_GROUPS.map((actionGroup) => { | ||
|  |             return ( | ||
|  |                 <tr> | ||
|  |                     <td>{ actionGroup.title }:</td> | ||
|  |                     {actionGroup.actions.map(({ actionName, actionTitle }) => | ||
|  |                         <Button | ||
|  |                             small text={actionTitle} | ||
|  |                             onClick={() => bulk_action.addAction("_bulkAction", actionName)} | ||
|  |                         /> | ||
|  |                     )} | ||
|  |                 </tr> | ||
|  |             ); | ||
|  |         })} | ||
|  |     </table>; | ||
|  | } | ||
|  | 
 | ||
|  | function ExistingActionsList({ existingActions }: { existingActions?: RenameNoteBulkAction[] }) { | ||
|  |     return ( | ||
|  |         <table class="bulk-existing-action-list"> | ||
|  |             { existingActions | ||
|  |                 ? existingActions | ||
|  |                     .map(action => { | ||
|  |                         const renderedAction = action.render(); | ||
|  |                         if (renderedAction) { | ||
|  |                             return <RawHtmlBlock | ||
|  |                                 html={renderedAction[0].innerHTML} | ||
|  |                                 style={{ display: "flex", alignItems: "center" }} /> | ||
|  |                         } else { | ||
|  |                             return null; | ||
|  |                         } | ||
|  |                     }) | ||
|  |                     .filter(renderedAction => renderedAction !== null)             | ||
|  |                 : <p>{t("bulk_actions.none_yet")}</p> | ||
|  |             } | ||
|  |         </table> | ||
|  |     ); | ||
|  | } | ||
|  | 
 | ||
|  | export default class BulkActionsDialog extends ReactBasicWidget { | ||
|  | 
 | ||
|  |     private props: BulkActionProps = {}; | ||
|  | 
 | ||
|  |     get component() { | ||
|  |         return <BulkActionComponent {...this.props} /> | ||
|  |     } | ||
|  | 
 | ||
|  |     async openBulkActionsDialogEvent({ selectedOrActiveNoteIds }: EventData<"openBulkActionsDialog">) {                 | ||
|  |         this.props = { | ||
|  |             selectedOrActiveNoteIds, | ||
|  |             bulkActionNote: await froca.getNote("_bulkAction") | ||
|  |         }; | ||
|  |         this.doRender(); | ||
|  |         openDialog(this.$widget); | ||
|  |     } | ||
|  | 
 | ||
|  |     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||
|  |         // only refreshing deleted attrs, otherwise components update themselves
 | ||
|  |         if (loadResults.getAttributeRows().find((row) => row.type === "label" && row.name === "action" && row.noteId === "_bulkAction" && row.isDeleted)) { | ||
|  |             // this may be triggered from e.g., sync without open widget, then no need to refresh the widget
 | ||
|  |             if (this.props.selectedOrActiveNoteIds && this.$widget.is(":visible")) { | ||
|  |                 this.doRender(); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  | } |