diff --git a/TODO b/TODO index 8f49b6646..7cbedcc17 100644 --- a/TODO +++ b/TODO @@ -2,3 +2,6 @@ - all ribbon tabs should have assignable shortcut - new icon - green theme +- polish becca entities API +- separate private and public APIs in becca entities +- handle FIXMEs diff --git a/src/becca/entities/note.js b/src/becca/entities/note.js index ca4cf174d..da88a5712 100644 --- a/src/becca/entities/note.js +++ b/src/becca/entities/note.js @@ -1008,6 +1008,24 @@ class Note extends AbstractEntity { */ removeRelation(name, value) { return this.removeAttribute(RELATION, name, value); } + searchNotesInSubtree(searchString) { + const searchService = require("../../services/search/services/search"); + + return searchService.searchNotes(searchString); + } + + searchNoteInSubtree(searchString) { + return this.searchNotesInSubtree(searchString)[0]; + } + + cloneTo(parentNoteId) { + const cloningService = require("../../services/cloning"); + + const branch = this.becca.getNote(parentNoteId).getParentBranches()[0]; + + return cloningService.cloneNoteToParent(this.noteId, branch.branchId); + } + decrypt() { if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { diff --git a/src/public/app/widgets/ribbon_widgets/search_definition.js b/src/public/app/widgets/ribbon_widgets/search_definition.js index e23b13964..b3efb9cb3 100644 --- a/src/public/app/widgets/ribbon_widgets/search_definition.js +++ b/src/public/app/widgets/ribbon_widgets/search_definition.js @@ -3,6 +3,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; import froca from "../../services/froca.js"; 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"; @@ -21,6 +22,8 @@ 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 toast from "../../services/toast.js"; const TPL = `
@@ -164,6 +167,11 @@ const TPL = ` Search & Execute actions + +
@@ -258,6 +266,19 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { this.$searchAndExecuteButton = this.$widget.find('.search-and-execute-button'); this.$searchAndExecuteButton.on('click', () => this.searchAndExecute()); + + this.$saveToNoteButton = this.$widget.find('.save-to-note-button'); + this.$saveToNoteButton.on('click', async () => { + const {notePath} = await server.post("save-search-note", {searchNoteId: this.noteId}); + + await ws.waitForMaxKnownEntityChangeId(); + + await appContext.tabManager.getActiveContext().setNote(notePath); + + console.log("notePath", notePath); + + toastService.showMessage("Search note has been saved into " + await treeService.getNotePathTitle(notePath)); + }); } async refreshResultsCommand() { @@ -278,6 +299,8 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { async refreshWithNote(note) { this.$component.show(); + this.$saveToNoteButton.toggle(!note.getAllNotePaths().find(notePathArr => !notePathArr.includes("hidden"))); + this.$searchOptions.empty(); for (const OptionClass of OPTION_CLASSES) { diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index 15753ae0c..b6ea87dc7 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -12,9 +12,16 @@ const utils = require('../../services/utils'); const path = require('path'); const Attribute = require('../../becca/entities/attribute.js'); const htmlSanitizer = require('../../services/html_sanitizer'); +const {formatAttrForSearch} = require("../../services/attribute_formatter.js"); function findClippingNote(todayNote, pageUrl) { - const notes = todayNote.getDescendantNotesWithLabel('pageUrl', pageUrl); + const notes = todayNote.searchNoteInSubtree( + formatAttrForSearch({ + type: 'label', + name: "pageUrl", + value: pageUrl + }, true) + ); for (const note of notes) { if (note.getOwnedLabelValue('clipType') === 'clippings') { diff --git a/src/routes/api/date_notes.js b/src/routes/api/date_notes.js index 1dc200512..ae3ce257d 100644 --- a/src/routes/api/date_notes.js +++ b/src/routes/api/date_notes.js @@ -13,11 +13,11 @@ function getInboxNote(req) { let inbox; - if (hoistedNote) { - ([inbox] = hoistedNote.getDescendantNotesWithLabel('hoistedInbox')); + if (!hoistedNote.isRoot()) { + inbox = hoistedNote.searchNoteInSubtree('#hoistedInbox'); if (!inbox) { - ([inbox] = hoistedNote.getDescendantNotesWithLabel('inbox')); + inbox = hoistedNote.searchNoteInSubtree('#inbox'); } if (!inbox) { @@ -114,39 +114,35 @@ function getSearchRoot() { return searchRoot; } +function saveSearchNote(req) { + const searchNote = becca.getNote(req.body.searchNoteId); + + const hoistedNote = getHoistedNote(); + let searchHome; + + if (!hoistedNote.isRoot()) { + searchHome = hoistedNote.searchNoteInSubtree('#hoistedSearchHome') + || hoistedNote.searchNoteInSubtree('#searchHome') + || hoistedNote; + } + else { + const today = dateUtils.localNowDate(); + + searchHome = hoistedNote.searchNoteInSubtree('#searchHome') + || dateNoteService.getDateNote(today); + } + + return searchNote.cloneTo(searchHome.noteId); +} + function createSearchNote(req) { const params = req.body; const searchString = params.searchString || ""; - let ancestorNoteId = params.ancestorNoteId; - const hoistedNote = getHoistedNote(); - - let searchHome = getSearchRoot(); - - // if (hoistedNote) { - // ([searchHome] = hoistedNote.getDescendantNotesWithLabel('hoistedSearchHome')); - // - // if (!searchHome) { - // ([searchHome] = hoistedNote.getDescendantNotesWithLabel('searchHome')); - // } - // - // if (!searchHome) { - // searchHome = hoistedNote; - // } - // - // if (!ancestorNoteId) { - // ancestorNoteId = hoistedNote.noteId; - // } - // } - // else { - // const today = dateUtils.localNowDate(); - // - // searchHome = attributeService.getNoteWithLabel('searchHome') - // || dateNoteService.getDateNote(today); - // } + const ancestorNoteId = params.ancestorNoteId || hoistedNote.noteId; const {note} = noteService.createNewNote({ - parentNoteId: searchHome.noteId, + parentNoteId: getSearchRoot().noteId, title: 'Search: ' + searchString, content: "", type: 'search', @@ -163,9 +159,7 @@ function createSearchNote(req) { } function getHoistedNote() { - return cls.getHoistedNoteId() && cls.getHoistedNoteId() !== 'root' - ? becca.getNote(cls.getHoistedNoteId()) - : null; + return becca.getNote(cls.getHoistedNoteId()); } module.exports = { @@ -175,5 +169,6 @@ module.exports = { getYearNote, getDateNotesForMonth, createSqlConsole, - createSearchNote + createSearchNote, + saveSearchNote }; diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 0b27f97ea..e526afe8e 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -92,7 +92,7 @@ function update(name, value) { } function getUserThemes() { - const notes = searchService.findNotes("#appTheme"); + const notes = searchService.searchNotes("#appTheme"); const ret = []; for (const note of notes) { diff --git a/src/routes/routes.js b/src/routes/routes.js index 716ba432e..3b71644df 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -212,6 +212,7 @@ function register(app) { apiRoute(GET, '/api/date-notes/notes-for-month/:month', dateNotesRoute.getDateNotesForMonth); apiRoute(POST, '/api/sql-console', dateNotesRoute.createSqlConsole); apiRoute(POST, '/api/search-note', dateNotesRoute.createSearchNote); + apiRoute(POST, '/api/save-search-note', dateNotesRoute.saveSearchNote); route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage); route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.uploadImage, apiResultHandler); diff --git a/src/services/attributes.js b/src/services/attributes.js index e91c8bf69..98450bcd8 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -60,7 +60,7 @@ const BUILTIN_ATTRIBUTES = [ ]; function getNotesWithLabel(name, value) { - return searchService.findNotes(formatAttrForSearch({type: 'label', name, value}, true)); + return searchService.searchNotes(formatAttrForSearch({type: 'label', name, value}, true)); } function getNoteIdsWithLabels(names) { @@ -75,6 +75,7 @@ function getNoteIdsWithLabels(names) { return Array.from(noteIds); } +// TODO: should be in search service function getNoteWithLabel(name, value) { const notes = getNotesWithLabel(name, value); diff --git a/src/services/cloning.js b/src/services/cloning.js index ca113e13f..5b69784cf 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.js @@ -8,6 +8,7 @@ const Branch = require('../becca/entities/branch.js'); const TaskContext = require("./task_context.js"); const utils = require('./utils'); const becca = require("../becca/becca.js"); +const beccaService = require("../becca/becca_service"); function cloneNoteToParent(noteId, parentBranchId, prefix) { const parentBranch = becca.getBranch(parentBranchId); @@ -32,7 +33,11 @@ function cloneNoteToParent(noteId, parentBranchId, prefix) { parentBranch.isExpanded = true; // the new target should be expanded so it immediately shows up to the user parentBranch.save(); - return { success: true, branchId: branch.branchId }; + return { + success: true, + branchId: branch.branchId, + notePath: beccaService.getNotePath(parentBranch.noteId).path + "/" + noteId + }; } function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js index d2bb85454..b0eec8be7 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.js @@ -136,7 +136,7 @@ function parseQueryToExpression(query, searchContext) { * @param {string} query * @return {Note[]} */ -function findNotes(query) { +function searchNotes(query) { const searchResults = findResultsWithQuery(query, new SearchContext()); return searchResults.map(sr => becca.notes[sr.noteId]); @@ -263,5 +263,5 @@ module.exports = { searchTrimmedNotes, searchNotesForAutocomplete, findResultsWithQuery, - findNotes + searchNotes };