mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08:00 
			
		
		
		
	delete notes preview dialog WIP
This commit is contained in:
		
							parent
							
								
									ec2f8ec796
								
							
						
					
					
						commit
						5b72b577b8
					
				| @ -1,25 +1,61 @@ | |||||||
|  | import server from "../services/server.js"; | ||||||
|  | import treeCache from "../services/tree_cache.js"; | ||||||
|  | import linkService from "../services/link.js"; | ||||||
|  | 
 | ||||||
| const $dialog = $("#delete-notes-dialog"); | const $dialog = $("#delete-notes-dialog"); | ||||||
| const $confirmContent = $("#delete-notes-dialog-content"); | const $confirmContent = $("#delete-notes-dialog-content"); | ||||||
| const $okButton = $("#delete-notes-dialog-ok-button"); | const $okButton = $("#delete-notes-dialog-ok-button"); | ||||||
| const $cancelButton = $("#delete-notes-dialog-cancel-button"); | const $cancelButton = $("#delete-notes-dialog-cancel-button"); | ||||||
| const $custom = $("#delete-notes-dialog-custom"); | const $custom = $("#delete-notes-dialog-custom"); | ||||||
|  | const $deleteNotesList = $("#delete-notes-list"); | ||||||
|  | const $brokenRelationsList = $("#broken-relations-list"); | ||||||
|  | const $deletedNotesCount = $("#deleted-notes-count"); | ||||||
|  | const $noNoteToDeleteWrapper = $("#no-note-to-delete-wrapper"); | ||||||
|  | const $deleteNotesListWrapper = $("#delete-notes-list-wrapper"); | ||||||
|  | const $brokenRelationsListWrapper = $("#broken-relations-wrapper"); | ||||||
| 
 | 
 | ||||||
| const DELETE_NOTE_BUTTON_ID = "delete-notes-dialog-delete-note"; | const DELETE_NOTE_BUTTON_ID = "delete-notes-dialog-delete-note"; | ||||||
| 
 | 
 | ||||||
| let $originallyFocused; // element focused before the dialog was opened so we can return to it afterwards
 | let $originallyFocused; // element focused before the dialog was opened so we can return to it afterwards
 | ||||||
| 
 | 
 | ||||||
