Skip to content

Commit 5668499

Browse files
authored
Merge pull request #184 from kpinnipa/improveSearch
Improve search
2 parents 9a3450e + 9656be7 commit 5668499

15 files changed

+1354
-913
lines changed

docs/contributor/codebase.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Description of each file in ``src/``
6161
confirmation dialog as a modal window after snippet creation.
6262
- FilterTools.tsx: this contains a react component that renders a
6363
search bar and filter box.
64-
- MoreOptions.ts: this contains a lumino widget that creates dropdown
64+
- CodeSnippetMenu.ts: this contains a lumino widget that creates dropdown
6565
dialog when three dots icon is clicked.
6666
- PreviewSnippet.ts: this contains a lumino widget used to create
6767
preview minimap.

src/CodeSnippetDisplay.tsx

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ interface ICodeSnippetDisplayState {
176176
matchIndices: number[][];
177177
searchValue: string;
178178
filterTags: string[];
179+
searchOptions: string[];
179180
}
180181

181182
/**
@@ -194,12 +195,14 @@ export class CodeSnippetDisplay extends React.Component<
194195
matchIndices: [],
195196
searchValue: '',
196197
filterTags: [],
198+
searchOptions: [],
197199
};
198200
this._drag = null;
199201
this._dragData = null;
200202
this.handleDragMove = this.handleDragMove.bind(this);
201203
this._evtMouseUp = this._evtMouseUp.bind(this);
202204
this.handleRenameSnippet = this.handleRenameSnippet.bind(this);
205+
this.setSearchOptions = this.setSearchOptions.bind(this);
203206
}
204207

205208
// Handle code snippet insert into a notebook or document
@@ -1284,6 +1287,7 @@ export class CodeSnippetDisplay extends React.Component<
12841287
matchIndices: [],
12851288
searchValue: '',
12861289
filterTags: [],
1290+
searchOptions: [],
12871291
};
12881292
}
12891293

@@ -1293,13 +1297,17 @@ export class CodeSnippetDisplay extends React.Component<
12931297
matchIndices: prevState.matchIndices,
12941298
searchValue: prevState.searchValue,
12951299
filterTags: prevState.filterTags,
1300+
searchOptions: prevState.searchOptions,
12961301
};
12971302
}
12981303
return null;
12991304
}
13001305

1301-
filterSnippets = (searchValue: string, filterTags: string[]): void => {
1302-
// filter with search
1306+
filterSnippets = (
1307+
searchValue: string,
1308+
filterTags: string[],
1309+
selectedLangTags: string[]
1310+
): void => {
13031311
let matchIndices: number[][] = [];
13041312
const matchResults: StringExt.IMatchResult[] = [];
13051313
let filteredSnippets = this.props.codeSnippets;
@@ -1308,9 +1316,14 @@ export class CodeSnippetDisplay extends React.Component<
13081316
snippet: ICodeSnippet;
13091317
}[] = [];
13101318
if (searchValue !== '') {
1319+
// language, title, code
13111320
filteredSnippets.forEach((snippet) => {
13121321
const matchResult = StringExt.matchSumOfSquares(
1313-
(snippet.language + snippet.name).toLowerCase(),
1322+
(
1323+
snippet.language +
1324+
snippet.name +
1325+
snippet.code.join('\n')
1326+
).toLowerCase(),
13141327
searchValue.replace(' ', '').toLowerCase()
13151328
);
13161329

@@ -1336,14 +1349,35 @@ export class CodeSnippetDisplay extends React.Component<
13361349
matchResults.forEach((res) => matchIndices.push(res.indices));
13371350
}
13381351

1339-
// filter with tags
13401352
if (filterTags.length !== 0) {
13411353
const newMatchIndices = matchIndices.slice();
13421354
filteredSnippets = filteredSnippets.filter((codeSnippet, id) => {
13431355
return filterTags.some((tag) => {
13441356
if (codeSnippet.tags) {
1345-
if (codeSnippet.tags.includes(tag)) {
1346-
return true;
1357+
if (selectedLangTags.length !== 0) {
1358+
// lang tags selected
1359+
console.log(selectedLangTags);
1360+
if (
1361+
codeSnippet.tags.includes(tag) &&
1362+
selectedLangTags.includes(codeSnippet.language)
1363+
) {
1364+
return true;
1365+
} else if (
1366+
filterTags.length === selectedLangTags.length &&
1367+
filterTags.every((value) => selectedLangTags.includes(value))
1368+
) {
1369+
//if only language tags are selected
1370+
console.log('hi');
1371+
if (selectedLangTags.includes(codeSnippet.language)) {
1372+
return true;
1373+
}
1374+
}
1375+
} else {
1376+
// no lang tags selected
1377+
console.log('reached');
1378+
if (codeSnippet.tags.includes(tag)) {
1379+
return true;
1380+
}
13471381
}
13481382
}
13491383
// if the snippet does not have the tag, remove its mathed index
@@ -1369,8 +1403,9 @@ export class CodeSnippetDisplay extends React.Component<
13691403
);
13701404
};
13711405

1372-
getActiveTags(): string[] {
1406+
getActiveTags(): string[][] {
13731407
const tags: string[] = [];
1408+
const languages: string[] = [];
13741409
for (const codeSnippet of this.props.codeSnippets) {
13751410
if (codeSnippet.tags) {
13761411
for (const tag of codeSnippet.tags) {
@@ -1379,10 +1414,37 @@ export class CodeSnippetDisplay extends React.Component<
13791414
}
13801415
}
13811416
}
1417+
if (!languages.includes(codeSnippet.language)) {
1418+
languages.push(codeSnippet.language);
1419+
}
13821420
}
1383-
return tags;
1421+
return [tags, languages];
13841422
}
13851423

1424+
getActiveTagsDictionary = (): Map<string, string[]> => {
1425+
const tagsAndLangs: Map<string, string[]> = new Map<string, string[]>();
1426+
for (const codeSnippet of this.props.codeSnippets) {
1427+
if (codeSnippet.tags) {
1428+
// check if tag is in dict, if it is add lang to value (if not already present)
1429+
// if tag not in dict add tag as key and lang as first val
1430+
for (const tag of codeSnippet.tags) {
1431+
if (tag !== codeSnippet.language) {
1432+
if (tagsAndLangs.has(tag)) {
1433+
const langs = tagsAndLangs.get(tag);
1434+
if (!langs.includes(codeSnippet.language)) {
1435+
langs.push(codeSnippet.language);
1436+
}
1437+
tagsAndLangs.set(tag, langs);
1438+
} else {
1439+
tagsAndLangs.set(tag, [codeSnippet.language]);
1440+
}
1441+
}
1442+
}
1443+
}
1444+
}
1445+
return tagsAndLangs;
1446+
};
1447+
13861448
private deleteCommand(codeSnippet: ICodeSnippet): void {
13871449
showDialog({
13881450
title: 'Delete snippet?',
@@ -1487,15 +1549,17 @@ export class CodeSnippetDisplay extends React.Component<
14871549
editSnip.className = CODE_SNIPPET_MORE_OTPIONS_EDIT;
14881550
editSnip.textContent = 'Edit snippet';
14891551
editSnip.onclick = (): void => {
1490-
const allTags = this.getActiveTags();
1552+
const allSnippetTags = this.getActiveTags()[0]; // snippet tags only
1553+
const allLangTags = this.getActiveTags()[1];
14911554
this.props.openCodeSnippetEditor({
14921555
name: codeSnippet.name,
14931556
description: codeSnippet.description,
14941557
language: codeSnippet.language,
14951558
code: codeSnippet.code,
14961559
id: codeSnippet.id,
1497-
selectedTags: codeSnippet.tags,
1498-
allTags: allTags,
1560+
selectedTags: codeSnippet.tags, // snippet tags
1561+
allSnippetTags: allSnippetTags,
1562+
allLangTags: allLangTags,
14991563
fromScratch: false,
15001564
});
15011565
this.removeOptionsNode();
@@ -1525,6 +1589,12 @@ export class CodeSnippetDisplay extends React.Component<
15251589
return body;
15261590
}
15271591

1592+
setSearchOptions(selectedOptions: string[]): void {
1593+
this.setState({
1594+
searchOptions: selectedOptions,
1595+
});
1596+
}
1597+
15281598
render(): React.ReactElement {
15291599
return (
15301600
<div>
@@ -1540,7 +1610,8 @@ export class CodeSnippetDisplay extends React.Component<
15401610
code: [],
15411611
id: this.state.codeSnippets.length,
15421612
selectedTags: [],
1543-
allTags: this.getActiveTags(),
1613+
allSnippetTags: this.getActiveTags()[0],
1614+
allLangTags: this.getActiveTags()[1],
15441615
fromScratch: true,
15451616
});
15461617
}}
@@ -1549,7 +1620,9 @@ export class CodeSnippetDisplay extends React.Component<
15491620
</button>
15501621
</header>
15511622
<FilterTools
1552-
tags={this.getActiveTags()}
1623+
tagDictionary={this.getActiveTagsDictionary()}
1624+
languageTags={this.getActiveTags()[1]}
1625+
snippetTags={this.getActiveTags()[0]}
15531626
onFilter={this.filterSnippets}
15541627
/>
15551628
<div className={CODE_SNIPPETS_CONTAINER}>

src/CodeSnippetEditor.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ export interface ICodeSnippetEditorMetadata {
5858
code: string[];
5959
id: number;
6060
selectedTags: string[];
61-
allTags: string[];
61+
allSnippetTags: string[];
62+
allLangTags: string[];
6263
fromScratch: boolean;
6364
}
6465

@@ -489,7 +490,7 @@ export class CodeSnippetEditor extends ReactWidget {
489490
}
490491

491492
this._codeSnippetEditorMetaData.selectedTags = selectedTags;
492-
this._codeSnippetEditorMetaData.allTags = allTags;
493+
this._codeSnippetEditorMetaData.allSnippetTags = allTags;
493494

494495
this.saved = false;
495496
}
@@ -595,7 +596,8 @@ export class CodeSnippetEditor extends ReactWidget {
595596
<label className={CODE_SNIPPET_EDITOR_LABEL}>Tags</label>
596597
<CodeSnippetEditorTags
597598
selectedTags={this.codeSnippetEditorMetadata.selectedTags}
598-
tags={this.codeSnippetEditorMetadata.allTags}
599+
snippetTags={this.codeSnippetEditorMetadata.allSnippetTags}
600+
langTags={this.codeSnippetEditorMetadata.allLangTags}
599601
handleChange={this.handleChangeOnTag}
600602
/>
601603
</section>

src/CodeSnippetEditorTags.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { checkIcon, addIcon } from '@jupyterlab/ui-components';
66
import React from 'react';
77

88
interface ICodeSnippetEditorTagProps {
9-
selectedTags: string[];
10-
tags: string[];
9+
selectedTags: string[]; // selected snippet tags only
10+
snippetTags: string[]; // snippet tags only
11+
langTags: string[];
1112
handleChange: (selectedTags: string[], allTags: string[]) => void;
1213
}
1314

@@ -44,7 +45,7 @@ export class CodeSnippetEditorTags extends React.Component<
4445
componentDidMount(): void {
4546
this.setState({
4647
selectedTags: this.props.selectedTags ? this.props.selectedTags : [],
47-
tags: this.props.tags ? this.props.tags : [],
48+
tags: this.props.snippetTags ? this.props.snippetTags : [],
4849
plusIconShouldHide: false,
4950
addingNewTag: false,
5051
});
@@ -54,7 +55,7 @@ export class CodeSnippetEditorTags extends React.Component<
5455
if (prevProps !== this.props) {
5556
this.setState({
5657
selectedTags: this.props.selectedTags ? this.props.selectedTags : [],
57-
tags: this.props.tags ? this.props.tags : [],
58+
tags: this.props.snippetTags ? this.props.snippetTags : [],
5859
});
5960
}
6061
}
@@ -117,6 +118,13 @@ export class CodeSnippetEditorTags extends React.Component<
117118
return;
118119
}
119120

121+
if (this.props.langTags.includes(inputElement.value)) {
122+
alert(
123+
'This tag already exists in language tags!\nIf you want to create this tag, lowercase the first letter.'
124+
);
125+
return;
126+
}
127+
120128
const newTag = inputElement.value;
121129

122130
// update state all tag and selected tag
@@ -182,6 +190,7 @@ export class CodeSnippetEditorTags extends React.Component<
182190
{hasTags
183191
? this.state.tags.map((tag: string, index: number) =>
184192
((): JSX.Element => {
193+
console.log(this.state.tags);
185194
if (!this.state.selectedTags) {
186195
return (
187196
<ul

src/CodeSnippetInputDialog.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export function showInputDialog(
121121
);
122122
} else {
123123
const tags = result.value.slice(3);
124+
//tags.push(result.value[2]);
124125
const newSnippet: ICodeSnippet = {
125126
name: result.value[0].replace(' ', ''),
126127
description: result.value[1],

src/CodeSnippetSearchOption.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import { checkIcon } from '@jupyterlab/ui-components';
3+
4+
const CODE_SNIPPET_SORT_OPTION_UNSELECT =
5+
'jp-codeSnippet-sort-option-unselected';
6+
const CODE_SNIPPET_SORT_OPTION_SELECT = 'jp-codeSnippet-sort-option-selected';
7+
8+
interface ISortSnippetOptionProps {
9+
optionSelected: boolean;
10+
optionName: string;
11+
onSelectMulti: (name: string, selected: boolean) => void;
12+
}
13+
14+
const SearchOption = ({
15+
optionSelected,
16+
optionName,
17+
onSelectMulti,
18+
}: ISortSnippetOptionProps): JSX.Element => {
19+
if (optionSelected) {
20+
return (
21+
<div
22+
className={CODE_SNIPPET_SORT_OPTION_SELECT}
23+
onClick={(): void => {
24+
onSelectMulti(optionName, false);
25+
}}
26+
>
27+
<checkIcon.react
28+
className="jupyterlab-CodeSnippets-sort-selected"
29+
elementPosition="center"
30+
height="16px"
31+
width="16px"
32+
marginLeft="2px"
33+
/>
34+
{optionName}
35+
</div>
36+
);
37+
} else {
38+
return (
39+
<div
40+
className={CODE_SNIPPET_SORT_OPTION_UNSELECT}
41+
onClick={(): void => {
42+
onSelectMulti(optionName, true);
43+
}}
44+
>
45+
{optionName}
46+
</div>
47+
);
48+
}
49+
};
50+
51+
export default SearchOption;

0 commit comments

Comments
 (0)