Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Wolfe api filter updates #2566

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/resources/css/module/_badge.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ $badges: (
font-size: 11px;
height: $unit * 3;
line-height: ($unit * 3) - 2;
margin: ($unit + 4) 0;
margin: ($unit + 4) $unit ($unit + 4) 0;
padding: 0 $unit;
text-align: center;
text-transform: uppercase;
Expand Down
2 changes: 1 addition & 1 deletion public/resources/css/module/_symbol.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ $api-symbols: (
),
type-alias: (
content: 'T',
background: $blue-grey-50
background: $light-green-600
)
);

Expand Down
279 changes: 203 additions & 76 deletions public/resources/js/directives/api-list.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
/*eslint no-unused-vars: "angularIO" */


/*
* API List & Filter Directive
*
* A page displaying all of the angular API methods available
* including a filter that can hide/show methods bases on filter
* settings.
*/

angularIO.directive('apiList', function () {
var API_FILTER_KEY = 'apiFilter';
var API_TYPE_KEY = 'apiType';
var QUERY_KEY = 'query';
var TYPE_KEY = 'type';
var STATUS_KEY = 'status';

return {
restrict: 'E',
template:
'<div ng-cloak="ng-cloak" class="banner is-plain api-filter clearfix">' +
' <div class="form-select-menu">' +
' <button ng-repeat="apiType in $ctrl.apiTypes" ng-if="$ctrl.apiType === apiType" class="form-select-button has-symbol" ng-click="$ctrl.toggleMenu()"><strong>Type:</strong><span class="symbol {{apiType.cssClass}}" ng-if="apiType.cssClass !== \'stable\'" ></span>{{apiType.title}}</button>'+
' <button class="form-select-button is-default" ng-if="$ctrl.apiType === null" ng-click="$ctrl.toggleMenu()"><strong>All Types</strong></button>'+
' <ul class="form-select-dropdown" ng-class="{ visible: $ctrl.showMenu === true }">' +
' <li ng-class="{ active: !$ctrl.apiType }" ng-click="$ctrl.clearType()">All Types</li>' +
' <li ng-repeat="apiType in $ctrl.apiTypes" ng-class="{ active: $ctrl.apiType === apiType }" ng-click="$ctrl.setType(apiType)"><span class="symbol {{apiType.cssClass}}"></span>{{apiType.title}}</li>' +
' <button ng-repeat="type in $ctrl.types" ng-if="$ctrl.type === type.matches[0]" class="form-select-button has-symbol" ng-click="$ctrl.toggleMenu(\'type\')"><strong>Type:</strong><span class="symbol {{type.cssClass}}" ng-if="type.cssClass !== \'stable\'" ></span>{{type.title}}</button>'+
' <button class="form-select-button is-default" ng-if="$ctrl.type === null" ng-click="$ctrl.toggleMenu(\'type\')"><strong>Type: All</strong></button>'+
' <ul class="form-select-dropdown" ng-class="{ visible: $ctrl.showTypeMenu === true }">' +
' <li ng-class="{ active: !$ctrl.type }" ng-click="$ctrl.clear(\'type\')">All</li>' +
' <li ng-repeat="type in $ctrl.types" ng-class="{ active: $ctrl.type === type }" ng-click="$ctrl.set(type, \'type\')"><span class="symbol {{type.cssClass}}"></span>{{type.title}}</li>' +
' </ul>' +
' <div class="overlay" ng-class="{ visible: $ctrl.showMenu === true }" ng-click="$ctrl.toggleMenu()"></div>' +
' <div class="overlay" ng-class="{ visible: $ctrl.showTypeMenu === true }" ng-click="$ctrl.toggleMenu(\'type\')"></div>' +
' </div>' +
' ' +
' <div class="form-select-menu">' +
' <button ng-repeat="status in $ctrl.statuses" ng-if="$ctrl.status === status.matches[0]" class="form-select-button" ng-click="$ctrl.toggleMenu(\'status\')"><strong>Status:</strong>{{status.title}}</button>'+
' <button class="form-select-button is-default" ng-if="$ctrl.status === null" ng-click="$ctrl.toggleMenu(\'status\')"><strong>Status: All</strong></button>'+
' <ul class="form-select-dropdown" ng-class="{ visible: $ctrl.showStatusMenu === true }">' +
' <li ng-class="{ active: !$ctrl.status }" ng-click="$ctrl.clear(\'status\')">All</li>' +
' <li ng-repeat="status in $ctrl.statuses" ng-class="{ active: $ctrl.status === status }" ng-click="$ctrl.set(status, \'status\')">{{status.title}}</li>' +
' </ul>' +
' <div class="overlay" ng-class="{ visible: $ctrl.showStatusMenu === true }" ng-click="$ctrl.toggleMenu(\'status\')"></div>' +
' </div>' +
' ' +
' <div class="form-search">' +
' <i class="material-icons">search</i>' +
' <input placeholder="Filter" ng-model="$ctrl.apiFilter" ng-model-options="{updateOn: \'default blur\', debounce: {\'default\': 350, \'blur\': 0}}">' +
' <input placeholder="Filter" ng-model="$ctrl.query" ng-model-options="{updateOn: \'default blur\', debounce: {\'default\': 350, \'blur\': 0}}">' +
' </div>' +
'</div>' +
' ' +
'<article class="l-content-small grid-fluid docs-content">' +
' <div ng-repeat="section in $ctrl.groupedSections" ng-if="$ctrl.isFiltered(section)" ng-cloak="ng-cloak">' +
' <div ng-repeat="section in $ctrl.groupedSections" ng-if="$ctrl.filterSections(section)" ng-cloak="ng-cloak">' +
' <h2>{{ section.title }}</h2>' +
' <ul class="api-list">' +
' <li ng-repeat="item in section.items" ng-show="item.show" class="api-item">' +
Expand All @@ -31,110 +56,212 @@ angularIO.directive('apiList', function () {
'</article>',
controllerAs: '$ctrl',
controller: function($scope, $attrs, $http, $location) {
// DEFAULT VALUES
var $ctrl = this;
$ctrl.showTypeMenu = false;
$ctrl.showStatusMenu = false;
$ctrl.status = null;
$ctrl.query = null;
$ctrl.type = null;
$ctrl.groupedSections = [];

$ctrl.showMenu = false;

var isForDart = $attrs.lang === 'dart';

$ctrl.apiTypes = [
{ cssClass: 'stable', title: 'Only Stable', matches: ['stable']},
// API TYPES
$ctrl.types = [
{ cssClass: 'directive', title: 'Directive', matches: ['directive'] },
{ cssClass: 'pipe', title: 'Pipe', matches: ['pipe'] },
{ cssClass: 'decorator', title: 'Decorator', matches: ['decorator'] },
{ cssClass: 'class', title: 'Class', matches: ['class'] },
{ cssClass: 'interface', title: 'Interface', matches: ['interface'] },
{ cssClass: 'function', title: 'Function', matches: ['function'] },
{ cssClass: 'enum', title: 'Enum', matches: ['enum'] },
{ cssClass: 'type-alias', title: 'Type Alias', matches: ['type-alias'] },
{ cssClass: 'const', title: 'Const', matches: ['var', 'let', 'const'] }
];

if (isForDart) $ctrl.apiTypes = $ctrl.apiTypes.filter(function (t) {
return !t.cssClass.match(/^(stable|directive|decorator|interface|enum)$/);
});
// STATUSES
$ctrl.statuses = [
{ cssClass: 'stable', title: 'Stable', matches: ['stable']},
{ cssClass: 'deprecated', title: 'Deprecated', matches: ['deprecated']},
{ cssClass: 'experimental', title: 'Experimental', matches: ['experimental']},
{ cssClass: 'security', title: 'Security Risk', matches: ['security']}
];

$ctrl.apiFilter = getApiFilterFromLocation();
$ctrl.apiType = getApiTypeFromLocation();
$ctrl.groupedSections = [];

$ctrl.setType = function (type) {
if (type === $ctrl.apiType) $ctrl.apiType = null;
else $ctrl.apiType = type;
$ctrl.showMenu = !$ctrl.showMenu;
};
// SET FILTER VALUES
getFilterValues();

$ctrl.clearType = function () {
$ctrl.apiType = null;
$ctrl.showMenu = !$ctrl.showMenu;
};

$ctrl.toggleMenu = function () {
$ctrl.showMenu = !$ctrl.showMenu;
};
// GRAB DATA FOR SECTIONS
$http.get($attrs.src).then(function(response) {
$ctrl.sections =  response.data;

$ctrl.isFiltered = function(section) {
var apiFilter = ($ctrl.apiFilter || '').toLowerCase();
var matchesModule = $ctrl.apiFilter === '' || $ctrl.apiFilter === null || section.title.toLowerCase().indexOf($ctrl.apiFilter.toLowerCase()) !== -1;
var isVisible = false;
$ctrl.groupedSections = Object.keys($ctrl.sections).map(function(title) {
return { title: title, items: $ctrl.sections[title] };
});
});

section.items.forEach(function(item) {

// Filter by stability (ericjim: only 'stable' for now)
if ($ctrl.apiType && $ctrl.apiType.matches.length === 1 &&
$ctrl.apiType.matches[0] === 'stable' && item.stability === 'stable') {
item.show = true;
isVisible = true;
return isVisible;
} // NOTE: other checks can be performed for stability (experimental, deprecated, etc)
// SET SELECTED VALUE FROM MENUS/FORM
$ctrl.set = function(item, kind) {
var value = (item && item.matches) ? item.matches[0] : null;

// Filter by docType
var matchesDocType = !$ctrl.apiType || $ctrl.apiType.matches.indexOf(item.docType) !== -1;
var matchesTitle = !apiFilter || item.title.toLowerCase().indexOf(apiFilter) !== -1;
item.show = matchesDocType && (matchesTitle || matchesModule);
switch(kind) {
case 'type': $ctrl.type = value ; break;
case 'query': $ctrl.query = value ; break;
case 'status': $ctrl.status = value ; break;
}

if (item.show) {
isVisible = true;
}
});
$ctrl.toggleMenu(kind);
}

return isVisible;

// CLEAR SELECTED VALUE FROM MENUS/FORM
$ctrl.clear = function (kind) {
switch(kind) {
case 'type': $ctrl.type = null ; break;
case 'query': $ctrl.query = null ; break;
case 'status': $ctrl.status = null ; break;
}

$ctrl.toggleMenu(kind);
};

$http.get($attrs.src).then(function(response) {
$ctrl.sections = response.data;
$ctrl.groupedSections = Object.keys($ctrl.sections).map(function(title) {
return { title: title, items: $ctrl.sections[title] };

// TOGGLE MENU
$ctrl.toggleMenu = function(kind) {
switch(kind) {
case 'type': $ctrl.showTypeMenu = !$ctrl.showTypeMenu; ; break;
case 'status': $ctrl.showStatusMenu = !$ctrl.showStatusMenu; ; break;
}
}


// UPDATE VALUES IF DART API
var isForDart = $attrs.lang === 'dart';
if (isForDart) {
$ctrl.apiTypes = $ctrl.apiTypes.filter(function (t) {
return !t.cssClass.match(/^(stable|directive|decorator|interface|enum)$/);
});
});
}


// SET URL WITH VALUES
$scope.$watchGroup(
[function() { return $ctrl.apiFilter; }, function() { return $ctrl.apiType; }, function() { return $ctrl.sections; }],
[
function() { return $ctrl.query; },
function() { return $ctrl.type; },
function() { return $ctrl.status; },
function() { return $ctrl.sections; }
],

function() {
var apiFilter = ($ctrl.apiFilter || '').toLowerCase();
var queryURL = $ctrl.query ? $ctrl.query.toLowerCase() : null;
var typeURL = $ctrl.type || null;
var statusURL = $ctrl.status || null;

$location.search(API_FILTER_KEY, apiFilter || null);
$location.search(API_TYPE_KEY, $ctrl.apiType && $ctrl.apiType.title || null);
// SET URLS
$location.search(QUERY_KEY, queryURL);
$location.search(STATUS_KEY, statusURL);
$location.search(TYPE_KEY, typeURL);
}
);

function getApiFilterFromLocation() {
return $location.search()[API_FILTER_KEY] || null;

// GET VALUES FROM URL
function getFilterValues() {
var urlParams = $location.search();

$ctrl.status = urlParams[STATUS_KEY] || null;
$ctrl.query = urlParams[QUERY_KEY] || null;;
$ctrl.type = urlParams[TYPE_KEY] || null;;
}

function getApiTypeFromLocation() {
var apiFilter = $location.search()[API_TYPE_KEY];
if (!apiFilter) {
return null;
} else if (!$ctrl.apiFilter || $ctrl.apiFilter.title != apiFilter) {
for (var i = 0, ii = $ctrl.apiTypes.length; i < ii; i++) {
if ($ctrl.apiTypes[i].title == apiFilter) {
return $ctrl.apiTypes[i];
}

// CHECK IF IT'S A CONSTANT TYPE
function isConst(item) {
var isConst = false;

switch(item.docType) {
case 'let': isConst = true; break;
case 'var': isConst = true; break;
case 'const': isConst = true; break;
default: isConst = false;
}

return isConst;
}

// FILTER SECTION & ITEMS LOOP
$ctrl.filterSections = function(section) {
var showSection = false;

section.items.forEach(function(item) {
item.show = false;

// CHECK IF TYPE IS NULL & STATUS, QUERY
if (($ctrl.type === null) && statusSelected(item) && queryEntered(section, item)) {
item.show = true;
}

// CHECK IF TYPE IS SELECTED & STATUS, QUERY
if (($ctrl.type === item.docType) && statusSelected(item) && queryEntered(section, item)) {
item.show = true;
}

// CHECK IF TYPE IS CONST & STATUS, QUERY
if (($ctrl.type === 'const') && isConst(item) && statusSelected(item) && queryEntered(section, item)) {
item.show = true;
}

// SHOW SECTION IF ONE ITEM IS VISIBLE
if(!showSection && item.show) {
showSection = true;
}
});

return showSection;
}


// CHECK FOR QUERY
function queryEntered(section, item) {
var isVisible = false;

// CHECK IF QUERY MATCH SECTION OR ITEM
var query = ($ctrl.query || '').toLowerCase();
var matchesSection = $ctrl.query === '' || $ctrl.query === null || section.title.toLowerCase().indexOf($ctrl.query.toLowerCase()) !== -1;
var matchesTitle = !query || item.title.toLowerCase().indexOf(query) !== -1;

// FILTER BY QUERY
if(matchesTitle || matchesSection) {
isVisible = true;
}
// If we get here then the apiType query didn't match any apiTypes
$location.search(API_TYPE_KEY, null);

return isVisible;
}


// CHECK IF AN API ITEM IS VISIBLE BY STATUS
function statusSelected(item) {
var status = item.stability;
var insecure = item.secure === 'false' ? false : true;
var isVisible = false;

if($ctrl.status === null) {
isVisible = true;
}

if(status === $ctrl.status) {
isVisible = true;
}

if(($ctrl.status === 'security') && insecure) {
isVisible = true;
}

return isVisible;
};
}
};
});