You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-13 00:10:37 +02:00
This commit is contained in:
@ -1,47 +1,18 @@
|
|||||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
|
||||||
const uuid = require('@joplin/lib/uuid').default;
|
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
name: 'search',
|
name: 'search',
|
||||||
iconName: 'icon-search',
|
iconName: 'icon-search',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runtime = (comp:any):CommandRuntime => {
|
export const runtime = ():CommandRuntime => {
|
||||||
return {
|
return {
|
||||||
execute: async (_context:CommandContext, query:string) => {
|
execute: async () => {
|
||||||
if (!comp.searchId_) comp.searchId_ = uuid.create();
|
// Doesn't do anything for now but could be used to
|
||||||
|
// automatically focus the search field (using the
|
||||||
comp.props.dispatch({
|
// `focusSearch` command) and then set an initial search.
|
||||||
type: 'SEARCH_UPDATE',
|
// However not straightforward to implement since the SearchBar
|
||||||
search: {
|
// is not a controlled component.
|
||||||
id: comp.searchId_,
|
|
||||||
title: query,
|
|
||||||
query_pattern: query,
|
|
||||||
query_folder_id: null,
|
|
||||||
type_: BaseModel.TYPE_SEARCH,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
comp.props.dispatch({
|
|
||||||
type: 'SEARCH_SELECT',
|
|
||||||
id: comp.searchId_,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Note: Normally there's no need to do anything when the search query
|
|
||||||
// is cleared as the reducer should handle all state changes.
|
|
||||||
// https://github.com/laurent22/joplin/issues/3748
|
|
||||||
|
|
||||||
// const note = await Note.load(comp.props.selectedNoteId);
|
|
||||||
// if (note) {
|
|
||||||
// comp.props.dispatch({
|
|
||||||
// type: 'FOLDER_AND_NOTE_SELECT',
|
|
||||||
// folderId: note.parent_id,
|
|
||||||
// noteId: note.id,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,8 @@ const Resource = require('@joplin/lib/models/Resource');
|
|||||||
// handler on the webContent. This function will return null if the point is
|
// handler on the webContent. This function will return null if the point is
|
||||||
// not within the TinyMCE editor.
|
// not within the TinyMCE editor.
|
||||||
function contextMenuElement(editor:any, x:number, y:number) {
|
function contextMenuElement(editor:any, x:number, y:number) {
|
||||||
|
if (!editor || !editor.getDoc()) return null;
|
||||||
|
|
||||||
const iframes = document.getElementsByClassName('tox-edit-area__iframe');
|
const iframes = document.getElementsByClassName('tox-edit-area__iframe');
|
||||||
if (!iframes.length) return null;
|
if (!iframes.length) return null;
|
||||||
|
|
||||||
@ -15,7 +17,6 @@ function contextMenuElement(editor:any, x:number, y:number) {
|
|||||||
if (iframeRect.x < x && iframeRect.y < y && iframeRect.right > x && iframeRect.bottom > y) {
|
if (iframeRect.x < x && iframeRect.y < y && iframeRect.right > x && iframeRect.bottom > y) {
|
||||||
const relativeX = x - iframeRect.x;
|
const relativeX = x - iframeRect.x;
|
||||||
const relativeY = y - iframeRect.y;
|
const relativeY = y - iframeRect.y;
|
||||||
|
|
||||||
return editor.getDoc().elementFromPoint(relativeX, relativeY);
|
return editor.getDoc().elementFromPoint(relativeX, relativeY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,83 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||||
import CommandService from '@joplin/lib/services/CommandService';
|
import CommandService from '@joplin/lib/services/CommandService';
|
||||||
import useSearch from './hooks/useSearch';
|
|
||||||
import { Root, SearchInput, SearchButton, SearchButtonIcon } from './styles';
|
import { Root, SearchInput, SearchButton, SearchButtonIcon } from './styles';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { stateUtils } from '@joplin/lib/reducer';
|
||||||
|
import BaseModel from '@joplin/lib/BaseModel';
|
||||||
|
import uuid from '@joplin/lib/uuid';
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
|
const Note = require('@joplin/lib/models/Note');
|
||||||
|
const debounce = require('debounce');
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
inputRef?: any,
|
inputRef?: any,
|
||||||
notesParentType: string,
|
notesParentType: string,
|
||||||
dispatch?: Function,
|
dispatch?: Function,
|
||||||
|
selectedNoteId: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchBar(props:Props) {
|
function SearchBar(props:Props) {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const iconName = !query ? CommandService.instance().iconName('search') : 'fa fa-times';
|
const [searchStarted, setSearchStarted] = useState(false);
|
||||||
|
const iconName = !searchStarted ? CommandService.instance().iconName('search') : 'fa fa-times';
|
||||||
|
const searchId = useRef(uuid.create());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function search(searchId:string, query:string, dispatch:Function) {
|
||||||
|
dispatch({
|
||||||
|
type: 'SEARCH_UPDATE',
|
||||||
|
search: {
|
||||||
|
id: searchId,
|
||||||
|
title: query,
|
||||||
|
query_pattern: query,
|
||||||
|
query_folder_id: null,
|
||||||
|
type_: BaseModel.TYPE_SEARCH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'SEARCH_SELECT',
|
||||||
|
id: searchId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedSearch = debounce(search, 500);
|
||||||
|
if (searchStarted) debouncedSearch(searchId.current, query, props.dispatch);
|
||||||
|
return () => {
|
||||||
|
debouncedSearch.clear();
|
||||||
|
};
|
||||||
|
}, [query, searchStarted]);
|
||||||
|
|
||||||
|
const onExitSearch = useCallback(async (navigateAway = true) => {
|
||||||
|
setQuery('');
|
||||||
|
setSearchStarted(false);
|
||||||
|
|
||||||
|
if (navigateAway) {
|
||||||
|
const note = props.selectedNoteId ? await Note.load(props.selectedNoteId) : null;
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
props.dispatch({
|
||||||
|
type: 'FOLDER_AND_NOTE_SELECT',
|
||||||
|
folderId: note.parent_id,
|
||||||
|
noteId: note.id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const folderId = Setting.value('activeFolderId');
|
||||||
|
if (folderId) {
|
||||||
|
props.dispatch({
|
||||||
|
type: 'FOLDER_SELECT',
|
||||||
|
id: folderId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [props.selectedNoteId]);
|
||||||
|
|
||||||
function onChange(event:any) {
|
function onChange(event:any) {
|
||||||
|
setSearchStarted(true);
|
||||||
setQuery(event.currentTarget.value);
|
setQuery(event.currentTarget.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,24 +100,22 @@ function SearchBar(props:Props) {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(event:any) {
|
const onKeyDown = useCallback((event:any) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
setQuery('');
|
|
||||||
if (document.activeElement) (document.activeElement as any).blur();
|
if (document.activeElement) (document.activeElement as any).blur();
|
||||||
|
onExitSearch();
|
||||||
}
|
}
|
||||||
}
|
}, [onExitSearch]);
|
||||||
|
|
||||||
const onSearchButtonClick = useCallback(() => {
|
const onSearchButtonClick = useCallback(() => {
|
||||||
setQuery('');
|
onExitSearch();
|
||||||
}, []);
|
}, [onExitSearch]);
|
||||||
|
|
||||||
useSearch(query);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.notesParentType !== 'Search') {
|
if (props.notesParentType !== 'Search') {
|
||||||
setQuery('');
|
onExitSearch(false);
|
||||||
}
|
}
|
||||||
}, [props.notesParentType]);
|
}, [props.notesParentType, onExitSearch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
@ -81,6 +139,7 @@ function SearchBar(props:Props) {
|
|||||||
const mapStateToProps = (state:any) => {
|
const mapStateToProps = (state:any) => {
|
||||||
return {
|
return {
|
||||||
notesParentType: state.notesParentType,
|
notesParentType: state.notesParentType,
|
||||||
|
selectedNoteId: stateUtils.selectedNoteId(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import CommandService from '@joplin/lib/services/CommandService';
|
|
||||||
const debounce = require('debounce');
|
|
||||||
|
|
||||||
export default function useSearch(query:string) {
|
|
||||||
useEffect(() => {
|
|
||||||
const search = debounce((query:string) => {
|
|
||||||
CommandService.instance().execute('search', query);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
search(query);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
search.clear();
|
|
||||||
};
|
|
||||||
}, [query]);
|
|
||||||
}
|
|
@ -1019,12 +1019,7 @@ const reducer = produce((draft: Draft<State> = defaultState, action:any) => {
|
|||||||
|
|
||||||
if (!found) searches.push(action.search);
|
if (!found) searches.push(action.search);
|
||||||
|
|
||||||
if (!action.search.query_pattern) {
|
|
||||||
draft.notesParentType = defaultNotesParentType(draft, 'Search');
|
|
||||||
} else {
|
|
||||||
draft.notesParentType = 'Search';
|
draft.notesParentType = 'Search';
|
||||||
}
|
|
||||||
|
|
||||||
draft.searches = searches;
|
draft.searches = searches;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -672,6 +672,8 @@ class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async search(searchString, options = null) {
|
async search(searchString, options = null) {
|
||||||
|
if (!searchString) return [];
|
||||||
|
|
||||||
options = Object.assign({}, {
|
options = Object.assign({}, {
|
||||||
searchType: SearchEngine.SEARCH_TYPE_AUTO,
|
searchType: SearchEngine.SEARCH_TYPE_AUTO,
|
||||||
fuzzy: Setting.value('db.fuzzySearchEnabled') === 1,
|
fuzzy: Setting.value('db.fuzzySearchEnabled') === 1,
|
||||||
|
Reference in New Issue
Block a user