1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Mobile: Add support for realtime search

This commit is contained in:
Laurent Cozic 2022-12-30 14:12:07 +00:00
parent a189b2eff0
commit 767213cdc1
7 changed files with 72 additions and 52 deletions

View File

@ -1005,6 +1005,9 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
packages/app-mobile/components/screens/encryption-config.d.ts packages/app-mobile/components/screens/encryption-config.d.ts
packages/app-mobile/components/screens/encryption-config.js packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/encryption-config.js.map packages/app-mobile/components/screens/encryption-config.js.map
packages/app-mobile/components/screens/search.d.ts
packages/app-mobile/components/screens/search.js
packages/app-mobile/components/screens/search.js.map
packages/app-mobile/components/side-menu-content.d.ts packages/app-mobile/components/side-menu-content.d.ts
packages/app-mobile/components/side-menu-content.js packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/side-menu-content.js.map packages/app-mobile/components/side-menu-content.js.map
@ -1878,6 +1881,9 @@ packages/lib/services/searchengine/filterParser.js.map
packages/lib/services/searchengine/filterParser.test.d.ts packages/lib/services/searchengine/filterParser.test.d.ts
packages/lib/services/searchengine/filterParser.test.js packages/lib/services/searchengine/filterParser.test.js
packages/lib/services/searchengine/filterParser.test.js.map packages/lib/services/searchengine/filterParser.test.js.map
packages/lib/services/searchengine/gotoAnythingStyleQuery.d.ts
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
packages/lib/services/searchengine/gotoAnythingStyleQuery.js.map
packages/lib/services/searchengine/queryBuilder.d.ts packages/lib/services/searchengine/queryBuilder.d.ts
packages/lib/services/searchengine/queryBuilder.js packages/lib/services/searchengine/queryBuilder.js
packages/lib/services/searchengine/queryBuilder.js.map packages/lib/services/searchengine/queryBuilder.js.map

6
.gitignore vendored
View File

@ -993,6 +993,9 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
packages/app-mobile/components/screens/encryption-config.d.ts packages/app-mobile/components/screens/encryption-config.d.ts
packages/app-mobile/components/screens/encryption-config.js packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/encryption-config.js.map packages/app-mobile/components/screens/encryption-config.js.map
packages/app-mobile/components/screens/search.d.ts
packages/app-mobile/components/screens/search.js
packages/app-mobile/components/screens/search.js.map
packages/app-mobile/components/side-menu-content.d.ts packages/app-mobile/components/side-menu-content.d.ts
packages/app-mobile/components/side-menu-content.js packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/side-menu-content.js.map packages/app-mobile/components/side-menu-content.js.map
@ -1866,6 +1869,9 @@ packages/lib/services/searchengine/filterParser.js.map
packages/lib/services/searchengine/filterParser.test.d.ts packages/lib/services/searchengine/filterParser.test.d.ts
packages/lib/services/searchengine/filterParser.test.js packages/lib/services/searchengine/filterParser.test.js
packages/lib/services/searchengine/filterParser.test.js.map packages/lib/services/searchengine/filterParser.test.js.map
packages/lib/services/searchengine/gotoAnythingStyleQuery.d.ts
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
packages/lib/services/searchengine/gotoAnythingStyleQuery.js.map
packages/lib/services/searchengine/queryBuilder.d.ts packages/lib/services/searchengine/queryBuilder.d.ts
packages/lib/services/searchengine/queryBuilder.js packages/lib/services/searchengine/queryBuilder.js
packages/lib/services/searchengine/queryBuilder.js.map packages/lib/services/searchengine/queryBuilder.js.map

View File

@ -8,6 +8,7 @@ const { connect } = require('react-redux');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const { themeStyle } = require('@joplin/lib/theme'); const { themeStyle } = require('@joplin/lib/theme');
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine'; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import gotoAnythingStyleQuery from '@joplin/lib/services/searchengine/gotoAnythingStyleQuery';
import BaseModel from '@joplin/lib/BaseModel'; import BaseModel from '@joplin/lib/BaseModel';
import Tag from '@joplin/lib/models/Tag'; import Tag from '@joplin/lib/models/Tag';
import Folder from '@joplin/lib/models/Folder'; import Folder from '@joplin/lib/models/Folder';
@ -242,19 +243,6 @@ class Dialog extends React.PureComponent<Props, State> {
}, 100); }, 100);
} }
makeSearchQuery(query: string) {
const output = [];
const splitted = query.split(' ');
for (let i = 0; i < splitted.length; i++) {
const s = splitted[i].trim();
if (!s) continue;
output.push(`${s}*`);
}
return output.join(' ');
}
async keywords(searchQuery: string) { async keywords(searchQuery: string) {
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery); const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery);
return SearchEngine.instance().allParsedQueryTerms(parsedQuery); return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
@ -321,7 +309,7 @@ class Dialog extends React.PureComponent<Props, State> {
} }
} else { // Note TITLE or BODY } else { // Note TITLE or BODY
listType = BaseModel.TYPE_NOTE; listType = BaseModel.TYPE_NOTE;
searchQuery = this.makeSearchQuery(this.state.query); searchQuery = gotoAnythingStyleQuery(this.state.query);
results = await SearchEngine.instance().search(searchQuery); results = await SearchEngine.instance().search(searchQuery);
resultsInBody = !!results.find((row: any) => row.fields.includes('body')); resultsInBody = !!results.find((row: any) => row.fields.includes('body'));

View File

@ -1,7 +1,7 @@
const React = require('react'); const React = require('react');
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { PureComponent, Component } from 'react'; import { PureComponent } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native';
const Icon = require('react-native-vector-icons/Ionicons').default; const Icon = require('react-native-vector-icons/Ionicons').default;
const { BackButtonService } = require('../services/back-button.js'); const { BackButtonService } = require('../services/back-button.js');
@ -46,7 +46,7 @@ type DispatchCommandType=(event: { type: string })=> void;
interface ScreenHeaderProps { interface ScreenHeaderProps {
selectedNoteIds: string[]; selectedNoteIds: string[];
noteSelectionEnabled: boolean; noteSelectionEnabled: boolean;
parentComponent: Component; parentComponent: any;
showUndoButton: boolean; showUndoButton: boolean;
undoButtonDisabled?: boolean; undoButtonDisabled?: boolean;
showRedoButton: boolean; showRedoButton: boolean;
@ -55,8 +55,8 @@ interface ScreenHeaderProps {
folders: FolderEntity[]; folders: FolderEntity[];
folderPickerOptions?: { folderPickerOptions?: {
enabled: boolean; enabled: boolean;
selectedFolderId: string; selectedFolderId?: string;
onValueChange: OnValueChangedListener; onValueChange?: OnValueChangedListener;
mustSelect?: boolean; mustSelect?: boolean;
}; };

View File

@ -1,23 +1,31 @@
const React = require('react'); const React = require('react');
const { StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require('react-native'); import { StyleSheet, View, TextInput, FlatList, TouchableHighlight } from 'react-native';
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { ScreenHeader } = require('../ScreenHeader'); import ScreenHeader from '../ScreenHeader';
const Icon = require('react-native-vector-icons/Ionicons').default; const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('@joplin/lib/locale'); import { _ } from '@joplin/lib/locale';
const Note = require('@joplin/lib/models/Note').default; import Note from '@joplin/lib/models/Note';
import gotoAnythingStyleQuery from '@joplin/lib/services/searchengine/gotoAnythingStyleQuery';
const { NoteItem } = require('../note-item.js'); const { NoteItem } = require('../note-item.js');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;
const SearchEngineUtils = require('@joplin/lib/services/searchengine/SearchEngineUtils').default; import SearchEngineUtils from '@joplin/lib/services/searchengine/SearchEngineUtils';
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine').default; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import { AppState } from '../../utils/types';
Icon.loadFont(); Icon.loadFont();
class SearchScreenComponent extends BaseScreenComponent { class SearchScreenComponent extends BaseScreenComponent {
private state: any = null;
private isMounted_ = false;
private styles_: any = {};
private scheduleSearchTimer_: any = null;
static navigationOptions() { static navigationOptions() {
return { header: null }; return { header: null } as any;
} }
constructor() { constructor() {
@ -26,8 +34,6 @@ class SearchScreenComponent extends BaseScreenComponent {
query: '', query: '',
notes: [], notes: [],
}; };
this.isMounted_ = false;
this.styles_ = {};
} }
styles() { styles() {
@ -36,7 +42,7 @@ class SearchScreenComponent extends BaseScreenComponent {
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId]; if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
this.styles_ = {}; this.styles_ = {};
const styles = { const styles: any = {
body: { body: {
flex: 1, flex: 1,
}, },
@ -65,7 +71,7 @@ class SearchScreenComponent extends BaseScreenComponent {
componentDidMount() { componentDidMount() {
this.setState({ query: this.props.query }); this.setState({ query: this.props.query });
this.refreshSearch(this.props.query); void this.refreshSearch(this.props.query);
this.isMounted_ = true; this.isMounted_ = true;
} }
@ -73,19 +79,6 @@ class SearchScreenComponent extends BaseScreenComponent {
this.isMounted_ = false; this.isMounted_ = false;
} }
searchTextInput_submit() {
const query = this.state.query.trim();
if (!query) return;
this.props.dispatch({
type: 'SEARCH_QUERY',
query: query,
});
this.setState({ query: query });
this.refreshSearch(query);
}
clearButton_press() { clearButton_press() {
this.props.dispatch({ this.props.dispatch({
type: 'SEARCH_QUERY', type: 'SEARCH_QUERY',
@ -93,13 +86,13 @@ class SearchScreenComponent extends BaseScreenComponent {
}); });
this.setState({ query: '' }); this.setState({ query: '' });
this.refreshSearch(''); void this.refreshSearch('');
} }
async refreshSearch(query = null) { async refreshSearch(query: string = null) {
if (!this.props.visible) return; if (!this.props.visible) return;
query = query === null ? this.state.query.trim : query.trim(); query = gotoAnythingStyleQuery(query);
let notes = []; let notes = [];
@ -134,8 +127,24 @@ class SearchScreenComponent extends BaseScreenComponent {
this.setState({ notes: notes }); this.setState({ notes: notes });
} }
searchTextInput_changeText(text) { scheduleSearch() {
if (this.scheduleSearchTimer_) clearTimeout(this.scheduleSearchTimer_);
this.scheduleSearchTimer_ = setTimeout(() => {
this.scheduleSearchTimer_ = null;
void this.refreshSearch(this.state.query);
}, 200);
}
searchTextInput_changeText(text: string) {
this.setState({ query: text }); this.setState({ query: text });
this.props.dispatch({
type: 'SEARCH_QUERY',
query: text,
});
this.scheduleSearch();
} }
render() { render() {
@ -172,9 +181,6 @@ class SearchScreenComponent extends BaseScreenComponent {
style={this.styles().searchTextInput} style={this.styles().searchTextInput}
autoFocus={this.props.visible} autoFocus={this.props.visible}
underlineColorAndroid="#ffffff00" underlineColorAndroid="#ffffff00"
onSubmitEditing={() => {
this.searchTextInput_submit();
}}
onChangeText={text => this.searchTextInput_changeText(text)} onChangeText={text => this.searchTextInput_changeText(text)}
value={this.state.query} value={this.state.query}
selectionColor={theme.textSelectionColor} selectionColor={theme.textSelectionColor}
@ -188,7 +194,7 @@ class SearchScreenComponent extends BaseScreenComponent {
<FlatList data={this.state.notes} keyExtractor={(item) => item.id} renderItem={event => <NoteItem note={event.item} />} /> <FlatList data={this.state.notes} keyExtractor={(item) => item.id} renderItem={event => <NoteItem note={event.item} />} />
</View> </View>
<DialogBox <DialogBox
ref={dialogbox => { ref={(dialogbox: any) => {
this.dialogbox = dialogbox; this.dialogbox = dialogbox;
}} }}
/> />
@ -197,7 +203,7 @@ class SearchScreenComponent extends BaseScreenComponent {
} }
} }
const SearchScreen = connect(state => { const SearchScreen = connect((state: AppState) => {
return { return {
query: state.searchQuery, query: state.searchQuery,
themeId: state.settings.theme, themeId: state.settings.theme,

View File

@ -3,7 +3,7 @@ import Note from '../../models/Note';
import Setting from '../../models/Setting'; import Setting from '../../models/Setting';
export default class SearchEngineUtils { export default class SearchEngineUtils {
static async notesForQuery(query: string, applyUserSettings: boolean, options: any = null, searchEngine: SearchEngine = null) { public static async notesForQuery(query: string, applyUserSettings: boolean, options: any = null, searchEngine: SearchEngine = null) {
if (!options) options = {}; if (!options) options = {};
if (!searchEngine) { if (!searchEngine) {

View File

@ -0,0 +1,14 @@
export default (query: string) => {
if (!query) return '';
const output = [];
const splitted = query.split(' ');
for (let i = 0; i < splitted.length; i++) {
const s = splitted[i].trim();
if (!s) continue;
output.push(`${s}*`);
}
return output.join(' ');
};