Skip to content

Commit 8739912

Browse files
committed
Add initial global search setup
Add search functionality and result data structures Add preview of found items, highlighted style, the rows of found items Add note preview Add selection of items in search Add text highlight in editor preview Add editor preview focus Add editor preview while clicking on found item Add better ID management and state update fix Add some stability updates (max searched content, max result line length) Add double click on search item to lead to focused item in editor Add focusLine number for editor to route params Add better colors across themes for search
1 parent ca2ebab commit 8739912

19 files changed

+569
-60
lines changed

src/components/PreferencesModal/styled.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
tableStyle,
88
disabledUiTextColor,
99
PrimaryTextColor,
10+
searchMatchHighlightStyle,
1011
} from '../../lib/styled/styleFunctions'
1112

1213
export const Section = styled.section`
@@ -43,7 +44,7 @@ export const SectionControl = styled.div`
4344
`
4445

4546
export const SectionSelect = styled.select`
46-
${selectStyle}
47+
${selectStyle};
4748
padding: 0 16px;
4849
width: 200px;
4950
height: 40px;
@@ -56,7 +57,7 @@ export const SectionSelect = styled.select`
5657
`
5758

5859
export const SectionPrimaryButton = styled.button`
59-
${primaryButtonStyle}
60+
${primaryButtonStyle};
6061
padding: 0 16px;
6162
height: 40px;
6263
border-radius: 2px;
@@ -66,7 +67,7 @@ export const SectionPrimaryButton = styled.button`
6667
`
6768

6869
export const SectionSecondaryButton = styled.button`
69-
${secondaryButtonStyle}
70+
${secondaryButtonStyle};
7071
padding: 0 16px;
7172
height: 40px;
7273
border-radius: 2px;
@@ -75,7 +76,7 @@ export const SectionSecondaryButton = styled.button`
7576
`
7677

