Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,72 @@
/* globals DataGridProvider, GaiaGrid, Search,
Promise, SyncDataStore, InMemoryStore */
(function(exports) {
'use strict';
var STORE_NAME = 'bookmarks_store';
function formatBookmark(bookmark) {
return {
data: new GaiaGrid.Bookmark({
id: bookmark.url,
name: bookmark.name || bookmark.url,
url: bookmark.url,
icon: bookmark.icon
})
};
}
function Bookmarks() {}
Bookmarks.prototype = {
__proto__: DataGridProvider.prototype,
name: 'Bookmarks',
init: function() {
DataGridProvider.prototype.init.apply(this, arguments);
this.persistStore = new InMemoryStore();
this.syncStore = new SyncDataStore(STORE_NAME, this.persistStore, 'url');
return this.syncStore.sync();
},
get: function(url) {
var results = this.persistStore.results;
for (var idx in results) {
var item = results[idx];
if (item.url === url) {
return formatBookmark(item);
}
}
return null;
},
search: function(filter) {
var self = this;
return new Promise(function(resolve, reject) {
var renderResults = [];
filter = filter.toLowerCase();
for(var elem in self.persistStore.results) {
var bookmark = self.persistStore.results[elem];
var name = ('name' in bookmark) && bookmark.name.toLowerCase();
if ((!bookmark.url || bookmark.url.indexOf(filter) == -1) &&
(!name || name.indexOf(filter) == -1)) {
continue;
}
renderResults.push(formatBookmark(bookmark));
}
resolve(renderResults);
});
}
};
exports.Bookmarks = new Bookmarks();
Search.provider(exports.Bookmarks);
}(window));

View file

@ -0,0 +1,77 @@
(function() {
'use strict';
/* global MozActivity, Promise, Provider, Search */
function Contacts() {
}
Contacts.prototype = {
__proto__: Provider.prototype,
name: 'Contacts',
click: function(e) {
var target = e.target;
Search.close();
/* jshint nonew: false */
new MozActivity({
name: 'open',
data: {
type: 'webcontacts/contact',
params: {
'id': target.dataset.contactId
}
}
});
},
search: function(input) {
return new Promise((resolve, reject) => {
var options = {
filterValue: input,
filterBy: ['givenName'],
filterOp: 'startsWith'
};
this.clear();
var request = navigator.mozContacts.find(options);
request.onsuccess = (function() {
var results = request.result;
if (!results.length) {
return reject();
}
var renderResults = [];
for (var i = 0; i < results.length; i++) {
var result = results[i];
for (var j = 0; j < result.name.length; j++) {
var renderObj = {
title: result.name[j],
dataset: {
contactId: result.id
}
};
if (result.photo) {
renderObj.icon = result.photo[0];
}
renderResults.push(renderObj);
}
}
resolve(renderResults);
}).bind(this);
request.onerror = function() {
};
});
}
};
Search.provider(new Contacts());
}());

View file

@ -0,0 +1,33 @@
/* global Provider */
'use strict';
function DataGridProvider() {
}
DataGridProvider.prototype = {
__proto__: Provider.prototype,
name: 'DataGridProvider',
isGridProvider: true,
init: function() {
this.grid = document.getElementById('icons');
},
clear: function() {
this.grid.clear();
},
render: function(results) {
results.forEach(function(config, index) {
this.grid.add(config.data);
}, this);
this.grid.render({
skipDivider: true
});
}
};

View file

