Skip to content

Commit

Permalink
Add fuzzy search in navigation pane [#33]
Browse files Browse the repository at this point in the history
- Add fuzzy search in table of contents and table of labels using
Fuse.js
- Set the compiler option `esModuleInterop` to true (to be able to
import Fuse)
  • Loading branch information
davidstraka2 committed May 4, 2021
1 parent 78c41c5 commit c7802c4
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 13 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"dependencies": {
"execa": "^5.0.0",
"fs-extra": "^9.1.0",
"fuse.js": "^6.4.6",
"lodash": "^4.17.21",
"mathjax": "^2.6.1",
"uuid": "^8.3.2",
Expand Down
1 change: 0 additions & 1 deletion src/lib/core/navigation/navigation-subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export class NavigationSubscriber {
}

private updateEditor(editor?: TextEditor | undefined): void {
this.contentCache = undefined;
this.editorSubscriptions.dispose();
this.editor = editor;
if (typeof editor === 'undefined') {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/core/navigation/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export class Navigation {
}

render(): Node {
const container = document.createDocumentFragment();
const container = document.createElement('div');
container.classList.add('wootom-navigation');
const heading = document.createElement('h1');
heading.append('Navigation');
const toc = new TableOfContents(
Expand Down
67 changes: 63 additions & 4 deletions src/lib/core/navigation/table-of-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,50 @@ import {variantRegistry} from '..';
import {jumpToPosition} from '../../util/editor/jump-to-position';
import {DocumentPart} from '../ast/document-part';
import {RenderingManager} from '../rendering/rendering-manager';
import Fuse from 'fuse.js';
import {removeAllChildren} from '../../util/html/remove-all-children';

type HeadingTreeNode = {
node: DocumentPart | undefined;
children: HeadingTreeNode[];
};

export class TableOfContents {
private list: HTMLElement;
private searchInput: HTMLInputElement;
private searchResults: HTMLElement;

constructor(
private readonly editor: TextEditor,
private readonly renderingManager: Required<RenderingManager>,
private readonly documentParts: DocumentPart[],
) {}
) {
this.list = document.createElement('ol');
this.searchInput = document.createElement('input');
this.searchResults = document.createElement('ul');
}

render(): Node {
const container = document.createDocumentFragment();
const heading = document.createElement('h2');
heading.append('Table of Contents');
const list = document.createElement('ol');
this.searchInput.classList.add('native-key-bindings', 'wootom-search');
this.searchInput.setAttribute(
'placeholder',
'Type here to search in table of contents...',
);
this.searchInput.addEventListener('input', this.search.bind(this));
const headingTree = this.constructHeadingTree(this.documentParts);
headingTree.forEach(node => list.append(this.renderTreeNode(node)));
container.append(heading, list);
headingTree.forEach(node =>
this.list.append(this.renderTreeNode(node)),
);
this.searchResults.hidden = true;
container.append(
heading,
this.searchInput,
this.list,
this.searchResults,
);
return container;
}

Expand Down Expand Up @@ -115,4 +138,40 @@ export class TableOfContents {
),
];
}

private renderSearchResults(results: DocumentPart[]): Node[] {
return results.map(res => {
const listItem = document.createElement('li');
listItem.append(this.renderHeading(res));
return listItem;
});
}

private search(): void {
const query = this.searchInput.value.trim();
if (query.length === 0) {
this.list.hidden = false;
this.searchResults.hidden = true;
return;
}
this.list.hidden = true;
this.searchResults.hidden = false;
removeAllChildren(this.searchResults);

const fuse = new Fuse(
this.documentParts.map(part => ({
text: this.renderingManager.render(part).textContent ?? '',
part,
})),
{
ignoreLocation: true,
keys: ['text'],
},
);

const results = fuse.search(query);
this.searchResults.append(
...this.renderSearchResults(results.map(res => res.item.part)),
);
}
}
65 changes: 59 additions & 6 deletions src/lib/core/navigation/table-of-labels.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
import {TextEditor} from 'atom';
import {jumpToPosition} from '../../util/editor/jump-to-position';
import {removeAllChildren} from '../../util/html/remove-all-children';
import {ASTNode} from '../ast/ast-node';
import {VariableASTNode} from '../ast/variable-ast-node';
import Fuse from 'fuse.js';

export class TableOfLabels {
private labeledNodes: ASTNode[];
private list: HTMLElement;
private searchInput: HTMLInputElement;
private searchResults: HTMLElement;

constructor(
private readonly editor: TextEditor,
private readonly root: ASTNode,
) {}
) {
this.labeledNodes = this.getLabeledNodes(this.root);
this.sortLabeledNodes(this.labeledNodes);
this.list = document.createElement('ul');
this.searchInput = document.createElement('input');
this.searchResults = document.createElement('ul');
}

render(): Node {
const container = document.createDocumentFragment();
const heading = document.createElement('h2');
heading.append('Table of Labels');
const list = document.createElement('ul');
const labeledNodes = this.getLabeledNodes(this.root);
this.sortLabeledNodes(labeledNodes);
labeledNodes.forEach(node => list.append(this.renderLabel(node)));
container.append(heading, list);
this.searchInput.classList.add('native-key-bindings', 'wootom-search');
this.searchInput.setAttribute(
'placeholder',
'Type here to search in labels...',
);
this.searchInput.addEventListener('input', this.search.bind(this));
this.labeledNodes.forEach(node =>
this.list.append(this.renderLabel(node)),
);
this.searchResults.hidden = true;
container.append(
heading,
this.searchInput,
this.list,
this.searchResults,
);
return container;
}

Expand Down Expand Up @@ -66,4 +90,33 @@ export class TableOfLabels {
listItem.append(anchor);
return listItem;
}

private renderSearchResults(results: ASTNode[]): Node[] {
return results.map(res => this.renderLabel(res));
}

private search(): void {
const query = this.searchInput.value.trim();
if (query.length === 0) {
this.list.hidden = false;
this.searchResults.hidden = true;
return;
}
this.list.hidden = true;
this.searchResults.hidden = false;
removeAllChildren(this.searchResults);

const fuse = new Fuse(
this.labeledNodes.map(node => ({label: this.getLabel(node), node})),
{
ignoreLocation: true,
keys: ['label'],
},
);

const results = fuse.search(query);
this.searchResults.append(
...this.renderSearchResults(results.map(res => res.item.node)),
);
}
}
2 changes: 1 addition & 1 deletion src/lib/template/tikz.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as execa from 'execa';
import execa from 'execa';
import * as fs from 'fs-extra';
import {v4 as uuid} from 'uuid';
import * as path from 'path';
Expand Down
3 changes: 3 additions & 0 deletions src/lib/util/html/remove-all-children.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function removeAllChildren(node: Node): void {
while (node.lastChild !== null) node.removeChild(node.lastChild);
}
16 changes: 16 additions & 0 deletions styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,20 @@
background-color: var(--color-white);
}
}

.wootom-navigation {
ol {
padding-inline-start: 2em;
}

input.wootom-search {
background: transparent;
border-radius: 25px;
border-style: solid;
border-width: 1px;
padding: 0 1rem;
min-width: 5rem;
width: 100%;
}
}
}
1 change: 1 addition & 0 deletions tsconfig-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"target": "ES6",
"module": "CommonJS",
"allowUnreachableCode": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"noImplicitReturns": true,
Expand Down

0 comments on commit c7802c4

Please sign in to comment.