From 5b72b577b8f8dc024367026dc446e350dfd42bf9 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 14 Mar 2021 22:54:39 +0100 Subject: [PATCH] delete notes preview dialog WIP --- src/public/app/dialogs/delete_notes.js | 44 ++++++++++++++++++-- src/public/app/services/branches.js | 2 +- src/routes/api/notes.js | 56 +++++++++++++++++++++++++- src/routes/api/stats.js | 2 +- src/routes/routes.js | 2 + src/services/sql.js | 18 ++++----- src/views/dialogs/delete_notes.ejs | 20 ++++++++- 7 files changed, 126 insertions(+), 18 deletions(-) diff --git a/src/public/app/dialogs/delete_notes.js b/src/public/app/dialogs/delete_notes.js index 35eb880f5..dc1903adc 100644 --- a/src/public/app/dialogs/delete_notes.js +++ b/src/public/app/dialogs/delete_notes.js @@ -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 $confirmContent = $("#delete-notes-dialog-content"); const $okButton = $("#delete-notes-dialog-ok-button"); const $cancelButton = $("#delete-notes-dialog-cancel-button"); 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"; 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'); $custom.hide(); glob.activeDialog = $dialog; - if (typeof message === 'string') { - message = $("
").text(message); + const response = await server.post('delete-notes-preview', {branchIdsToDelete}); + + $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( + $("
  • ").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( + $("
  • ") + .append(`Note `) + .append(await linkService.createNoteLink(attr.value)) + .append(` (to be deleted) is referenced by relation ${attr.name} originating from `) + .append(await linkService.createNoteLink(attr.noteId)) + ); + } $dialog.modal(); diff --git a/src/public/app/services/branches.js b/src/public/app/services/branches.js index 8f368626b..66fdb0f7d 100644 --- a/src/public/app/services/branches.js +++ b/src/public/app/services/branches.js @@ -75,7 +75,7 @@ async function deleteNotes(branchIdsToDelete) { } const deleteNotesDialog = await import("../dialogs/delete_notes.js"); - deleteNotesDialog.showDialog(); + deleteNotesDialog.showDialog(branchIdsToDelete); const $deleteClonesCheckbox = $('
    ') .append($('')) diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 370058eaf..5f997a737 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -3,6 +3,7 @@ const noteService = require('../../services/notes'); const treeService = require('../../services/tree'); const repository = require('../../services/repository'); +const sql = require('../../services/sql'); const utils = require('../../services/utils'); const log = require('../../services/log'); const TaskContext = require('../../services/task_context'); @@ -220,6 +221,58 @@ function 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 = { getNote, updateNote, @@ -232,5 +285,6 @@ module.exports = { getRelationMap, changeTitle, duplicateSubtree, - eraseDeletedNotesNow + eraseDeletedNotesNow, + getDeleteNotesPreview }; diff --git a/src/routes/api/stats.js b/src/routes/api/stats.js index 1bd030367..a00b87cee 100644 --- a/src/routes/api/stats.js +++ b/src/routes/api/stats.js @@ -31,7 +31,7 @@ function getSubtreeSize(req) { const subTreeNoteIds = note.subtreeNotes.map(note => note.noteId); - sql.fillNoteIdList(subTreeNoteIds); + sql.fillParamList(subTreeNoteIds); const subTreeSize = sql.getValue(` SELECT diff --git a/src/routes/routes.js b/src/routes/routes.js index 006edfbe9..9fdfa0abf 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -290,6 +290,8 @@ function register(app) { apiRoute(GET, '/api/stats/note-size/:noteId', statsRoute.getNoteSize); apiRoute(GET, '/api/stats/subtree-size/:noteId', statsRoute.getSubtreeSize); + apiRoute(POST, '/api/delete-notes-preview', notesApiRoute.getDeleteNotesPreview); + app.use('', router); } diff --git a/src/services/sql.js b/src/services/sql.js index 5ba56f8cc..69cc8b99a 100644 --- a/src/services/sql.js +++ b/src/services/sql.js @@ -246,8 +246,8 @@ function transactional(func) { } } -function fillNoteIdList(noteIds, truncate = true) { - if (noteIds.length === 0) { +function fillParamList(paramIds, truncate = true) { + if (paramIds.length === 0) { return; } @@ -255,18 +255,18 @@ function fillNoteIdList(noteIds, truncate = true) { execute("DELETE FROM param_list"); } - noteIds = Array.from(new Set(noteIds)); + paramIds = Array.from(new Set(paramIds)); - if (noteIds.length > 30000) { - fillNoteIdList(noteIds.slice(30000), false); + if (paramIds.length > 30000) { + 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 - 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 = { @@ -287,5 +287,5 @@ module.exports = { executeScript, transactional, upsert, - fillNoteIdList + fillParamList }; diff --git a/src/views/dialogs/delete_notes.ejs b/src/views/dialogs/delete_notes.ejs index 4fab2f68d..81a0db7e4 100644 --- a/src/views/dialogs/delete_notes.ejs +++ b/src/views/dialogs/delete_notes.ejs @@ -1,5 +1,5 @@