diff --git a/src/public/app/widgets/dialogs/bulk_actions.js b/src/public/app/widgets/dialogs/bulk_actions.js
index df179c1f7..c9783ff83 100644
--- a/src/public/app/widgets/dialogs/bulk_actions.js
+++ b/src/public/app/widgets/dialogs/bulk_actions.js
@@ -47,8 +47,8 @@ const TPL = `
Affected notes: 0
-
-
@@ -71,6 +71,12 @@ const TPL = `
export default class BulkActionsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
+
+ this.$includeDescendants = this.$widget.find(".include-descendants");
+ this.$includeDescendants.on("change", () => this.refresh());
+
+ this.$affectedNoteCount = this.$widget.find(".affected-note-count");
+
this.$availableActionList = this.$widget.find(".bulk-available-action-list");
this.$existingActionList = this.$widget.find(".bulk-existing-action-list");
@@ -84,7 +90,10 @@ export default class BulkActionsDialog extends BasicWidget {
this.$executeButton = this.$widget.find(".execute-bulk-actions");
this.$executeButton.on("click", async () => {
- await server.post("bulk-action", { noteIds: this.selectedOrActiveNoteIds });
+ await server.post("bulk-action/execute", {
+ noteIds: this.selectedOrActiveNoteIds,
+ includeDescendants: this.$includeDescendants.is(":checked")
+ });
toastService.showMessage("Bulk actions have been executed successfully.", 3000);
@@ -95,6 +104,13 @@ export default class BulkActionsDialog extends BasicWidget {
async refresh() {
this.renderAvailableActions();
+ const {affectedNoteCount} = await server.post('bulk-action/affected-notes', {
+ noteIds: this.selectedOrActiveNoteIds,
+ includeDescendants: this.$includeDescendants.is(":checked")
+ });
+
+ this.$affectedNoteCount.text(affectedNoteCount);
+
const bulkActionNote = await froca.getNote('bulkaction');
const actions = bulkActionService.parseActions(bulkActionNote);
@@ -138,6 +154,7 @@ export default class BulkActionsDialog extends BasicWidget {
async bulkActionsEvent({selectedOrActiveNoteIds}) {
this.selectedOrActiveNoteIds = selectedOrActiveNoteIds;
+ this.$includeDescendants.prop("checked", false);
await this.refresh();
diff --git a/src/routes/api/bulk_action.js b/src/routes/api/bulk_action.js
index 20104a46b..902e9a675 100644
--- a/src/routes/api/bulk_action.js
+++ b/src/routes/api/bulk_action.js
@@ -2,13 +2,47 @@ const becca = require("../../becca/becca");
const bulkActionService = require("../../services/bulk_actions");
function execute(req) {
- const {noteIds} = req.body;
+ const {noteIds, includeDescendants} = req.body;
+
+ const affectedNoteIds = getAffectedNoteIds(noteIds, includeDescendants);
const bulkActionNote = becca.getNote('bulkaction');
- bulkActionService.executeActions(bulkActionNote, noteIds);
+ bulkActionService.executeActions(bulkActionNote, affectedNoteIds);
+}
+
+function getAffectedNoteCount(req) {
+ const {noteIds, includeDescendants} = req.body;
+
+ const affectedNoteIds = getAffectedNoteIds(noteIds, includeDescendants);
+
+ return {
+ affectedNoteCount: affectedNoteIds.size
+ };
+}
+
+function getAffectedNoteIds(noteIds, includeDescendants) {
+ const affectedNoteIds = new Set();
+
+ for (const noteId of noteIds) {
+ const note = becca.getNote(noteId);
+
+ if (!note) {
+ continue;
+ }
+
+ affectedNoteIds.add(noteId);
+
+ if (includeDescendants) {
+ for (const descendantNoteId of note.getDescendantNoteIds()) {
+ affectedNoteIds.add(descendantNoteId);
+ }
+ }
+ }
+ return affectedNoteIds;
}
module.exports = {
- execute
+ execute,
+ getAffectedNoteCount
};
diff --git a/src/routes/routes.js b/src/routes/routes.js
index 543c3822c..b9f2d5e5e 100644
--- a/src/routes/routes.js
+++ b/src/routes/routes.js
@@ -358,7 +358,8 @@ function register(app) {
apiRoute(GET, '/api/search/:searchString', searchRoute.search);
apiRoute(GET, '/api/search-templates', searchRoute.searchTemplates);
- apiRoute(POST, '/api/bulk-action', bulkActionRoute.execute);
+ apiRoute(POST, '/api/bulk-action/execute', bulkActionRoute.execute);
+ apiRoute(POST, '/api/bulk-action/affected-notes', bulkActionRoute.getAffectedNoteCount);
route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler);
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)