feat: filter icons by stroke and fill

This commit is contained in:
Vjacheslav Trushkin 2022-11-01 10:51:04 +02:00
parent d542004aab
commit 836b75ce79
13 changed files with 394 additions and 39 deletions

View File

@ -51,6 +51,10 @@ export const appConfig: AppConfig = {
// Requires `enableIconLists` to be enabled
// Disable this option if you do not need search functionality
enableSearchEngine: true,
// Enables filtering icons by style: 'fill' or 'stroke'
// Works only if search engine is enabled
allowFilterIconsByStyle: true,
};
/**

View File

@ -1,7 +1,13 @@
import type { IconifyAliases, IconifyJSON, IconifyOptional } from '@iconify/types';
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
import { appConfig } from '../../../config/app';
import type { IconSetIconNames, IconSetIconsListIcons, IconSetIconsListTag } from '../../../types/icon-set/extra';
import type {
IconSetIconNames,
IconSetIconsListIcons,
IconSetIconsListTag,
IconStyle,
} from '../../../types/icon-set/extra';
import { getIconStyle } from './style';
const customisableProps = Object.keys(defaultIconProps) as (keyof IconifyOptional)[];
@ -43,6 +49,12 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
}
// Parse all icons
let detectedIconStyle: IconStyle | undefined | null;
const iconsWithStroke: Set<IconSetIconNames> = new Set();
const iconsWithFill: Set<IconSetIconNames> = new Set();
const checkIconStyle =
appConfig.allowFilterIconsByStyle && appConfig.enableSearchEngine && appConfig.enableIconLists;
for (const name in iconSetIcons) {
const isVisible = !iconSetIcons[name].hidden;
const icon: IconSetIconNames = [name];
@ -65,6 +77,22 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
uncategorised.push(icon);
}
}
// Check content
if (checkIconStyle) {
const body = iconSetIcons[name].body;
const iconStyle = getIconStyle(body);
if (iconStyle) {
(iconStyle === 'stroke' ? iconsWithStroke : iconsWithFill).add(icon);
}
if (detectedIconStyle === void 0) {
// First item
detectedIconStyle = iconStyle;
} else if (detectedIconStyle && detectedIconStyle !== iconStyle) {
// Different style
detectedIconStyle = null;
}
}
}
}
@ -145,6 +173,14 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
}
}
}
// Add style
if (iconsWithFill.has(parentIcon)) {
iconsWithFill.add(icon);
}
if (iconsWithStroke.has(parentIcon)) {
iconsWithStroke.add(icon);
}
}
} else {
// Treat as alias: add to parent icon
@ -216,6 +252,22 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
});
}
}
// Icon style
if (checkIconStyle) {
if (detectedIconStyle) {
result.iconStyle = detectedIconStyle;
} else if (iconsWithFill.size || iconsWithStroke.size) {
// Mixed styles: assign to icon object
result.iconStyle = 'mixed';
iconsWithFill.forEach((item) => {
item._is = 'fill';
});
iconsWithStroke.forEach((item) => {
item._is = 'stroke';
});
}
}
}
return result;

View File

@ -0,0 +1,39 @@
import type { IconStyle } from '../../../types/icon-set/extra';
function getValues(body: string, prop: string): string[] {
const chunks = body.split(prop + '="');
chunks.shift();
return chunks.map((item) => {
const index = item.indexOf('"');
return index > 0 ? item.slice(0, index) : '';
});
}
function hasValues(body: string, prop: string): boolean {
const fills = getValues(body, prop);
for (let i = 0; i < fills.length; i++) {
switch (fills[i].toLowerCase()) {
case '':
case 'none':
case 'transparent':
case 'inherit':
break;
default:
return true;
}
}
return false;
}
/**
* Check if icon uses fill or stroke
*
* Returns null on failure
*/
export function getIconStyle(body: string): IconStyle | null {
const hasStroke = hasValues(body, 'stroke');
const hasFill = hasValues(body, 'fill');
return hasStroke ? (hasFill ? null : 'stroke') : hasFill ? 'fill' : null;
}

View File

