Index: pkg/dartdoc/dropdown.dart |
diff --git a/pkg/dartdoc/dropdown.dart b/pkg/dartdoc/dropdown.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fad4a2f99a4d9ff26f1f2bc2a048ef119d91a65b |
--- /dev/null |
+++ b/pkg/dartdoc/dropdown.dart |
@@ -0,0 +1,344 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+List libraryList; |
+InputElement searchInput; |
+DivElement dropdown; |
+ |
+/** |
+ * Update the search drop down based on the current search text. |
+ */ |
+updateDropDown(Event event) { |
+ if (libraryList == null) return; |
+ if (searchInput == null) return; |
+ if (dropdown == null) return; |
+ |
+ var results = <Result>[]; |
+ String text = searchInput.value; |
+ if (text == currentSearchText) { |
+ return; |
+ } |
+ if (text.isEmpty()) { |
+ updateResults(text, results); |
+ hideDropDown(); |
+ return; |
+ } |
+ if (text.contains('.')) { |
+ // Search type members. |
+ String typeText = text.substring(0, text.indexOf('.')); |
+ String memberText = text.substring(text.indexOf('.') + 1); |
+ |
+ if (typeText.isEmpty() && memberText.isEmpty()) { |
+ // Don't search on '.'. |
+ } else if (typeText.isEmpty()) { |
+ // Search text is of the form '.id' => Look up members. |
+ matchAllMembers(results, memberText); |
+ } else if (memberText.isEmpty()) { |
+ // Search text is of the form 'Type.' => Look up members in 'Type'. |
+ matchAllMembersInType(results, typeText, memberText); |
+ } else { |
+ // Search text is of the form 'Type.id' => Look up member 'id' in 'Type'. |
+ matchMembersInType(results, text, typeText, memberText); |
+ } |
+ } else { |
+ // Search all entities. |
+ var searchText = new SearchText(text); |
+ for (Map<String,Dynamic> library in libraryList) { |
+ matchLibrary(results, searchText, library); |
+ matchLibraryMembers(results, searchText, library); |
+ matchTypes(results, searchText, library); |
+ } |
+ } |
+ var elements = <Element>[]; |
+ var table = new TableElement(); |
+ table.classes.add('drop-down-table'); |
+ elements.add(table); |
+ |
+ if (results.isEmpty()) { |
+ var row = table.insertRow(0); |
+ row.innerHTML = "<tr><td>No matches found for '$text'.</td></tr>"; |
+ } else { |
+ results.sort(resultComparator); |
+ |
+ var count = 0; |
+ for (Result result in results) { |
+ result.addRow(table); |
+ if (++count >= 10) { |
+ break; |
+ } |
+ } |
+ if (results.length >= 10) { |
+ var row = table.insertRow(table.rows.length); |
+ row.innerHTML = '<tr><td>+ ${results.length-10} more.</td></tr>'; |
+ results = results.getRange(0, 10); |
+ } |
+ } |
+ dropdown.elements = elements; |
+ updateResults(text, results); |
+ showDropDown(); |
+} |
+ |
+void matchAllMembers(List<Result> results, String memberText) { |
+ var searchText = new SearchText(memberText); |
+ for (Map<String,Dynamic> library in libraryList) { |
+ String libraryName = library[NAME]; |
+ if (library.containsKey(TYPES)) { |
+ for (Map<String,Dynamic> type in library[TYPES]) { |
+ String typeName = type[NAME]; |
+ if (type.containsKey(MEMBERS)) { |
+ for (Map<String,Dynamic> member in type[MEMBERS]) { |
+ StringMatch memberMatch = obtainMatch(searchText, member[NAME]); |
+ if (memberMatch != null) { |
+ results.add(new Result(memberMatch, member[KIND], |
+ getTypeMemberUrl(libraryName, typeName, member), |
+ library: libraryName, type: typeName, args: type[ARGS])); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+void matchAllMembersInType(List<Result> results, |
+ String typeText, String memberText) { |
+ var searchText = new SearchText(typeText); |
+ var emptyText = new SearchText(memberText); |
+ for (Map<String,Dynamic> library in libraryList) { |
+ String libraryName = library[NAME]; |
+ if (library.containsKey(TYPES)) { |
+ for (Map<String,Dynamic> type in library[TYPES]) { |
+ String typeName = type[NAME]; |
+ StringMatch typeMatch = obtainMatch(searchText, typeName); |
+ if (typeMatch != null) { |
+ if (type.containsKey(MEMBERS)) { |
+ for (Map<String,Dynamic> member in type[MEMBERS]) { |
+ StringMatch memberMatch = obtainMatch(emptyText, |
+ member[NAME]); |
+ results.add(new Result(memberMatch, member[KIND], |
+ getTypeMemberUrl(libraryName, typeName, member), |
+ library: libraryName, prefix: typeMatch)); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+void matchMembersInType(List<Result> results, |
+ String text, String typeText, String memberText) { |
+ var searchText = new SearchText(text); |
+ var typeSearchText = new SearchText(typeText); |
+ var memberSearchText = new SearchText(memberText); |
+ for (Map<String,Dynamic> library in libraryList) { |
+ String libraryName = library[NAME]; |
+ if (library.containsKey(TYPES)) { |
+ for (Map<String,Dynamic> type in library[TYPES]) { |
+ String typeName = type[NAME]; |
+ StringMatch typeMatch = obtainMatch(typeSearchText, typeName); |
+ if (typeMatch != null) { |
+ if (type.containsKey(MEMBERS)) { |
+ for (Map<String,Dynamic> member in type[MEMBERS]) { |
+ // Check for constructor match. |
+ StringMatch constructorMatch = obtainMatch(searchText, |
+ member[NAME]); |
+ if (constructorMatch != null) { |
+ results.add(new Result(constructorMatch, member[KIND], |
+ getTypeMemberUrl(libraryName, typeName, member), |
+ library: libraryName)); |
+ } else { |
+ // Try member match. |
+ StringMatch memberMatch = obtainMatch(memberSearchText, |
+ member[NAME]); |
+ if (memberMatch != null) { |
+ results.add(new Result(memberMatch, member[KIND], |
+ getTypeMemberUrl(libraryName, typeName, member), |
+ library: libraryName, prefix: typeMatch, |
+ args: type[ARGS])); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+void matchLibrary(List<Result> results, SearchText searchText, Map library) { |
+ String libraryName = library[NAME]; |
+ StringMatch libraryMatch = obtainMatch(searchText, libraryName); |
+ if (libraryMatch != null) { |
+ results.add(new Result(libraryMatch, LIBRARY, |
+ getLibraryUrl(libraryName))); |
+ } |
+} |
+ |
+void matchLibraryMembers(List<Result> results, SearchText searchText, |
+ Map library) { |
+ if (library.containsKey(MEMBERS)) { |
+ String libraryName = library[NAME]; |
+ for (Map<String,Dynamic> member in library[MEMBERS]) { |
+ StringMatch memberMatch = obtainMatch(searchText, member[NAME]); |
+ if (memberMatch != null) { |
+ results.add(new Result(memberMatch, member[KIND], |
+ getLibraryMemberUrl(libraryName, member), |
+ library: libraryName)); |
+ } |
+ } |
+ } |
+} |
+ |
+void matchTypes(List<Result> results, SearchText searchText, |
+ Map library) { |
+ if (library.containsKey(TYPES)) { |
+ String libraryName = library[NAME]; |
+ for (Map<String,Dynamic> type in library[TYPES]) { |
+ String typeName = type[NAME]; |
+ matchType(results, searchText, libraryName, type); |
+ matchTypeMembers(results, searchText, libraryName, type); |
+ } |
+ } |
+} |
+ |
+void matchType(List<Result> results, SearchText searchText, |
+ String libraryName, Map type) { |
+ String typeName = type[NAME]; |
+ StringMatch typeMatch = obtainMatch(searchText, typeName); |
+ if (typeMatch != null) { |
+ results.add(new Result(typeMatch, type[KIND], |
+ getTypeUrl(libraryName, type), |
+ library: libraryName, args: type[ARGS])); |
+ } |
+} |
+ |
+void matchTypeMembers(List<Result> results, SearchText searchText, |
+ String libraryName, Map type) { |
+ if (type.containsKey(MEMBERS)) { |
+ String typeName = type[NAME]; |
+ for (Map<String,Dynamic> member in type[MEMBERS]) { |
+ StringMatch memberMatch = obtainMatch(searchText, member[NAME]); |
+ if (memberMatch != null) { |
+ results.add(new Result(memberMatch, member[KIND], |
+ getTypeMemberUrl(libraryName, typeName, member), |
+ library: libraryName, type: typeName, args: type[ARGS])); |
+ } |
+ } |
+ } |
+} |
+ |
+String currentSearchText; |
+Result _currentResult; |
+List<Result> currentResults = const <Result>[]; |
+ |
+void updateResults(String searchText, List<Result> results) { |
+ currentSearchText = searchText; |
+ currentResults = results; |
+ if (currentResults.isEmpty()) { |
+ _currentResultIndex = -1; |
+ currentResult = null; |
+ } else { |
+ _currentResultIndex = 0; |
+ currentResult = currentResults[0]; |
+ } |
+} |
+ |
+int _currentResultIndex; |
+ |
+void set currentResultIndex(int index) { |
+ if (index < -1) { |
+ return; |
+ } |
+ if (index >= currentResults.length) { |
+ return; |
+ } |
+ if (index != _currentResultIndex) { |
+ _currentResultIndex = index; |
+ if (index >= 0) { |
+ currentResult = currentResults[_currentResultIndex]; |
+ } else { |
+ currentResult = null; |
+ } |
+ } |
+} |
+ |
+int get currentResultIndex() => _currentResultIndex; |
+ |
+void set currentResult(Result result) { |
+ if (_currentResult != result) { |
+ if (_currentResult != null) { |
+ _currentResult.row.classes.remove('drop-down-link-select'); |
+ } |
+ _currentResult = result; |
+ if (_currentResult != null) { |
+ _currentResult.row.classes.add('drop-down-link-select'); |
+ } |
+ } |
+} |
+ |
+Result get currentResult() => _currentResult; |
+ |
+/** |
+ * Navigate the search drop down using up/down inside the search field. Follow |
+ * the result link on enter. |
+ */ |
+void handleUpDown(KeyboardEvent event) { |
+ if (event.keyIdentifier == KeyName.UP) { |
+ currentResultIndex--; |
+ event.preventDefault(); |
+ } else if (event.keyIdentifier == KeyName.DOWN) { |
+ currentResultIndex++; |
+ event.preventDefault(); |
+ } else if (event.keyIdentifier == KeyName.ENTER) { |
+ if (currentResult != null) { |
+ window.location.href = currentResult.url; |
+ event.preventDefault(); |
+ hideDropDown(); |
+ } |
+ } |
+} |
+ |
+/** Show the search drop down unless there are no current results. */ |
+void showDropDown() { |
+ if (currentResults.isEmpty()) { |
+ hideDropDown(); |
+ } else { |
+ dropdown.style.visibility = 'visible'; |
+ } |
+} |
+ |
+/** Used to prevent hiding the drop down when it is clicked. */ |
+bool hideDropDownSuspend = false; |
+ |
+/** Hide the search drop down unless suspended. */ |
+void hideDropDown() { |
+ if (hideDropDownSuspend) return; |
+ |
+ dropdown.style.visibility = 'hidden'; |
+} |
+ |
+/** Activate search on Ctrl+F and F3. */ |
+void shortcutHandler(KeyboardEvent event) { |
+ if (event.keyCode == 0x46/*F*/ && event.ctrlKey || |
+ event.keyIdentifier == KeyName.F3) { |
+ searchInput.focus(); |
+ event.preventDefault(); |
+ } |
+} |
+ |
+/** Setup search hooks. */ |
+void setupSearch(var libraries) { |
+ libraryList = libraries; |
+ searchInput = query('#q'); |
+ dropdown = query('#drop-down'); |
+ |
+ searchInput.on.keyDown.add(handleUpDown); |
+ searchInput.on.keyUp.add(updateDropDown); |
+ searchInput.on.change.add(updateDropDown); |
+ searchInput.on.reset.add(updateDropDown); |
+ searchInput.on.focus.add((event) => showDropDown()); |
+ searchInput.on.blur.add((event) => hideDropDown()); |
+ window.on.keyDown.add(shortcutHandler); |
+} |