123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- /**
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
- */
- var Preact = window.Preact = require('preact'),
- h = require('preact').h,
- createClass = require('preact-compat').createClass,
- SearchBar = require('./searchbar.jsx'),
- PluginList = require('./pluginlist.jsx'),
- PlatformButton = require('./platformbutton.jsx')
- App = {},
- SortDropdown = require('./sortdropdown.jsx'),
- SortCriteria = require('./SortCriteria'),
- ZeroClipboard = require("../js/lib/ZeroClipboard.js");
- var INPUT_DELAY = 500; // in milliseconds
- var timer = null;
- var Constants = {
- DownloadCountBatch: 100,
- NpmSearchInitialSize: 500
- }
- var UrlParameters = {
- SortBy: 'sortBy',
- Query: 'q',
- Platfroms: 'platforms',
- }
- var App = createClass({
- getInitialState: function() {
- var staticFilters = [];
- staticFilters['platforms'] = [];
- staticFilters['authors'] = [];
- staticFilters['licenses'] = [];
- var platforms = App.getURLParameter(UrlParameters.Platfroms);
- if(platforms) {
- staticFilters['platforms'] = staticFilters['platforms'].concat(platforms.split(','));
- }
- var q = App.getURLParameter(UrlParameters.Query);
- var sortBy = App.getURLParameter(UrlParameters.SortBy);
- if (!sortBy) {
- sortBy = SortCriteria.Quality;
- }
- var state = {
- plugins: [],
- placeHolderText: 'Loading...',
- searchResults: [],
- staticFilters: staticFilters,
- sortCriteria: sortBy,
- flashEnabled: true,
- downloadsReceived: false
- }
- if (q) {
- state.filterText = q;
- } else {
- state.filterText = '';
- }
- return state;
- },
- handleUserInput: function(filterText) {
- var self = this;
- /* We receive events for all inputs, so make sure text changed */
- if(this.state.filterText !== filterText) {
- /* Routing logic */
- var platformFilters = this.state.staticFilters["platforms"];
- delay(function(){
- App.updateURL(filterText, platformFilters, self.state.sortCriteria);
- }, INPUT_DELAY);
- this.setState({
- filterText: filterText,
- searchResults: App.filterPlugins(self.state.plugins, filterText, self.state.staticFilters)
- });
- }
- },
- toggleCondition: function(keyword, condition) {
- var state = this.state;
- this.setState(function(previousState, currentProps) {
- var conditionIndex = previousState.staticFilters[keyword].indexOf(condition);
- if(conditionIndex > -1) {
- previousState.staticFilters[keyword].splice(conditionIndex, 1);
- }
- else {
- previousState.staticFilters[keyword].push(condition);
- }
- App.updateURL(previousState.filterText, previousState.staticFilters['platforms'], previousState.sortCriteria);
- return {
- staticFilters: previousState.staticFilters,
- plugins: previousState.plugins,
- searchResults: App.filterPlugins(previousState.plugins, state.filterText, state.staticFilters)
- };
- });
- },
- setSort: function(sort) {
- var state = this.state;
- this.setState(function(previousState, currentProps) {
- App.sortPlugins(previousState.plugins, sort)
- delay(function(){
- App.updateURL(previousState.filterText, previousState.staticFilters['platforms'], previousState.sortCriteria);
- }, INPUT_DELAY);
- return {
- plugins: previousState.plugins,
- searchResults: App.filterPlugins(previousState.plugins, state.filterText, state.staticFilters),
- sortCriteria: sort
- }
- });
- },
- statics: {
- appendURLParameter : function(qs, urlParameter) {
- if(!qs) {
- qs = '?' + urlParameter + '=';
- } else {
- qs = qs + '&' + urlParameter + '=';
- }
- return qs;
- },
- getURLParameter : function(name) {
- try {
- return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)
- ||[,""])[1].replace(/\+/g, '%20'))||null;
- } catch(error) {
- // Improperly encoded URLs are ignored
- if (error instanceof URIError) {
- window.history.replaceState({}, "", "./");
- return null;
- }
- // Throw other errors back out
- throw error;
- }
- },
- shallowCopy: function(src) {
- var dst = {};
- for(var i in src) {
- if(src.hasOwnProperty(i)) {
- dst[i] = src[i];
- }
- }
- return dst;
- },
- tagOfficialPlugins: function() {
- },
- filterPlugins: function(plugins, filter, staticFilters) {
- var contains = function(values, pluginInfo) {
- var allValuesPresent = true;
- if(values.length == 0) {
- return allValuesPresent;
- }
- if(!pluginInfo) {
- return false;
- }
- values.forEach(function(value) {
- var valuePresent = false;
- for(var index=0; index < pluginInfo.length; index++) {
- if(pluginInfo[index] && pluginInfo[index].toLowerCase().indexOf(value.toLowerCase()) > -1) {
- valuePresent = true;
- }
- }
- if(!valuePresent) {
- allValuesPresent = false;
- }
- });
- return allValuesPresent;
- }
- var populateFilters = function(filterText) {
- var searchStrings = filterText.split(" ");
- var filters = [];
- filters['platforms'] = [];
- filters['authors'] = [];
- filters['licenses'] = [];
- filters['searchWords'] = [];
- searchStrings.forEach(function(searchString) {
- var keywords = searchString.split(":");
- if(keywords.length == 1) {
- var param = keywords[0].trim();
- if(param) {
- filters['searchWords'].push(param);
- }
- }
- else if(keywords[1].trim()) {
- var param = keywords[1].trim();
- switch(keywords[0]) {
- case 'platform':
- filters['platforms'].push(param);
- break;
- case 'author':
- filters['authors'].push(param);
- break;
- case 'license':
- filters['licenses'].push(param);
- break;
- default:
- filters['searchWords'].push(searchString);
- }
- }
- else {
- filters['searchWords'].push(searchString);
- }
- });
- return filters;
- }
- var results = [];
- var filters = populateFilters(filter);
- var combine = function(filter1, filter2) {
- var result = [].concat(filter1)
- for(var i = 0; i < filter2.length; i++) {
- if(result.indexOf(filter2[i]) === -1) {
- result.push(filter2[i])
- }
- }
- return result;
- }
- for (var i = 0; i < plugins.length; i++) {
- var plugin = plugins[i];
- var fullPluginText = plugin.name.concat(plugin.author, plugin.keywords, plugin.license, plugin.description);
- if(contains(combine(filters['platforms'], staticFilters['platforms']), plugin.keywords)
- && contains(combine(filters['authors'], staticFilters['authors']), plugin.author)
- && contains(combine(filters['licenses'], staticFilters['licenses']), plugin.license)
- && contains(filters['searchWords'], fullPluginText)) {
- results.push(plugin);
- }
- };
- return results;
- },
- sortPlugins: function(plugins, criteria) {
- // Search results should be deterministic, so we need a secondary
- // sort function for cases where the plugins are equal
- var compareName = function(p1, p2) {
- if(p1.name === p2.name) {
- return 0;
- } else if(p1.name > p2.name) {
- return 1;
- } else {
- return -1;
- }
- }
- switch(criteria) {
- case SortCriteria.Downloads:
- plugins.sort(function(p1, p2) {
- if(!p1.downloadCount) {
- return 1;
- } else if(!p2.downloadCount) {
- return -1;
- }
- if(p2.downloadCount === p1.downloadCount) {
- return compareName(p1, p2);
- };
- return p2.downloadCount - p1.downloadCount;
- });
- break;
- case SortCriteria.RecentlyUpdated:
- plugins.sort(function(p1, p2) {
- if(p2.modified === p1.modified) {
- return compareName(p1, p2);
- };
- return p1.modified - p2.modified;
- });
- break;
- case SortCriteria.Quality:
- default:
- plugins.sort(function(p1, p2) {
- if(p2.rating === p1.rating) {
- return compareName(p1, p2);
- };
- return p2.rating - p1.rating;
- });
- break;
- }
- return plugins;
- },
- updateURL: function(filterText, platformFilters, sortCriteria) {
- var query = '';
- var stateObj = {};
- if(filterText) {
- var filterTextLowerCase = filterText;
- query = App.appendURLParameter(query, UrlParameters.Query);
- query += encodeURIComponent(filterTextLowerCase);
- stateObj.filterText = filterTextLowerCase;
- }
- if(platformFilters.length > 0) {
- query = App.appendURLParameter(query, UrlParameters.Platfroms);
- query += encodeURIComponent(platformFilters.join());
- stateObj.platforms = platformFilters;
- }
- if(sortCriteria !== SortCriteria.Quality) {
- query = App.appendURLParameter(query, UrlParameters.SortBy);
- query += encodeURIComponent(sortCriteria);
- stateObj.sortBy = sortCriteria;
- }
- window.history.replaceState(stateObj, "", query);
- ga('send', 'pageview', '/index.html' + query);
- },
- },
- componentWillMount: function() {
- var that = this;
- ZeroClipboard.config({swfPath: "{{ site.baseurl }}/static/js/lib/ZeroClipboard.swf"});
- ZeroClipboard.on({
- "ready": function(event) {that.setState({ flashEnabled: true });},
- "error": function(event) {that.setState({ flashEnabled: false });}
- });
- },
- componentDidMount: function() {
- var plugins = [],
- officialPlugins = require('./official-plugins.json').plugins,
- blacklistedPlugins = require('./blacklisted-plugins.json').plugins,
- pluginCount = 0,
- self = this,
- queryProtocol = window.location.protocol === "https:" ? "https:" : "http:",
- queryHost = queryProtocol + "//npmsearch.com/query",
- queryFields = "fields=name,keywords,license,description,author,modified,homepage,version,rating",
- queryKeywords = "q=keywords:%22ecosystem:cordova%22",
- queryInitialSize = Constants.NpmSearchInitialSize,
- baseUrl = queryHost + "?" + queryFields + "&" + queryKeywords + "&sort=rating:desc";
- xhrRequest(baseUrl + "&size=" + queryInitialSize + "&start=0", function(xhrResult) {
- plugins = xhrResult.results;
- pluginCount = xhrResult.total;
- if (pluginCount <= queryInitialSize) {
- processPlugins.bind(self, officialPlugins, plugins)();
- } else {
- xhrRequest(baseUrl + "&size=" + (pluginCount - queryInitialSize) + "&start=" + queryInitialSize, function(xhrResult) {
- plugins = [].concat(plugins, xhrResult.results);
- processPlugins.bind(self, officialPlugins, plugins)();
- }, function() { console.log('xhr err'); });
- }
- }, function() { console.log('xhr err'); });
- var getDownloadCount = function(plugins) {
- var packageNames = "";
- var downloadCountRequests = [];
- for(var index = 0; index < plugins.length; index++) {
- if (/^@.*\//.test(plugins[index].name)) {
- continue;
- }
- packageNames += plugins[index].name + ",";
- if(index % Constants.DownloadCountBatch === 0 || index === plugins.length - 1) {
- downloadCountRequests.push($.getJSON("https://api.npmjs.org/downloads/point/last-month/" + packageNames));
- packageNames = "";
- }
- }
- // When all the download count requests return - we can populate the plugins and sort them
- $.when.apply($, downloadCountRequests).done(function() {
- for(var i = 0; i < arguments.length; i ++) {
- var xhrResult = arguments[i][0];
- for(var j = 0; j < plugins.length; j++) {
- if(xhrResult[plugins[j].name]) {
- plugins[j] = App.shallowCopy(plugins[j]);
- plugins[j].downloadCount = xhrResult[plugins[j].name].downloads;
- }
- }
- }
- if(self.state.sortCriteria === SortCriteria.Downloads) {
- App.sortPlugins(plugins, self.state.sortCriteria);
- }
- self.setState({
- plugins: plugins,
- searchResults: App.filterPlugins(plugins, self.state.filterText, self.state.staticFilters),
- downloadsReceived: true
- });
- })
- .fail( function() { console.log('xhr err'); });
- }
- function processPlugins(officialPlugins, plugins) {
- var pluginCount = plugins.length,
- dateNow = new Date(),
- oneDay = 1000*60*60*24;
- officialPlugins.forEach(function(plugin) {
- for (var i = 0; i < plugins.length; i++) {
- // Check if plugin name is in official list
- if (plugins[i].name[0] === plugin) {
- plugins[i].isOfficial = true;
- return;
- }
- };
- });
- for(var i = plugins.length -1; i >= 0 ; i--)
- {
- for(var j = 0; j < blacklistedPlugins.length; j++)
- {
- if(plugins[i].name[0] === blacklistedPlugins[j])
- {
- plugins.splice(i, 1);
- break;
- }
- }
- }
- for (var i = 0; i < plugins.length; i++) {
- // Calculate last time plugin is modified (in days)
- plugins[i].modified = Math.ceil((dateNow - new Date(plugins[i].modified)) / oneDay);
- };
- // Initial sort cannot be on downloads as download counts have not been populated.
- if (this.state.sortCriteria !== SortCriteria.Downloads) {
- plugins = App.sortPlugins(plugins, this.state.sortCriteria);
- } else {
- plugins = App.sortPlugins(plugins, SortCriteria.Quality);
- }
- if (this.isMounted()) {
- var q = App.getURLParameter(UrlParameters.Query);
- if(q) {
- this.setState({
- plugins: plugins,
- filterText: q,
- placeHolderText: 'Search ' + plugins.length + ' plugins...',
- searchResults: App.filterPlugins(plugins, q, this.state.staticFilters)
- });
- }
- else {
- this.setState({
- plugins: plugins,
- placeHolderText: 'Search ' + plugins.length + ' plugins...',
- searchResults: App.filterPlugins(plugins, '', this.state.staticFilters)
- });
- }
- getDownloadCount(plugins);
- }
- }
- },
- render: function() {
- var toggleCondition = this.toggleCondition;
- var createPlatformButton = function(platform, keyword, state) {
- var active = state.staticFilters["platforms"].indexOf(keyword) > -1;
- return (
- <PlatformButton platform={platform} keyword={keyword} initiallyActive={active} toggleCondition={toggleCondition}/>
- );
- }
- var listContent = <PluginList plugins={this.state.searchResults} flashEnabled={this.state.flashEnabled}/>;
- return (
- <div>
- <div className="container">
- <div className="row">
- <div className="col-sm-12 text-center">
- <h1>Cordova Plugins</h1>
- </div>
- </div>
- <div className="row">
- <div className="col-sm-12">
- <div className="plugins_search_container">
- <SearchBar
- initialValue={this.state.filterText}
- placeHolderText={this.state.placeHolderText}
- onUserInput={this.handleUserInput}
- />
- <div className="whatisplugin_box">
- <img src="{{ site.baseurl }}/static/img/pluggy.png"/>
- <h2>What is a Cordova plugin?</h2>
- <p>A plugin is a bit of add-on code that provides JavaScript interface to native components. They allow your app to use native device capabilities beyond what is available to pure web apps.</p>
- </div>
- <div className="clearfix"></div>
- </div>
- <div className="plugins_links">
- <ul className="nav nav-justified">
- <li><a href="{{ site.baseurl }}/docs/en//{{ site.default_linked_docs_version }}/guide/hybrid/plugins/index.html#publishing-plugins"><span className="glyphicon glyphicon-plus"></span><i> </i>Contribute Plugins</a></li>
- <li><a href="{{ site.baseurl }}/plugins/faq.html"><span className="glyphicon glyphicon-question-sign"></span><i> </i>Plugin Help</a></li>
- </ul>
- </div>
- </div>
- </div>
- <div className="row filter-by-platforms">
- <div className="col-sm-9">
- <div className="filter-by-platform-label"><span>Must Support Platform(s):</span></div>
- <div className="filter-by-platform-filters">
- {createPlatformButton('Android', 'cordova-android', this.state)}
- {createPlatformButton('iOS', 'cordova-ios', this.state)}
- {createPlatformButton('Windows', 'cordova-windows', this.state)}
- {createPlatformButton('macOS', 'cordova-osx', this.state)}
- {createPlatformButton('Browser', 'cordova-browser', this.state)}
- </div>
- </div>
- <div className="col-sm-3">
- <div className="plugin-results-number">{this.state.searchResults.length} result(s) found</div>
- <SortDropdown setSort={this.setSort} selected={this.state.sortCriteria} downloadsEnabled={this.state.downloadsReceived}/>
- </div>
- </div>
- </div>
- {listContent}
- <div className="row plugin-search-credit">
- Search results powered by <a target="_blank" href="https://npmsearch.com/">npmsearch.com</a>
- </div>
- </div>
- );
- }
- });
- App.start = function() {
- Preact.render(<App />, document.getElementById('pluginsAppContainer'));
- };
- function delay(callback, ms){
- clearTimeout (timer);
- timer = setTimeout(callback, ms);
- }
- function xhrRequest(url, success, fail) {
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function() {
- if (xhr.readyState == XMLHttpRequest.DONE ) {
- if(xhr.status == 200){
- success(JSON.parse(xhr.responseText));
- return;
- } else {
- fail();
- return;
- }
- }
- }.bind(this)
- xhr.open("GET", url, true);
- xhr.send();
- }
- module.exports = window.App = App;
- // run the app
- App.start();
|