diff --git a/.idea/misc.xml b/.idea/misc.xml index bc4ec316b..8524fae44 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/src/public/app/dialogs/bulk_assign_attributes.js b/src/public/app/dialogs/bulk_assign_attributes.js index 4691618f0..21aab712f 100644 --- a/src/public/app/dialogs/bulk_assign_attributes.js +++ b/src/public/app/dialogs/bulk_assign_attributes.js @@ -1,7 +1,39 @@ import utils from "../services/utils.js"; +import bulkActionService from "../services/bulk_action.js"; +import froca from "../services/froca.js"; const $dialog = $("#bulk-assign-attributes-dialog"); +const $availableActionList = $("#bulk-available-action-list"); +const $existingActionList = $("#bulk-existing-action-list"); + +$dialog.on('click', '[data-action-add]', async event => { + const actionName = $(event.target).attr('data-action-add'); + + await bulkActionService.addAction('bulkaction', actionName); + + await refresh(); +}); + +for (const action of bulkActionService.ACTION_CLASSES) { + $availableActionList.append( + $('') + .attr('data-action-add', action.actionName) + .text(action.actionTitle) + ); +} + +async function refresh() { + const bulkActionNote = await froca.getNote('bulkaction'); + + const actions = bulkActionService.parseActions(bulkActionNote); + + $existingActionList + .empty() + .append(...actions.map(action => action.render())); +} export async function showDialog(nodes) { + await refresh(); + utils.openDialog($dialog); } diff --git a/src/public/app/services/bulk_action.js b/src/public/app/services/bulk_action.js new file mode 100644 index 000000000..ea7841cf8 --- /dev/null +++ b/src/public/app/services/bulk_action.js @@ -0,0 +1,68 @@ +import server from "./server.js"; +import ws from "./ws.js"; +import MoveNoteSearchAction from "../widgets/search_actions/move_note.js"; +import DeleteNoteSearchAction from "../widgets/search_actions/delete_note.js"; +import DeleteNoteRevisionsSearchAction from "../widgets/search_actions/delete_note_revisions.js"; +import DeleteLabelSearchAction from "../widgets/search_actions/delete_label.js"; +import DeleteRelationSearchAction from "../widgets/search_actions/delete_relation.js"; +import RenameLabelSearchAction from "../widgets/search_actions/rename_label.js"; +import RenameRelationSearchAction from "../widgets/search_actions/rename_relation.js"; +import SetLabelValueSearchAction from "../widgets/search_actions/set_label_value.js"; +import SetRelationTargetSearchAction from "../widgets/search_actions/set_relation_target.js"; +import ExecuteScriptSearchAction from "../widgets/search_actions/execute_script.js"; + +const ACTION_CLASSES = [ + MoveNoteSearchAction, + DeleteNoteSearchAction, + DeleteNoteRevisionsSearchAction, + DeleteLabelSearchAction, + DeleteRelationSearchAction, + RenameLabelSearchAction, + RenameRelationSearchAction, + SetLabelValueSearchAction, + SetRelationTargetSearchAction, + ExecuteScriptSearchAction +]; + +async function addAction(noteId, actionName) { + await server.post(`notes/${noteId}/attributes`, { + type: 'label', + name: 'action', + value: JSON.stringify({ + name: actionName + }) + }); + + await ws.waitForMaxKnownEntityChangeId(); +} + +function parseActions(note) { + const actionLabels = note.getLabels('action'); + + return actionLabels.map(actionAttr => { + let actionDef; + + try { + actionDef = JSON.parse(actionAttr.value); + } catch (e) { + logError(`Parsing of attribute: '${actionAttr.value}' failed with error: ${e.message}`); + return null; + } + + const ActionClass = ACTION_CLASSES.find(actionClass => actionClass.actionName === actionDef.name); + + if (!ActionClass) { + logError(`No action class for '${actionDef.name}' found.`); + return null; + } + + return new ActionClass(actionAttr, actionDef); + }) + .filter(action => !!action); +} + +export default { + addAction, + parseActions, + ACTION_CLASSES +}; \ No newline at end of file diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index 1b365074a..9e2e3bde3 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -539,6 +539,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { subNode.load(); } }); + }, + select: () => { + // TODO } }); diff --git a/src/public/app/widgets/ribbon_widgets/search_definition.js b/src/public/app/widgets/ribbon_widgets/search_definition.js index 824bd4669..c90d2e667 100644 --- a/src/public/app/widgets/ribbon_widgets/search_definition.js +++ b/src/public/app/widgets/ribbon_widgets/search_definition.js @@ -5,14 +5,6 @@ import ws from "../../services/ws.js"; import toastService from "../../services/toast.js"; import treeService from "../../services/tree.js"; -import DeleteNoteSearchAction from "../search_actions/delete_note.js"; -import DeleteLabelSearchAction from "../search_actions/delete_label.js"; -import DeleteRelationSearchAction from "../search_actions/delete_relation.js"; -import RenameLabelSearchAction from "../search_actions/rename_label.js"; -import SetLabelValueSearchAction from "../search_actions/set_label_value.js"; -import SetRelationTargetSearchAction from "../search_actions/set_relation_target.js"; -import RenameRelationSearchAction from "../search_actions/rename_relation.js"; -import ExecuteScriptSearchAction from "../search_actions/execute_script.js" import SearchString from "../search_options/search_string.js"; import FastSearch from "../search_options/fast_search.js"; import Ancestor from "../search_options/ancestor.js"; @@ -20,10 +12,9 @@ import IncludeArchivedNotes from "../search_options/include_archived_notes.js"; import OrderBy from "../search_options/order_by.js"; import SearchScript from "../search_options/search_script.js"; import Limit from "../search_options/limit.js"; -import DeleteNoteRevisionsSearchAction from "../search_actions/delete_note_revisions.js"; import Debug from "../search_options/debug.js"; import appContext from "../../services/app_context.js"; -import MoveNoteSearchAction from "../search_actions/move_note.js"; +import bulkActionService from "../../services/bulk_action.js"; const TPL = ` @@ -127,28 +118,7 @@ const TPL = ` action - - - Move note - - Delete note - - Delete note revisions - - Delete label - - Delete relation - - Rename label - - Rename relation - - Set label value - - Set relation target - - Execute script - + @@ -193,23 +163,6 @@ const OPTION_CLASSES = [ Debug ]; -const ACTION_CLASSES = {}; - -for (const clazz of [ - MoveNoteSearchAction, - DeleteNoteSearchAction, - DeleteNoteRevisionsSearchAction, - DeleteLabelSearchAction, - DeleteRelationSearchAction, - RenameLabelSearchAction, - RenameRelationSearchAction, - SetLabelValueSearchAction, - SetRelationTargetSearchAction, - ExecuteScriptSearchAction -]) { - ACTION_CLASSES[clazz.actionName] = clazz; -} - export default class SearchDefinitionWidget extends NoteContextAwareWidget { isEnabled() { return this.note && this.note.type === 'search'; @@ -228,6 +181,15 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { this.$widget = $(TPL); this.contentSized(); this.$component = this.$widget.find('.search-definition-widget'); + this.$actionList = this.$widget.find('.action-list'); + + for (const action of bulkActionService.ACTION_CLASSES) { + this.$actionList.append( + $('') + .attr('data-action-add', action.actionName) + .text(action.actionTitle) + ); + } this.$widget.on('click', '[data-search-option-add]', async event => { const searchOptionName = $(event.target).attr('data-search-option-add'); @@ -244,19 +206,11 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { }); this.$widget.on('click', '[data-action-add]', async event => { - const actionName = $(event.target).attr('data-action-add'); - - await server.post(`notes/${this.noteId}/attributes`, { - type: 'label', - name: 'action', - value: JSON.stringify({ - name: actionName - }) - }); - this.$widget.find('.action-add-toggle').dropdown('toggle'); - await ws.waitForMaxKnownEntityChangeId(); + const actionName = $(event.target).attr('data-action-add'); + + await bulkActionService.addAction(this.noteId, actionName); this.refresh(); }); @@ -319,35 +273,13 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { } } - this.$actionOptions.empty(); + const actions = bulkActionService.parseActions(this.note); - const actionLabels = this.note.getLabels('action'); + this.$actionOptions + .empty() + .append(...actions.map(action => action.render())); - for (const actionAttr of actionLabels) { - let actionDef; - - try { - actionDef = JSON.parse(actionAttr.value); - } - catch (e) { - logError(`Parsing of attribute: '${actionAttr.value}' failed with error: ${e.message}`); - continue; - } - - const ActionClass = ACTION_CLASSES[actionDef.name]; - - if (!ActionClass) { - logError(`No action class for '${actionDef.name}' found.`); - continue; - } - - const action = new ActionClass(actionAttr, actionDef).setParent(this); - this.child(action); - - this.$actionOptions.append(action.render()); - } - - this.$searchAndExecuteButton.css('visibility', actionLabels.length > 0 ? 'visible' : 'hidden'); + this.$searchAndExecuteButton.css('visibility', actions.length > 0 ? 'visible' : 'hidden'); } getContent() { diff --git a/src/public/app/widgets/search_actions/abstract_search_action.js b/src/public/app/widgets/search_actions/abstract_search_action.js index 28d3133a8..3966d9355 100644 --- a/src/public/app/widgets/search_actions/abstract_search_action.js +++ b/src/public/app/widgets/search_actions/abstract_search_action.js @@ -3,10 +3,8 @@ import ws from "../../services/ws.js"; import Component from "../component.js"; import utils from "../../services/utils.js"; -export default class AbstractSearchAction extends Component { +export default class AbstractSearchAction { constructor(attribute, actionDef) { - super(); - this.attribute = attribute; this.actionDef = actionDef; } diff --git a/src/public/app/widgets/search_actions/delete_label.js b/src/public/app/widgets/search_actions/delete_label.js index 7141e44e7..cf2916849 100644 --- a/src/public/app/widgets/search_actions/delete_label.js +++ b/src/public/app/widgets/search_actions/delete_label.js @@ -20,6 +20,7 @@ const TPL = ` export default class DeleteLabelSearchAction extends AbstractSearchAction { static get actionName() { return "deleteLabel"; } + static get actionTitle() { return "Delete label"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/delete_note.js b/src/public/app/widgets/search_actions/delete_note.js index 9e996dd4d..9e65e861b 100644 --- a/src/public/app/widgets/search_actions/delete_note.js +++ b/src/public/app/widgets/search_actions/delete_note.js @@ -14,6 +14,7 @@ const TPL = ` export default class DeleteNoteSearchAction extends AbstractSearchAction { static get actionName() { return "deleteNote"; } + static get actionTitle() { return "Delete note"; } doRender() { return $(TPL); diff --git a/src/public/app/widgets/search_actions/delete_note_revisions.js b/src/public/app/widgets/search_actions/delete_note_revisions.js index 00e06c179..3305780ee 100644 --- a/src/public/app/widgets/search_actions/delete_note_revisions.js +++ b/src/public/app/widgets/search_actions/delete_note_revisions.js @@ -21,6 +21,7 @@ const TPL = ` export default class DeleteNoteRevisionsSearchAction extends AbstractSearchAction { static get actionName() { return "deleteNoteRevisions"; } + static get actionTitle() { return "Delete note revisions"; } doRender() { return $(TPL); diff --git a/src/public/app/widgets/search_actions/delete_relation.js b/src/public/app/widgets/search_actions/delete_relation.js index eb60bae35..aace37fc2 100644 --- a/src/public/app/widgets/search_actions/delete_relation.js +++ b/src/public/app/widgets/search_actions/delete_relation.js @@ -22,6 +22,7 @@ const TPL = ` export default class DeleteRelationSearchAction extends AbstractSearchAction { static get actionName() { return "deleteRelation"; } + static get actionTitle() { return "Delete relation"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/execute_script.js b/src/public/app/widgets/search_actions/execute_script.js index e84d3b394..088fc379c 100644 --- a/src/public/app/widgets/search_actions/execute_script.js +++ b/src/public/app/widgets/search_actions/execute_script.js @@ -35,6 +35,7 @@ const TPL = ` export default class ExecuteScriptSearchAction extends AbstractSearchAction { static get actionName() { return "executeScript"; } + static get actionTitle() { return "Execute script"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/move_note.js b/src/public/app/widgets/search_actions/move_note.js index 3d7fcf139..fffcae00f 100644 --- a/src/public/app/widgets/search_actions/move_note.js +++ b/src/public/app/widgets/search_actions/move_note.js @@ -35,6 +35,7 @@ const TPL = ` export default class MoveNoteSearchAction extends AbstractSearchAction { static get actionName() { return "moveNote"; } + static get actionTitle() { return "Move note"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/rename_label.js b/src/public/app/widgets/search_actions/rename_label.js index b61364c62..6253159c5 100644 --- a/src/public/app/widgets/search_actions/rename_label.js +++ b/src/public/app/widgets/search_actions/rename_label.js @@ -29,6 +29,7 @@ const TPL = ` export default class RenameLabelSearchAction extends AbstractSearchAction { static get actionName() { return "renameLabel"; } + static get actionTitle() { return "Rename label"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/rename_relation.js b/src/public/app/widgets/search_actions/rename_relation.js index 0e4d36248..93d13aee1 100644 --- a/src/public/app/widgets/search_actions/rename_relation.js +++ b/src/public/app/widgets/search_actions/rename_relation.js @@ -29,6 +29,7 @@ const TPL = ` export default class RenameRelationSearchAction extends AbstractSearchAction { static get actionName() { return "renameRelation"; } + static get actionTitle() { return "Rename relation"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/set_label_value.js b/src/public/app/widgets/search_actions/set_label_value.js index a8be44cba..745b2af20 100644 --- a/src/public/app/widgets/search_actions/set_label_value.js +++ b/src/public/app/widgets/search_actions/set_label_value.js @@ -39,6 +39,7 @@ const TPL = ` export default class SetLabelValueSearchAction extends AbstractSearchAction { static get actionName() { return "setLabelValue"; } + static get actionTitle() { return "Set label value"; } doRender() { const $action = $(TPL); diff --git a/src/public/app/widgets/search_actions/set_relation_target.js b/src/public/app/widgets/search_actions/set_relation_target.js index 7633a89fa..4ab121d81 100644 --- a/src/public/app/widgets/search_actions/set_relation_target.js +++ b/src/public/app/widgets/search_actions/set_relation_target.js @@ -41,6 +41,7 @@ const TPL = ` export default class SetRelationTargetSearchAction extends AbstractSearchAction { static get actionName() { return "setRelationTarget"; } + static get actionTitle() { return "Set relation target"; } doRender() { const $action = $(TPL); diff --git a/src/services/special_notes.js b/src/services/special_notes.js index 6e82e37dd..ebe447d98 100644 --- a/src/services/special_notes.js +++ b/src/services/special_notes.js @@ -219,10 +219,28 @@ function getShareRoot() { return shareRoot; } +function getBulkActionNote() { + let bulkActionNote = becca.getNote('bulkaction'); + + if (!bulkActionNote) { + bulkActionNote = noteService.createNewNote({ + branchId: 'bulkaction', + noteId: 'bulkaction', + title: 'Bulk action', + type: 'text', + content: '', + parentNoteId: getHiddenRoot().noteId + }).note; + } + + return bulkActionNote; +} + function createMissingSpecialNotes() { getSinglesNoteRoot(); getSqlConsoleRoot(); getGlobalNoteMap(); + getBulkActionNote(); // share root is not automatically created since it's visible in the tree and many won't need it/use it const hidden = getHiddenRoot(); @@ -239,5 +257,6 @@ module.exports = { createSearchNote, saveSearchNote, createMissingSpecialNotes, - getShareRoot + getShareRoot, + getBulkActionNote, }; diff --git a/src/views/dialogs/bulk_assign_attributes.ejs b/src/views/dialogs/bulk_assign_attributes.ejs index abaa95011..e22f31daf 100644 --- a/src/views/dialogs/bulk_assign_attributes.ejs +++ b/src/views/dialogs/bulk_assign_attributes.ejs @@ -1,3 +1,12 @@ + + @@ -10,10 +19,23 @@ - Hi! + Affected notes: 0 + + + + + Include descendant notes + + + + Available actions: + + + +