@ -0,0 +1,170 @@
/* global Search, DataGridProvider, GaiaGrid, Promise */
(function() {
'use strict';
function LocalApps() {
this.apps = {};
this.appListing = [];
this.blacklist;
var mozApps = navigator.mozApps.mgmt;
mozApps.oninstall = e => {
var app = e.application;
var processApp = () => {
this.apps[app.manifestURL] = app;
this.createAppListing();
};
if (app.downloading) {
app.ondownloadapplied = processApp;
} else {
processApp();
}
};
mozApps.onuninstall = e => {
delete this.apps[e.application.manifestURL];
this.createAppListing();
};
mozApps.getAll().onsuccess = e => {
e.target.result.forEach(app => {
this.apps[app.manifestURL] = app;
});
this.createBlacklist().then(() => {
this.createAppListing();
});
};
}
LocalApps.prototype = {
__proto__: DataGridProvider.prototype,
name: 'LocalApps',
dedupes: true,
dedupeStrategy: 'exact',
createBlacklist: function() {
return new Promise((resolve, reject) => {
var self = this;
var key = 'app.launch_path.blacklist';
var req = navigator.mozSettings.createLock().get(key);
req.onsuccess = function onsuccess() {
self.blacklist = req.result[key] || [];
resolve();
};
});
},
createAppListing: function() {
var appListing = [];
var blacklist = this.blacklist;
var manifestURLs = Object.keys(this.apps);
manifestURLs.forEach(function eachManifest(manifestURL) {
var app = this.apps[manifestURL];
var manifest = app.manifest || app.updateManifest;
var HIDDEN_ROLES = [
'system', 'input', 'homescreen', 'search', 'theme', 'addon',
'langpack'
];
if (HIDDEN_ROLES.indexOf(manifest.role) !== -1) {
return;
}
var entryPoints = manifest.entry_points;
if (entryPoints) {
for (var i in entryPoints) {
var entry = entryPoints[i];
entry.entryPoint = i;
entry.manifestURL = app.manifestURL;
appListing.push(entry);
}
} else {
manifest.manifestURL = app.manifestURL;
appListing.push(manifest);
}
if (blacklist) {
blacklist.forEach(function(blackentry) {
for (var i in appListing) {
var app = appListing[i];
if (app.launch_path === blackentry) {
appListing.splice(i, 1);
}
}
});
}
}, this);
this.appListing = appListing;
},
removeAppListing: function(app) {
for (var i in this.appListing) {
var manifest = this.appListing[i];
if (manifest.manifestURL === app.manifestURL) {
this.appListing.splice(i, 1);
}
}
},
search: function(input) {
return new Promise((resolve, reject) => {
var results = this.find(input);
var formatted = [];
results.forEach(function eachResult(result) {
formatted.push({
dedupeId: result.app.manifestURL,
data: new GaiaGrid.Mozapp(result.app, result.entryPoint)
});
}, this);
resolve(formatted);
});
},
/**
* Checks if an app manifest matches a query.
*/
matches: function(manifest, query) {
query = query.toLowerCase();
// Get the localized name from the query.
var shortName = manifest.short_name || '';
var userLang = document.documentElement.lang;
var locales = manifest.locales;
var localized = locales && locales[userLang] && locales[userLang].name;
localized = localized || '';
return shortName.toLowerCase().indexOf(query) != -1 ||
manifest.name.toLowerCase().indexOf(query) != -1 ||
localized.toLowerCase().indexOf(query) != -1;
},
find: function(query) {
var results = [];
this.appListing.forEach(function(manifest) {
if (this.matches(manifest, query)) {
results.push({
app: this.apps[manifest.manifestURL],
entryPoint: manifest.entryPoint
});
}
}, this);
return results;
}
};
Search.provider(new LocalApps());
}());

View file

@ -0,0 +1,85 @@
/* global Search, DataGridProvider, GaiaGrid, Promise */
(function() {
'use strict';
const NUM_DISPLAY = 4;
var apiUrl = null;
function Marketplace() {}
Marketplace.prototype = {
__proto__: DataGridProvider.prototype,
name: 'Marketplace',
dedupes: true,
dedupeStrategy: 'exact',
remote: true,
init: function() {
DataGridProvider.prototype.init.apply(this, arguments);
var urlKey = 'search.marketplace.url';
var req = navigator.mozSettings.createLock().get(urlKey);
req.onsuccess = function () {
if (req.result[urlKey]) {
apiUrl = req.result[urlKey]
.replace('{limit}', NUM_DISPLAY)
.replace('{lang}', document.documentElement.lang);
}
};
},
search: function(input) {
return new Promise((resolve, reject) => {
this.abort();
if (!input || !apiUrl) {
return resolve([]);
}
var req = new XMLHttpRequest();
req.open('GET', apiUrl.replace('{q}', encodeURIComponent(input)), true);
req.onload = (function onload() {
var results = JSON.parse(req.responseText);
if (!results.length) {
resolve([]); return;
}
var length = Math.min(NUM_DISPLAY, results.length);
var formatted = [];
for (var i = 0; i < length; i++) {
var app = results[i];
formatted.push({
dedupeId: app.manifest_url,
data: new GaiaGrid.MarketPlaceApp({
id: app.manifest_url,
name: navigator.mozL10n.get('install-marketplace-title', {
title: app.name
}),
icons: app.icons,
slug: app.slug
})
});
}
resolve(formatted);
}).bind(this);
req.onerror = function onerror() {
console.log('Marketplace error.');
reject();
};
req.ontimeout = function ontimeout() {
console.log('Marketplace timeout.');
reject();
};
req.send();
this.request = req;
});
}
};
Search.provider(new Marketplace());
}());

