Commit b9f402c5 authored by manuk's avatar manuk Committed by Commit Bot

[chrome:omnibox] Add input box and a filter/highlight toggle to search results.

When the filter option is selected, matches (rows) which do not match the search query are hidden; when the highlight option is selected, matches which do match the search query are highlighted (light blue).

Bug: 891303
Change-Id: I4c34dae1a1cef2c7baeffdd6de0355ef7ea53f0a
Reviewed-on: https://chromium-review.googlesource.com/c/1340904Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: default avatarTommy Li <tommycli@chromium.org>
Commit-Queue: manuk hovanesian <manukh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610969}
parent dcdb2ecc
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
background-color: #C0C0C0; background-color: #C0C0C0;
} }
.autocomplete-results-table td { .autocomplete-results-table tr {
background-color: #F0F0F0; background-color: #F0F0F0;
} }
...@@ -19,6 +19,10 @@ ...@@ -19,6 +19,10 @@
text-decoration: underline; text-decoration: underline;
} }
.left-20 {
margin-left: 20px
}
.check-mark, .check-mark,
.x-mark { .x-mark {
background-position: center; background-position: center;
...@@ -53,3 +57,32 @@ p { ...@@ -53,3 +57,32 @@ p {
.additional-info-value { .additional-info-value {
white-space: nowrap; white-space: nowrap;
} }
.filtered-hidden {
display: none;
}
.autocomplete-results-table .filtered-highlighted {
background-color: lightskyblue;
/* TODO(manukh) This is a placholder color until other ui changes occur. */
}
.toggle input,
.toggle input:not(:checked) ~ .toggle-on,
.toggle input:checked ~ .toggle-off {
display: none;
}
.toggle span {
user-select: none;
display: inline-block;
padding: 3px 5px;
min-width: 60px;
text-align: center;
cursor: pointer;
border: 1px solid;
}
.toggle span:hover {
background-color: #f0f0f0;
}
...@@ -72,8 +72,18 @@ ...@@ -72,8 +72,18 @@
Show results per provider, not just merged results Show results per provider, not just merged results
</label> </label>
</p> </p>
<div class="section">
<button id="copy-text" title="Copy visible table in text format. This is affected by the visibility of ouput; i.e. toggling `Show all details` affects what will be copied.">Copy as text</button> <button id="copy-text" title="Copy visible table in text format. This is affected by the visibility of ouput; i.e. toggling `Show all details` affects what will be copied.">Copy as text</button>
<button id="copy-json" title="Copy responses in JSON format. This is not affected by the visibility of output and will copy responses in their entirety.">Copy as JSON</button> <button id="copy-json" title="Copy responses in JSON format. This is not affected by the visibility of output and will copy responses in their entirety.">Copy as JSON</button>
</div>
<div class="section">
<input id="filter-text" type="text" size="60" placeholder="Filter output">
<label class="toggle left-20">
<input id="filter-hide" type="checkbox">
<span class="toggle-off">Highlight</span>
<span class="toggle-on">Filter</span>
</label>
</div>
</template> </template>
<template id="omnibox-output-template"> <template id="omnibox-output-template">
...@@ -107,7 +117,11 @@ ...@@ -107,7 +117,11 @@
</table> </table>
</template> </template>
<omnibox-inputs id="omnibox-inputs" class="section"></omnibox-inputs> <div class="section">
<omnibox-output id="omnibox-output" class="section"></omnibox-output> <omnibox-inputs id="omnibox-inputs"></omnibox-inputs>
</div>
<div class="section">
<omnibox-output id="omnibox-output"></omnibox-output>
</div>
</body> </body>
</html> </html>
...@@ -65,11 +65,11 @@ ...@@ -65,11 +65,11 @@
} }
} }
/** @type {BrowserProxy} */ /** @type {!BrowserProxy} */
const browserProxy = new BrowserProxy(); const browserProxy = new BrowserProxy();
/** @type {OmniboxInputs} */ /** @type {!OmniboxInputs} */
let omniboxInputs; let omniboxInputs;
/** @type {omnibox_output.OmniboxOutput} */ /** @type {!omnibox_output.OmniboxOutput} */
let omniboxOutput; let omniboxOutput;
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
...@@ -95,5 +95,9 @@ ...@@ -95,5 +95,9 @@
event => event.detail === 'text' ? event => event.detail === 'text' ?
omniboxOutput.copyDelegate.copyTextOutput() : omniboxOutput.copyDelegate.copyTextOutput() :
omniboxOutput.copyDelegate.copyJsonOutput()); omniboxOutput.copyDelegate.copyJsonOutput());
omniboxInputs.addEventListener(
'filter-input-changed',
event => omniboxOutput.filterDelegate.filter(
event.detail.filterText, event.detail.filterHide));
}); });
})(); })();
...@@ -14,22 +14,41 @@ class OmniboxElement extends HTMLElement { ...@@ -14,22 +14,41 @@ class OmniboxElement extends HTMLElement {
this.attachShadow({mode: 'open'}); this.attachShadow({mode: 'open'});
const template = OmniboxElement.getTemplate(templateId); const template = OmniboxElement.getTemplate(templateId);
this.shadowRoot.appendChild(template); this.shadowRoot.appendChild(template);
/** @type {!ShadowRoot} */
this.shadowRoot;
} }
/** /**
* Searches local shadow root for element by id * Get an element that's known to exist within this OmniboxElement.
* Searches local shadow root for element by id.
* @param {string} id * @param {string} id
* @return {Element} * @return {!Element}
*/ */
$$(id) { $$(id) {
return this.shadowRoot.getElementById(id); return OmniboxElement.getById_(id, (this.shadowRoot));
} }
/** /**
* Get a template that's known to exist within the DOM document.
* @param {string} templateId * @param {string} templateId
* @return {Element} * @return {!Element}
*/ */
static getTemplate(templateId) { static getTemplate(templateId) {
return $(templateId).content.cloneNode(true); return OmniboxElement.getById_(templateId).content.cloneNode(true);
}
/**
* Get an element that's known to exist by its ID. We use this instead of just
* calling getElementById because this lets us satisfy the JSCompiler type
* system.
* @private
* @param {string} id
* @param {!Node=} context
* @return {!Element}
*/
static getById_(id, context) {
return assertInstanceof(
(context || document).getElementById(id), Element,
`Missing required element: ${id}`);
} }
} }
...@@ -41,6 +41,7 @@ class OmniboxInputs extends OmniboxElement { ...@@ -41,6 +41,7 @@ class OmniboxInputs extends OmniboxElement {
setupElementListeners_() { setupElementListeners_() {
const onQueryInputsChanged = this.onQueryInputsChanged_.bind(this); const onQueryInputsChanged = this.onQueryInputsChanged_.bind(this);
const onDisplayInputsChanged = this.onDisplayInputsChanged_.bind(this); const onDisplayInputsChanged = this.onDisplayInputsChanged_.bind(this);
const onFilterInputsChange = this.onFilterInputChange_.bind(this);
this.$$('input-text').addEventListener('input', onQueryInputsChanged); this.$$('input-text').addEventListener('input', onQueryInputsChanged);
this.$$('lock-cursor-position') this.$$('lock-cursor-position')
...@@ -59,11 +60,16 @@ class OmniboxInputs extends OmniboxElement { ...@@ -59,11 +60,16 @@ class OmniboxInputs extends OmniboxElement {
.addEventListener('click', () => this.onCopyOutput_('text')); .addEventListener('click', () => this.onCopyOutput_('text'));
this.$$('copy-json') this.$$('copy-json')
.addEventListener('click', () => this.onCopyOutput_('json')); .addEventListener('click', () => this.onCopyOutput_('json'));
this.$$('filter-text').addEventListener('input', onFilterInputsChange);
this.$$('filter-hide').addEventListener('change', onFilterInputsChange);
} }
// TODO (manukh) rename below on*InputsChanged methods to on*Changed to reduce
// verbosity.
/** @private */ /** @private */
onQueryInputsChanged_() { onQueryInputsChanged_() {
/** @type {QueryInputs} */ /** @type {!QueryInputs} */
const queryInputs = { const queryInputs = {
inputText: this.$$('input-text').value, inputText: this.$$('input-text').value,
cursorPosition: this.cursorPosition_, cursorPosition: this.cursorPosition_,
...@@ -77,7 +83,7 @@ class OmniboxInputs extends OmniboxElement { ...@@ -77,7 +83,7 @@ class OmniboxInputs extends OmniboxElement {
/** @private */ /** @private */
onDisplayInputsChanged_() { onDisplayInputsChanged_() {
/** @type {DisplayInputs} */ /** @type {!DisplayInputs} */
const displayInputs = { const displayInputs = {
showIncompleteResults: this.$$('show-incomplete-results').checked, showIncompleteResults: this.$$('show-incomplete-results').checked,
showDetails: this.$$('show-details').checked, showDetails: this.$$('show-details').checked,
...@@ -104,6 +110,16 @@ class OmniboxInputs extends OmniboxElement { ...@@ -104,6 +110,16 @@ class OmniboxInputs extends OmniboxElement {
onCopyOutput_(format) { onCopyOutput_(format) {
this.dispatchEvent(new CustomEvent('copy-request', {detail: format})); this.dispatchEvent(new CustomEvent('copy-request', {detail: format}));
} }
/** @private */
onFilterInputChange_() {
this.dispatchEvent(new CustomEvent('filter-input-changed', {
detail: {
filterText: this.$$('filter-text').value,
filterHide: this.$$('filter-hide').checked,
}
}));
}
} }
window.customElements.define(OmniboxInputs.is, OmniboxInputs); window.customElements.define(OmniboxInputs.is, OmniboxInputs);
...@@ -175,7 +175,7 @@ cr.define('omnibox_output', function() { ...@@ -175,7 +175,7 @@ cr.define('omnibox_output', function() {
* provides a single public interface to interact with the output: * provides a single public interface to interact with the output:
* 1. Render tables from responses (RenderDelegate) * 1. Render tables from responses (RenderDelegate)
* 2. Control visibility based on display options (TODO) * 2. Control visibility based on display options (TODO)
* 3. Control visibility and coloring based on search text (TODO) * 3. Control visibility and coloring based on search text (FilterDelegate)
* 4. Export and copy output (CopyDelegate) * 4. Export and copy output (CopyDelegate)
* 5. Preserve inputs and reset inputs to default (TODO) * 5. Preserve inputs and reset inputs to default (TODO)
* 6. Export and import inputs (TODO) * 6. Export and import inputs (TODO)
...@@ -195,26 +195,28 @@ cr.define('omnibox_output', function() { ...@@ -195,26 +195,28 @@ cr.define('omnibox_output', function() {
constructor() { constructor() {
super('omnibox-output-template'); super('omnibox-output-template');
/** @type {RenderDelegate} */ /** @type {!RenderDelegate} */
this.renderDelegate = new RenderDelegate(this.$$('contents')); this.renderDelegate = new RenderDelegate(this.$$('contents'));
/** @type {CopyDelegate} */ /** @type {!CopyDelegate} */
this.copyDelegate = new CopyDelegate(this); this.copyDelegate = new CopyDelegate(this);
/** @type {!FilterDelegate} */
this.filterDelegate = new FilterDelegate(this);
/** @type {!Array<!mojom.OmniboxResult>} */ /** @type {!Array<!mojom.OmniboxResult>} */
this.responses = []; this.responses = [];
/** @private {QueryInputs} */ /** @private {!QueryInputs} */
this.queryInputs_ = /** @type {QueryInputs} */ ({}); this.queryInputs_ = /** @type {!QueryInputs} */ ({});
/** @private {DisplayInputs} */ /** @private {!DisplayInputs} */
this.displayInputs_ = /** @type {DisplayInputs} */ ({}); this.displayInputs_ = /** @type {!DisplayInputs} */ ({});
} }
/** @param {QueryInputs} queryInputs */ /** @param {!QueryInputs} queryInputs */
updateQueryInputs(queryInputs) { updateQueryInputs(queryInputs) {
this.queryInputs_ = queryInputs; this.queryInputs_ = queryInputs;
this.refresh_(); this.refresh_();
} }
/** @param {DisplayInputs} displayInputs */ /** @param {!DisplayInputs} displayInputs */
updateDisplayInputs(displayInputs) { updateDisplayInputs(displayInputs) {
this.displayInputs_ = displayInputs; this.displayInputs_ = displayInputs;
this.refresh_(); this.refresh_();
...@@ -236,52 +238,55 @@ cr.define('omnibox_output', function() { ...@@ -236,52 +238,55 @@ cr.define('omnibox_output', function() {
this.renderDelegate.refresh( this.renderDelegate.refresh(
this.queryInputs_, this.responses, this.displayInputs_); this.queryInputs_, this.responses, this.displayInputs_);
} }
/** @return {!Array<!OutputMatch>} */
get matches() {
return this.renderDelegate.matches;
}
} }
// Responsible for rendering the output HTML. // Responsible for rendering the output HTML.
class RenderDelegate { class RenderDelegate {
/** @param {Element} containerElement */ /** @param {!Element} containerElement */
constructor(containerElement) { constructor(containerElement) {
this.containerElement = containerElement; /** @private {!Element} */
this.containerElement_ = containerElement;
} }
/** /**
* @param {QueryInputs} queryInputs * @param {!QueryInputs} queryInputs
* @param {!Array<!mojom.OmniboxResult>} responses * @param {!Array<!mojom.OmniboxResult>} responses
* @param {DisplayInputs} displayInputs * @param {!DisplayInputs} displayInputs
*/ */
refresh(queryInputs, responses, displayInputs) { refresh(queryInputs, responses, displayInputs) {
this.clearOutput_(); if (!responses.length)
if (responses.length) { return;
/** @private {!Array<OutputResultsGroup>} */
this.resultsGroup_;
if (displayInputs.showIncompleteResults) { if (displayInputs.showIncompleteResults) {
responses.forEach( this.resultsGroup_ = responses.map(
response => this.addOutputResultsGroup_( response =>
response, queryInputs, displayInputs)); new OutputResultsGroup(response, queryInputs.cursorPosition));
} else { } else {
this.addOutputResultsGroup_( const lastResponse = responses[responses.length - 1];
responses[responses.length - 1], queryInputs, displayInputs); this.resultsGroup_ =
} [new OutputResultsGroup(lastResponse, queryInputs.cursorPosition)];
}
} }
/** this.clearOutput_();
* @private this.resultsGroup_.forEach(
* @param {!mojom.OmniboxResult} response resultsGroup =>
* @param {QueryInputs} queryInputs this.containerElement_.appendChild(resultsGroup.render(
* @param {DisplayInputs} displayInputs
*/
addOutputResultsGroup_(response, queryInputs, displayInputs) {
this.containerElement.appendChild(
new OutputResultsGroup(response, queryInputs.cursorPosition)
.render(
displayInputs.showDetails, displayInputs.showDetails,
displayInputs.showIncompleteResults, displayInputs.showIncompleteResults,
displayInputs.showAllProviders)); displayInputs.showAllProviders)));
} }
/** @private */ /** @private */
clearOutput_() { clearOutput_() {
let contents = this.containerElement; const contents = this.containerElement_;
// Clears all children. // Clears all children.
while (contents.firstChild) while (contents.firstChild)
contents.removeChild(contents.firstChild); contents.removeChild(contents.firstChild);
...@@ -289,7 +294,12 @@ cr.define('omnibox_output', function() { ...@@ -289,7 +294,12 @@ cr.define('omnibox_output', function() {
/** @return {string} */ /** @return {string} */
get visibletableText() { get visibletableText() {
return this.containerElement.innerText; return this.containerElement_.innerText;
}
/** @return {!Array<!OutputMatch>} */
get matches() {
return this.resultsGroup_.flatMap(resultsGroup => resultsGroup.matches);
} }
} }
...@@ -316,10 +326,10 @@ cr.define('omnibox_output', function() { ...@@ -316,10 +326,10 @@ cr.define('omnibox_output', function() {
host: resultsGroup.host, host: resultsGroup.host,
isTypedHost: resultsGroup.isTypedHost isTypedHost: resultsGroup.isTypedHost
}; };
/** @type {OutputResultsTable} */ /** @type {!OutputResultsTable} */
this.combinedResults = this.combinedResults =
new OutputResultsTable(resultsGroup.combinedResults); new OutputResultsTable(resultsGroup.combinedResults);
/** @type {Array<OutputResultsTable>} */ /** @type {!Array<!OutputResultsTable>} */
this.individualResultsList = this.individualResultsList =
resultsGroup.resultsByProvider resultsGroup.resultsByProvider
.map(resultsWrapper => resultsWrapper.results) .map(resultsWrapper => resultsWrapper.results)
...@@ -332,7 +342,7 @@ cr.define('omnibox_output', function() { ...@@ -332,7 +342,7 @@ cr.define('omnibox_output', function() {
* @param {boolean} showDetails * @param {boolean} showDetails
* @param {boolean} showIncompleteResults * @param {boolean} showIncompleteResults
* @param {boolean} showAllProviders * @param {boolean} showAllProviders
* @return {Element} * @return {!Element}
*/ */
render(showDetails, showIncompleteResults, showAllProviders) { render(showDetails, showIncompleteResults, showAllProviders) {
const resultsGroupNode = const resultsGroupNode =
...@@ -352,7 +362,7 @@ cr.define('omnibox_output', function() { ...@@ -352,7 +362,7 @@ cr.define('omnibox_output', function() {
/** /**
* @private * @private
* @return {Element} * @return {!Element}
*/ */
renderDetails_() { renderDetails_() {
const details = const details =
...@@ -370,7 +380,7 @@ cr.define('omnibox_output', function() { ...@@ -370,7 +380,7 @@ cr.define('omnibox_output', function() {
/** /**
* @private * @private
* @param {boolean} showDetails * @param {boolean} showDetails
* @return {Element} * @return {!Element}
*/ */
renderIndividualResults_(showDetails) { renderIndividualResults_(showDetails) {
const individualResultsNode = OmniboxElement.getTemplate( const individualResultsNode = OmniboxElement.getTemplate(
...@@ -380,6 +390,13 @@ cr.define('omnibox_output', function() { ...@@ -380,6 +390,13 @@ cr.define('omnibox_output', function() {
individualResults.render(showDetails))); individualResults.render(showDetails)));
return individualResultsNode; return individualResultsNode;
} }
/** @return {!Array<!OutputMatch>} */
get matches() {
return [this.combinedResults]
.concat(this.individualResultsList)
.flatMap(results => results.matches);
}
} }
/** /**
...@@ -387,16 +404,16 @@ cr.define('omnibox_output', function() { ...@@ -387,16 +404,16 @@ cr.define('omnibox_output', function() {
* rendered by OutputMatch below. * rendered by OutputMatch below.
*/ */
class OutputResultsTable { class OutputResultsTable {
/** @param {Array<!mojom.AutocompleteMatch>} results */ /** @param {!Array<!mojom.AutocompleteMatch>} results */
constructor(results) { constructor(results) {
/** @type {Array<OutputMatch>} */ /** @type {!Array<!OutputMatch>} */
this.matches = results.map(match => new OutputMatch(match)); this.matches = results.map(match => new OutputMatch(match));
} }
/** /**
* Creates a HTML Node representing this data. * Creates a HTML Node representing this data.
* @param {boolean} showDetails * @param {boolean} showDetails
* @return {Element} * @return {!Element}
*/ */
render(showDetails) { render(showDetails) {
const resultsTable = OmniboxElement.getTemplate('results-table-template'); const resultsTable = OmniboxElement.getTemplate('results-table-template');
...@@ -437,12 +454,15 @@ cr.define('omnibox_output', function() { ...@@ -437,12 +454,15 @@ cr.define('omnibox_output', function() {
this.additionalProperties[propertyName] = propertyValue; this.additionalProperties[propertyName] = propertyValue;
} }
}); });
/** @type {!Element} */
this.associatedElement;
} }
/** /**
* Creates a HTML Node representing this data. * Creates a HTML Node representing this data.
* @param {boolean} showDetails * @param {boolean} showDetails
* @return {Element} * @return {!Element}
*/ */
render(showDetails) { render(showDetails) {
const row = document.createElement('tr'); const row = document.createElement('tr');
...@@ -464,7 +484,8 @@ cr.define('omnibox_output', function() { ...@@ -464,7 +484,8 @@ cr.define('omnibox_output', function() {
row.appendChild( row.appendChild(
OutputMatch.renderJsonProperty_(this.additionalProperties)); OutputMatch.renderJsonProperty_(this.additionalProperties));
} }
return row; this.associatedElement = row;
return this.associatedElement;
} }
/** /**
...@@ -472,7 +493,7 @@ cr.define('omnibox_output', function() { ...@@ -472,7 +493,7 @@ cr.define('omnibox_output', function() {
* rendering becomes more substantial * rendering becomes more substantial
* @private * @private
* @param {string} propertyValue * @param {string} propertyValue
* @return {Element} * @return {!Element}
*/ */
static renderTextProperty_(propertyValue) { static renderTextProperty_(propertyValue) {
const cell = document.createElement('td'); const cell = document.createElement('td');
...@@ -483,7 +504,7 @@ cr.define('omnibox_output', function() { ...@@ -483,7 +504,7 @@ cr.define('omnibox_output', function() {
/** /**
* @private * @private
* @param {Object} propertyValue * @param {Object} propertyValue
* @return {Element} * @return {!Element}
*/ */
static renderJsonProperty_(propertyValue) { static renderJsonProperty_(propertyValue) {
const cell = document.createElement('td'); const cell = document.createElement('td');
...@@ -496,7 +517,7 @@ cr.define('omnibox_output', function() { ...@@ -496,7 +517,7 @@ cr.define('omnibox_output', function() {
/** /**
* @private * @private
* @param {boolean} propertyValue * @param {boolean} propertyValue
* @return {Element} * @return {!Element}
*/ */
static renderBooleanProperty_(propertyValue) { static renderBooleanProperty_(propertyValue) {
const cell = document.createElement('td'); const cell = document.createElement('td');
...@@ -510,11 +531,11 @@ cr.define('omnibox_output', function() { ...@@ -510,11 +531,11 @@ cr.define('omnibox_output', function() {
/** /**
* @private * @private
* @param {string} propertyValue * @param {string} propertyValue
* @return {Element} * @return {!Element}
*/ */
static renderLinkProperty_(propertyValue) { static renderLinkProperty_(propertyValue) {
let cell = document.createElement('td'); const cell = document.createElement('td');
let link = document.createElement('a'); const link = document.createElement('a');
link.textContent = propertyValue; link.textContent = propertyValue;
link.href = propertyValue; link.href = propertyValue;
cell.appendChild(link); cell.appendChild(link);
...@@ -525,7 +546,7 @@ cr.define('omnibox_output', function() { ...@@ -525,7 +546,7 @@ cr.define('omnibox_output', function() {
* @private * @private
* @param {boolean} showDetails * @param {boolean} showDetails
* @param {boolean} showAdditionalHeader * @param {boolean} showAdditionalHeader
* @return {Element} * @return {!Element}
*/ */
static renderHeader_(showDetails, showAdditionalHeader) { static renderHeader_(showDetails, showAdditionalHeader) {
const row = document.createElement('tr'); const row = document.createElement('tr');
...@@ -548,7 +569,7 @@ cr.define('omnibox_output', function() { ...@@ -548,7 +569,7 @@ cr.define('omnibox_output', function() {
* @param {string} name * @param {string} name
* @param {string=} url * @param {string=} url
* @param {string=} tooltip * @param {string=} tooltip
* @return {Element} * @return {!Element}
*/ */
static renderHeaderCell_(name, url, tooltip) { static renderHeaderCell_(name, url, tooltip) {
const cell = document.createElement('th'); const cell = document.createElement('th');
...@@ -565,7 +586,7 @@ cr.define('omnibox_output', function() { ...@@ -565,7 +586,7 @@ cr.define('omnibox_output', function() {
} }
/** /**
* @return {Array<PresentationInfoRecord>} Array representing which columns * @return {!Array<!PresentationInfoRecord>} Array representing which columns
* need to be displayed. * need to be displayed.
*/ */
static displayedProperties(showDetails) { static displayedProperties(showDetails) {
...@@ -586,18 +607,18 @@ cr.define('omnibox_output', function() { ...@@ -586,18 +607,18 @@ cr.define('omnibox_output', function() {
/** Responsible for setting clipboard contents. */ /** Responsible for setting clipboard contents. */
class CopyDelegate { class CopyDelegate {
/** @param {omnibox_output.OmniboxOutput} omniboxOutput */ /** @param {!omnibox_output.OmniboxOutput} omniboxOutput */
constructor(omniboxOutput) { constructor(omniboxOutput) {
/** @type {omnibox_output.OmniboxOutput} */ /** @private {!omnibox_output.OmniboxOutput} */
this.omniboxOutput = omniboxOutput; this.omniboxOutput_ = omniboxOutput;
} }
copyTextOutput() { copyTextOutput() {
this.copy_(this.omniboxOutput.renderDelegate.visibletableText); this.copy_(this.omniboxOutput_.renderDelegate.visibletableText);
} }
copyJsonOutput() { copyJsonOutput() {
this.copy_(JSON.stringify(this.omniboxOutput.responses, null, 2)); this.copy_(JSON.stringify(this.omniboxOutput_.responses, null, 2));
} }
/** /**
...@@ -610,6 +631,74 @@ cr.define('omnibox_output', function() { ...@@ -610,6 +631,74 @@ cr.define('omnibox_output', function() {
} }
} }
/** Responsible for highlighting and hiding rows using filter text. */
class FilterDelegate {
/** @param {!omnibox_output.OmniboxOutput} omniboxOutput */
constructor(omniboxOutput) {
/** @private {!omnibox_output.OmniboxOutput} */
this.omniboxOutput_ = omniboxOutput;
}
/**
* @param {string} filterText
* @param {boolean} filterHide
*/
filter(filterText, filterHide) {
this.omniboxOutput_.matches.filter(match => match.associatedElement)
.forEach(match => {
const row = match.associatedElement;
row.classList.remove('filtered-hidden');
row.classList.remove('filtered-highlighted');
if (!filterText)
return;
const isMatch = FilterDelegate.filterMatch_(match, filterText);
row.classList.toggle('filtered-hidden', filterHide && !isMatch);
row.classList.toggle(
'filtered-highlighted', !filterHide && isMatch);
});
}
/**
* Checks if a omnibox match fuzzy-matches a filter string. Each character
* of filterText must be present in the match text, either adjacent to the
* previous matched character, or at the start of a new word (see
* textToWords_).
* E.g. `abc` matches `abc`, `a big cat`, `a-bigCat`, `a very big cat`, and
* `an amBer cat`; but does not match `abigcat` or `an amber cat`.
* `green rainbow` is matched by `gre rain`, but not by `gre bow`.
* One exception is the first character, which may be matched mid-word.
* E.g. `een rain` can also match `green rainbow`.
* @private
* @param {!OutputMatch} match
* @param {string} filterText
* @return {boolean}
*/
static filterMatch_(match, filterText) {
const row = match.associatedElement;
const cells = Array.from(row.querySelectorAll('td'));
const regexFilter = Array.from(filterText).join('(.*\\.)?');
return cells
.map(cell => FilterDelegate.textToWords_(cell.textContent).join('.'))
.some(text => text.match(regexFilter));
}
/**
* Splits a string into words, delimited by either capital letters, groups
* of digits, or non alpha characters.
* E.g., `https://google.com/the-dog-ate-134pies` will be split to:
* https, :, /, /, google, ., com, /, the, -, dog, -, ate, -, 134, pies
* We don't use `Array.split`, because we want to group digits, e.g. 134.
* @private
* @param {string} text
* @return {!Array<string>}
*/
static textToWords_(text) {
return text.match(/[a-z]+|[A-Z][a-z]*|\d+|./g) || [];
}
}
window.customElements.define(OmniboxOutput.is, OmniboxOutput); window.customElements.define(OmniboxOutput.is, OmniboxOutput);
return {OmniboxOutput: OmniboxOutput}; return {OmniboxOutput: OmniboxOutput};
......
...@@ -112,3 +112,15 @@ Clipboard.prototype.writeText = function(text) {}; ...@@ -112,3 +112,15 @@ Clipboard.prototype.writeText = function(text) {};
/** @const {!Clipboard} */ /** @const {!Clipboard} */
Navigator.prototype.clipboard; Navigator.prototype.clipboard;
/**
* TODO(manukh): remove this once it is added to Closure Compiler itself.
* @see https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap
* @param {?function(this:S, T, number, !Array<T>): R} callback
* @param {S=} opt_this
* @return {!Array<R>}
* @this {IArrayLike<T>|string}
* @template T,S,R
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
*/
Array.prototype.flatMap = function(callback, opt_this) {};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment