Skip to content

Commit f29c8f9

Browse files
committed
find-as-you-type search
@devoncarew @keertip the plan is to generate a simple JSON file, enough to power a find-as-you-type search. Do either of you have an opinion on how to implement the HTML bits for this? (e.g. found a good example?) Closes #799 Squashed commit of the following: commit c2596c0 Author: Seth Ladd <[email protected]> Date: Thu Aug 13 15:11:32 2015 -0700 search box is now slightly rounded commit b9d0c67 Author: Seth Ladd <[email protected]> Date: Wed Aug 12 23:39:05 2015 -0700 shorter name breaks ties commit e607947 Author: Seth Ladd <[email protected]> Date: Wed Aug 12 23:28:50 2015 -0700 perform basic weighting of results commit e954ad7 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 22:13:44 2015 -0700 tweaking display of form commit 3e5ba61 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 21:50:51 2015 -0700 tweaks to styles commit 5b8299e Author: Seth Ladd <[email protected]> Date: Tue Aug 11 21:49:00 2015 -0700 tweaks to styles commit fc70444 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 21:21:47 2015 -0700 more style tweaks for mobile commit 9a384e6 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 20:56:50 2015 -0700 aligning text commit 2064f7d Merge: 9e92254 6ca3b28 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 17:34:39 2015 -0700 Merge branch 'master' of github.com:dart-lang/dartdoc into sl-find-as-you-type commit 9e92254 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 17:34:32 2015 -0700 human-friendly type names for model elements commit 1b04a93 Author: Seth Ladd <[email protected]> Date: Tue Aug 11 10:46:08 2015 -0700 tweaks to styles commit d3f0b6c Author: Seth Ladd <[email protected]> Date: Tue Aug 11 07:41:20 2015 -0700 rename from review comments commit e15b7e3 Author: Seth Ladd <[email protected]> Date: Mon Aug 10 22:43:21 2015 -0700 max 10 results, scroll the overflow commit bd6015c Merge: 2f59b45 0729031 Author: Seth Ladd <[email protected]> Date: Mon Aug 10 22:22:22 2015 -0700 Merge branch 'master' of github.com:dart-lang/dartdoc into sl-find-as-you-type commit 2f59b45 Author: Seth Ladd <[email protected]> Date: Mon Aug 10 22:21:43 2015 -0700 display what the suggestion is and what it is enclosed by, selecting a suggestion takes the user there commit 6a51aec Author: Seth Ladd <[email protected]> Date: Mon Aug 10 18:38:41 2015 -0700 style tweaks commit 3a0c2c4 Author: Seth Ladd <[email protected]> Date: Mon Aug 10 15:59:02 2015 -0700 search inside of strings commit 272cc44 Author: Seth Ladd <[email protected]> Date: Mon Aug 10 15:17:35 2015 -0700 more work on find as you type commit 5dce5ef Merge: cfc583c 4612975 Author: Seth Ladd <[email protected]> Date: Mon Aug 10 09:08:27 2015 -0700 Merge branch 'master' of github.com:dart-lang/dartdoc into sl-find-as-you-type commit cfc583c Author: Seth Ladd <[email protected]> Date: Sat Aug 8 11:07:46 2015 -0700 a bit more about what encloses something commit fa37be7 Author: Seth Ladd <[email protected]> Date: Sat Aug 8 11:01:49 2015 -0700 write a JSON index of all elements
1 parent 6ca3b28 commit f29c8f9

10 files changed

+478
-81
lines changed

lib/resources/script.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,124 @@ function shiftWindow() {
5858
scrollBy(0, -68);
5959
}
6060

