mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-18 09:35:20 +02:00
parent
48fd5d30f7
commit
e652db05e1
@ -13,7 +13,7 @@ import Folder from '@joplin/lib/models/Folder';
|
|||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
import ItemList from '../gui/ItemList';
|
import ItemList from '../gui/ItemList';
|
||||||
import HelpButton from '../gui/HelpButton';
|
import HelpButton from '../gui/HelpButton';
|
||||||
import { surroundKeywords, nextWhitespaceIndex, removeDiacritics } from '@joplin/lib/string-utils';
|
import { surroundKeywords, nextWhitespaceIndex, removeDiacritics, escapeRegExp, KeywordType } from '@joplin/lib/string-utils';
|
||||||
import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils';
|
import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils';
|
||||||
import markupLanguageUtils from '@joplin/lib/utils/markupLanguageUtils';
|
import markupLanguageUtils from '@joplin/lib/utils/markupLanguageUtils';
|
||||||
import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
|
import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
|
||||||
@ -56,7 +56,7 @@ interface State {
|
|||||||
query: string;
|
query: string;
|
||||||
results: GotoAnythingSearchResult[];
|
results: GotoAnythingSearchResult[];
|
||||||
selectedItemId: string;
|
selectedItemId: string;
|
||||||
keywords: string[];
|
keywords: (string | ComplexTerm)[];
|
||||||
listType: number;
|
listType: number;
|
||||||
showHelp: boolean;
|
showHelp: boolean;
|
||||||
resultsInBody: boolean;
|
resultsInBody: boolean;
|
||||||
@ -376,9 +376,13 @@ class DialogComponent extends React.PureComponent<Props, State> {
|
|||||||
results = results.filter(r => !!notesById[r.id])
|
results = results.filter(r => !!notesById[r.id])
|
||||||
.map(r => ({ ...r, title: notesById[r.id].title }));
|
.map(r => ({ ...r, title: notesById[r.id].title }));
|
||||||
|
|
||||||
const normalizedKeywords = (await this.keywords(searchQuery)).map(
|
const keywordRegexes = (await this.keywords(searchQuery)).map(term => {
|
||||||
({ valueRegex }: ComplexTerm) => new RegExp(removeDiacritics(valueRegex), 'ig'),
|
if (typeof term === 'string') {
|
||||||
);
|
return new RegExp(escapeRegExp(term), 'ig');
|
||||||
|
} else {
|
||||||
|
return new RegExp(removeDiacritics(term.valueRegex), 'ig');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
const row = results[i];
|
const row = results[i];
|
||||||
@ -393,7 +397,7 @@ class DialogComponent extends React.PureComponent<Props, State> {
|
|||||||
const normalizedBody = removeDiacritics(body);
|
const normalizedBody = removeDiacritics(body);
|
||||||
|
|
||||||
// Iterate over all matches in the body for each search keyword
|
// Iterate over all matches in the body for each search keyword
|
||||||
for (const keywordRegex of normalizedKeywords) {
|
for (const keywordRegex of keywordRegexes) {
|
||||||
for (const match of normalizedBody.matchAll(keywordRegex)) {
|
for (const match of normalizedBody.matchAll(keywordRegex)) {
|
||||||
// Populate 'indices' with [begin index, end index] of each note fragment
|
// Populate 'indices' with [begin index, end index] of each note fragment
|
||||||
// Begins at the regex matching index, ends at the next whitespace after seeking 15 characters to the right
|
// Begins at the regex matching index, ends at the next whitespace after seeking 15 characters to the right
|
||||||
@ -547,7 +551,7 @@ class DialogComponent extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
const wrapKeywordMatches = (unescapedContent: string) => {
|
const wrapKeywordMatches = (unescapedContent: string) => {
|
||||||
return surroundKeywords(
|
return surroundKeywords(
|
||||||
this.state.keywords,
|
this.state.keywords as KeywordType,
|
||||||
unescapedContent,
|
unescapedContent,
|
||||||
`<span class="match-highlight" style="font-weight: bold; color: ${theme.searchMarkerColor}; background-color: ${theme.searchMarkerBackgroundColor}">`,
|
`<span class="match-highlight" style="font-weight: bold; color: ${theme.searchMarkerColor}; background-color: ${theme.searchMarkerBackgroundColor}">`,
|
||||||
'</span>',
|
'</span>',
|
||||||
|
@ -7,13 +7,13 @@ import useQueuedAsyncEffect from '@joplin/lib/hooks/useQueuedAsyncEffect';
|
|||||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||||
import SearchEngineUtils from '@joplin/lib/services/search/SearchEngineUtils';
|
import SearchEngineUtils from '@joplin/lib/services/search/SearchEngineUtils';
|
||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
import SearchEngine from '@joplin/lib/services/search/SearchEngine';
|
import SearchEngine, { ComplexTerm } from '@joplin/lib/services/search/SearchEngine';
|
||||||
import { ProgressBar } from 'react-native-paper';
|
import { ProgressBar } from 'react-native-paper';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
query: string;
|
query: string;
|
||||||
onHighlightedWordsChange: (highlightedWords: string[])=> void;
|
onHighlightedWordsChange: (highlightedWords: (ComplexTerm | string)[])=> void;
|
||||||
|
|
||||||
ftsEnabled: number;
|
ftsEnabled: number;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import IconButton from '../../IconButton';
|
import IconButton from '../../IconButton';
|
||||||
import SearchResults from './SearchResults';
|
import SearchResults from './SearchResults';
|
||||||
import AccessibleView from '../../accessibility/AccessibleView';
|
import AccessibleView from '../../accessibility/AccessibleView';
|
||||||
|
import { ComplexTerm } from '@joplin/lib/services/search/SearchEngine';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
@ -73,7 +74,7 @@ const SearchScreenComponent: React.FC<Props> = props => {
|
|||||||
setQuery('');
|
setQuery('');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onHighlightedWordsChange = useCallback((words: string[]) => {
|
const onHighlightedWordsChange = useCallback((words: (ComplexTerm | string)[]) => {
|
||||||
props.dispatch({
|
props.dispatch({
|
||||||
type: 'SET_HIGHLIGHTED',
|
type: 'SET_HIGHLIGHTED',
|
||||||
words,
|
words,
|
||||||
|
@ -39,7 +39,7 @@ const SyncTargetAmazonS3 = require('./SyncTargetAmazonS3.js');
|
|||||||
import EncryptionService from './services/e2ee/EncryptionService';
|
import EncryptionService from './services/e2ee/EncryptionService';
|
||||||
import ResourceFetcher from './services/ResourceFetcher';
|
import ResourceFetcher from './services/ResourceFetcher';
|
||||||
import SearchEngineUtils from './services/search/SearchEngineUtils';
|
import SearchEngineUtils from './services/search/SearchEngineUtils';
|
||||||
import SearchEngine, { ProcessResultsRow } from './services/search/SearchEngine';
|
import SearchEngine, { ComplexTerm, ProcessResultsRow } from './services/search/SearchEngine';
|
||||||
import RevisionService from './services/RevisionService';
|
import RevisionService from './services/RevisionService';
|
||||||
import ResourceService from './services/ResourceService';
|
import ResourceService from './services/ResourceService';
|
||||||
import DecryptionWorker from './services/DecryptionWorker';
|
import DecryptionWorker from './services/DecryptionWorker';
|
||||||
@ -240,7 +240,7 @@ export default class BaseApplication {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let notes: NoteEntity[] = [];
|
let notes: NoteEntity[] = [];
|
||||||
let highlightedWords: string[] = [];
|
let highlightedWords: (ComplexTerm | string)[] = [];
|
||||||
let searchResults: ProcessResultsRow[] = [];
|
let searchResults: ProcessResultsRow[] = [];
|
||||||
|
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
|
@ -63,11 +63,22 @@ export interface ComplexTerm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Terms {
|
export interface Terms {
|
||||||
|
// This `string | ComplexTerm` type that ends up propagating throughout the app is a bit of a
|
||||||
|
// mess, but it reflects how it was originally done in plain JS. Ideally it should be refactor
|
||||||
|
// to use a simple type.
|
||||||
_: (string | ComplexTerm)[];
|
_: (string | ComplexTerm)[];
|
||||||
title: (string | ComplexTerm)[];
|
title: (string | ComplexTerm)[];
|
||||||
body: (string | ComplexTerm)[];
|
body: (string | ComplexTerm)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ParsedQuery {
|
||||||
|
termCount: number;
|
||||||
|
keys: string[];
|
||||||
|
terms: Terms; // text terms
|
||||||
|
allTerms: Term[];
|
||||||
|
any: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default class SearchEngine {
|
export default class SearchEngine {
|
||||||
|
|
||||||
public static instance_: SearchEngine = null;
|
public static instance_: SearchEngine = null;
|
||||||
@ -521,7 +532,7 @@ export default class SearchEngine {
|
|||||||
return regexString;
|
return regexString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async parseQuery(query: string) {
|
public async parseQuery(query: string): Promise<ParsedQuery> {
|
||||||
|
|
||||||
const trimQuotes = (str: string) => str.startsWith('"') ? str.substr(1, str.length - 2) : str;
|
const trimQuotes = (str: string) => str.startsWith('"') ? str.substr(1, str.length - 2) : str;
|
||||||
|
|
||||||
@ -606,14 +617,11 @@ export default class SearchEngine {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
public allParsedQueryTerms(parsedQuery: ParsedQuery) {
|
||||||
public allParsedQueryTerms(parsedQuery: any) {
|
|
||||||
if (!parsedQuery || !parsedQuery.termCount) return [];
|
if (!parsedQuery || !parsedQuery.termCount) return [];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
let output: typeof parsedQuery.terms._ = [];
|
||||||
let output: any[] = [];
|
for (const col of Object.keys(parsedQuery.terms) as (keyof Terms)[]) {
|
||||||
for (const col in parsedQuery.terms) {
|
|
||||||
if (!parsedQuery.terms.hasOwnProperty(col)) continue;
|
|
||||||
output = output.concat(parsedQuery.terms[col]);
|
output = output.concat(parsedQuery.terms[col]);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
|
@ -100,6 +100,10 @@ export function removeDiacritics(str: string) {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function escapeRegExp(keyword: string) {
|
||||||
|
return keyword.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||||
|
}
|
||||||
|
|
||||||
export function wrap(text: string, indent: string, width: number) {
|
export function wrap(text: string, indent: string, width: number) {
|
||||||
const wrap_ = require('word-wrap');
|
const wrap_ = require('word-wrap');
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user