mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
server-ts: Port services/search/services/search
This commit is contained in:
parent
15169289f0
commit
2fbd2e3c29
@ -1,4 +1,4 @@
|
||||
const searchService = require('../../src/services/search/services/search.js');
|
||||
const searchService = require('../../src/services/search/services/search');
|
||||
const BNote = require('../../src/becca/entities/bnote.js');
|
||||
const BBranch = require('../../src/becca/entities/bbranch.js');
|
||||
const SearchContext = require('../../src/services/search/search_context');
|
||||
|
@ -78,13 +78,13 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
|
||||
// following attributes are filled during searching in the database
|
||||
/** size of the content in bytes */
|
||||
private contentSize!: number | null;
|
||||
contentSize!: number | null;
|
||||
/** size of the note content, attachment contents in bytes */
|
||||
private contentAndAttachmentsSize!: number | null;
|
||||
contentAndAttachmentsSize!: number | null;
|
||||
/** size of the note content, attachment contents and revision contents in bytes */
|
||||
private contentAndAttachmentsAndRevisionsSize!: number | null;
|
||||
contentAndAttachmentsAndRevisionsSize!: number | null;
|
||||
/** number of note revisions for this note */
|
||||
private revisionCount!: number | null;
|
||||
revisionCount!: number | null;
|
||||
|
||||
constructor(row?: Partial<NoteRow>) {
|
||||
super();
|
||||
|
@ -5,7 +5,7 @@ const mappers = require('./mappers.js');
|
||||
const noteService = require('../services/notes');
|
||||
const TaskContext = require('../services/task_context');
|
||||
const v = require('./validators.js');
|
||||
const searchService = require('../services/search/services/search.js');
|
||||
const searchService = require('../services/search/services/search');
|
||||
const SearchContext = require('../services/search/search_context');
|
||||
const zipExportService = require('../services/export/zip.js');
|
||||
const zipImportService = require('../services/import/zip.js');
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const beccaService = require('../../becca/becca_service');
|
||||
const searchService = require('../../services/search/services/search.js');
|
||||
const searchService = require('../../services/search/services/search');
|
||||
const log = require('../../services/log');
|
||||
const utils = require('../../services/utils');
|
||||
const cls = require('../../services/cls');
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
const optionService = require('../../services/options');
|
||||
const log = require('../../services/log');
|
||||
const searchService = require('../../services/search/services/search.js');
|
||||
const searchService = require('../../services/search/services/search');
|
||||
const ValidationError = require('../../errors/validation_error');
|
||||
|
||||
// options allowed to be updated directly in the Options dialog
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
const becca = require('../../becca/becca');
|
||||
const SearchContext = require('../../services/search/search_context');
|
||||
const searchService = require('../../services/search/services/search.js');
|
||||
const searchService = require('../../services/search/services/search');
|
||||
const bulkActionService = require('../../services/bulk_actions.js');
|
||||
const cls = require('../../services/cls');
|
||||
const {formatAttrForSearch} = require('../../services/attribute_formatter');
|
||||
|
@ -46,7 +46,7 @@ const attributesRoute = require('./api/attributes.js');
|
||||
const scriptRoute = require('./api/script.js');
|
||||
const senderRoute = require('./api/sender.js');
|
||||
const filesRoute = require('./api/files.js');
|
||||
const searchRoute = require('./api/search.js');
|
||||
const searchRoute = require('./api/search');
|
||||
const bulkActionRoute = require('./api/bulk_action.js');
|
||||
const specialNotesRoute = require('./api/special_notes.js');
|
||||
const noteMapRoute = require('./api/note_map.js');
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
const searchService = require('./search/services/search.js');
|
||||
const searchService = require('./search/services/search');
|
||||
const sql = require('./sql');
|
||||
const becca = require('../becca/becca');
|
||||
const BAttribute = require('../becca/entities/battribute');
|
||||
|
@ -11,7 +11,7 @@ const dayjs = require('dayjs');
|
||||
const xml2js = require('xml2js');
|
||||
const cloningService = require('./cloning.js');
|
||||
const appInfo = require('./app_info');
|
||||
const searchService = require('./search/services/search.js');
|
||||
const searchService = require('./search/services/search');
|
||||
const SearchContext = require('./search/search_context');
|
||||
const becca = require('../becca/becca');
|
||||
const ws = require('./ws');
|
||||
|
@ -5,7 +5,7 @@ const attributeService = require('./attributes.js');
|
||||
const dateUtils = require('./date_utils');
|
||||
const sql = require('./sql');
|
||||
const protectedSessionService = require('./protected_session');
|
||||
const searchService = require('../services/search/services/search.js');
|
||||
const searchService = require('../services/search/services/search');
|
||||
const SearchContext = require('../services/search/search_context');
|
||||
const hoistedNoteService = require('./hoisted_note');
|
||||
|
||||
|
@ -1,20 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import hoistedNoteService = require('../hoisted_note');
|
||||
|
||||
interface SearchParams {
|
||||
fastSearch?: boolean;
|
||||
includeArchivedNotes?: boolean;
|
||||
includeHiddenNotes?: boolean;
|
||||
ignoreHoistedNote?: boolean;
|
||||
ancestorNoteId?: string;
|
||||
ancestorDepth?: string;
|
||||
orderBy?: string;
|
||||
orderDirection?: string;
|
||||
limit?: number;
|
||||
debug?: boolean;
|
||||
fuzzyAttributeSearch?: boolean;
|
||||
}
|
||||
import { SearchParams } from './services/types';
|
||||
|
||||
class SearchContext {
|
||||
|
||||
@ -26,9 +13,9 @@ class SearchContext {
|
||||
ancestorDepth?: string;
|
||||
orderBy?: string;
|
||||
orderDirection?: string;
|
||||
limit?: number;
|
||||
limit?: number | null;
|
||||
debug?: boolean;
|
||||
debugInfo: string | null;
|
||||
debugInfo: {} | null;
|
||||
fuzzyAttributeSearch: boolean;
|
||||
highlightedTokens: string[];
|
||||
originalQuery: string;
|
||||
|
@ -4,13 +4,15 @@ import beccaService = require('../../becca/becca_service');
|
||||
import becca = require('../../becca/becca');
|
||||
|
||||
class SearchResult {
|
||||
private notePathArray: string[];
|
||||
private notePathTitle: string;
|
||||
private score?: number;
|
||||
notePathArray: string[];
|
||||
score: number;
|
||||
notePathTitle: string;
|
||||
highlightedNotePathTitle?: string;
|
||||
|
||||
constructor(notePathArray: string[]) {
|
||||
this.notePathArray = notePathArray;
|
||||
this.notePathTitle = beccaService.getNoteTitleForPath(notePathArray);
|
||||
this.score = 0;
|
||||
}
|
||||
|
||||
get notePath() {
|
||||
|
@ -448,13 +448,14 @@ function getExpression(tokens: TokenData[], searchContext: SearchContext, level
|
||||
|
||||
function parse({fulltextTokens, expressionTokens, searchContext}: {
|
||||
fulltextTokens: TokenData[],
|
||||
expressionTokens: TokenData[],
|
||||
searchContext: SearchContext
|
||||
expressionTokens: (TokenData | TokenData[])[],
|
||||
searchContext: SearchContext,
|
||||
originalQuery: string
|
||||
}) {
|
||||
let expression: Expression | undefined | null;
|
||||
|
||||
try {
|
||||
expression = getExpression(expressionTokens, searchContext);
|
||||
expression = getExpression(expressionTokens as TokenData[], searchContext);
|
||||
}
|
||||
catch (e: any) {
|
||||
searchContext.addError(e.message);
|
||||
@ -475,7 +476,7 @@ function parse({fulltextTokens, expressionTokens, searchContext}: {
|
||||
exp = new OrderByAndLimitExp([{
|
||||
valueExtractor: new ValueExtractor(searchContext, ['note', searchContext.orderBy]),
|
||||
direction: searchContext.orderDirection
|
||||
}], searchContext.limit);
|
||||
}], searchContext.limit || undefined);
|
||||
|
||||
(exp as any).subExpression = filterExp;
|
||||
}
|
||||
|
@ -1,22 +1,28 @@
|
||||
"use strict";
|
||||
|
||||
const normalizeString = require("normalize-strings");
|
||||
const lex = require('./lex');
|
||||
const handleParens = require('./handle_parens');
|
||||
const parse = require('./parse');
|
||||
const SearchResult = require('../search_result');
|
||||
const SearchContext = require('../search_context');
|
||||
const becca = require('../../../becca/becca');
|
||||
const beccaService = require('../../../becca/becca_service');
|
||||
const utils = require('../../utils');
|
||||
const log = require('../../log');
|
||||
const hoistedNoteService = require('../../hoisted_note');
|
||||
import normalizeString = require("normalize-strings");
|
||||
import lex = require('./lex');
|
||||
import handleParens = require('./handle_parens');
|
||||
import parse = require('./parse');
|
||||
import SearchResult = require('../search_result');
|
||||
import SearchContext = require('../search_context');
|
||||
import becca = require('../../../becca/becca');
|
||||
import beccaService = require('../../../becca/becca_service');
|
||||
import utils = require('../../utils');
|
||||
import log = require('../../log');
|
||||
import hoistedNoteService = require('../../hoisted_note');
|
||||
import BNote = require("../../../becca/entities/bnote");
|
||||
import BAttribute = require("../../../becca/entities/battribute");
|
||||
import { SearchParams, TokenData } from "./types";
|
||||
import Expression = require("../expressions/expression");
|
||||
import sql = require("../../sql");
|
||||
|
||||
function searchFromNote(note) {
|
||||
let searchResultNoteIds, highlightedTokens;
|
||||
function searchFromNote(note: BNote) {
|
||||
let searchResultNoteIds;
|
||||
let highlightedTokens: string[];
|
||||
|
||||
const searchScript = note.getRelationValue('searchScript');
|
||||
const searchString = note.getLabelValue('searchString');
|
||||
const searchString = note.getLabelValue('searchString') || "";
|
||||
let error = null;
|
||||
|
||||
if (searchScript) {
|
||||
@ -25,12 +31,12 @@ function searchFromNote(note) {
|
||||
} else {
|
||||
const searchContext = new SearchContext({
|
||||
fastSearch: note.hasLabel('fastSearch'),
|
||||
ancestorNoteId: note.getRelationValue('ancestor'),
|
||||
ancestorDepth: note.getLabelValue('ancestorDepth'),
|
||||
ancestorNoteId: note.getRelationValue('ancestor') || undefined,
|
||||
ancestorDepth: note.getLabelValue('ancestorDepth') || undefined,
|
||||
includeArchivedNotes: note.hasLabel('includeArchivedNotes'),
|
||||
orderBy: note.getLabelValue('orderBy'),
|
||||
orderDirection: note.getLabelValue('orderDirection'),
|
||||
limit: note.getLabelValue('limit'),
|
||||
orderBy: note.getLabelValue('orderBy') || undefined,
|
||||
orderDirection: note.getLabelValue('orderDirection') || undefined,
|
||||
limit: parseInt(note.getLabelValue('limit') || "0", 10),
|
||||
debug: note.hasLabel('debug'),
|
||||
fuzzyAttributeSearch: false
|
||||
});
|
||||
@ -51,7 +57,7 @@ function searchFromNote(note) {
|
||||
};
|
||||
}
|
||||
|
||||
function searchFromRelation(note, relationName) {
|
||||
function searchFromRelation(note: BNote, relationName: string) {
|
||||
const scriptNote = note.getRelationTarget(relationName);
|
||||
|
||||
if (!scriptNote) {
|
||||
@ -90,18 +96,21 @@ function searchFromRelation(note, relationName) {
|
||||
}
|
||||
|
||||
function loadNeededInfoFromDatabase() {
|
||||
const sql = require('../../sql');
|
||||
|
||||
/**
|
||||
* This complex structure is needed to calculate total occupied space by a note. Several object instances
|
||||
* (note, revisions, attachments) can point to a single blobId, and thus the blob size should count towards the total
|
||||
* only once.
|
||||
*
|
||||
* @var {Object.<string, Object.<string, int>>} - noteId => { blobId => blobSize }
|
||||
* noteId => { blobId => blobSize }
|
||||
*/
|
||||
const noteBlobs = {};
|
||||
const noteBlobs: Record<string, Record<string, number>> = {};
|
||||
|
||||
const noteContentLengths = sql.getRows(`
|
||||
type NoteContentLengthsRow = {
|
||||
noteId: string;
|
||||
blobId: string;
|
||||
length: number;
|
||||
};
|
||||
const noteContentLengths = sql.getRows<NoteContentLengthsRow>(`
|
||||
SELECT
|
||||
noteId,
|
||||
blobId,
|
||||
@ -122,7 +131,12 @@ function loadNeededInfoFromDatabase() {
|
||||
noteBlobs[noteId] = { [blobId]: length };
|
||||
}
|
||||
|
||||
const attachmentContentLengths = sql.getRows(`
|
||||
type AttachmentContentLengthsRow = {
|
||||
noteId: string;
|
||||
blobId: string;
|
||||
length: number;
|
||||
};
|
||||
const attachmentContentLengths = sql.getRows<AttachmentContentLengthsRow>(`
|
||||
SELECT
|
||||
ownerId AS noteId,
|
||||
attachments.blobId,
|
||||
@ -151,7 +165,13 @@ function loadNeededInfoFromDatabase() {
|
||||
becca.notes[noteId].contentAndAttachmentsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0);
|
||||
}
|
||||
|
||||
const revisionContentLengths = sql.getRows(`
|
||||
type RevisionRow = {
|
||||
noteId: string;
|
||||
blobId: string;
|
||||
length: number;
|
||||
isNoteRevision: true;
|
||||
};
|
||||
const revisionContentLengths = sql.getRows<RevisionRow>(`
|
||||
SELECT
|
||||
noteId,
|
||||
revisions.blobId,
|
||||
@ -186,8 +206,11 @@ function loadNeededInfoFromDatabase() {
|
||||
|
||||
noteBlobs[noteId][blobId] = length;
|
||||
|
||||
if (isNoteRevision) {
|
||||
becca.notes[noteId].revisionCount++;
|
||||
if (isNoteRevision) {
|
||||
const noteRevision = becca.notes[noteId];
|
||||
if (noteRevision && noteRevision.revisionCount) {
|
||||
noteRevision.revisionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,20 +219,16 @@ function loadNeededInfoFromDatabase() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Expression} expression
|
||||
* @param {SearchContext} searchContext
|
||||
* @returns {SearchResult[]}
|
||||
*/
|
||||
function findResultsWithExpression(expression, searchContext) {
|
||||
function findResultsWithExpression(expression: Expression, searchContext: SearchContext): SearchResult[] {
|
||||
if (searchContext.dbLoadNeeded) {
|
||||
loadNeededInfoFromDatabase();
|
||||
}
|
||||
|
||||
const allNoteSet = becca.getAllNoteSet();
|
||||
|
||||
const noteIdToNotePath: Record<string, string[]> = {};
|
||||
const executionContext = {
|
||||
noteIdToNotePath: {}
|
||||
noteIdToNotePath
|
||||
};
|
||||
|
||||
const noteSet = expression.execute(allNoteSet, executionContext, searchContext);
|
||||
@ -250,16 +269,16 @@ function findResultsWithExpression(expression, searchContext) {
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
function parseQueryToExpression(query, searchContext) {
|
||||
function parseQueryToExpression(query: string, searchContext: SearchContext) {
|
||||
const {fulltextQuery, fulltextTokens, expressionTokens} = lex(query);
|
||||
searchContext.fulltextQuery = fulltextQuery;
|
||||
|
||||
let structuredExpressionTokens;
|
||||
let structuredExpressionTokens: (TokenData | TokenData[])[];
|
||||
|
||||
try {
|
||||
structuredExpressionTokens = handleParens(expressionTokens);
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
structuredExpressionTokens = [];
|
||||
searchContext.addError(e.message);
|
||||
}
|
||||
@ -284,23 +303,13 @@ function parseQueryToExpression(query, searchContext) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {object} params - see SearchContext
|
||||
* @returns {BNote[]}
|
||||
*/
|
||||
function searchNotes(query, params = {}) {
|
||||
function searchNotes(query: string, params: SearchParams = {}): BNote[] {
|
||||
const searchResults = findResultsWithQuery(query, new SearchContext(params));
|
||||
|
||||
return searchResults.map(sr => becca.notes[sr.noteId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {SearchContext} searchContext
|
||||
* @returns {SearchResult[]}
|
||||
*/
|
||||
function findResultsWithQuery(query, searchContext) {
|
||||
function findResultsWithQuery(query: string, searchContext: SearchContext): SearchResult[] {
|
||||
query = query || "";
|
||||
searchContext.originalQuery = query;
|
||||
|
||||
@ -313,18 +322,13 @@ function findResultsWithQuery(query, searchContext) {
|
||||
return findResultsWithExpression(expression, searchContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {SearchContext} searchContext
|
||||
* @returns {BNote|null}
|
||||
*/
|
||||
function findFirstNoteWithQuery(query, searchContext) {
|
||||
function findFirstNoteWithQuery(query: string, searchContext: SearchContext): BNote | null {
|
||||
const searchResults = findResultsWithQuery(query, searchContext);
|
||||
|
||||
return searchResults.length > 0 ? becca.notes[searchResults[0].noteId] : null;
|
||||
}
|
||||
|
||||
function searchNotesForAutocomplete(query) {
|
||||
function searchNotesForAutocomplete(query: string) {
|
||||
const searchContext = new SearchContext({
|
||||
fastSearch: true,
|
||||
includeArchivedNotes: false,
|
||||
@ -351,7 +355,7 @@ function searchNotesForAutocomplete(query) {
|
||||
});
|
||||
}
|
||||
|
||||
function highlightSearchResults(searchResults, highlightedTokens) {
|
||||
function highlightSearchResults(searchResults: SearchResult[], highlightedTokens: string[]) {
|
||||
highlightedTokens = Array.from(new Set(highlightedTokens));
|
||||
|
||||
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
||||
@ -387,7 +391,7 @@ function highlightSearchResults(searchResults, highlightedTokens) {
|
||||
}
|
||||
}
|
||||
|
||||
function wrapText(text, start, length, prefix, suffix) {
|
||||
function wrapText(text: string, start: number, length: number, prefix: string, suffix: string) {
|
||||
return text.substring(0, start) + prefix + text.substr(start, length) + suffix + text.substring(start + length);
|
||||
}
|
||||
|
||||
@ -403,6 +407,7 @@ function highlightSearchResults(searchResults, highlightedTokens) {
|
||||
let match;
|
||||
|
||||
// Find all matches
|
||||
if (!result.highlightedNotePathTitle) { continue; }
|
||||
while ((match = tokenRegex.exec(normalizeString(result.highlightedNotePathTitle))) !== null) {
|
||||
result.highlightedNotePathTitle = wrapText(result.highlightedNotePathTitle, match.index, token.length, "{", "}");
|
||||
|
||||
@ -413,6 +418,7 @@ function highlightSearchResults(searchResults, highlightedTokens) {
|
||||
}
|
||||
|
||||
for (const result of searchResults) {
|
||||
if (!result.highlightedNotePathTitle) { continue; }
|
||||
result.highlightedNotePathTitle = result.highlightedNotePathTitle
|
||||
.replace(/"/g, "<small>")
|
||||
.replace(/'/g, "</small>")
|
||||
@ -421,7 +427,7 @@ function highlightSearchResults(searchResults, highlightedTokens) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatAttribute(attr) {
|
||||
function formatAttribute(attr: BAttribute) {
|
||||
if (attr.type === 'relation') {
|
||||
return `~${utils.escapeHtml(attr.name)}=…`;
|
||||
}
|
||||
@ -438,7 +444,7 @@ function formatAttribute(attr) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
searchFromNote,
|
||||
searchNotesForAutocomplete,
|
||||
findResultsWithQuery,
|
@ -3,4 +3,18 @@ export interface TokenData {
|
||||
inQuotes?: boolean;
|
||||
startIndex?: number;
|
||||
endIndex?: number;
|
||||
}
|
||||
|
||||
export interface SearchParams {
|
||||
fastSearch?: boolean;
|
||||
includeArchivedNotes?: boolean;
|
||||
includeHiddenNotes?: boolean;
|
||||
ignoreHoistedNote?: boolean;
|
||||
ancestorNoteId?: string;
|
||||
ancestorDepth?: string;
|
||||
orderBy?: string;
|
||||
orderDirection?: string;
|
||||
limit?: number | null;
|
||||
debug?: boolean;
|
||||
fuzzyAttributeSearch?: boolean;
|
||||
}
|
@ -5,7 +5,7 @@ const noteService = require('./notes');
|
||||
const dateUtils = require('./date_utils');
|
||||
const log = require('./log');
|
||||
const hoistedNoteService = require('./hoisted_note');
|
||||
const searchService = require('./search/services/search.js');
|
||||
const searchService = require('./search/services/search');
|
||||
const SearchContext = require('./search/search_context');
|
||||
const {LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT} = require('./hidden_subtree');
|
||||
|
||||
|
@ -9,7 +9,7 @@ const shareRoot = require('./share_root.js');
|
||||
const contentRenderer = require('./content_renderer.js');
|
||||
const assetPath = require('../services/asset_path');
|
||||
const appPath = require('../services/app_path');
|
||||
const searchService = require('../services/search/services/search.js');
|
||||
const searchService = require('../services/search/services/search');
|
||||
const SearchContext = require('../services/search/search_context');
|
||||
const log = require('../services/log');
|
||||
|
||||
|
5
src/types.d.ts
vendored
5
src/types.d.ts
vendored
@ -11,4 +11,9 @@ declare module 'unescape' {
|
||||
declare module 'html2plaintext' {
|
||||
function html2plaintext(htmlText: string): string;
|
||||
export = html2plaintext;
|
||||
}
|
||||
|
||||
declare module 'normalize-strings' {
|
||||
function normalizeString(string: string): string;
|
||||
export = normalizeString;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user