7778
export const SectionInput = styled.input`
78-
${inputStyle}
79+
${inputStyle};
7980
padding: 0 16px;
8081
width: 200px;
8182
height: 40px;
@@ -96,11 +97,24 @@ export const TopMargin = styled.div`
9697
`
9798

9899
export const DeleteStorageButton = styled.button`
99-
${secondaryButtonStyle}
100+
${secondaryButtonStyle};
100101
padding: 0 16px;
101102
height: 40px;
102103
border-radius: 2px;
103104
cursor: pointer;
104105
vertical-align: middle;
105106
align-items: center;
106107
`
108+
109+
export const SectionListSelect = styled.div`
110+
${selectStyle};
111+
padding: 0 16px;
112+
width: 200px;
113+
height: 40px;
114+
border-radius: 2px;
115+
font-size: 14px;
116+
`
117+
118+
export const SearchMatchHighlight = styled.span`
119+
${searchMatchHighlightStyle}
120+
`

src/components/molecules/SearchModalNoteResultItem.tsx

Lines changed: 138 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,50 +8,121 @@ import {
88
borderBottom,
99
textOverflow,
1010
} from '../../lib/styled/styleFunctions'
11+
import {
12+
getSearchResultKey,
13+
MAX_SEARCH_PREVIEW_LINE_LENGTH,
14+
SearchResult,
15+
} from '../../lib/search/search'
16+
import { isColorBright } from '../../lib/colors'
17+
import { SearchMatchHighlight } from '../PreferencesModal/styled'
1118

1219
interface SearchModalNoteResultItemProps {
1320
note: NoteDoc
21+
selectedItemId: string
22+
searchResults: SearchResult[]
1423
navigateToNote: (noteId: string) => void
24+
updateSelectedItem: (note: NoteDoc, selectedId: string) => void
25+
navigateToEditorFocused: (noteId: string, lineNum: number) => void
1526
}
1627

1728
const SearchModalNoteResultItem = ({
1829
note,
30+
searchResults,
1931
navigateToNote,
32+
selectedItemId,
33+
updateSelectedItem,
34+
navigateToEditorFocused,
2035
}: SearchModalNoteResultItemProps) => {
2136
const navigate = useCallback(() => {
2237
navigateToNote(note._id)
2338
}, [navigateToNote, note._id])
2439

40+
const highlightMatchedTerm = useCallback((line, matchStr) => {
41+
const parts = line.split(new RegExp(`(${matchStr})`, 'gi'))
42+
return (
43+
<span>
44+
{parts.map((part: string, i: number) =>
45+
part.toLowerCase() === matchStr.toLowerCase() ? (
46+
<SearchMatchHighlight key={i}>{matchStr}</SearchMatchHighlight>
47+
) : (
48+
part
49+
)
50+
)}
51+
</span>
52+
)
53+
}, [])
54+
const beautifyPreviewLine = useCallback(
55+
(line, matchStr) => {
56+
const beautifiedLine =
57+
line.substring(0, MAX_SEARCH_PREVIEW_LINE_LENGTH) +
58+
(line.length > MAX_SEARCH_PREVIEW_LINE_LENGTH ? '...' : '')
59+
return highlightMatchedTerm(beautifiedLine, matchStr)
60+
},
61+
[highlightMatchedTerm]
62+
)
63+
2564
return (
26-
<Container onClick={navigate}>
27-
<div className='header'>
28-
<div className='icon'>
29-
<Icon path={mdiTextBoxOutline} />
30-
</div>
31-
<div className='title'>{note.title}</div>
32-
</div>
33-
<div className='meta'>
34-
<div className='folderPathname'>
35-
<Icon className='icon' path={mdiFolder} />
36-
{note.folderPathname}
65+
<Container>
66+
<MetaContainer onClick={navigate}>
67+
<div className='header'>
68+
<div className='icon'>
69+
<Icon path={mdiTextBoxOutline} />
70+
</div>
71+
<div className='title'>{note.title}</div>
3772
</div>
38-
{note.tags.length > 0 && (
39-
<div className='tags'>
40-
<Icon className='icon' path={mdiTagMultiple} />{' '}
41-
{note.tags.map((tag) => tag).join(', ')}
73+
<div className='meta'>
74+
<div className='folderPathname'>
75+
<Icon className='icon' path={mdiFolder} />
76+
{note.folderPathname}
4277
</div>
43-
)}
44-
</div>
78+
{note.tags.length > 0 && (
79+
<div className='tags'>
80+
<Icon className='icon' path={mdiTagMultiple} />{' '}
81+
{note.tags.map((tag) => tag).join(', ')}
82+
</div>
83+
)}
84+
</div>
85+
</MetaContainer>
86+
87+
<SearchResultContainer>
88+
{searchResults.length > 0 &&
89+
searchResults.map((result) => (
90+
<SearchResultItem
91+
className={
92+
selectedItemId == result.id ? 'search-result-selected' : ''
93+
}
94+
key={getSearchResultKey(note._id, result.id)}
95+
onClick={() => updateSelectedItem(note, result.id)}
96+
onDoubleClick={() =>
97+
navigateToEditorFocused(note._id, result.lineNum - 1)
98+
}
99+
>
100+
<SearchResultLeft title={result.lineStr}>
101+
{beautifyPreviewLine(result.lineStr, result.matchStr)}
102+
</SearchResultLeft>
103+
<SearchResultRight>{result.lineNum}</SearchResultRight>
104+
</SearchResultItem>
105+
))}
106+
</SearchResultContainer>
45107
</Container>
46108
)
47109
}
48110

49111
export default SearchModalNoteResultItem
50112

51-
const Container = styled.div`
113+
const Container = styled.div``
114+
115+
const SearchResultContainer = styled.div`
116+
padding: 10px;
117+
cursor: pointer;
118+
${borderBottom};
119+
user-select: none;
120+
`
121+
122+
const MetaContainer = styled.div`
52123
padding: 10px;
53124
cursor: pointer;
54-
${borderBottom}
125+
${borderBottom};
55126
user-select: none;
56127
57128
&:hover {
@@ -60,6 +131,7 @@ const Container = styled.div`
60131
&:hover:active {
61132
background-color: ${({ theme }) => theme.navItemHoverActiveBackgroundColor};
62133
}
134+
63135
& > .header {
64136
font-size: 18px;
65137
display: flex;
@@ -109,3 +181,50 @@ const Container = styled.div`
109181
border-bottom: none;
110182
}
111183
`
184+
185+
const SearchResultItem = styled.div`
186+
display: flex;
187+
flex-direction: row;
188+
width: 100%;
189+
height: 100%;
190+
justify-content: space-between;
191+
overflow: hidden;
192+
193+
margin-top: 0.3em;
194+
195+
&.search-result-selected {
196+
border-radius: 4px;
197+
padding: 2px;
198+
background-color: ${({ theme }) =>
199+
theme.searchItemSelectionBackgroundColor};
200+
filter: brightness(
201+
${({ theme }) => (isColorBright(theme.activeBackgroundColor) ? 85 : 115)}%
202+
);
203+
}
204+
}
205+
206+
&:hover {
207+
border-radius: 4px;
208+
background-color: ${({ theme }) =>
209+
theme.secondaryButtonHoverBackgroundColor};
210+
filter: brightness(
211+
${({ theme }) =>
212+
isColorBright(theme.secondaryButtonHoverBackgroundColor) ? 85 : 115}%
213+
);
214+
}
215+
`
216+
217+
const SearchResultLeft = styled.div`
218+
align-self: flex-start;
219+
text-overflow: ellipsis;
220+
white-space: nowrap;
221+
overflow: hidden;
222+
223+
&:before {
224+
content: attr(content);
225+
}
226+
`
227+
228+
const SearchResultRight = styled.div`
229+
align-self: flex-end;
230+
`

src/components/organisms/NoteDetail.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type NoteDetailProps = {
3434
props: Partial<NoteDocEditibleProps>
3535
) => Promise<void | NoteDoc>
3636
viewMode: ViewModeType
37+
initialCursorPos: EditorPosition
3738
addAttachments(storageId: string, files: File[]): Promise<Attachment[]>
3839
}
3940