View file

@ -0,0 +1,400 @@
/* globals Provider, SyncDataStore, Promise, IconsHelper */
/* globals Search, PlacesIdbStore */
/* globals DateHelper */
/* globals asyncStorage */
/* globals LazyLoader */
(function(exports) {
'use strict';
var _ = navigator.mozL10n.get;
// Maximum number of results to show show for a single query
var MAX_AWESOME_RESULTS = 4;
var MAX_HISTORY_RESULTS = 20;
var MAX_TOPSITES_RESULTS = 6;
// Name of the datastore we pick up places from
var STORE_NAME = 'places';
var topSitesWrapper = document.getElementById('top-sites');
var historyWrapper = document.getElementById('history');
// These elements are only included in the newtab page.
if (topSitesWrapper && historyWrapper) {
topSitesWrapper.addEventListener('click', itemClicked);
historyWrapper.addEventListener('click', itemClicked);
}
var cachedLink = document.createElement('a');
function parseUrl(url) {
cachedLink.href = url;
return cachedLink;
}
// Storage for locally fetched icons
var icons = {};
var iconUrls = {};
function getIcon(place) {
if (place.url in icons && icons[place.url]) {
return icons[place.url];
}
IconsHelper.getIcon(place.url, null, place).then(icon => {
saveIcon(place.url, icon);
});
return false;
}
function saveIcon(key, url) {
if (key in icons) {
return;
}
icons[key] = null;
iconUrls[key] = url;
fetchIcon(url, function(err, icon) {
if (!err) {
icons[key] = icon;
}
});
}
function fetchIcon(uri, callback) {
var xhr = new XMLHttpRequest({mozSystem: true});
xhr.open('GET', uri, true);
xhr.responseType = 'blob';
xhr.addEventListener('load', function() {
// Check icon was successfully downloded
// 0 is due to https://bugzilla.mozilla.org/show_bug.cgi?id=716491
if (!(xhr.status === 200 || xhr.status === 0)) {
return callback(new Error('error_downloading'));
}
var blob = xhr.response;
if (blob.size > this.MAX_ICON_SIZE) {
return callback(new Error('image_to_large'));
}
// Only save the icon if it can be loaded as an image bigger than 0px
var img = document.createElement('img');
img.src = window.URL.createObjectURL(blob);
img.onload = function() {
window.URL.revokeObjectURL(img.src);
if (img.naturalWidth <= 0) {
return callback(new Error('Cannot load image'));
}
callback(null, blob);
};
img.onerror = function() {
window.URL.revokeObjectURL(img.src);
return callback(new Error('Cannot load image'));
};
});
xhr.onerror = function(err) {
return callback(new Error('Cannot load uri'));
};
xhr.send();
}
function itemClicked(e) {
if (e.target.dataset.url) {
Search.navigate(e.target.dataset.url);
}
}
var listTemplate = createList();
function createList() {
var list = document.createElement('ul');
list.setAttribute('role', 'listbox');
return list;
}
function incrementHistoryThreshold(timestamp, currentThreshold, thresholds) {
var newThreshold = currentThreshold += 1;
if (timestamp < thresholds[newThreshold]) {
return incrementHistoryThreshold(timestamp, newThreshold, thresholds);
}
return newThreshold;
}
function drawHistoryHeading(parent, threshold, timestamp) {
var LABELS = [
'future',
'today',
'yesterday',
'last-7-days',
'this-month',
'last-6-months',
'older-than-6-months'
];
var text = '';
// Special case for month headings
if (threshold == 5 && timestamp) {
var date = new Date(timestamp);
var now = new Date();
text = _('month-' + date.getMonth());
if (date.getFullYear() != now.getFullYear()) {
text += ' ' + date.getFullYear();
}
} else {
text = _(LABELS[threshold]);
}
var h3 = document.createElement('h3');
var spanNode = document.createElement('span');
spanNode.textContent = text;
var ul = listTemplate.cloneNode(true);
h3.appendChild(spanNode);
parent.appendChild(h3);
parent.appendChild(ul);
}
function updateIcon(visit, iconWrapper) {
var iconDom = iconWrapper.querySelector('img');
IconsHelper.getIcon(visit.url, null, visit).then((icon) => {
if (icon && iconDom) {
iconDom.onload = function () {
iconWrapper.classList.remove('empty');
iconDom.style.display = 'block';
};
iconDom.src = icon;
}
});
}
function buildHistory(visits) {
var thresholds = [
Date.now(), // 0. Now
DateHelper.todayStarted(), // 1. Today
DateHelper.yesterdayStarted(), // 2. Yesterday
DateHelper.thisWeekStarted(), // 3. This week
DateHelper.thisMonthStarted(), // 4. This month
DateHelper.lastSixMonthsStarted(), // 5. Six months
0 // 6. Epoch!
];
var threshold = 0;
var month = null;
var year = null;
var fragment = document.createDocumentFragment();
visits.forEach(function(visit) {
// Draw new heading if new threshold reached
if (visit.date > 0 && visit.date < thresholds[threshold]) {
threshold = incrementHistoryThreshold(visit.date,
threshold, thresholds);
// Special case for month headings
if (threshold != 5) {
drawHistoryHeading(fragment, threshold);
}
}
if (threshold === 5) {
var timestampDate = new Date(visit.date);
if (timestampDate.getMonth() != month ||
timestampDate.getFullYear() != year) {
month = timestampDate.getMonth();
year = timestampDate.getFullYear();
drawHistoryHeading(fragment, threshold, visit.date);
}
}
visit.meta = visit.url;
visit.dataset = { url: visit.url };
var dom = exports.Places.buildResultsDom([visit]);
var iconDom = dom.querySelector('.icon');
fragment.appendChild(dom);
updateIcon(visit, iconDom);
});
return fragment;
}
function showStartPage() {
if (!topSitesWrapper || !historyWrapper) {
return;
}
var store = exports.Places.persistStore;
var urls = [];
store.readVisits(MAX_HISTORY_RESULTS, function(results) {
var docFragment = buildHistory(results);
historyWrapper.innerHTML = '';
historyWrapper.appendChild(docFragment);
}, function filter(visit) {
var isStored = visit.url in urls;
urls[visit.url] = true;
return !isStored;
});
store.read('frecency', MAX_TOPSITES_RESULTS, function(results) {
var docFragment = document.createDocumentFragment();
results.forEach(function(x) {
docFragment.appendChild(formatTopResult(x));
});
topSitesWrapper.innerHTML = '';
topSitesWrapper.appendChild(docFragment);
});
}
function formatTopResult(result) {
var div = document.createElement('div');
var span = document.createElement('span');
if (result.title) {
span.setAttribute('dir', 'auto');
span.textContent = result.title;
} else {
span.setAttribute('dir', 'ltr');
span.textContent = result.url;
}
div.dataset.url = result.url;
div.classList.add('top-site');
div.appendChild(span);
div.setAttribute('role', 'link');
if (result.screenshot || result.tile) {
var img = result.screenshot || result.tile;
var imgUrl = (typeof img === 'string') ? img : URL.createObjectURL(img);
div.style.backgroundImage = 'url(' + imgUrl + ')';
}
return div;
}
function matchesFilter(value, filter) {
return !filter || (value && value.match(new RegExp(filter, 'i')) !== null);
}
function formatPlace(placeObj, filter) {
var result = {
'title': placeObj.title,
'meta': placeObj.url,
'icon': getIcon(placeObj),
'dataset': {
'url': placeObj.url
},
'label': placeObj.title
};
return result;
}
function Places() {}
Places.prototype = {
__proto__: Provider.prototype,
name: 'Places',
click: itemClicked,
init: function() {
// If not on new tab page, set places div as container
if (!topSitesWrapper || !historyWrapper) {
this.header = document.getElementById(this.name.toLowerCase() +
'-header');
this.container = document.getElementById(this.name.toLowerCase());
this.container.addEventListener('click', this.click.bind(this));
}
this.persistStore = new PlacesIdbStore();
this.persistStore.init().then(() => {
this.syncStore =
new SyncDataStore(STORE_NAME, this.persistStore, 'url');
this.syncStore.filter = function(place) {
return place.url.startsWith('app://') ||
place.url === 'about:blank';
};
this.syncStore.onChange = function() {
showStartPage();
};
// Make init return a promise, so we know when
// we did the sync. Used right now for testing
// porpoises.
var rev = this.persistStore.latestRevision || 0;
return this.syncStore.sync(rev).then(() => {
return new Promise(resolve => {
function done() {
showStartPage();
resolve();
}
asyncStorage.getItem('have-preloaded-sites', (havePreloaded) => {
if (!havePreloaded) {
this.preloadTopSites().then(() => {
asyncStorage.setItem('have-preloaded-sites', true);
done();
});
} else {
done();
}
});
});
});
});
},
saveSites: function(sites) {
return Promise.all(sites.map(site => {
site.frecency = -2;
return this.persistStore.addPlace(site);
}));
},
preloadTopSites: function() {
// load default build top sites
return LazyLoader.getJSON('/js/inittopsites.json').then(sites => {
return this.saveSites(sites);
});
},
search: function(filter) {
return new Promise((resolve, reject) => {
var matchedOrigins = {};
this.persistStore.read('frecency', MAX_AWESOME_RESULTS, (results) => {
resolve(results.map(function(result) {
return formatPlace(result, filter);
}));
}, function filterFun(result) {
if (result.frecency <= 0) {
return false;
}
var url = parseUrl(result.url);
var matches = !(url.hostname in matchedOrigins) &&
(matchesFilter(result.title, filter) ||
matchesFilter(result.url, filter));
if (matches) {
matchedOrigins[url.hostname] = true;
}
return matches;
});
});
}
};
exports.Places = new Places();
Search.provider(exports.Places);
}(window));

