delete notes preview dialog WIP

This commit is contained in:
zadam 2021-03-14 22:54:39 +01:00
parent ec2f8ec796
commit 5b72b577b8
7 changed files with 126 additions and 18 deletions

View File

@ -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();

View File

@ -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">'))

View File

@ -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
}; };

View File

@ -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

View File

@ -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);
} }

View File

@ -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
}; };

View File

@ -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>