@@ -73,6 +74,12 @@ class NoteDetail extends React.Component<NoteDetailProps, NoteDetailState> {
7374
codeMirror?: CodeMirror.EditorFromTextArea
7475
codeMirrorRef = (codeMirror: CodeMirror.EditorFromTextArea) => {
7576
this.codeMirror = codeMirror
77+
78+
// Update cursor if needed
79+
if (this.props.initialCursorPos) {
80+
this.codeMirror.focus()
81+
this.codeMirror.setCursor(this.props.initialCursorPos)
82+
}
7683
}
7784

7885
static getDerivedStateFromProps(
@@ -86,8 +93,8 @@ class NoteDetail extends React.Component<NoteDetailProps, NoteDetailState> {
8693
prevNoteId: note._id,
8794
content: note.content,
8895
currentCursor: {
89-
line: 0,
90-
ch: 0,
96+
line: props.initialCursorPos ? props.initialCursorPos.line : 0,
97+
ch: props.initialCursorPos ? props.initialCursorPos.ch : 0,
9198
},
9299
currentSelections: [
93100
{
@@ -285,13 +292,13 @@ class NoteDetail extends React.Component<NoteDetailProps, NoteDetailState> {
285292
}
286293

287294
render() {
288-
const { note, storage, viewMode } = this.props
295+
const { note, storage, viewMode, initialCursorPos } = this.props
289296
const { currentCursor, currentSelections } = this.state
290297

291298
const codeEditor = (
292299
<CustomizedCodeEditor
293300
className='editor'
294-
key={note._id}
301+
key={note._id + initialCursorPos.line}
295302
codeMirrorRef={this.codeMirrorRef}
296303
value={this.state.content}
297304
onChange={this.updateContent}

src/components/organisms/NoteStorageNavigator.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ const NoteStorageNavigator = ({ storage }: NoteStorageNavigatorProps) => {
215215
const { toggleShowSearchModal } = useSearchModal()
216216

217217
useGlobalKeyDownHandler((event) => {
218-
if (isWithGeneralCtrlKey(event) && event.key === 'p') {
218+
if (isWithGeneralCtrlKey(event) && event.key.toLowerCase() === 'p') {
219219
toggleShowSearchModal()
220220
}
221221
})
@@ -300,7 +300,6 @@ const TopButton = styled.button`
300300
const SearchButton = styled.button`
301301
margin: 0 8px;
302302
height: 34px;
303-
padding: 0;
304303
color: ${({ theme }) => theme.secondaryButtonLabelColor};
305304
background-color: ${({ theme }) => theme.secondaryButtonBackgroundColor};
306305
border: none;
@@ -321,7 +320,7 @@ const SearchButton = styled.button`
321320
& > .icon {
322321
width: 24px;
323322
height: 24px;
324-
${flexCenter}
323+
${flexCenter};
325324
flex-shrink: 0;
326325
}
327326
& > .label {
@@ -333,7 +332,7 @@ const SearchButton = styled.button`
333332
display: none;
334333
font-size: 12px;
335334
margin-left: 5px;
336-
${textOverflow}
335+
${textOverflow};
337336
align-items: center;
338337
flex-shrink: 0;
339338
}
@@ -342,7 +341,6 @@ const SearchButton = styled.button`
342341
const NewNoteButton = styled.button`
343342
margin: 8px 8px;
344343
height: 34px;
345-
padding: 0;
346344
color: ${({ theme }) => theme.primaryButtonLabelColor};
347345
background-color: ${({ theme }) => theme.primaryButtonBackgroundColor};
348346
border: none;
@@ -363,7 +361,7 @@ const NewNoteButton = styled.button`
363361
& > .icon {
364362
width: 24px;
365363
height: 24px;
366-
${flexCenter}
364+
${flexCenter};
367365
flex-shrink: 0;
368366
}
369367
& > .label {
@@ -374,7 +372,7 @@ const NewNoteButton = styled.button`
374372
display: none;
375373
font-size: 12px;
376374
margin-left: 5px;
377-
${textOverflow}
375+
${textOverflow};
378376
align-items: center;
379377
& > .icon {
380378
flex-shrink: 0;

0 commit comments

Comments
 (0)