| export function showDialog(message) { | export async function showDialog(branchIdsToDelete) { | ||||||
|     $originallyFocused = $(':focus'); |     $originallyFocused = $(':focus'); | ||||||
| 
 | 
 | ||||||
|     $custom.hide(); |     $custom.hide(); | ||||||
| 
 | 
 | ||||||
|     glob.activeDialog = $dialog; |     glob.activeDialog = $dialog; | ||||||
| 
 | 
 | ||||||
|     if (typeof message === 'string') { |     const response = await server.post('delete-notes-preview', {branchIdsToDelete}); | ||||||
|         message = $("<div>").text(message); | 
 | ||||||
|  |     $deleteNotesList.empty(); | ||||||
|  |     $brokenRelationsList.empty(); | ||||||
|  | 
 | ||||||
|  |     $deleteNotesListWrapper.toggle(response.noteIdsToBeDeleted.length > 0); | ||||||
|  |     $noNoteToDeleteWrapper.toggle(response.noteIdsToBeDeleted.length === 0); | ||||||
|  | 
 | ||||||
|  |     for (const note of await treeCache.getNotes(response.noteIdsToBeDeleted)) { | ||||||
|  |         $deleteNotesList.append( | ||||||
|  |             $("<li>").append( | ||||||
|  |                 await linkService.createNoteLink(note.noteId, {showNotePath: true}) | ||||||
|  |             ) | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     $confirmContent.empty().append(message); |     $deletedNotesCount.text(response.noteIdsToBeDeleted.length); | ||||||
|  | 
 | ||||||
|  |     $brokenRelationsListWrapper.toggle(response.brokenRelations.length > 0); | ||||||
|  | 
 | ||||||
|  |     await treeCache.getNotes(response.brokenRelations.map(br => br.noteId)); | ||||||
|  | 
 | ||||||
|  |     for (const attr of response.brokenRelations) { | ||||||
|  |         $brokenRelationsList.append( | ||||||
|  |             $("<li>") | ||||||
|  |                 .append(`Note `) | ||||||
|  |                 .append(await linkService.createNoteLink(attr.value)) | ||||||
|  |                 .append(` (to be deleted) is referenced by relation <code>${attr.name}</code> originating from `) | ||||||
|  |                 .append(await linkService.createNoteLink(attr.noteId)) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     $dialog.modal(); |     $dialog.modal(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -75,7 +75,7 @@ async function deleteNotes(branchIdsToDelete) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const deleteNotesDialog = await import("../dialogs/delete_notes.js"); |     const deleteNotesDialog = await import("../dialogs/delete_notes.js"); | ||||||
|     deleteNotesDialog.showDialog(); |     deleteNotesDialog.showDialog(branchIdsToDelete); | ||||||
| 
 | 
 | ||||||
|     const $deleteClonesCheckbox = $('<div class="form-check">') |     const $deleteClonesCheckbox = $('<div class="form-check">') | ||||||
|         .append($('<input type="checkbox" class="form-check-input" id="delete-clones-checkbox">')) |         .append($('<input type="checkbox" class="form-check-input" id="delete-clones-checkbox">')) | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
| const noteService = require('../../services/notes'); | const noteService = require('../../services/notes'); | ||||||
| const treeService = require('../../services/tree'); | const treeService = require('../../services/tree'); | ||||||
| const repository = require('../../services/repository'); | const repository = require('../../services/repository'); | ||||||
|  | const sql = require('../../services/sql'); | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const log = require('../../services/log'); | const log = require('../../services/log'); | ||||||
| const TaskContext = require('../../services/task_context'); | const TaskContext = require('../../services/task_context'); | ||||||
| @ -220,6 +221,58 @@ function eraseDeletedNotesNow() { | |||||||
|     noteService.eraseDeletedNotesNow(); |     noteService.eraseDeletedNotesNow(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function getDeleteNotesPreview(req) { | ||||||
|  |     const {branchIdsToDelete} = req.body; | ||||||
|  | 
 | ||||||
|  |     const noteIdsToBeDeleted = new Set(); | ||||||
|  |     const branchCountToDelete = {}; // noteId => count (integer)
 | ||||||
|  | 
 | ||||||
|  |     function branchPreviewDeletion(branch) { | ||||||
|  |         branchCountToDelete[branch.branchId] = branchCountToDelete[branch.branchId] || 0; | ||||||
|  |         branchCountToDelete[branch.branchId]++; | ||||||
|  | 
 | ||||||
|  |         const note = branch.getNote(); | ||||||
|  | 
 | ||||||
|  |         if (note.getBranches().length <= branchCountToDelete[branch.branchId]) { | ||||||
|  |             noteIdsToBeDeleted.add(note.noteId); | ||||||
|  | 
 | ||||||
|  |             for (const childBranch of note.getChildBranches()) { | ||||||
|  |                 branchPreviewDeletion(childBranch); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (const branchId of branchIdsToDelete) { | ||||||
|  |         const branch = repository.getBranch(branchId); | ||||||
|  | 
 | ||||||
|  |         if (!branch) { | ||||||
|  |             log.error(`Branch ${branchId} was not found and delete preview can't be calculated for this note.`); | ||||||
|  | 
 | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         branchPreviewDeletion(branch); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let brokenRelations = []; | ||||||
|  | 
 | ||||||
|  |     if (noteIdsToBeDeleted.length > 0) { | ||||||
|  |         sql.fillParamList(noteIdsToBeDeleted); | ||||||
|  | 
 | ||||||
|  |         brokenRelations = sql.getRows(` | ||||||
|  |             SELECT attr.noteId, attr.name, attr.value | ||||||
|  |             FROM attributes attr | ||||||
|  |                      JOIN param_list ON param_list.paramId = attr.value | ||||||
|  |             WHERE attr.isDeleted = 0 | ||||||
|  |               AND attr.type = 'relation'`).filter(attr => !noteIdsToBeDeleted.has(attr.noteId));
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return { | ||||||
|  |         noteIdsToBeDeleted: Array.from(noteIdsToBeDeleted), | ||||||
|  |         brokenRelations | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNote, |     getNote, | ||||||
|     updateNote, |     updateNote, | ||||||
| @ -232,5 +285,6 @@ module.exports = { | |||||||
|     getRelationMap, |     getRelationMap, | ||||||
|     changeTitle, |     changeTitle, | ||||||
|     duplicateSubtree, |     duplicateSubtree, | ||||||
|     eraseDeletedNotesNow |     eraseDeletedNotesNow, | ||||||
|  |     getDeleteNotesPreview | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ function getSubtreeSize(req) { | |||||||
| 
 | 
 | ||||||
|     const subTreeNoteIds = note.subtreeNotes.map(note => note.noteId); |     const subTreeNoteIds = note.subtreeNotes.map(note => note.noteId); | ||||||
| 
 | 
 | ||||||
|     sql.fillNoteIdList(subTreeNoteIds); |     sql.fillParamList(subTreeNoteIds); | ||||||
| 
 | 
 | ||||||
|     const subTreeSize = sql.getValue(` |     const subTreeSize = sql.getValue(` | ||||||
|         SELECT |         SELECT | ||||||
|  | |||||||
| @ -290,6 +290,8 @@ function register(app) { | |||||||
|     apiRoute(GET, '/api/stats/note-size/:noteId', statsRoute.getNoteSize); |     apiRoute(GET, '/api/stats/note-size/:noteId', statsRoute.getNoteSize); | ||||||
|     apiRoute(GET, '/api/stats/subtree-size/:noteId', statsRoute.getSubtreeSize); |     apiRoute(GET, '/api/stats/subtree-size/:noteId', statsRoute.getSubtreeSize); | ||||||
| 
 | 
 | ||||||
|  |     apiRoute(POST, '/api/delete-notes-preview', notesApiRoute.getDeleteNotesPreview); | ||||||
|  | 
 | ||||||
|     app.use('', router); |     app.use('', router); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -246,8 +246,8 @@ function transactional(func) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function fillNoteIdList(noteIds, truncate = true) { | function fillParamList(paramIds, truncate = true) { | ||||||
|     if (noteIds.length === 0) { |     if (paramIds.length === 0) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -255,18 +255,18 @@ function fillNoteIdList(noteIds, truncate = true) { | |||||||
|         execute("DELETE FROM param_list"); |         execute("DELETE FROM param_list"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     noteIds = Array.from(new Set(noteIds)); |     paramIds = Array.from(new Set(paramIds)); | ||||||
| 
 | 
 | ||||||
|     if (noteIds.length > 30000) { |     if (paramIds.length > 30000) { | ||||||
|         fillNoteIdList(noteIds.slice(30000), false); |         fillParamList(paramIds.slice(30000), false); | ||||||
| 
 | 
 | ||||||
|         noteIds = noteIds.slice(0, 30000); |         paramIds = paramIds.slice(0, 30000); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // doing it manually to avoid this showing up on the sloq query list
 |     // doing it manually to avoid this showing up on the sloq query list
 | ||||||
|     const s = stmt(`INSERT INTO param_list VALUES ` + noteIds.map(noteId => `(?)`).join(','), noteIds); |     const s = stmt(`INSERT INTO param_list VALUES ` + paramIds.map(paramId => `(?)`).join(','), paramIds); | ||||||
| 
 | 
 | ||||||
|     s.run(noteIds); |     s.run(paramIds); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
| @ -287,5 +287,5 @@ module.exports = { | |||||||
|     executeScript, |     executeScript, | ||||||
|     transactional, |     transactional, | ||||||
|     upsert, |     upsert, | ||||||
|     fillNoteIdList |     fillParamList | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <div id="delete-notes-dialog" class="modal mx-auto" tabindex="-1" role="dialog"> | <div id="delete-notes-dialog" class="modal mx-auto" tabindex="-1" role="dialog"> | ||||||
|     <div class="modal-dialog modal-dialog-scrollable" role="document"> |     <div class="modal-dialog modal-dialog-scrollable modal-xl" role="document"> | ||||||
|         <div class="modal-content"> |         <div class="modal-content"> | ||||||
|             <div class="modal-header"> |             <div class="modal-header"> | ||||||
|                 <h5 class="modal-title mr-auto">Delete notes</h5> |                 <h5 class="modal-title mr-auto">Delete notes</h5> | ||||||
| @ -9,7 +9,23 @@ | |||||||
|                 </button> |                 </button> | ||||||
|             </div> |             </div> | ||||||
|             <div class="modal-body"> |             <div class="modal-body"> | ||||||
|                 ... delete |                 <div id="delete-notes-list-wrapper"> | ||||||
|  |                     <h5>Following notes will be deleted (<span id="deleted-notes-count"></span>)</h5> | ||||||
|  | 
 | ||||||
|  |                     <ul id="delete-notes-list" style="max-height: 400px; overflow: auto;"></ul> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <div id="no-note-to-delete-wrapper"> | ||||||
|  |                     <strong>No note will be deleted (only clones).</strong> | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |                 <div id="broken-relations-wrapper"> | ||||||
|  |                     <h5>Broken relations</h5> | ||||||
|  | 
 | ||||||
|  |                     Below can be seen relations which will be broken after notes mentioned above are deleted. | ||||||
|  | 
 | ||||||
|  |                     <ul id="broken-relations-list" style="max-height: 400px; overflow: auto;"></ul> | ||||||
|  |                 </div> | ||||||
|             </div> |             </div> | ||||||
|             <div class="modal-footer"> |             <div class="modal-footer"> | ||||||
|                 <button class="btn btn-sm" id="delete-notes-dialog-cancel-button">Cancel</button> |                 <button class="btn btn-sm" id="delete-notes-dialog-cancel-button">Cancel</button> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam