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:
parent
a189b2eff0
commit
767213cdc1
@ -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
6
.gitignore
vendored
@ -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
|
||||||
|
@ -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'));
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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,
|
@ -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) {
|
||||||
|
14
packages/lib/services/searchengine/gotoAnythingStyleQuery.ts
Normal file
14
packages/lib/services/searchengine/gotoAnythingStyleQuery.ts
Normal 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(' ');
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user