@ -1,3 +1,4 @@
import { appConfig } from '../../config/app';
import type { IconSetIconNames } from '../../types/icon-set/extra';
import type { IconSetEntry } from '../../types/importers';
import type { SearchIndexData, SearchKeywordsEntry, SearchParams, SearchResultsData } from '../../types/search';
@ -19,6 +20,13 @@ export function search(
return;
}
// Merge params
const fullParams = {
...params,
// Params extracted from query override default params
...keywords.params,
};
// Make sure all keywords exist
keywords.searches = keywords.searches.filter((search) => {
for (let i = 0; i < search.keywords.length; i++) {
@ -54,15 +62,10 @@ export function search(
}
// Get prefixes
const basePrefixes = filterSearchPrefixes(data, iconSets, {
...params,
// Params extracted from query override default params
...keywords.params,
});
const basePrefixes = filterSearchPrefixes(data, iconSets, fullParams);
// Prepare variables
const addedIcons = Object.create(null) as Record<string, Set<IconSetIconNames>>;
const foundPrefixes: Set<string> = new Set();
const results: string[] = [];
const limit = params.limit;
@ -147,6 +150,20 @@ export function search(
continue;
}
// Check style
if (
// Style is set
fullParams.style &&
// Enabled in config
appConfig.allowFilterIconsByStyle &&
// Icon set has mixed style (so it is assigned to icons) -> check icon
iconSetIcons.iconStyle === 'mixed' &&
item._is !== fullParams.style
) {
// Different icon style
continue;
}
// Find icon name that matches all keywords
const name = item.find((name) => {
for (let i = 0; i < testMatches.length; i++) {

View File

@ -1,3 +1,4 @@
import { appConfig } from '../../config/app';
import type { IconSetEntry } from '../../types/importers';
import type { SearchIndexData, SearchParams } from '../../types/search';
@ -36,11 +37,6 @@ export function filterSearchPrefixes(
): Readonly<string[]> {
let prefixes: string[] | undefined;
// Filter by prefix
if (params.prefixes) {
prefixes = filterSearchPrefixesList(prefixes || data.sortedPrefixes, params.prefixes);
}
// Filter by palette
const palette = params.palette;
if (typeof palette === 'boolean') {
@ -50,6 +46,22 @@ export function filterSearchPrefixes(
});
}
// Filter by style
if (appConfig.allowFilterIconsByStyle) {
const style = params.style;
if (style) {
prefixes = (prefixes || data.sortedPrefixes).filter((prefix) => {
const iconSetStyle = iconSets[prefix].item.icons.iconStyle;
return iconSetStyle === style || iconSetStyle === 'mixed';
});
}
}
// Filter by prefix
if (params.prefixes) {
prefixes = filterSearchPrefixesList(prefixes || data.sortedPrefixes, params.prefixes);
}
// TODO: add more filter options
return prefixes || data.sortedPrefixes;

View File

@ -1,5 +1,6 @@
import { matchIconName } from '@iconify/utils/lib/icon/name';
import { paramToBoolean } from '../../misc/bool';
import type { IconStyle } from '../../types/icon-set/extra';
import type { SearchKeywords, SearchKeywordsEntry } from '../../types/search';
import { minPartialKeywordLength } from './partial';
@ -224,6 +225,7 @@ function addPartialPrefix(prefix: string, set: Set<string>): boolean {
export function splitKeyword(keyword: string, allowPartial = true): SearchKeywords | undefined {
const commonPrefixes: Set<string> = new Set();
let palette: boolean | undefined;
let iconStyle: IconStyle | undefined;
// Split by space, check for prefixes and reserved keywords
const keywordChunks = keyword.toLowerCase().trim().split(/\s+/);
@ -243,9 +245,32 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
if (prefixChunks.length === 2) {
const keyword = prefixChunks[0];
const value = prefixChunks[1];
let isKeyword = false;
switch (keyword) {
case 'palette': {
palette = paramToBoolean(value);
if (typeof palette === 'boolean') {
isKeyword = true;
}
break;
}
// style:fill, style:stroke
case 'style': {
if (value === 'fill' || value === 'stroke') {
iconStyle = value;
isKeyword = true;
}
break;
}
// fill:true, stroke:true
case 'fill':
case 'stroke': {
if (paramToBoolean(value)) {
iconStyle = keyword;
isKeyword = true;
}
break;
}
@ -270,38 +295,40 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
// All prefixes are bad: invalidate search query
return;
}
isKeyword = true;
break;
}
}
default: {
// Icon with prefix
if (hasPrefixes) {
// Already had entry with prefix: invalidate query
return;
}
if (!isKeyword) {
// Icon with prefix
if (hasPrefixes) {
// Already had entry with prefix: invalidate query
return;
}
const values = keyword.split(',');
let invalid = true;
hasPrefixes = true;
for (let j = 0; j < values.length; j++) {
const prefix = values[j].trim();
if (matchIconName.test(prefix)) {
commonPrefixes.add(prefix);
invalid = false;
}
const values = keyword.split(',');
let invalid = true;
hasPrefixes = true;
for (let j = 0; j < values.length; j++) {
const prefix = values[j].trim();
if (matchIconName.test(prefix)) {
commonPrefixes.add(prefix);
invalid = false;
}
}
if (invalid) {
// All prefixes are bad: invalidate search query
return;
}
if (invalid) {
// All prefixes are bad: invalidate search query
return;
}
if (value.length) {
// Add icon name, unless it is empty: 'mdi:'
// Allow partial if enabled
checkPartial = allowPartial;
keywords.push(value);
}
if (value.length) {
// Add icon name, unless it is empty: 'mdi:'
// Allow partial if enabled
checkPartial = allowPartial;
keywords.push(value);
}
}
continue;
@ -316,8 +343,9 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
}
if (paramChunks.length === 2) {
const keyword = paramChunks[0];
const value = paramChunks[1] as string;
switch (paramChunks[0]) {
switch (keyword) {
// 'palette=true', 'palette=false' -> filter icon sets by palette
case 'palette':
palette = paramToBoolean(value);
@ -326,6 +354,28 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
}
break;
// style=fill, style=stroke
case 'style': {
if (value === 'fill' || value === 'stroke') {
iconStyle = value;
} else {
return;
}
break;
}
// fill=true, stroke=true
// accepts only true as value
case 'fill':
case 'stroke': {
if (paramToBoolean(value)) {
iconStyle = keyword;
} else {
return;
}
break;
}
// 'prefix=material-symbols', 'prefix=material-'
// 'prefixes=material-symbols,material-'
case 'prefix':
@ -390,6 +440,9 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
if (typeof palette === 'boolean') {
params.palette = palette;
}
if (iconStyle) {
params.style = iconStyle;
}
return {
searches,
params,

View File

@ -41,4 +41,8 @@ export interface AppConfig {
// Requires `enableIconLists` to be enabled
// Disable this option if you do not need search functionality
enableSearchEngine: boolean;
// Enables filtering icons by style: 'fill' or 'stroke'
// Works only if search engine is enabled
allowFilterIconsByStyle: boolean;
}

View File

@ -1,9 +1,21 @@
import type { IconifyInfo } from '@iconify/types';
/**
* Icon style
*/
export type IconStyle = 'fill' | 'stroke';
/**
* Extra props added to icons
*/
export interface ExtraIconSetIconNamesProps {
_is?: IconStyle;
}
/**
* Icon. First entry is main name, other entries are aliases
*/
export type IconSetIconNames = [string, ...string[]];
export type IconSetIconNames = [string, ...string[]] & ExtraIconSetIconNamesProps;
/**
* Tag
@ -41,6 +53,9 @@ export interface IconSetIconsListIcons {
// Keywords, set if search engine is enabled
keywords?: Record<string, Set<IconSetIconNames>>;
// Extra info
iconStyle?: IconStyle | 'mixed';
}
/**

View File

@ -1,3 +1,5 @@
import type { IconStyle } from './icon-set/extra';
/**
* List of keywords that can be used to autocomplete keyword
*/
@ -39,6 +41,9 @@ export interface SearchParams {
// Filter icon sets by palette
palette?: boolean;
// Filter icons by style
style?: IconStyle;
// Keyword
keyword: string;

View File

@ -0,0 +1,76 @@
import { getIconStyle } from '../../lib/data/icon-set/lists/style';
describe('Checking icon style', () => {
test('Fill', () => {
expect(
getIconStyle(
'<path fill="currentColor" d="M244.377 427.349c0 6.85-.044 13.703-.044 20.597c60.027.09 120.053 0 180.077.046c0-6.853-.043-13.703-.043-20.554c-12.067-2.432-26.964-5.835-31.605-18.962c-46.101-114.703-92.869-229.187-138.352-344.11c-33.24-.575-66.479-.354-99.719-.132c6.188 13.88 11.36 28.2 17.77 41.99c-38.942 101.178-78.502 202.178-118.02 303.136c-4.464 12.862-19.096 15.737-30.808 18.167c0 6.763-.043 13.57-.043 20.378c38.278.177 76.556.044 114.835.088c-.044-6.939-.044-13.835-.087-20.73c-12.023-1.856-25.814-1.635-35.583-9.946c-7.69-7.425-4.287-19.007-1.105-27.715c7.647-19.757 14.897-39.693 23.25-59.186c46.37.266 92.69-.043 139.059.132c6.366 15.824 12.73 31.692 19.096 47.56c4.244 10.696 8.09 22.676 4.64 34.168c-8.93 14.81-28.289 13.927-43.318 15.074zM138.735 293.64c17.77-47.208 36.599-94.061 54.014-141.401c18.787 47.163 37.704 94.282 56.534 141.401a11615.148 11615.148 0 0 1-110.548 0z"/>'
)
).toBe('fill');
expect(
getIconStyle(
'<g fill="currentColor" fill-rule="evenodd" stroke-width=".133"><path d="M64 256v192h384V64H64Zm375.742 0v183.742H72.258V72.258h367.484Z"/><path d="M326.606 277.232c-2.89 9.083-4.748 11.56-10.735 14.451c-5.368 2.684-7.226 4.749-7.226 8.671c0 4.336 1.032 5.161 5.987 5.161h5.987l.826 33.446c.62 27.664 1.239 34.89 4.336 40.464c5.987 10.942 21.47 15.277 40.464 11.561c5.78-1.238 6.4-2.064 6.4-9.083v-7.846l-11.561.413c-16.723.413-17.342-1.032-17.342-38.4v-30.555h28.903V289h-28.903v-22.71h-6.813c-6.4 0-7.02.619-10.323 10.942zm-149.058 12.18c-3.303 1.239-8.67 4.542-11.767 7.432c-5.987 5.575-5.575 5.781-8.878-4.748c-.62-2.064-3.716-3.097-9.29-3.097h-8.258v101.162h22.71v-32.826c0-18.168.825-35.303 1.858-37.987c3.716-9.29 10.322-13.833 21.058-13.833c8.051 0 10.529 1.033 14.038 5.368c3.923 5.161 4.336 8.258 4.336 42.323v36.955h22.709v-34.065c0-44.8 2.684-50.58 23.536-50.58c7.432 0 9.91 1.032 13.42 5.367c3.922 5.161 4.335 8.258 4.335 42.323v36.955h23.122l-.825-39.846c-.826-43.974-2.89-52.438-14.246-58.425c-15.07-7.64-36.335-5.162-47.07 5.367l-5.988 6.194l-3.922-5.161c-6.813-9.497-26.839-13.833-40.878-8.878zM326.606 130.65c-2.89 9.085-4.748 11.562-10.735 14.453c-5.368 2.683-7.226 4.748-7.226 8.67c0 4.336 1.032 5.162 5.987 5.162h5.987l.826 33.445c.62 27.664 1.239 34.89 4.336 40.464c5.987 10.942 21.47 15.278 40.464 11.562c5.78-1.239 6.4-2.065 6.4-9.084v-7.845l-11.561.413c-16.723.413-17.342-1.033-17.342-38.4v-30.555h28.903v-16.516h-28.903v-22.71h-6.813c-6.4 0-7.02.62-10.323 10.942zm-149.058 12.182c-3.303 1.238-8.67 4.541-11.767 7.432c-5.987 5.574-5.575 5.78-8.878-4.749c-.62-2.064-3.716-3.096-9.29-3.096h-8.258v101.16h22.71v-32.825c0-18.168.825-35.303 1.858-37.987c3.716-9.29 10.322-13.832 21.058-13.832c8.051 0 10.529 1.032 14.038 5.368c3.923 5.16 4.336 8.258 4.336 42.322v36.955h22.709v-34.065c0-44.8 2.684-50.58 23.536-50.58c7.432 0 9.91 1.032 13.42 5.368c3.922 5.16 4.335 8.258 4.335 42.322v36.955h23.122l-.825-39.845c-.826-43.974-2.89-52.439-14.246-58.426c-15.07-7.639-36.335-5.161-47.07 5.368l-5.988 6.193l-3.922-5.161c-6.813-9.497-26.839-13.832-40.878-8.877z"/></g>'
)
).toBe('fill');
expect(
getIconStyle(
'<path fill="currentColor" fill-rule="evenodd" d="M10 2a1 1 0 0 0-1.79-.614l-7 9a1 1 0 0 0 0 1.228l7 9A1 1 0 0 0 10 20v-3.99c5.379.112 7.963 1.133 9.261 2.243c1.234 1.055 1.46 2.296 1.695 3.596l.061.335a1 1 0 0 0 1.981-.122c.171-2.748-.086-6.73-2.027-10.061C19.087 8.768 15.694 6.282 10 6.022V2Z" clip-rule="evenodd"/>'
)
).toBe('fill');
expect(
getIconStyle(
'<mask id="svgIDa"><circle cx="256" cy="256" r="256" fill="#fff"/></mask><g mask="url(#svgIDa)"><path fill="#0052b4" d="M256 0h256v512H0V256Z"/><path fill="#eee" d="M0 0v32l32 32L0 96v160h32l32-32l32 32h32v-83l83 83h45l-8-16l8-15v-14l-83-83h83V96l-32-32l32-32V0H96L64 32L32 0Z"/><path fill="#d80027" d="M32 0v32H0v64h32v160h64V96h160V32H96V0Zm96 128l128 128v-31l-97-97z"/><path fill="#6da544" d="m320 144l48-80l48 80z"/><circle cx="368" cy="144" r="48" fill="#acabb1"/><path fill="#338af3" d="M320 144v77c0 36 48 48 48 48s48-12 48-48v-77z"/><rect width="32" height="128" x="288" y="128" fill="#ff9811" rx="16" ry="16"/><rect width="32" height="128" x="416" y="128" fill="#ff9811" rx="16" ry="16"/><path fill="#6da544" d="m368 160l-48 67c2 11 9 19 16 26l32-45l32 45c8-7 14-15 16-26z"/></g>'
)
).toBe('fill');
expect(
getIconStyle(
'<path fill="currentColor" fill-opacity=".3" d="M17 5.33C17 4.6 16.4 4 15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33V15h10V5.33z"/><path fill="currentColor" d="M7 15v5.67C7 21.4 7.6 22 8.33 22h7.33c.74 0 1.34-.6 1.34-1.33V15H7z"/>'
)
).toBe('fill');
});
test('Stroke', () => {
expect(
getIconStyle(
'<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8h7a3 3 0 1 0-3-3M4 16h11a3 3 0 1 1-3 3M2 12h17a3 3 0 1 0-3-3"/>'
)
).toBe('stroke');
expect(
getIconStyle(
'<circle cx="23.573" cy="14.408" r="9.309" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><circle cx="23.573" cy="14.251" r="4.515" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m21.344 23.789l-.072 14.045c-4.271-.972-7.874-2.772-9.793-6.66L14.17 28.5l-9.67.018l.071 9.511l2.87-2.78C11.12 40.62 16.78 42.957 24 42.899m2.763-19.606l-.036 14.54c4.272-.971 7.875-2.771 9.794-6.659L33.83 28.5l9.67.018l-.071 9.511l-2.87-2.78C36.88 40.62 31.22 42.957 24 42.899"/>'
)
).toBe('stroke');
expect(
getIconStyle(
'<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16m-10 6h10M6 18h14"/>'
)
).toBe('stroke');
});
test('Mixed / none', () => {
expect(getIconStyle('<g />')).toBeNull();
expect(getIconStyle('<path fill="none" d="" />')).toBeNull();
expect(getIconStyle('<path fill="" d="" />')).toBeNull();
expect(getIconStyle('<path fill="currentColor fill="" />')).toBeNull();
expect(
getIconStyle(
'<path fill="currentColor" d="M212.277 418v382.37h1574.785V418z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="70" d="M213.105 1000V418h1573.79v1164H600V791.606V1582H213.105v-401.564h1573.79h-1573.79z"/>'
)
).toBeNull();
expect(
getIconStyle(
'<path fill="#e6e7e8" d="M63.6 56.737a6.856 6.856 0 0 1-6.854 6.86H6.857c-3.784.001-6.853-3.071-6.853-6.86V6.856A6.856 6.856 0 0 1 6.857 0h49.889A6.855 6.855 0 0 1 63.6 6.856v49.881"/><path fill="none" stroke="#005" stroke-linecap="round" stroke-opacity=".221" stroke-width=".4" d="M9.429 4.424v55.15zm6.451 0v55.15zm6.451 0v55.15zm6.451 0v55.15zm6.448 0v55.15zm6.454 0v55.15zm6.446 0v55.15zm6.46 0v55.15zM4.185 54.775h55.15zm0-6.455h55.15zm0-6.447h55.15zm0-6.451h55.15z"/><path fill="#f05a28" d="M36.725 63.65V36.99c0-2.06-2.669-3.62-4.728-3.62c-2.059 0-4.729 1.561-4.729 3.62v26.66h9.457"/><path fill="#d04427" d="M33.778 33.718c-.594-.217-1.211-.348-1.781-.348c-2.059 0-4.729 1.561-4.729 3.62v26.66h3.565V36.99c0-1.488 1.397-2.706 2.945-3.272"/><path fill="none" stroke="#005" stroke-linecap="round" stroke-opacity=".221" stroke-width=".4" d="M4.185 28.971h55.15zm0-6.451h55.15z"/><path fill="#208d55" d="M19.11 63.65V24.988c0-2.059-3.668-3.727-5.728-3.727c-2.059 0-5.729 1.668-5.729 3.727V63.65H19.11"/><path fill="#1b8049" d="M15.551 21.789c-.771-.27-1.542-.438-2.163-.438c-2.059 0-5.729 1.668-5.729 3.727V63.74h4.325V25.078c0-1.438 1.788-2.668 3.567-3.289"/><path fill="none" stroke="#005" stroke-linecap="round" stroke-opacity=".221" stroke-width=".4" d="M4.185 16.07h55.15zm0-6.451h55.15z"/><path fill="#0867a3" d="M55.942 63.65V6.985c0-2.059-3.669-3.727-5.728-3.727c-2.059 0-5.729 1.668-5.729 3.727V63.65h11.457"/><path fill="#055e8c" d="M52.35 3.684c-.762-.262-1.52-.426-2.135-.426c-2.059 0-5.729 1.668-5.729 3.727V63.65h4.271V6.985c0-1.443 1.804-2.682 3.593-3.301"/>'
)
).toBeNull();
expect(
getIconStyle(
'<path fill="#c7e755" d="M59.5 30.6C59.5 54.1 32 62 32 62S4.5 54.1 4.5 30.6C4.5 13.1 15.5 2 32 2s27.5 11.1 27.5 28.6z"/><g fill="#454749"><path d="M23.4 26.4c4 3.8 5.1 8.9 2.6 11.4c-2.5 2.4-7.8 1.3-11.7-2.5c-4-3.8-5.1-8.9-2.6-11.4c2.5-2.5 7.7-1.4 11.7 2.5"/><path d="M26.4 30.6c-2.6-2.3-5.2-4.1-8-5.7c-1.4-.8-2.9-1.5-4.4-2.1c-1.5-.6-3.1-1.2-4.7-1.8c1.7-.2 3.5-.1 5.2.3c1.7.4 3.4 1 4.9 1.8c1.5.8 2.9 1.9 4.2 3.2c1.1 1.2 2.2 2.6 2.8 4.3"/></g><path fill="#fff" d="M20.4 25c2 1.2 3.1 3.1 2.5 4.1c-.7 1-2.9.8-4.9-.4c-2-1.2-3.1-3.1-2.5-4.1c.7-1 2.8-.8 4.9.4"/><g fill="#454749"><path d="M40.6 26.4c-4 3.8-5.1 8.9-2.6 11.4c2.5 2.4 7.8 1.3 11.7-2.5c4-3.8 5.1-8.9 2.6-11.4c-2.5-2.5-7.7-1.4-11.7 2.5"/><path d="M37.6 30.6c.6-1.6 1.7-3 2.9-4.3c1.2-1.2 2.6-2.3 4.2-3.2c1.5-.9 3.2-1.5 4.9-1.8c1.7-.4 3.4-.5 5.2-.3c-1.6.6-3.2 1.1-4.7 1.8c-1.5.6-3 1.3-4.4 2.1c-2.9 1.5-5.5 3.4-8.1 5.7"/></g><path fill="#fff" d="M43.6 25c-2 1.2-3.1 3.1-2.5 4.1c.7 1 2.9.8 4.9-.4c2-1.2 3.1-3.1 2.5-4.1c-.7-1-2.8-.8-4.9.4"/><path fill="#454749" stroke="#454749" stroke-miterlimit="10" d="M32 48.6c-7.6 0-10.7-3.7-10.7-2.4c0 1.9 4.8 4.4 10.7 4.4s10.7-2.5 10.7-4.4c0-1.3-3.1 2.4-10.7 2.4z"/>'
)
).toBeNull();
expect(
getIconStyle(
'<g fill="none" stroke="#000" stroke-linejoin="round" stroke-width="4"><path fill="#2F88FF" d="M44 44V20L24 4L4 20L4 44H16V26H32V44H44Z"/><path stroke-linecap="round" d="M24 44V34"/></g>'
)
).toBeNull();
});
});

View File

@ -160,5 +160,19 @@ describe('Creating search index, checking prefixes', () => {
palette: true,
})
).toEqual([]);
// Style
expect(
filterSearchPrefixes(searchIndex, iconSets, {
...baseParams,
style: 'fill',
})
).toEqual(['mdi-light', 'mdi-test-prefix']);
expect(
filterSearchPrefixes(searchIndex, iconSets, {
...baseParams,
style: 'stroke',
})
).toEqual([]);
}, 5000);
});

View File

@ -97,5 +97,21 @@ describe('Searching icons', () => {
],
hasMore: false,
});
// Search with style and palette
expect(
search(
{
keyword: 'bookmark style:fill palette:true',
limit: 999,
},
searchIndex,
iconSets
)
).toEqual({
prefixes: ['emojione-v1'],
names: ['emojione-v1:bookmark', 'emojione-v1:bookmark-tabs'],
hasMore: false,
});
}, 5000);
});

View File

@ -305,6 +305,54 @@ describe('Splitting keywords', () => {
},
});
expect(splitKeyword('home style:fill')).toEqual({
searches: [
{
keywords: [],
},
],
partial: 'home',
params: {
style: 'fill',
},
});
expect(splitKeyword('home style=stroke')).toEqual({
searches: [
{
keywords: [],
},
],
partial: 'home',
params: {
style: 'stroke',
},
});
expect(splitKeyword('home fill=true')).toEqual({
searches: [
{
keywords: [],
},
],
partial: 'home',
params: {
style: 'fill',
},
});
expect(splitKeyword('home stroke=true')).toEqual({
searches: [
{
keywords: [],
},
],
partial: 'home',
params: {
style: 'stroke',
},
});
// Too short for partial
expect(splitKeyword('ab')).toEqual({
searches: [