View file

@ -0,0 +1,168 @@
/* globals IconsHelper */
'use strict';
/**
* Base Provider class
*/
function Provider() {
}
Provider.prototype = {
/**
* Name of the provider
* Overridden at the child provider level
*/
name: 'Provider',
/**
* Whether or not this provider dedupes results.
*/
dedupes: false,
/**
* Initializes the provider container and adds listeners
*/
init: function(searchObj) {
this.searchObj = searchObj;
this.container = document.getElementById(this.name.toLowerCase());
this.container.addEventListener('click', this.click.bind(this));
},
/**
* Clears the rendered results of this provider from the app grid
*/
clear: function() {
this.container.innerHTML = '';
},
/**
* Handler when a result is clicked
*/
click: function() {},
/**
* Aborts any in-progress request.
*/
abort: function() {
if (this.request && this.request.abort) {
this.request.abort();
}
},
/**
* Update an icon once it has been loaded.
*
* @param {Object} config A configuration object.
* @param {Object} iconWrapper An iconWrapper DOM object.
*/
updateIcon: function(config, iconWrapper) {
var iconDom = iconWrapper.querySelector('img');
IconsHelper.getIcon(config.dataset.url, null, config).then((icon) => {
if (icon && iconDom) {
iconDom.onload = function () {
iconWrapper.classList.remove('empty');
iconDom.style.display = 'block';
};
iconDom.src = icon;
}
});
},
/**
* Renders a set of results.
* Each result may contain the following attributes:
* - title: The title of the app.
* - meta: Secondary content to show for the result.
* - icon: The icon of the result.
* - dataset: Data attributes to apply to the result.
* - label: Aria-label for the result.
* - description: Additional description for the result.
*/
buildResultsDom: function(results) {
//<div class="result" data-url="mozilla.com" role="link"
// aria-label="My Urlasljd alskdja lsdjka sldjk"
// aria-describedby="description-0">
// <div class="icon">
// <img role="presentation" src="..." />
// </div>
// <div class="urlwrapper">
// <span class="title">My Urlasljd <span class="highlight">alskd</span>
// ja lsdjka sldjk</span>
// <small id="description-0" class="url"
// aria-label="http://url.com">http://url.com</small>
// </div>
//</div>
var frag = document.createDocumentFragment();
results.forEach(function(config, index) {
var result = document.createElement('div');
var iconWrapper = document.createElement('div');
var icon = document.createElement('img');
var description = document.createElement('div');
var title = document.createElement('span');
var meta = document.createElement('small');
result.classList.add('result');
iconWrapper.classList.add('icon');
description.classList.add('description');
title.classList.add('title');
meta.classList.add('meta');
for (var i in config.dataset) {
result.dataset[i] = config.dataset[i];
}
if (config.title) {
title.setAttribute('dir', 'auto');
title.textContent = config.title;
} else {
title.setAttribute('dir', 'ltr');
title.textContent = config.url;
}
if (config.meta) {
meta.textContent = config.meta;
// Expose meta infrormation as a helpful description for each result.
if (config.description) {
meta.id = this.name + '-description-' + index;
meta.setAttribute('aria-label', config.description);
result.setAttribute('aria-describedby', meta.id);
}
}
icon.setAttribute('role', 'presentation');
if (config.icon) {
icon.src = window.URL.createObjectURL(config.icon);
} else {
iconWrapper.classList.add('empty');
}
result.setAttribute('role', 'link');
// Either use an explicit label or, if not present, title.
result.setAttribute('aria-label', config.label || config.title);
description.appendChild(title);
description.appendChild(meta);
iconWrapper.appendChild(icon);
result.appendChild(iconWrapper);
result.appendChild(description);
frag.appendChild(result);
if (!config.icon) {
this.updateIcon(config, iconWrapper);
}
}, this);
return frag;
},
render: function(results) {
var dom = this.buildResultsDom(results);
this.container.appendChild(dom);
if (this.header) {
results.length ? this.header.classList.remove('hidden') :
this.header.classList.add('hidden');
}
}
};

