implemented "search in subtree"

This commit is contained in:
zadam
2020-12-05 23:00:28 +01:00
parent b0e5ab7533
commit 90d33f56c3
13 changed files with 98 additions and 82 deletions

View File

@@ -1,5 +1,6 @@
import treeCache from "./tree_cache.js";
import server from "./server.js";
import ws from "./ws.js";
/** @return {NoteShort} */
async function getInboxNote() {
@@ -42,10 +43,20 @@ async function createSqlConsole() {
}
/** @return {NoteShort} */
async function createSearchNote() {
async function createSearchNote(subTreeNoteId = null) {
const note = await server.post('search-note');
return await treeCache.getNote(note.noteId);
if (subTreeNoteId) {
await server.put(`notes/${note.noteId}/attributes`, [
{ type: 'label', name: 'subTreeNoteId', value: subTreeNoteId }
]);
}
await ws.waitForMaxKnownEntityChangeId();
const noteShort = await treeCache.getNote(note.noteId);
return noteShort;
}
export default {

View File

@@ -1,7 +1,7 @@
import Component from "../widgets/component.js";
import appContext from "./app_context.js";
import dateNoteService from "../services/date_notes.js";
import noteCreateService from "../services/note_create.js";
import treeService from "../services/tree.js";
export default class DialogCommandExecutor extends Component {
jumpToNoteCommand() {
@@ -75,6 +75,16 @@ export default class DialogCommandExecutor extends Component {
appContext.triggerCommand('focusOnSearchDefinition', {tabId: tabContext.tabId});
}
async searchInSubtreeCommand({notePath}) {
const noteId = treeService.getNoteIdFromNotePath(notePath);
const searchNote = await dateNoteService.createSearchNote(noteId);
const tabContext = await appContext.tabManager.openTabWithNote(searchNote.noteId, true);
appContext.triggerCommand('focusOnSearchDefinition', {tabId: tabContext.tabId});
}
showBackendLogCommand() {
import("../dialogs/backend_log.js").then(d => d.showDialog());
}

View File

@@ -176,21 +176,6 @@ export default class Entrypoints extends Component {
}
}
async searchForResultsCommand({searchText}) {
const response = await server.get('search/' + encodeURIComponent(searchText) + '?includeNoteContent=true&excludeArchived=true&fuzzyAttributeSearch=false');
if (!response.success) {
toastService.showError("Search failed: " + response.message, 10000);
// even in this case we'll show the results
}
this.triggerEvent('searchResults', {results: response.results});
// have at least some feedback which is good especially in situations
// when the result list does not change with a query
toastService.showMessage("Search finished successfully.");
}
async switchToDesktopVersionCommand() {
utils.setCookie('trilium-device', 'desktop');

View File

@@ -129,6 +129,8 @@ class TreeContextMenu {
});
}
else {
console.log("Triggering", command, notePath);
this.treeWidget.triggerCommand(command, {node: this.node, notePath: notePath});
}
}

View File

@@ -1220,7 +1220,9 @@ export default class NoteTreeWidget extends TabAwareWidget {
for (const action of actions) {
for (const shortcut of action.effectiveShortcuts) {
hotKeyMap[utils.normalizeShortcut(shortcut)] = node => {
this.triggerCommand(action.actionName, {node});
const notePath = treeService.getNotePath(node);
this.triggerCommand(action.actionName, {node, notePath});
return false;
}

View File

@@ -96,13 +96,13 @@ export default class SearchDefinitionWidget extends TabAwareWidget {
async updateSearch() {
const searchString = this.$searchString.val();
const subNoteId = this.$limitSearchToSubtree.getSelectedNoteId();
const subTreeNoteId = this.$limitSearchToSubtree.getSelectedNoteId();
const includeNoteContent = this.$searchWithinNoteContent.is(":checked");
await server.put(`notes/${this.noteId}/attributes`, [
{ type: 'label', name: 'searchString', value: searchString },
{ type: 'label', name: 'includeNoteContent', value: includeNoteContent ? 'true' : 'false' },
subNoteId ? { type: 'label', name: 'subTreeNoteId', value: subNoteId } : undefined,
subTreeNoteId ? { type: 'label', name: 'subTreeNoteId', value: subTreeNoteId } : undefined,
].filter(it => !!it));
if (this.note.title.startsWith('Search: ')) {
@@ -122,7 +122,13 @@ export default class SearchDefinitionWidget extends TabAwareWidget {
this.$component.show();
this.$searchString.val(this.note.getLabelValue('searchString'));
this.$searchWithinNoteContent.prop('checked', this.note.getLabelValue('includeNoteContent') === 'true');
this.$limitSearchToSubtree.val(this.note.getLabelValue('subTreeNoteId'));
const subTreeNoteId = this.note.getLabelValue('subTreeNoteId');
const subTreeNote = subTreeNoteId ? await treeCache.getNote(subTreeNoteId, true) : null;
this.$limitSearchToSubtree
.val(subTreeNote ? subTreeNote.title : "")
.setSelectedNotePath(subTreeNoteId);
this.refreshResults(); // important specifically when this search note was not yet refreshed
}

View File

@@ -6,30 +6,6 @@ const log = require('../../services/log');
const scriptService = require('../../services/script');
const searchService = require('../../services/search/services/search');
function searchNotes(req) {
const searchContext = new SearchContext({
includeNoteContent: req.query.includeNoteContent === 'true',
excludeArchived: req.query.excludeArchived === 'true',
fuzzyAttributeSearch: req.query.fuzzyAttributeSearch === 'true'
});
const {count, results} = searchService.searchTrimmedNotes(req.params.searchString, searchContext);
try {
return {
success: !searchContext.hasError(),
message: searchContext.getError(),
count,
results
}
}
catch {
return {
success: false
}
}
}
async function searchFromNote(req) {
const note = repository.getNote(req.params.noteId);
@@ -58,6 +34,7 @@ async function searchFromNote(req) {
else if (searchString) {
const searchContext = new SearchContext({
includeNoteContent: note.getLabelValue('includeNoteContent') === 'true',
subTreeNoteId: note.getLabelValue('subTreeNoteId'),
excludeArchived: true,
fuzzyAttributeSearch: false
});
@@ -197,7 +174,6 @@ function formatValue(val) {
}
module.exports = {
searchNotes,
searchFromNote,
getRelatedNotes
};

View File

@@ -254,7 +254,6 @@ function register(app) {
route(POST, '/api/sender/image', [auth.checkToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
route(POST, '/api/sender/note', [auth.checkToken], senderRoute.saveNote, apiResultHandler);
apiRoute(GET, '/api/search/:searchString', searchRoute.searchNotes);
apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote);
apiRoute(POST, '/api/search-related', searchRoute.getRelatedNotes);

View File

@@ -0,0 +1,28 @@
"use strict";
const Expression = require('./expression');
const NoteSet = require('../note_set');
const log = require('../../log');
const noteCache = require('../../note_cache/note_cache');
class SubTreeExp extends Expression {
constructor(subTreeNoteId) {
super();
this.subTreeNoteId = subTreeNoteId;
}
execute(inputNoteSet, searchContext) {
const subTreeNote = noteCache.notes[this.subTreeNoteId];
if (!subTreeNote) {
log.error(`Subtree note '${this.subTreeNoteId}' was not not found.`);
return new NoteSet([]);
}
return new NoteSet(subTreeNote.subtreeNotes).intersection(inputNoteSet);
}
}
module.exports = SubTreeExp;

View File

@@ -3,6 +3,7 @@
class SearchContext {
constructor(params = {}) {
this.includeNoteContent = !!params.includeNoteContent;
this.subTreeNoteId = params.subTreeNoteId;
this.excludeArchived = !!params.excludeArchived;
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
this.highlightedTokens = [];

View File

@@ -15,6 +15,7 @@ const NoteCacheFulltextExp = require('../expressions/note_cache_flat_text.js');
const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext.js');
const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext.js');
const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js');
const SubTreeExp = require("../expressions/sub_tree.js");
const buildComparator = require('./build_comparator.js');
const ValueExtractor = require('../value_extractor.js');
@@ -409,6 +410,7 @@ function getExpression(tokens, searchContext, level = 0) {
function parse({fulltextTokens, expressionTokens, searchContext}) {
return AndExp.of([
searchContext.excludeArchived ? new PropertyComparisonExp("isarchived", buildComparator("=", "false")) : null,
searchContext.subTreeNoteId ? new SubTreeExp(searchContext.subTreeNoteId) : null,
getFulltext(fulltextTokens, searchContext),
getExpression(expressionTokens, searchContext)
]);