| 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);
|
| +}
|
|
|