View file

@ -0,0 +1,140 @@
(function() {
'use strict';
/* globals Promise, Provider, Search, MetricsHelper */
/* globals LazyLoader */
/* globals SearchProvider */
var suggestionsWrapper = document.getElementById('suggestions-wrapper');
var suggestionsProvider = document.getElementById('suggestions-provider');
var suggestionsSelect = document.getElementById('suggestions-select');
function providerUpdated() {
navigator.mozL10n.setAttributes(suggestionsProvider, 'search-header', {
provider: SearchProvider('title').toUpperCase()
});
var providers = SearchProvider.providers();
var selectFragment = document.createDocumentFragment();
var optionNode = document.createElement('option');
Object.keys(providers).forEach(provider => {
var option = optionNode.cloneNode();
option.value = provider;
option.text = providers[provider].title;
if (provider === SearchProvider.selected()) {
option.selected = true;
}
selectFragment.appendChild(option);
});
suggestionsSelect.innerHTML = '';
suggestionsSelect.appendChild(selectFragment);
}
function encodeTerms(str, search) {
return str.replace('{searchTerms}', encodeURIComponent(search));
}
function Suggestions() {}
Suggestions.prototype = {
__proto__: Provider.prototype,
name: 'Suggestions',
currentSearch: null,
init: function() {
this.metrics = new MetricsHelper();
this.metrics.init();
Provider.prototype.init.apply(this, arguments);
suggestionsSelect.addEventListener('change', function(e) {
SearchProvider.setProvider(e.target.value);
});
SearchProvider.ready().then(() => {
providerUpdated();
SearchProvider.providerUpdated(() => {
providerUpdated();
this.redoSearch();
});
});
},
redoSearch: function() {
if (!this.currentSearch) {
return;
}
this.search(this.currentSearch).then(results => {
this.render(results);
});
},
click: function(e) {
this.metrics.report('websearch', SearchProvider('title'));
var suggestion = e.target.dataset.suggestion;
var url = encodeTerms(SearchProvider('searchUrl'), suggestion);
Search.navigate(url);
},
search: function(input, preventRemote) {
this.currentSearch = preventRemote ? null : input;
this.render([input]);
if (!navigator.onLine || preventRemote) {
return Promise.resolve([input]);
}
suggestionsWrapper.dataset.loading = true;
return SearchProvider.ready().then(() => {
return new Promise((resolve, reject) => {
var url = encodeTerms(SearchProvider('suggestUrl'), input);
LazyLoader.getJSON(url, true).then(result => {
var results = result[1];
// We add an item to search the entered term as well
results.unshift(input);
resolve(results);
}).catch(function() {
suggestionsWrapper.dataset.loading = false;
reject();
});
});
});
},
render: function(items) {
var ul = document.createElement('ul');
ul.setAttribute('role', 'listbox');
items.forEach(function each(item) {
var li = document.createElement('li');
li.dataset.suggestion = li.textContent = item;
li.setAttribute('role', 'option');
ul.appendChild(li);
});
suggestionsWrapper.dataset.loading = false;
this.container.innerHTML = '';
if (ul.childNodes.length) {
this.container.appendChild(ul);
}
},
clear: function() {
Provider.prototype.clear.apply(this, arguments);
this.currentSearch = null;
}
};
Search.provider(new Suggestions());
}());