61+
function initSearch() {
62+
var searchIndex; // the JSON data
63+
64+
var weights = {
65+
'library' : 2,
66+
'class' : 2,
67+
'typedef' : 3,
68+
'method' : 4,
69+
'accessor' : 4,
70+
'operator' : 4,
71+
'property' : 4,
72+
'constructor' : 4
73+
};
74+
75+
function findMatches(q, cb) {
76+
var allMatches = []; // ist of matches
77+
78+
function score(element, num) {
79+
var weightFactor = weights[element.type] || 4;
80+
return {e: element, score: (num / weightFactor) >> 0};
81+
}
82+
83+
// iterate through the pool of strings and for any string that
84+
// contains the substring `q`, add it to the `matches` array
85+
$.each(searchIndex, function(i, element) {
86+
// TODO: prefer matches in the current library
87+
// TODO: help prefer a named constructor
88+
89+
var lowerName = element.name.toLowerCase();
90+
var lowerQ = q.toLowerCase();
91+
92+
if (element.name === q) {
93+
// exact match, maximum score
94+
allMatches.push(score(element, 1000));
95+
} else if (lowerName === lowerQ) {
96+
// case-insensitive exact match
97+
allMatches.push(score(element, 900));
98+
} else if (element.name.indexOf(q) === 0) {
99+
// starts with
100+
allMatches.push(score(element, 750));
101+
} else if (lowerName.indexOf(lowerQ) === 0) {
102+
// case-insensitive starts with
103+
allMatches.push(score(element, 650));
104+
} else if (element.name.indexOf(q) >= 0) {
105+
// contains
106+
allMatches.push(score(element, 500));
107+
} else if (lowerName.indexOf(lowerQ) >= 0) {
108+
allMatches.push(score(element, 400));
109+
}
110+
});
111+
112+
allMatches.sort(function(a, b) {
113+
var x = b.score - a.score;
114+
if (x === 0) {
115+
// tie-breaker: shorter name wins
116+
return a.e.name.length - b.e.name.length;
117+
} else {
118+
return x;
119+
}
120+
});
121+
122+
var sortedMatches = [];
123+
for (var i = 0; i < allMatches.length; i++) {
124+
sortedMatches.push(allMatches[i].e);
125+
}
126+
127+
cb(sortedMatches);
128+
};
129+
130+
function initTypeahead() {
131+
$('#search-box').prop('disabled', false);
132+
133+
$('#search-box.typeahead').typeahead({
134+
hint: true,
135+
highlight: true,
136+
minLength: 3
137+
},
138+
{
139+
name: 'elements',
140+
limit: 10,
141+
source: findMatches,
142+
display: function(element) { return element.name; },
143+
templates: {
144+
suggestion: function(match) {
145+
return [
146+
'<div>',
147+
match.name,
148+
' ',
149+
match.type.toLowerCase(),
150+
(match.enclosedBy ? [
151+
'<div class="search-from-lib">from ',
152+
match.enclosedBy.name,
153+
'</div>'].join('') : ''),
154+
'</div>'
155+
].join('');
156+
}
157+
}
158+
});
159+
160+
$('#search-box.typeahead').bind('typeahead:select', function(ev, suggestion) {
161+
window.location = suggestion.href;
162+
});
163+
}
164+
165+
var jsonReq = new XMLHttpRequest();
166+
jsonReq.open('GET', 'index.json', true);
167+
jsonReq.addEventListener('load', function() {
168+
searchIndex = JSON.parse(jsonReq.responseText);
169+
initTypeahead();
170+
});
171+
jsonReq.send();
172+
}
173+
61174
document.addEventListener("DOMContentLoaded", function() {
62175
prettyPrint();
63176
initScroller();
64177
initSideNav();
178+
initSearch();
65179

66180
// Make sure the anchors scroll past the fixed page header (#648).
67181
if (location.hash) shiftWindow();

lib/resources/styles.css

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,24 @@ nav {
3232
/* some of this is to reset bootstrap */
3333
nav.navbar {
3434
background-color: inherit;
35-
min-height: 56px;
35+
min-height: 48px;
3636
border: 0;
37-
display: flex;
38-
flex-direction: column;
39-
justify-content: center;
40-
display: -webkit-flex;
41-
-webkit-flex-direction: column;
42-
-webkit-justify-content: center;
37+
}
38+
39+
nav.navbar .row {
40+
padding-top: 8px;
4341
}
4442

4543
nav .container {
4644
white-space: nowrap;
4745
}
4846

47+
@media screen and (min-width: 500px) and (max-width: 768px) {
48+
.navbar-right {
49+
float: right!important;
50+
}
51+
}
52+
4953
header {
5054
background-color: #2196F3;
5155
color: white;
@@ -64,6 +68,17 @@ header .masthead {
6468
padding-top: 64px;
6569
}
6670

71+
header .contents {
72+
padding: 0;
73+
}
74+
75+
@media screen and (max-width:768px) {
76+
header .contents {
77+
padding-left: 15px;
78+
padding-right: 15px;
79+
}
80+
}
81+
6782
.body {
6883
margin-top: 24px;
6984
}
@@ -99,6 +114,10 @@ h5 {
99114
font-size: 16px;
100115
}
101116

117+
strong {
118+
font-weight: 500;
119+
}
120+
102121
.subtitle {
103122
font-size: 17px;
104123
min-height: 1.4em;
@@ -373,6 +392,12 @@ footer .container-fluid {
373392
line-height: 1;
374393
}
375394

395+
@media screen and (min-width: 768px) {
396+
nav ol.breadcrumbs {
397+
float: left;
398+
}
399+
}
400+
376401
@media screen and (max-width: 768px) {
377402
.breadcrumbs {
378403
margin: 0 0 24px 0;
@@ -541,11 +566,6 @@ button {
541566

542567
/* left-nav disappears, and can transition in from the left */
543568
@media screen and (max-width:768px) {
544-
.main-content {
545-
padding-left: 0;
546-
padding-right: 0;
547-
}
548-
549569
#sidenav-left-toggle {
550570
display: inline;
551571
background: no-repeat url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path fill='#FFFFFF' d='M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z'/></svg>");
@@ -610,8 +630,99 @@ button {
610630
}
611631
}
612632

613-
/* source code method bodies */
614-
615633
#overlay-under-drawer {
616634
display: none;
617635
}
636+
637+
/* find-as-you-type search box */
638+
639+
/* override bootstrap defaults */
640+
.form-control {
641+
border-radius: 0;
642+
border: 0;
643+
}
644+
645+
form.search {
646+
display: inline;
647+
}
648+
649+
@media screen and (max-width: 500px) {
650+
form.search {
651+
display: none;
652+
}
653+
}
654+
655+
.typeahead,
656+
.tt-query,
657+
.tt-hint {
658+
width: 200px;
659+
height: 30px;
660+
padding: 8px 12px;
661+
line-height: 30px;
662+
outline: none;
663+
}
664+
665+
.typeahead {
666+
background-color: #fff;
667+
border-radius: 2px;
668+
}
669+
670+
.tt-query {
671+
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
672+
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
673+
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
674+
}
675+
676+
.tt-hint {
677+
color: #999
678+
}
679+
680+
.tt-menu {
681+
right:0;
682+
left: inherit !important;
683+
width: 422px;
684+
max-height: 250px;
685+
overflow-y: auto;
686+
font-size: 14px;
687+
margin: 0;
688+
padding: 8px 0;
689+
background-color: #fff;
690+
border: 1px solid #ccc;
691+
border: 1px solid rgba(0, 0, 0, 0.2);
692+
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
693+
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
694+
box-shadow: 0 5px 10px rgba(0,0,0,.2);
695+
}
696+
697+
.tt-suggestion {
698+
padding: 3px 20px;
699+
color: #212121;
700+
}
701+
702+
.tt-suggestion:hover {
703+
cursor: pointer;
704+
color: #fff;
705+
background-color: #0097cf;
706+
}
707+
708+
.tt-suggestion:hover .search-from-lib {
709+
color: #ddd;
710+
}
711+
712+
.tt-suggestion.tt-cursor {
713+
color: #fff;
714+
background-color: #0097cf;
715+
}
716+
717+
.tt-suggestion.tt-cursor .search-from-lib {
718+
color: #ddd;
719+
}
720+
721+
.tt-suggestion p {
722+
margin: 0;
723+
}
724+
725+
.search-from-lib {
726+
font-style: italic;
727+
color: gray;
728+
}

lib/resources/typeahead.bundle.min.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/src/html_generator.dart

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
library dartdoc.html_generator;
66

77
import 'dart:async' show Future;
8-
import 'dart:io';
8+
import 'dart:io' show Directory, File;
9+
import 'dart:convert' show JSON;
910
import 'dart:typed_data' show Uint8List;
1011

1112
import 'package:mustache4dart/mustache4dart.dart';
@@ -17,6 +18,7 @@ import 'resources.g.dart' as resources;
1718
import '../generator.dart';
1819
import '../markdown_processor.dart';
1920
import '../resource_loader.dart' as loader;
21+
import 'io_utils.dart' show createOutputFile;
2022

2123
String dartdocVersion = 'unknown';
2224

@@ -156,7 +158,7 @@ class HtmlGeneratorInstance {
156158
final Package package;
157159
final Directory out;
158160

159-
final List<String> _htmlFiles = [];
161+
final List<ModelElement> documentedElements = [];
160162

161163
HtmlGeneratorInstance(this.url, this._templates, this.package, this.out);
162164

@@ -166,13 +168,30 @@ class HtmlGeneratorInstance {
166168

167169
if (package != null) {
168170
_generateDocs();
171+
_generateSearchIndex();
172+
// TODO: generate sitemap
169173
}
170174

171-
//if (url != null) generateSiteMap();
172-
173175
await _copyResources();
174176
}
175177

178+
void _generateSearchIndex() {
179+
File jsonFile = createOutputFile(out, 'index.json');
180+
String json = JSON.encode(documentedElements.map((ModelElement e) {
181+
// TODO: find a better string for type
182+
Map data = {'name': e.name, 'href': e.href, 'type': e.kind};
183+
if (e is EnclosedElement) {
184+
EnclosedElement ee = e as EnclosedElement;
185+
data['enclosedBy'] = {
186+
'name': ee.enclosingElement.name,
187+
'type': ee.enclosingElement.kind
188+
};
189+
}
190+
return data;
191+
}).toList());
192+
jsonFile.writeAsStringSync(json);
193+
}
194+
176195
void _generateDocs() {
177196
if (package == null) return;
178197

@@ -354,22 +373,16 @@ class HtmlGeneratorInstance {
354373
}
355374
}
356375

357-
File _createOutputFile(String filename) {
358-
File f = new File(path.join(out.path, filename));
359-
if (!f.existsSync()) f.createSync(recursive: true);
360-
_htmlFiles.add(filename);
361-
return f;
362-
}
363-
364376
void _build(String filename, TemplateRenderer template, TemplateData data) {
365377
String content = template(data,
366378
assumeNullNonExistingProperty: false, errorOnMissingProperty: true);
367379

368380
_writeFile(filename, content);
381+
if (data.self is ModelElement) documentedElements.add(data.self);
369382
}
370383

371384
void _writeFile(String filename, String content) {
372-
File f = _createOutputFile(filename);
385+
File f = createOutputFile(out, filename);
373386
f.writeAsStringSync(content);
374387
}
375388
}

0 commit comments

Comments
 (0)