mirror of
https://github.com/zadam/trilium.git
synced 2025-01-15 19:51:57 +08:00
implemented "search in subtree"
This commit is contained in:
parent
b0e5ab7533
commit
90d33f56c3
13 changed files with 98 additions and 82 deletions
54
package-lock.json
generated
54
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "trilium",
|
||||
"version": "0.45.5",
|
||||
"version": "0.45.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -2044,9 +2044,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"commonmark": {
|
||||
"version": "0.29.2",
|
||||
"resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.2.tgz",
|
||||
"integrity": "sha512-spe43MvEIaPpHss1T7z4yQaFQfLGmMu+yvCwv6xqhELIwkG/ZGgDpxOPzKxnuYzYT2c+aziCCc8m2rBVLA7jUA==",
|
||||
"version": "0.29.3",
|
||||
"resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz",
|
||||
"integrity": "sha512-fvt/NdOFKaL2gyhltSy6BC4LxbbxbnPxBMl923ittqO/JBM0wQHaoYZliE4tp26cRxX/ZZtRsJlZzQrVdUkXAA==",
|
||||
"requires": {
|
||||
"entities": "~2.0",
|
||||
"mdurl": "~1.0.1",
|
||||
|
@ -2358,9 +2358,9 @@
|
|||
}
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.9.6",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.6.tgz",
|
||||
"integrity": "sha512-HngNLtPEBWRo8EFVmHFmSXAjtCX8rGNqeXQI0Gh7wCTSqwaKgPIDqu9m07wABVopNwzvOeCb+2711vQhDlcIXw=="
|
||||
"version": "1.9.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.7.tgz",
|
||||
"integrity": "sha512-IC877KBdMhBrCfBfJXHQlo0G8keZ0Opy7YIIq5QKtUbCuHMzim8S4PyiVK4YmihI3iOF9lhfUBW4AQWHTR5WHA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
|
@ -2910,9 +2910,9 @@
|
|||
}
|
||||
},
|
||||
"electron-osx-sign": {
|
||||
"version": "0.4.17",
|
||||
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.17.tgz",
|
||||
"integrity": "sha512-wUJPmZJQCs1zgdlQgeIpRcvrf7M5/COQaOV68Va1J/SgmWx5KL2otgg+fAae7luw6qz9R8Gvu/Qpe9tAOu/3xQ==",
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz",
|
||||
"integrity": "sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.5.0",
|
||||
|
@ -2944,16 +2944,16 @@
|
|||
}
|
||||
},
|
||||
"electron-packager": {
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.1.0.tgz",
|
||||
"integrity": "sha512-THNm4bz1DfvR9f0g51+NjuAYELflM8+1vhQ/iv/G8vyZNKzSMuFd5doobngQKq3rRsLdPNZVnGqDdgS884d7Og==",
|
||||
"version": "15.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.2.0.tgz",
|
||||
"integrity": "sha512-BaklTBRQy1JTijR3hi8XxHf/uo76rHbDCNM/eQHSblzE9C0NoNfOe86nPxB7y1u2jwlqoEJ4zFiHpTFioKGGRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron/get": "^1.6.0",
|
||||
"asar": "^3.0.0",
|
||||
"debug": "^4.0.1",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"electron-osx-sign": "^0.4.11",
|
||||
"electron-osx-sign": "^0.5.0",
|
||||
"extract-zip": "^2.0.0",
|
||||
"filenamify": "^4.1.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
|
@ -2965,7 +2965,7 @@
|
|||
"rcedit": "^2.0.0",
|
||||
"resolve": "^1.1.6",
|
||||
"semver": "^7.1.3",
|
||||
"yargs-parser": "^19.0.1"
|
||||
"yargs-parser": "^20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"asar": {
|
||||
|
@ -3001,12 +3001,6 @@
|
|||
"requires": {
|
||||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-19.0.1.tgz",
|
||||
"integrity": "sha512-2UuJKZmPN9S9/0s3FSCG3aNUSyC/qz56oJsMZG0NV2B44QxTXaNySp4xXW10CizmUs0DXgPY0y114dOGLvtYHg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3674,9 +3668,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"filenamify": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.1.0.tgz",
|
||||
"integrity": "sha512-KQV/uJDI9VQgN7sHH1Zbk6+42cD6mnQ2HONzkXUfPJ+K2FC8GZ1dpewbbHw0Sz8Tf5k3EVdHVayM4DoAwWlmtg==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.2.0.tgz",
|
||||
"integrity": "sha512-pkgE+4p7N1n7QieOopmn3TqJaefjdWXwEkj2XLZJLKfOgcQKkn11ahvGNgTD8mLggexLiDFQxeTs14xVU22XPA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"filename-reserved-regex": "^2.0.0",
|
||||
|
@ -3854,9 +3848,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
|
@ -7951,9 +7945,9 @@
|
|||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
|
||||
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ=="
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
|
||||
"integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ=="
|
||||
},
|
||||
"xdg-basedir": {
|
||||
"version": "4.0.0",
|
||||
|
|
|
@ -29,10 +29,10 @@
|
|||
"better-sqlite3": "7.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"commonmark": "0.29.2",
|
||||
"commonmark": "0.29.3",
|
||||
"cookie-parser": "1.4.5",
|
||||
"csurf": "1.11.0",
|
||||
"dayjs": "1.9.6",
|
||||
"dayjs": "1.9.7",
|
||||
"ejs": "3.1.5",
|
||||
"electron-debug": "3.1.0",
|
||||
"electron-dl": "3.0.2",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"turndown": "7.0.0",
|
||||
"turndown-plugin-gfm": "1.0.2",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "7.4.0",
|
||||
"ws": "7.4.1",
|
||||
"yauzl": "2.10.0",
|
||||
"yazl": "2.5.1"
|
||||
},
|
||||
|
@ -79,7 +79,7 @@
|
|||
"cross-env": "7.0.3",
|
||||
"electron": "9.3.5",
|
||||
"electron-builder": "22.9.1",
|
||||
"electron-packager": "15.1.0",
|
||||
"electron-packager": "15.2.0",
|
||||
"electron-rebuild": "2.3.4",
|
||||
"esm": "3.2.25",
|
||||
"jasmine": "3.6.3",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -129,6 +129,8 @@ class TreeContextMenu {
|
|||
});
|
||||
}
|
||||
else {
|
||||
console.log("Triggering", command, notePath);
|
||||
|
||||
this.treeWidget.triggerCommand(command, {node: this.node, notePath: notePath});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
28
src/services/search/expressions/sub_tree.js
Normal file
28
src/services/search/expressions/sub_tree.js
Normal 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;
|
|
@ -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 = [];
|
||||
|
|
|
@ -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)
|
||||
]);
|
||||
|
|
Loading…
Reference in a new issue