mirror of https://github.com/iconify/api.git
Compare commits
71 Commits
3.0.0-beta
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
a3d158f99c | |
|
|
407792e2bc | |
|
|
8e35844326 | |
|
|
f79638ef5f | |
|
|
23b5dc510e | |
|
|
238e12a016 | |
|
|
26828c1152 | |
|
|
d24e229645 | |
|
|
62fa26b541 | |
|
|
65af386d3c | |
|
|
ae0e4c929d | |
|
|
116d8e20df | |
|
|
a7e3d99d9e | |
|
|
2521142eca | |
|
|
84590145c9 | |
|
|
88b88251aa | |
|
|
262783f704 | |
|
|
48b0da6d55 | |
|
|
119538fbff | |
|
|
3141131ab7 | |
|
|
ba72402310 | |
|
|
6f33b7c4c5 | |
|
|
44884118f3 | |
|
|
9ec6b365a0 | |
|
|
5e1dbe8d4f | |
|
|
9b7b1a4279 | |
|
|
871028175b | |
|
|
a752e1ee7f | |
|
|
39e5bb4817 | |
|
|
2afff788df | |
|
|
8300891cba | |
|
|
f62b8ba8e6 | |
|
|
65b0eca32e | |
|
|
a02dd19d51 | |
|
|
e44822a40d | |
|
|
c5260541b8 | |
|
|
1d55e2dafd | |
|
|
5d296cd782 | |
|
|
9c6a063676 | |
|
|
c153237943 | |
|
|
283142a8d7 | |
|
|
c1ac0a5609 | |
|
|
49a077bc7b | |
|
|
cb8baf9acf | |
|
|
0b9427baa4 | |
|
|
95423f118b | |
|
|
a391ce96cf | |
|
|
2963c7a666 | |
|
|
ceb3fc4394 | |
|
|
2181b8022c | |
|
|
6471e051e9 | |
|
|
32582d8dae | |
|
|
ceaea7ceca | |
|
|
5994184446 | |
|
|
c53a1e7a82 | |
|
|
bcad340030 | |
|
|
2e2ca0f3b2 | |
|
|
bdd286b32d | |
|
|
5c31cfb4fd | |
|
|
21d5bdb462 | |
|
|
5cb4d91fb7 | |
|
|
1c3a9af9c6 | |
|
|
a4fa8bfb4b | |
|
|
02cadfd2d9 | |
|
|
2d8424a40e | |
|
|
20a8ad39ce | |
|
|
8be5f675e5 | |
|
|
624b63deb8 | |
|
|
e4e66a0a75 | |
|
|
9bc5b3f3b8 | |
|
|
6c613eeba6 |
|
|
@ -1 +0,0 @@
|
|||
github: cyberalien
|
||||
|
|
@ -15,10 +15,10 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 'latest'
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: npm ci
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/.idea
|
||||
/.vscode
|
||||
.DS_Store
|
||||
/.env
|
||||
/.editorconfig
|
||||
/.prettierrc
|
||||
*.map
|
||||
/docker.sh
|
||||
/Dockerfile
|
||||
/tsconfig*.*
|
||||
/vitest.config.*
|
||||
/.github
|
||||
/src
|
||||
/node_modules
|
||||
/cache
|
||||
/tmp
|
||||
/icons
|
||||
/tests
|
||||
36
Dockerfile
36
Dockerfile
|
|
@ -1,11 +1,11 @@
|
|||
ARG ARCH=amd64
|
||||
ARG NODE_VERSION=16
|
||||
ARG NODE_VERSION=22
|
||||
ARG OS=bullseye-slim
|
||||
ARG ICONIFY_API_VERSION=3.0.0
|
||||
ARG ICONIFY_API_VERSION=3.2.0
|
||||
ARG SRC_PATH=./
|
||||
|
||||
#### Stage BASE ########################################################################################################
|
||||
FROM ${ARCH}/node:${NODE_VERSION}-${OS} AS base
|
||||
FROM --platform=${ARCH} node:${NODE_VERSION}-${OS} AS base
|
||||
|
||||
# This gives node.js apps access to the OS CAs
|
||||
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
||||
|
|
@ -28,10 +28,6 @@ RUN set -ex && \
|
|||
nano \
|
||||
git && \
|
||||
mkdir -p /data/iconify-api && \
|
||||
npm i selfupdate --location=global && \
|
||||
deluser --remove-home node && \
|
||||
useradd --home-dir /data/iconify-api --uid 1000 --shell /bin/bash iconify-api && \
|
||||
chown -R iconify-api:root /data/iconify-api && chmod -R g+rwX /data/iconify-api && \
|
||||
apt-get clean && \
|
||||
rm -rf /tmp/* && \
|
||||
# Restore the original sources.list
|
||||
|
|
@ -46,15 +42,19 @@ WORKDIR /data/iconify-api
|
|||
FROM base AS iconify-api-install
|
||||
ARG SRC_PATH
|
||||
|
||||
# Make CERTAIN peer dependencies are installed, otherwise this will very likely fail
|
||||
COPY ${SRC_PATH} /data/iconify-api/
|
||||
COPY init.sh /init.sh
|
||||
# Copy package files, install dependencies
|
||||
COPY ${SRC_PATH}*.json ./
|
||||
RUN npm ci
|
||||
|
||||
RUN cp -fR /data/iconify-api/src/config /data/config_default && \
|
||||
npm install
|
||||
# Copy src and icons
|
||||
COPY ${SRC_PATH}src/ /data/iconify-api/src/
|
||||
COPY ${SRC_PATH}icons/ /data/iconify-api/icons/
|
||||
|
||||
#### Stage RELEASE #####################################################################################################
|
||||
FROM iconify-api-install AS RELEASE
|
||||
# Build API
|
||||
RUN npm run build
|
||||
|
||||
#### Stage release #####################################################################################################
|
||||
FROM iconify-api-install AS release
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG BUILD_REF
|
||||
|
|
@ -65,13 +65,13 @@ ARG TAG_SUFFIX=default
|
|||
LABEL org.label-schema.build-date=${BUILD_DATE} \
|
||||
org.label-schema.docker.dockerfile="Dockerfile" \
|
||||
org.label-schema.license="MIT" \
|
||||
org.label-schema.name="Iconify API.js" \
|
||||
org.label-schema.name="Iconify API" \
|
||||
org.label-schema.version=${BUILD_VERSION} \
|
||||
org.label-schema.description="Node.js version of api.iconify.design" \
|
||||
org.label-schema.url="https://github.com/iconify/api.js" \
|
||||
org.label-schema.url="https://github.com/iconify/api" \
|
||||
org.label-schema.vcs-ref=${BUILD_REF} \
|
||||
org.label-schema.vcs-type="Git" \
|
||||
org.label-schema.vcs-url="https://github.com/iconify/api.js" \
|
||||
org.label-schema.vcs-url="https://github.com/iconify/api" \
|
||||
org.label-schema.arch=${ARCH} \
|
||||
authors="Vjacheslav Trushkin"
|
||||
|
||||
|
|
@ -86,4 +86,4 @@ EXPOSE 3000
|
|||
# Add a healthcheck (default every 30 secs)
|
||||
HEALTHCHECK CMD curl http://localhost:3000/ || exit 1
|
||||
|
||||
ENTRYPOINT ["/init.sh"]
|
||||
CMD ["npm", "run", "start"]
|
||||
|
|
|
|||
37
README.md
37
README.md
|
|
@ -6,6 +6,29 @@ This repository contains Iconify API script. It is a HTTP server, written in Nod
|
|||
- Generates SVG, which you can link to in HTML or stylesheet.
|
||||
- Provides search engine for hosted icons, which can be used by icon pickers.
|
||||
|
||||
## NPM Package
|
||||
|
||||
This package is also available at NPM, allowing using API code in custom wrappers.
|
||||
|
||||
NPM package contains only compiled files, to build custom Docker image you need to use source files from Git repository, not NPM package.
|
||||
|
||||
## Docker
|
||||
|
||||
To build a Docker image, run `./docker.sh`.
|
||||
|
||||
If you want to customise config, fork this repo, customise source code, then build Docker image and deploy API.
|
||||
|
||||
To run a Docker image, run `docker run -d -p 3000:3000 iconify/api` (change first 3000 to port you want to run API on).
|
||||
|
||||
NPM commands for working with Docker images:
|
||||
|
||||
- `npm run docker:build` - builds Docker image.
|
||||
- `npm run docker:start` - starts Docker container on port 3000.
|
||||
- `npm run docker:stop` - stops all Iconify API Docker containers.
|
||||
- `npm run docker:cleanup` - removes all unused Iconify API Docker containers.
|
||||
|
||||
There is no command to remove unused images because of Docker limitations. You need to do it manually from Docker Desktop or command line.
|
||||
|
||||
## How to use it
|
||||
|
||||
First, you need to install NPM dependencies and run build script:
|
||||
|
|
@ -24,6 +47,7 @@ npm run start
|
|||
By default, server will:
|
||||
|
||||
- Automatically load latest icons from [`@iconify/json`](https://github.com/iconify/icon-sets).
|
||||
- Load custom icon sets from `icons` directory.
|
||||
- Serve data on port 3000.
|
||||
|
||||
You can customise API to:
|
||||
|
|
@ -54,12 +78,21 @@ Options that can be changed with environment variables and their default values
|
|||
|
||||
- `HOST=0.0.0.0`: IP address or hostname HTTP server listens on.
|
||||
- `PORT=3000`: port HTTP server listens on.
|
||||
- `ICONIFY_SOURCE=full`: source for Iconify icon sets. Set to `full` to use `@iconify/json` package, `split` to use `@iconify-json/*` packages, `none` to use only custom icon sets.
|
||||
- `REDIRECT_INDEX=https://iconify.design/`: redirect for `/` route. API does not serve any pages, so index page redirects to main website.
|
||||
- `STATUS_REGION=`: custom text to add to `/version` route response. Iconify API is ran on network of servers, visitor is routed to closest server. It is used to tell which server user is connected to.
|
||||
- `ENABLE_ICON_LISTS=true`: enables `/collections` route that lists icon sets and `/collection?prefix=whatever` route to get list of icons. Used by icon pickers. Disable it if you are using API only to serve icon data.
|
||||
- `ENABLE_SEARCH_ENGINE=true`: enables `/search` route. Requires `ENABLE_ICON_LISTS` to be enabled.
|
||||
- `ALLOW_FILTER_ICONS_BY_STYLE=true`: allows searching for icons based on fill or stroke, such as adding `style=fill` to search query. This feature uses a bit of memory, so it can be disabled. Requires `ENABLE_SEARCH_ENGINE` to be enabled.
|
||||
|
||||
### Memory management
|
||||
|
||||
By default, API will use memory management functions. It stores only recently used icons in memory, reducing memory usage.
|
||||
000
|
||||
If your API gets a lot of traffic (above 1k requests per minute), it is better to not use memory management. With such high number of queries, disc read/write operations might cause degraded performance. API can easily handle 10 times more traffic on a basic VPS if everything is in memory and can be accessed instantly.
|
||||
|
||||
See [memory management in full API docs](https://iconify.design/docs/api/hosting-js/config.html).
|
||||
|
||||
### Updating icons
|
||||
|
||||
Icons are automatically updated when server starts.
|
||||
|
|
@ -98,7 +131,7 @@ Previous version of API was also available as PHP script. This has been disconti
|
|||
|
||||
This file is basic.
|
||||
|
||||
Full documentation is available on [Iconify documentation website](https://docs.iconify.design/api/).
|
||||
Full documentation is available on [Iconify documentation website](https://iconify.design/docs/api/).
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
|
@ -116,4 +149,4 @@ Iconify API is licensed under MIT license.
|
|||
|
||||
This licence does not apply to icons hosted on API and files generated by API. You can host icons with any license, without any restrictions. Common decency applies, such as not hosting pirated versions of commercial icon sets (not sure why anyone would use commercial icon sets when so many excellent open source icon sets are available, but anyway...).
|
||||
|
||||
© 2022 Vjacheslav Trushkin / Iconify OÜ
|
||||
© 2022-PRESENT Vjacheslav Trushkin
|
||||
|
|
|
|||
21
docker.sh
21
docker.sh
|
|
@ -5,13 +5,13 @@
|
|||
#./docker.sh arm64v8
|
||||
|
||||
# To test the docker image a command like this can be used:
|
||||
#docker run --rm -p 3123:3000 --name iconify-api -v $(realpath "../iconify-cache"):/data/iconify-api/cache -v $(realpath "../iconify-config"):/data/iconify-api/src/config iconify/iconify-api:latest
|
||||
#docker run --rm -p 3123:3000 --name iconify-api -v /absolute/path/iconify-cache:/data/iconify-api/cache -v /absolute/path/iconify-config:/data/iconify-api/src/config iconify/iconify-api:latest
|
||||
#docker run --rm -p 3123:3000 --name iconify-api -v $(realpath "../iconify-cache"):/data/iconify-api/cache -v $(realpath "../iconify-config"):/data/iconify-api/src/config iconify/api:latest
|
||||
#docker run --rm -p 3123:3000 --name iconify-api -v /absolute/path/iconify-cache:/data/iconify-api/cache -v /absolute/path/iconify-config:/data/iconify-api/src/config iconify/api:latest
|
||||
DOCKER_REPO=iconify/api
|
||||
ICONIFY_API_REPO=$(realpath "./")
|
||||
BUILD_SOURCE=$(realpath "./")
|
||||
SHARED_DIR=$BUILD_SOURCE/../shared
|
||||
DOCKERFILE=$(realpath "./Dockerfile")
|
||||
REPO_BRANCH="dev3"
|
||||
SRC_PATH="./"
|
||||
if [ -z "$1" ]; then
|
||||
ARCH=amd64
|
||||
|
|
@ -21,19 +21,10 @@ else
|
|||
fi
|
||||
echo "Starting to build for arch: $ARCH"
|
||||
echo "Build BASE dir: $BUILD_SOURCE"
|
||||
if [ ! -s "./package.json" ] && [ -s "./iconify-api.js/package.json" ]; then
|
||||
# If the repo is not the same as where the Docker file is located,
|
||||
# this will fix all paths
|
||||
ICONIFY_API_REPO=$(realpath "./iconify-api.js/")
|
||||
SRC_PATH="iconify-api.js/"
|
||||
cd $ICONIFY_API_REPO
|
||||
git checkout $REPO_BRANCH
|
||||
cd $BUILD_SOURCE
|
||||
fi
|
||||
|
||||
export ICONIFY_API_VERSION=$(grep -oE "\"version\": \"(\w*.\w*.\w*(-\w*)?)" $ICONIFY_API_REPO/package.json | cut -d\" -f4)
|
||||
export ICONIFY_API_VERSION=$(grep -oE "\"version\": \"([0-9]+.[0-9]+.[a-z0-9.-]+)" $ICONIFY_API_REPO/package.json | cut -d\" -f4)
|
||||
|
||||
echo "iconify-api.js version: ${ICONIFY_API_VERSION}"
|
||||
echo "Iconify API version: ${ICONIFY_API_VERSION}"
|
||||
|
||||
mkdir -p $BUILD_SOURCE/tmp
|
||||
|
||||
|
|
@ -62,6 +53,6 @@ time docker build --rm=false \
|
|||
--build-arg TAG_SUFFIX=default \
|
||||
--build-arg SRC_PATH="$SRC_PATH" \
|
||||
--file $DOCKERFILE \
|
||||
--tag iconify/iconify-api:latest --tag iconify/iconify-api:${ICONIFY_API_VERSION} $BUILD_SOURCE
|
||||
--tag ${DOCKER_REPO}:latest --tag ${DOCKER_REPO}:${ICONIFY_API_VERSION} $BUILD_SOURCE
|
||||
|
||||
rm -fR $BUILD_SOURCE/tmp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# Icons
|
||||
|
||||
This directory contains custom icon sets and icons.
|
||||
|
||||
## Icon sets
|
||||
|
||||
Icon sets are stored in IconifyJSON format.
|
||||
|
||||
You can use Iconify Tools to create icon sets. See [Iconify Tools documentation](https://docs.iconify.design/tools/tools2/).
|
||||
|
||||
Each icon set has prefix. For icon set to be imported, filename must match icon set prefix, for example, `line-md.json` for icon set with `line-md` prefix.
|
||||
|
||||
Icon sets that have `info` property and are not marked as hidden, appear in icon sets list and search results (unless those features are disabled).
|
||||
If you want icon set to be hidden, all you have to do is not add `info` property when creating icon set or remove it.
|
||||
|
||||
## Icons
|
||||
|
||||
TODO
|
||||
|
||||
Currently not supported yet. Create icon sets instead.
|
||||
|
||||
## Conflicts
|
||||
|
||||
If API serves both custom and Iconify icon sets, it is possible to have conflicting names. If 2 icon sets with identical prefix exist in both sources, custom icon set will be used.
|
||||
|
||||
To disable Iconify icon sets, set env variable `ICONIFY_SOURCE` to `none`. You can use `.env` file in root directory.
|
||||
14
init.sh
14
init.sh
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/bash -e
|
||||
# This file is included in the Docker image
|
||||
exit_func() {
|
||||
echo "SIGTERM detected"
|
||||
exit 1
|
||||
}
|
||||
trap exit_func SIGTERM SIGINT
|
||||
|
||||
echo "Initializing Iconify API.js..."
|
||||
cd /data/iconify-api
|
||||
# Only copy files which don't exist in target
|
||||
cp -rn /data/config_default/. /data/iconify-api/src/config/
|
||||
npm run build
|
||||
node --expose-gc lib/index.js
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Vjacheslav Trushkin
|
||||
Copyright (c) 2022-PRESENT Vjacheslav Trushkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
|
|
@ -3,35 +3,40 @@
|
|||
"description": "Iconify API",
|
||||
"author": "Vjacheslav Trushkin",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"version": "3.0.0-beta.3",
|
||||
"version": "3.2.0",
|
||||
"type": "module",
|
||||
"bugs": "https://github.com/iconify/api/issues",
|
||||
"homepage": "https://github.com/iconify/api",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconify/api.git"
|
||||
},
|
||||
"packageManager": "npm@8.19.2",
|
||||
"packageManager": "npm@11.6.4",
|
||||
"engines": {
|
||||
"node": ">=16.15.0"
|
||||
"node": ">=22.20.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"test": "vitest --config vitest.config.mjs",
|
||||
"start": "node --expose-gc lib/index.js"
|
||||
"start": "node --expose-gc lib/index.js",
|
||||
"docker:build": "./docker.sh",
|
||||
"docker:start": "docker run -d -p 3000:3000 iconify/api",
|
||||
"docker:stop": "docker ps -q --filter ancestor=iconify/api | xargs -r docker stop",
|
||||
"docker:cleanup": "docker ps -q -a --filter ancestor=iconify/api | xargs -r docker rm",
|
||||
"docker:publish": "docker push iconify/api"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/formbody": "^7.4.0",
|
||||
"@iconify/tools": "^2.2.0",
|
||||
"@fastify/formbody": "^8.0.2",
|
||||
"@iconify/tools": "^5.0.0",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"@iconify/utils": "^2.0.11",
|
||||
"dotenv": "^16.0.3",
|
||||
"fastify": "^4.11.0"
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"fastify": "^5.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.5",
|
||||
"@types/node": "^18.11.18",
|
||||
"typescript": "^4.9.4",
|
||||
"vitest": "^0.26.3"
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.10.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.14"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"],
|
||||
"rangeStrategy": "bump",
|
||||
"packageRules": [
|
||||
{
|
||||
"matchDepTypes": ["peerDependencies"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["fastify"],
|
||||
"allowedVersions": "<4.0.0"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["@fastify/formbody"],
|
||||
"allowedVersions": "<8.0.0"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["@iconify/utils"],
|
||||
"allowedVersions": "<3.0.0"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["@iconify/tools"],
|
||||
"allowedVersions": "<5.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import type { AppConfig } from '../types/config/app';
|
||||
import type { SplitIconSetConfig } from '../types/config/split';
|
||||
import type { MemoryStorageConfig } from '../types/storage';
|
||||
import type { AppConfig } from '../types/config/app.js';
|
||||
import type { SplitIconSetConfig } from '../types/config/split.js';
|
||||
import type { MemoryStorageConfig } from '../types/storage.js';
|
||||
|
||||
/**
|
||||
* Main configuration
|
||||
*/
|
||||
export const appConfig: AppConfig = {
|
||||
// Index page
|
||||
redirectIndex: 'https://iconify.design/',
|
||||
redirectIndex: 'https://iconify.design/docs/api/',
|
||||
|
||||
// Region to add to `/version` response
|
||||
// Used to tell which server is responding when running multiple servers
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { DirectoryDownloader } from '../downloaders/directory';
|
||||
import { createJSONDirectoryImporter } from '../importers/full/directory-json';
|
||||
import { directoryExists } from '../misc/files';
|
||||
import type { Importer } from '../types/importers';
|
||||
import type { ImportedData } from '../types/importers/common';
|
||||
import { fullPackageImporter } from './importers/full-package';
|
||||
import { splitPackagesImporter } from './importers/split-packages';
|
||||
import { DirectoryDownloader } from '../downloaders/directory.js';
|
||||
import { createJSONDirectoryImporter } from '../importers/full/directory-json.js';
|
||||
import { directoryExists } from '../misc/files.js';
|
||||
import type { Importer } from '../types/importers.js';
|
||||
import type { ImportedData } from '../types/importers/common.js';
|
||||
import { fullPackageImporter } from './importers/full-package.js';
|
||||
import { splitPackagesImporter } from './importers/split-packages.js';
|
||||
|
||||
/**
|
||||
* Sources
|
||||
|
|
@ -20,8 +20,8 @@ export async function getImporters(): Promise<Importer[]> {
|
|||
*
|
||||
* Uses pre-configured importers. See `importers` sub-directory
|
||||
*/
|
||||
type IconifyIconSetsOptions = 'full' | 'split' | false;
|
||||
const iconifyIconSets = 'full' as IconifyIconSetsOptions;
|
||||
type IconifyIconSetsOptions = 'full' | 'split' | 'none';
|
||||
const iconifyIconSets = (process.env['ICONIFY_SOURCE'] || 'full') as IconifyIconSetsOptions;
|
||||
|
||||
switch (iconifyIconSets) {
|
||||
case 'full':
|
||||
|
|
@ -34,11 +34,14 @@ export async function getImporters(): Promise<Importer[]> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add custom icons from `json` directory
|
||||
* Add custom icons from `icons` directory
|
||||
*/
|
||||
if (await directoryExists('json')) {
|
||||
if (await directoryExists('icons')) {
|
||||
importers.push(
|
||||
createJSONDirectoryImporter(new DirectoryDownloader<ImportedData>('json'), {
|
||||
createJSONDirectoryImporter(new DirectoryDownloader<ImportedData>('icons'), {
|
||||
// Skip icon sets with mismatched prefix
|
||||
ignoreInvalidPrefix: false,
|
||||
|
||||
// Filter icon sets. Returns true if icon set should be included, false if not.
|
||||
filter: (prefix) => {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { createRequire } from 'node:module';
|
||||
import { dirname } from 'node:path';
|
||||
import { Importer } from '../../types/importers.js';
|
||||
import { createIconSetsPackageImporter } from '../../importers/full/json.js';
|
||||
import { ImportedData } from '../../types/importers/common.js';
|
||||
import { DirectoryDownloader } from '../../downloaders/directory.js';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
|
||||
import { RemoteDownloader } from '../../downloaders/remote.js';
|
||||
|
||||
/**
|
||||
* Create importer for package
|
||||
*/
|
||||
export function createPackageIconSetImporter(
|
||||
packageName = '@iconify/json',
|
||||
useRemoteFallback = false,
|
||||
autoUpdateRemotePackage = false
|
||||
): Importer {
|
||||
// Try to locate package
|
||||
let dir: string | undefined;
|
||||
try {
|
||||
const req = createRequire(import.meta.url);
|
||||
const filename = req.resolve(`${packageName}/package.json`);
|
||||
dir = filename ? dirname(filename) : undefined;
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
if (dir) {
|
||||
return createIconSetsPackageImporter(new DirectoryDownloader<ImportedData>(dir), {});
|
||||
}
|
||||
if (!useRemoteFallback) {
|
||||
throw new Error(`Cannot find package "${packageName}"`);
|
||||
}
|
||||
|
||||
// Try to download it, update if
|
||||
const npm: RemoteDownloaderOptions = {
|
||||
downloadType: 'npm',
|
||||
package: packageName,
|
||||
};
|
||||
return createIconSetsPackageImporter(new RemoteDownloader<ImportedData>(npm, autoUpdateRemotePackage));
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { RemoteDownloader } from '../../downloaders/remote';
|
||||
import { createIconSetsPackageImporter } from '../../importers/full/json';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import { RemoteDownloader } from '../../downloaders/remote.js';
|
||||
import { createIconSetsPackageImporter } from '../../importers/full/json.js';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
|
||||
/**
|
||||
* Importer for all icon sets from `@iconify/json` package
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { RemoteDownloader } from '../../downloaders/remote';
|
||||
import { createJSONCollectionsListImporter } from '../../importers/collections/collections';
|
||||
import { createJSONPackageIconSetImporter } from '../../importers/icon-set/json-package';
|
||||
import type { IconSetImportedData, ImportedData } from '../../types/importers/common';
|
||||
import { RemoteDownloader } from '../../downloaders/remote.js';
|
||||
import { createJSONCollectionsListImporter } from '../../importers/collections/collections.js';
|
||||
import { createJSONPackageIconSetImporter } from '../../importers/icon-set/json-package.js';
|
||||
import type { IconSetImportedData, ImportedData } from '../../types/importers/common.js';
|
||||
|
||||
// Automatically update on startup: boolean
|
||||
const autoUpdate = true;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { IconifyJSON } from '@iconify/types';
|
||||
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from '../../../types/icon-set/extra';
|
||||
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from '../../../types/icon-set/extra.js';
|
||||
|
||||
/**
|
||||
* Prepare data for icons list API v2 response
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import type { IconifyAliases, IconifyJSON, IconifyOptional } from '@iconify/types';
|
||||
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
|
||||
import { appConfig } from '../../../config/app';
|
||||
import { appConfig } from '../../../config/app.js';
|
||||
import type {
|
||||
IconSetIconNames,
|
||||
IconSetIconsListIcons,
|
||||
IconSetIconsListTag,
|
||||
IconStyle,
|
||||
} from '../../../types/icon-set/extra';
|
||||
import { getIconStyle } from './style';
|
||||
} from '../../../types/icon-set/extra.js';
|
||||
import { getIconStyle } from './style.js';
|
||||
|
||||
const customisableProps = Object.keys(defaultIconProps) as (keyof IconifyOptional)[];
|
||||
|
||||
/**
|
||||
* Generate icons tree
|
||||
*/
|
||||
export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsListIcons {
|
||||
export function generateIconSetIconsTree(iconSet: IconifyJSON, commonChunks?: string[]): IconSetIconsListIcons {
|
||||
const iconSetIcons = iconSet.icons;
|
||||
const iconSetAliases = iconSet.aliases || (Object.create(null) as IconifyAliases);
|
||||
|
||||
|
|
@ -245,7 +245,10 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
|
|||
|
||||
const iconKeywords: Set<string> = new Set();
|
||||
for (let i = 0; i < icon.length; i++) {
|
||||
icon[i].split('-').forEach((chunk) => {
|
||||
const name = icon[i];
|
||||
|
||||
// Add keywords
|
||||
name.split('-').forEach((chunk) => {
|
||||
if (iconKeywords.has(chunk)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -253,6 +256,17 @@ export function generateIconSetIconsTree(iconSet: IconifyJSON): IconSetIconsList
|
|||
(keywords[chunk] || (keywords[chunk] = new Set())).add(icon);
|
||||
});
|
||||
}
|
||||
|
||||
// Check for length based on first name
|
||||
if (commonChunks) {
|
||||
for (let j = 0; j < commonChunks.length; j++) {
|
||||
const chunk = commonChunks[j];
|
||||
if (name.startsWith(chunk + '-') || name.endsWith('-' + chunk)) {
|
||||
icon._l = name.length - chunk.length - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Icon style
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { IconStyle } from '../../../types/icon-set/extra';
|
||||
import type { IconStyle } from '../../../types/icon-set/extra.js';
|
||||
|
||||
function getValues(body: string, prop: string): string[] {
|
||||
const chunks = body.split(prop + '="');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { IconifyJSON } from '@iconify/types';
|
||||
import type { IconSetIconsListIcons } from '../../../types/icon-set/extra';
|
||||
import type { IconSetIconsListIcons } from '../../../types/icon-set/extra.js';
|
||||
|
||||
/**
|
||||
* Removes bad items
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { IconifyIcons, IconifyJSON } from '@iconify/types';
|
||||
import { defaultIconDimensions } from '@iconify/utils/lib/icon/defaults';
|
||||
import type { SplitIconSetConfig } from '../../../types/config/split';
|
||||
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split';
|
||||
import type { SplitIconSetConfig } from '../../../types/config/split.js';
|
||||
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split.js';
|
||||
|
||||
const iconDimensionProps = Object.keys(defaultIconDimensions) as (keyof typeof defaultIconDimensions)[];
|
||||
|
||||
|
|
@ -21,7 +21,14 @@ export function splitIconSetMainData(iconSet: IconifyJSON): SplitIconifyJSONMain
|
|||
for (let i = 0; i < iconSetMainDataProps.length; i++) {
|
||||
const prop = iconSetMainDataProps[i];
|
||||
if (iconSet[prop]) {
|
||||
result[prop as 'prefix'] = iconSet[prop as 'prefix'];
|
||||
const value = iconSet[prop as 'prefix'];
|
||||
if (typeof value === 'object') {
|
||||
// Make sure object has null as constructor
|
||||
result[prop as 'prefix'] = Object.create(null);
|
||||
Object.assign(result[prop as 'prefix'], iconSet[prop as 'prefix']);
|
||||
} else {
|
||||
result[prop as 'prefix'] = iconSet[prop as 'prefix'];
|
||||
}
|
||||
} else if (prop === 'aliases') {
|
||||
result[prop] = Object.create(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
import type { IconifyIcons, IconifyJSON } from '@iconify/types';
|
||||
import { appConfig, splitIconSetConfig, storageConfig } from '../../../config/app';
|
||||
import type { SplitIconSetConfig } from '../../../types/config/split';
|
||||
import type { StorageIconSetThemes, StoredIconSet, StoredIconSetDone } from '../../../types/icon-set/storage';
|
||||
import type { SplitRecord } from '../../../types/split';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../../types/storage';
|
||||
import { createSplitRecordsTree, splitRecords } from '../../storage/split';
|
||||
import { createStorage, createStoredItem } from '../../storage/create';
|
||||
import { getIconSetSplitChunksCount, splitIconSetMainData } from './split';
|
||||
import { removeBadIconSetItems } from '../lists/validate';
|
||||
import { prepareAPIv2IconsList } from '../lists/icons-v2';
|
||||
import { generateIconSetIconsTree } from '../lists/icons';
|
||||
import { appConfig, splitIconSetConfig, storageConfig } from '../../../config/app.js';
|
||||
import type { SplitIconSetConfig } from '../../../types/config/split.js';
|
||||
import type { StorageIconSetThemes, StoredIconSet, StoredIconSetDone } from '../../../types/icon-set/storage.js';
|
||||
import type { SplitRecord } from '../../../types/split.js';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../../types/storage.js';
|
||||
import { createSplitRecordsTree, splitRecords } from '../../storage/split.js';
|
||||
import { createStorage, createStoredItem } from '../../storage/create.js';
|
||||
import { getIconSetSplitChunksCount, splitIconSetMainData } from './split.js';
|
||||
import { removeBadIconSetItems } from '../lists/validate.js';
|
||||
import { prepareAPIv2IconsList } from '../lists/icons-v2.js';
|
||||
import { generateIconSetIconsTree } from '../lists/icons.js';
|
||||
import { themeKeys, findIconSetThemes } from './themes.js';
|
||||
|
||||
/**
|
||||
* Storage
|
||||
*/
|
||||
export const iconSetsStorage = createStorage<IconifyIcons>(storageConfig);
|
||||
|
||||
/**
|
||||
* Themes to copy
|
||||
*/
|
||||
const themeKeys: (keyof StorageIconSetThemes)[] = ['themes', 'prefixes', 'suffixes'];
|
||||
|
||||
/**
|
||||
* Counter for prefixes
|
||||
*/
|
||||
|
|
@ -36,7 +32,33 @@ export function storeLoadedIconSet(
|
|||
storage: MemoryStorage<IconifyIcons> = iconSetsStorage,
|
||||
config: SplitIconSetConfig = splitIconSetConfig
|
||||
) {
|
||||
const icons = generateIconSetIconsTree(iconSet);
|
||||
let themes: StorageIconSetThemes | undefined;
|
||||
let themeParts: string[] | undefined;
|
||||
|
||||
if (appConfig.enableIconLists) {
|
||||
// Get themes
|
||||
if (appConfig.enableIconLists) {
|
||||
const themesList: StorageIconSetThemes = {};
|
||||
for (let i = 0; i < themeKeys.length; i++) {
|
||||
const key = themeKeys[i];
|
||||
if (iconSet[key]) {
|
||||
themesList[key as 'prefixes'] = iconSet[key as 'prefixes'];
|
||||
themes = themesList;
|
||||
}
|
||||
}
|
||||
|
||||
// Get common parts of icon names for optimised search
|
||||
if (appConfig.enableSearchEngine) {
|
||||
const data = findIconSetThemes(iconSet);
|
||||
if (data.length) {
|
||||
themeParts = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get icons
|
||||
const icons = generateIconSetIconsTree(iconSet, themeParts);
|
||||
removeBadIconSetItems(iconSet, icons);
|
||||
|
||||
// Fix icons counter
|
||||
|
|
@ -47,17 +69,6 @@ export function storeLoadedIconSet(
|
|||
// Get common items
|
||||
const common = splitIconSetMainData(iconSet);
|
||||
|
||||
// Get themes
|
||||
const themes: StorageIconSetThemes = {};
|
||||
if (appConfig.enableIconLists) {
|
||||
for (let i = 0; i < themeKeys.length; i++) {
|
||||
const key = themeKeys[i];
|
||||
if (iconSet[key]) {
|
||||
themes[key as 'prefixes'] = iconSet[key as 'prefixes'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get number of chunks
|
||||
const chunksCount = getIconSetSplitChunksCount(iconSet.icons, config);
|
||||
|
||||
|
|
@ -94,16 +105,16 @@ export function storeLoadedIconSet(
|
|||
items: storedItems,
|
||||
tree,
|
||||
icons,
|
||||
themes,
|
||||
};
|
||||
if (iconSet.info) {
|
||||
result.info = iconSet.info;
|
||||
}
|
||||
if (appConfig.enableIconLists) {
|
||||
for (const key in themes) {
|
||||
result.themes = themes;
|
||||
break;
|
||||
}
|
||||
result.apiV2IconsCache = prepareAPIv2IconsList(iconSet, icons);
|
||||
if (appConfig.enableSearchEngine && themeParts?.length) {
|
||||
result.themeParts = themeParts;
|
||||
}
|
||||
}
|
||||
done(result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import { IconifyJSON } from '@iconify/types';
|
||||
import { StorageIconSetThemes } from '../../../types/icon-set/storage.js';
|
||||
|
||||
/**
|
||||
* Themes to copy
|
||||
*/
|
||||
export const themeKeys: (keyof StorageIconSetThemes)[] = ['prefixes', 'suffixes'];
|
||||
|
||||
/**
|
||||
* Hardcoded list of themes
|
||||
*
|
||||
* Should contain only simple items, without '-'
|
||||
*/
|
||||
const hardcodedThemes: Set<string> = new Set([
|
||||
'baseline',
|
||||
'outline',
|
||||
'round',
|
||||
'sharp',
|
||||
'twotone',
|
||||
'thin',
|
||||
'light',
|
||||
'bold',
|
||||
'fill',
|
||||
'duotone',
|
||||
'linear',
|
||||
'line',
|
||||
'solid',
|
||||
'filled',
|
||||
'outlined',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Find icon
|
||||
*/
|
||||
export function findIconSetThemes(iconSet: IconifyJSON): string[] {
|
||||
const results: Set<string> = new Set();
|
||||
|
||||
// Add prefixes / suffixes from themes
|
||||
themeKeys.forEach((key) => {
|
||||
const items = iconSet[key];
|
||||
if (items) {
|
||||
Object.keys(items).forEach((item) => {
|
||||
if (item) {
|
||||
results.add(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check all icons and aliases
|
||||
const names = Object.keys(iconSet.icons).concat(Object.keys(iconSet.aliases || {}));
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
const parts = name.split('-');
|
||||
if (parts.length > 1) {
|
||||
const firstChunk = parts.shift() as string;
|
||||
const lastChunk = parts.pop() as string;
|
||||
if (hardcodedThemes.has(firstChunk)) {
|
||||
results.add(firstChunk);
|
||||
}
|
||||
if (hardcodedThemes.has(lastChunk)) {
|
||||
results.add(lastChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return as array, sorted by length
|
||||
return Array.from(results).sort((a, b) => b.length - a.length);
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import type { ExtendedIconifyAlias, ExtendedIconifyIcon, IconifyIcons } from '@iconify/types';
|
||||
import { mergeIconData } from '@iconify/utils/lib/icon/merge';
|
||||
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split';
|
||||
import type { StoredIconSet } from '../../../types/icon-set/storage';
|
||||
import { searchSplitRecordsTree } from '../../storage/split';
|
||||
import { getStoredItem } from '../../storage/get';
|
||||
import type { SplitIconifyJSONMainData } from '../../../types/icon-set/split.js';
|
||||
import type { StoredIconSet } from '../../../types/icon-set/storage.js';
|
||||
import { searchSplitRecordsTree } from '../../storage/split.js';
|
||||
import { getStoredItem } from '../../storage/get.js';
|
||||
|
||||
interface PrepareResult {
|
||||
// Merged properties
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { IconifyJSON, IconifyAliases, IconifyIcons } from '@iconify/types';
|
||||
import type { StoredIconSet } from '../../../types/icon-set/storage';
|
||||
import { searchSplitRecordsTreeForSet } from '../../storage/split';
|
||||
import { getStoredItem } from '../../storage/get';
|
||||
import type { StoredIconSet } from '../../../types/icon-set/storage.js';
|
||||
import { searchSplitRecordsTreeForSet } from '../../storage/split.js';
|
||||
import { getStoredItem } from '../../storage/get.js';
|
||||
|
||||
/**
|
||||
* Get list of icons that must be retrieved
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { StoredIconSet } from '../types/icon-set/storage';
|
||||
import type { IconSetEntry, Importer } from '../types/importers';
|
||||
import { updateSearchIndex } from './search';
|
||||
import type { StoredIconSet } from '../types/icon-set/storage.js';
|
||||
import type { IconSetEntry, Importer } from '../types/importers.js';
|
||||
import { updateSearchIndex } from './search.js';
|
||||
|
||||
/**
|
||||
* All importers
|
||||
|
|
@ -125,8 +125,9 @@ export function updateIconSets(): number {
|
|||
/**
|
||||
* Trigger update
|
||||
*/
|
||||
export function triggerIconSetsUpdate() {
|
||||
export function triggerIconSetsUpdate(index?: number | null, done?: (success?: boolean) => void) {
|
||||
if (!importers) {
|
||||
done?.();
|
||||
return;
|
||||
}
|
||||
console.log('Checking for updates...');
|
||||
|
|
@ -140,6 +141,9 @@ export function triggerIconSetsUpdate() {
|
|||
// Check for updates
|
||||
let updated = false;
|
||||
for (let i = 0; i < importers?.length; i++) {
|
||||
if (typeof index === 'number' && i !== index) {
|
||||
continue;
|
||||
}
|
||||
updated = (await importers[i].checkForUpdate()) || updated;
|
||||
}
|
||||
return updated;
|
||||
|
|
@ -147,6 +151,10 @@ export function triggerIconSetsUpdate() {
|
|||
.then((updated) => {
|
||||
console.log(updated ? 'Update complete' : 'Nothing to update');
|
||||
updateIconSets();
|
||||
done?.(true);
|
||||
})
|
||||
.catch(console.error);
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
done?.(false);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ export function loaded() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state
|
||||
*/
|
||||
export function isLoading() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run when app is ready
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { appConfig } from '../config/app';
|
||||
import type { IconSetEntry } from '../types/importers';
|
||||
import type { SearchIndexData } from '../types/search';
|
||||
import { appConfig } from '../config/app.js';
|
||||
import type { IconSetEntry } from '../types/importers.js';
|
||||
import type { SearchIndexData } from '../types/search.js';
|
||||
|
||||
interface SearchIndex {
|
||||
data?: SearchIndexData;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
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';
|
||||
import { getPartialKeywords } from './partial';
|
||||
import { filterSearchPrefixes, filterSearchPrefixesList } from './prefixes';
|
||||
import { splitKeyword } from './split';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type { IconSetIconNames } from '../../types/icon-set/extra.js';
|
||||
import type { IconSetEntry } from '../../types/importers.js';
|
||||
import type { SearchIndexData, SearchKeywordsEntry, SearchParams, SearchResultsData } from '../../types/search.js';
|
||||
import { getPartialKeywords } from './partial.js';
|
||||
import { filterSearchPrefixes, filterSearchPrefixesList } from './prefixes.js';
|
||||
import { splitKeyword } from './split.js';
|
||||
|
||||
/**
|
||||
* Run search
|
||||
|
|
@ -41,145 +41,160 @@ export function search(
|
|||
return;
|
||||
}
|
||||
|
||||
// Check for partial
|
||||
const partial = keywords.partial;
|
||||
let partialKeywords: string[] | undefined;
|
||||
|
||||
if (partial) {
|
||||
// Get all partial keyword matches
|
||||
const cache = getPartialKeywords(partial, true, data);
|
||||
const exists = data.keywords[partial];
|
||||
if (!cache || !cache.length) {
|
||||
// No partial matches: check if keyword exists
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
partialKeywords = [partial];
|
||||
} else {
|
||||
// Partial keywords exist
|
||||
partialKeywords = exists ? [partial].concat(cache) : cache.slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Get prefixes
|
||||
const basePrefixes = filterSearchPrefixes(data, iconSets, fullParams);
|
||||
|
||||
// Prepare variables
|
||||
const addedIcons = Object.create(null) as Record<string, Set<IconSetIconNames>>;
|
||||
const results: string[] = [];
|
||||
|
||||
// Results, sorted
|
||||
interface TemporaryResultItem {
|
||||
length: number;
|
||||
partial: boolean;
|
||||
names: string[];
|
||||
}
|
||||
const allMatches: TemporaryResultItem[] = [];
|
||||
let allMatchesLength = 0;
|
||||
const getMatchResult = (length: number, partial: boolean): TemporaryResultItem => {
|
||||
const result = allMatches.find((item) => item.length === length && item.partial === partial);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const newItem: TemporaryResultItem = {
|
||||
length,
|
||||
partial,
|
||||
names: [],
|
||||
};
|
||||
allMatches.push(newItem);
|
||||
return newItem;
|
||||
};
|
||||
const limit = params.limit;
|
||||
const softLimit = params.softLimit;
|
||||
|
||||
// Run all searches
|
||||
const check = (partial?: string) => {
|
||||
for (let searchIndex = 0; searchIndex < keywords.searches.length; searchIndex++) {
|
||||
// Add prefixes cache to avoid re-calculating it for every partial keyword
|
||||
interface ExtendedSearchKeywordsEntry extends SearchKeywordsEntry {
|
||||
filteredPrefixes?: Readonly<string[]>;
|
||||
interface ExtendedSearchKeywordsEntry extends SearchKeywordsEntry {
|
||||
// Add prefixes cache to avoid re-calculating it for every partial keyword
|
||||
filteredPrefixes?: Readonly<string[]>;
|
||||
}
|
||||
const runSearch = (search: ExtendedSearchKeywordsEntry, isExact: boolean, partial?: string) => {
|
||||
// Filter prefixes (or get it from cache)
|
||||
let filteredPrefixes: Readonly<string[]>;
|
||||
if (search.filteredPrefixes) {
|
||||
filteredPrefixes = search.filteredPrefixes;
|
||||
} else {
|
||||
filteredPrefixes = search.prefixes ? filterSearchPrefixesList(basePrefixes, search.prefixes) : basePrefixes;
|
||||
|
||||
// Filter by required keywords
|
||||
for (let i = 0; i < search.keywords.length; i++) {
|
||||
filteredPrefixes = filteredPrefixes.filter((prefix) => data.keywords[search.keywords[i]]?.has(prefix));
|
||||
}
|
||||
const search = keywords.searches[searchIndex] as ExtendedSearchKeywordsEntry;
|
||||
|
||||
// Filter prefixes (or get it from cache)
|
||||
let filteredPrefixes: Readonly<string[]>;
|
||||
if (search.filteredPrefixes) {
|
||||
filteredPrefixes = search.filteredPrefixes;
|
||||
} else {
|
||||
filteredPrefixes = search.prefixes
|
||||
? filterSearchPrefixesList(basePrefixes, search.prefixes)
|
||||
: basePrefixes;
|
||||
search.filteredPrefixes = filteredPrefixes;
|
||||
}
|
||||
if (!filteredPrefixes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter by required keywords
|
||||
for (let i = 0; i < search.keywords.length; i++) {
|
||||
filteredPrefixes = filteredPrefixes.filter((prefix) =>
|
||||
data.keywords[search.keywords[i]].has(prefix)
|
||||
);
|
||||
}
|
||||
// Get keywords
|
||||
const testKeywords = partial ? search.keywords.concat([partial]) : search.keywords;
|
||||
const testMatches = search.test ? search.test.concat(testKeywords) : testKeywords;
|
||||
|
||||
search.filteredPrefixes = filteredPrefixes;
|
||||
}
|
||||
if (!filteredPrefixes.length) {
|
||||
// Check for partial keyword if testing for exact match
|
||||
if (partial) {
|
||||
filteredPrefixes = filteredPrefixes.filter((prefix) => data.keywords[partial]?.has(prefix));
|
||||
}
|
||||
|
||||
// Check icons
|
||||
for (let prefixIndex = 0; prefixIndex < filteredPrefixes.length; prefixIndex++) {
|
||||
const prefix = filteredPrefixes[prefixIndex];
|
||||
const prefixAddedIcons = addedIcons[prefix] || (addedIcons[prefix] = new Set());
|
||||
const iconSet = iconSets[prefix].item;
|
||||
const iconSetIcons = iconSet.icons;
|
||||
const iconSetKeywords = iconSetIcons.keywords;
|
||||
if (!iconSetKeywords) {
|
||||
// This should not happen!
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get keywords
|
||||
const testKeywords = partial ? search.keywords.concat([partial]) : search.keywords;
|
||||
const testMatches = search.test ? search.test.concat(testKeywords) : testKeywords;
|
||||
// Check icons in current prefix
|
||||
let matches: IconSetIconNames[] | undefined;
|
||||
let failed = false;
|
||||
for (let keywordIndex = 0; keywordIndex < testKeywords.length && !failed; keywordIndex++) {
|
||||
const keyword = testKeywords[keywordIndex];
|
||||
const keywordMatches = iconSetKeywords[keyword];
|
||||
if (!keywordMatches) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for partial keyword if testing for exact match
|
||||
if (partial) {
|
||||
filteredPrefixes = filteredPrefixes.filter((prefix) => data.keywords[partial].has(prefix));
|
||||
if (!matches) {
|
||||
// Copy all matches
|
||||
matches = Array.from(keywordMatches);
|
||||
} else {
|
||||
// Match previous set
|
||||
matches = matches.filter((item) => keywordMatches.has(item));
|
||||
}
|
||||
}
|
||||
|
||||
// Check icons
|
||||
for (let prefixIndex = 0; prefixIndex < filteredPrefixes.length; prefixIndex++) {
|
||||
const prefix = filteredPrefixes[prefixIndex];
|
||||
const prefixAddedIcons = addedIcons[prefix] || (addedIcons[prefix] = new Set());
|
||||
const iconSet = iconSets[prefix].item;
|
||||
const iconSetIcons = iconSet.icons;
|
||||
const iconSetKeywords = iconSetIcons.keywords;
|
||||
if (!iconSetKeywords) {
|
||||
// This should not happen!
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check icons in current prefix
|
||||
let matches: IconSetIconNames[] | undefined;
|
||||
let failed = false;
|
||||
for (let keywordIndex = 0; keywordIndex < testKeywords.length && !failed; keywordIndex++) {
|
||||
const keyword = testKeywords[keywordIndex];
|
||||
const keywordMatches = iconSetKeywords[keyword];
|
||||
if (!keywordMatches) {
|
||||
failed = true;
|
||||
break;
|
||||
// Test matched icons
|
||||
if (!failed && matches) {
|
||||
for (let matchIndex = 0; matchIndex < matches.length; matchIndex++) {
|
||||
const item = matches[matchIndex];
|
||||
if (prefixAddedIcons.has(item)) {
|
||||
// Already added
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!matches) {
|
||||
// Copy all matches
|
||||
matches = Array.from(keywordMatches);
|
||||
} else {
|
||||
// Match previous set
|
||||
matches = matches.filter((item) => keywordMatches.has(item));
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Test matched icons
|
||||
if (!failed && matches) {
|
||||
for (let matchIndex = 0; matchIndex < matches.length; matchIndex++) {
|
||||
const item = matches[matchIndex];
|
||||
if (prefixAddedIcons.has(item)) {
|
||||
// Already added
|
||||
continue;
|
||||
}
|
||||
// Find icon name that matches all keywords
|
||||
let length: number | undefined;
|
||||
const name = item.find((name, index) => {
|
||||
for (let i = 0; i < testMatches.length; i++) {
|
||||
if (name.indexOf(testMatches[i]) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
if (name.indexOf(testMatches[i]) === -1) {
|
||||
return false;
|
||||
// Get length
|
||||
if (!index) {
|
||||
// First item sets `_l`, unless it didn't match any prefixes/suffixes
|
||||
length = item._l || name.length;
|
||||
} else if (iconSet.themeParts) {
|
||||
// Alias: calculate length
|
||||
const themeParts = iconSet.themeParts;
|
||||
for (let partIndex = 0; partIndex < themeParts.length; partIndex++) {
|
||||
const part = themeParts[partIndex];
|
||||
if (name.startsWith(part + '-') || name.endsWith('-' + part)) {
|
||||
length = name.length - part.length - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (name) {
|
||||
// Add icon
|
||||
prefixAddedIcons.add(item);
|
||||
results.push(prefix + ':' + name);
|
||||
if (results.length >= limit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (name) {
|
||||
// Add icon
|
||||
prefixAddedIcons.add(item);
|
||||
|
||||
const list = getMatchResult(length || name.length, !isExact);
|
||||
list.names.push(prefix + ':' + name);
|
||||
allMatchesLength++;
|
||||
|
||||
if (!isExact && allMatchesLength >= limit) {
|
||||
// Return only if checking for partials and limit reached
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,23 +202,72 @@ export function search(
|
|||
}
|
||||
};
|
||||
|
||||
// Check all keywords
|
||||
if (!partialKeywords) {
|
||||
check();
|
||||
} else {
|
||||
let partial: string | undefined;
|
||||
while ((partial = partialKeywords.shift())) {
|
||||
check(partial);
|
||||
if (results.length >= limit) {
|
||||
break;
|
||||
const runAllSearches = (isExact: boolean) => {
|
||||
for (let searchIndex = 0; searchIndex < keywords.searches.length; searchIndex++) {
|
||||
const search = keywords.searches[searchIndex];
|
||||
const partial = search.partial;
|
||||
if (partial) {
|
||||
// Has partial
|
||||
if (isExact) {
|
||||
if (data.keywords[partial]) {
|
||||
runSearch(search, true, partial);
|
||||
}
|
||||
} else {
|
||||
// Get all partial matches
|
||||
const keywords = getPartialKeywords(partial, true, data);
|
||||
if (keywords) {
|
||||
for (let keywordIndex = 0; keywordIndex < keywords.length; keywordIndex++) {
|
||||
runSearch(search, false, keywords[keywordIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No partial for this search
|
||||
if (!isExact) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exact match
|
||||
runSearch(search, true);
|
||||
}
|
||||
|
||||
// Check limit
|
||||
if (!isExact && allMatchesLength >= limit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Check all keywords
|
||||
try {
|
||||
runAllSearches(true);
|
||||
if (allMatchesLength < limit) {
|
||||
runAllSearches(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Got exception when searching for:', params);
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Generate results
|
||||
if (results.length) {
|
||||
if (allMatchesLength) {
|
||||
// Sort matches
|
||||
allMatches.sort((a, b) => (a.partial !== b.partial ? (a.partial ? 1 : -1) : a.length - b.length));
|
||||
|
||||
// Extract results
|
||||
const results: string[] = [];
|
||||
const prefixes: Set<string> = new Set();
|
||||
for (let i = 0; i < allMatches.length && (softLimit || results.length < limit); i++) {
|
||||
const { names } = allMatches[i];
|
||||
for (let j = 0; j < names.length && (softLimit || results.length < limit); j++) {
|
||||
const name = names[j];
|
||||
results.push(name);
|
||||
prefixes.add(name.split(':').shift() as string);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
prefixes: Object.keys(addedIcons).filter((prefix) => !!addedIcons[prefix]?.size),
|
||||
prefixes: Array.from(prefixes),
|
||||
names: results,
|
||||
hasMore: results.length >= limit,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { PartialKeywords, SearchIndexData } from '../../types/search';
|
||||
import { searchIndex } from '../search';
|
||||
import type { PartialKeywords, SearchIndexData } from '../../types/search.js';
|
||||
import { searchIndex } from '../search.js';
|
||||
|
||||
export const minPartialKeywordLength = 2;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { appConfig } from '../../config/app';
|
||||
import type { IconSetEntry } from '../../types/importers';
|
||||
import type { SearchIndexData, SearchParams } from '../../types/search';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type { IconSetEntry } from '../../types/importers.js';
|
||||
import type { SearchIndexData, SearchParams } from '../../types/search.js';
|
||||
|
||||
/**
|
||||
* Filter prefixes by keyword
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
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';
|
||||
import { paramToBoolean } from '../../misc/bool.js';
|
||||
import type { IconStyle } from '../../types/icon-set/extra.js';
|
||||
import type { SearchKeywords, SearchKeywordsEntry } from '../../types/search.js';
|
||||
import { minPartialKeywordLength } from './partial.js';
|
||||
|
||||
interface SplitOptions {
|
||||
// Can include prefix
|
||||
|
|
@ -21,23 +21,18 @@ interface SplitResultItem {
|
|||
|
||||
// Strings to test icon name
|
||||
test?: string[];
|
||||
}
|
||||
|
||||
interface SplitResult {
|
||||
searches: SplitResultItem[];
|
||||
|
||||
// Partial keyword. It is last chunk of last keyword, which cannot be treated
|
||||
// as prefix, so it is identical to all searches
|
||||
// Partial keyword. It is last chunk of last keyword, which cannot be treated as prefix
|
||||
partial?: string;
|
||||
}
|
||||
|
||||
type SplitResult = SplitResultItem[];
|
||||
|
||||
export function splitKeywordEntries(values: string[], options: SplitOptions): SplitResult | undefined {
|
||||
const results: SplitResult = {
|
||||
searches: [],
|
||||
};
|
||||
const results: SplitResult = [];
|
||||
let invalid = false;
|
||||
|
||||
// Split each entry
|
||||
// Split each entry into arrays
|
||||
interface Entry {
|
||||
value: string;
|
||||
empty: boolean;
|
||||
|
|
@ -83,8 +78,14 @@ export function splitKeywordEntries(values: string[], options: SplitOptions): Sp
|
|||
return (items[0].empty ? '-' : '') + items.map((item) => item.value).join('-');
|
||||
}
|
||||
|
||||
interface ResultsSet {
|
||||
keywords: Set<string>;
|
||||
test: Set<string>;
|
||||
partial?: string;
|
||||
}
|
||||
|
||||
// Function to add item
|
||||
function add(items: Entry[], keywords: Set<string>, test: Set<string>, checkPartial: boolean) {
|
||||
function addToSet(items: Entry[], set: ResultsSet, allowPartial: boolean) {
|
||||
let partial: string | undefined;
|
||||
|
||||
// Add keywords
|
||||
|
|
@ -92,10 +93,10 @@ export function splitKeywordEntries(values: string[], options: SplitOptions): Sp
|
|||
for (let i = 0; i <= max; i++) {
|
||||
const value = items[i];
|
||||
if (!value.empty) {
|
||||
if (i === max && checkPartial && value.value.length >= minPartialKeywordLength) {
|
||||
if (i === max && allowPartial && value.value.length >= minPartialKeywordLength) {
|
||||
partial = value.value;
|
||||
} else {
|
||||
keywords.add(value.value);
|
||||
set.keywords.add(value.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,20 +104,30 @@ export function splitKeywordEntries(values: string[], options: SplitOptions): Sp
|
|||
// Get test value
|
||||
const testValue = valuesToString(items);
|
||||
if (testValue) {
|
||||
test.add(testValue);
|
||||
set.test.add(testValue);
|
||||
}
|
||||
|
||||
// Validate partial
|
||||
if (checkPartial) {
|
||||
if (results.searches.length) {
|
||||
if (results.partial !== partial) {
|
||||
// Partial should be identical for all searches. Something went wrong !!!
|
||||
console.error('Mismatches partials when splitting keywords:', values);
|
||||
delete results.partial;
|
||||
}
|
||||
} else {
|
||||
results.partial = partial;
|
||||
// Add partial
|
||||
if (allowPartial && partial) {
|
||||
if (set.partial && set.partial !== partial) {
|
||||
console.error('Different partial keywords. This should not be happening!');
|
||||
}
|
||||
set.partial = partial;
|
||||
}
|
||||
}
|
||||
|
||||
// Add results set to result
|
||||
function addToResult(set: ResultsSet, prefix?: string) {
|
||||
if (set.keywords.size || set.partial) {
|
||||
const item: SplitResultItem = {
|
||||
keywords: Array.from(set.keywords),
|
||||
prefix,
|
||||
partial: set.partial,
|
||||
};
|
||||
if (set.test.size) {
|
||||
item.test = Array.from(set.test);
|
||||
}
|
||||
results.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,22 +145,14 @@ export function splitKeywordEntries(values: string[], options: SplitOptions): Sp
|
|||
const prefix = firstItem.length > 1 ? valuesToString(firstItem) : firstItem[0].value;
|
||||
if (prefix) {
|
||||
// Valid prefix
|
||||
const keywords: Set<string> = new Set();
|
||||
const test: Set<string> = new Set();
|
||||
const set: ResultsSet = {
|
||||
keywords: new Set(),
|
||||
test: new Set(),
|
||||
};
|
||||
for (let i = 1; i <= lastIndex; i++) {
|
||||
add(splitValues[i], keywords, test, options.partial && i === lastIndex);
|
||||
}
|
||||
|
||||
if (keywords.size || results.partial) {
|
||||
const item: SplitResultItem = {
|
||||
keywords: Array.from(keywords),
|
||||
prefix,
|
||||
};
|
||||
if (test.size) {
|
||||
item.test = Array.from(test);
|
||||
}
|
||||
results.searches.push(item);
|
||||
addToSet(splitValues[i], set, options.partial && i === lastIndex);
|
||||
}
|
||||
addToResult(set, prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -159,40 +162,76 @@ export function splitKeywordEntries(values: string[], options: SplitOptions): Sp
|
|||
if (maxFirstItemIndex && !firstItem[0].empty && !firstItem[1].empty) {
|
||||
const modifiedFirstItem = firstItem.slice(0);
|
||||
const prefix = modifiedFirstItem.shift()!.value;
|
||||
const keywords: Set<string> = new Set();
|
||||
const test: Set<string> = new Set();
|
||||
const set: ResultsSet = {
|
||||
keywords: new Set(),
|
||||
test: new Set(),
|
||||
};
|
||||
for (let i = 0; i <= lastIndex; i++) {
|
||||
add(i ? splitValues[i] : modifiedFirstItem, keywords, test, options.partial && i === lastIndex);
|
||||
}
|
||||
|
||||
if (keywords.size || results.partial) {
|
||||
const item: SplitResultItem = {
|
||||
keywords: Array.from(keywords),
|
||||
prefix,
|
||||
};
|
||||
if (test.size) {
|
||||
item.test = Array.from(test);
|
||||
}
|
||||
results.searches.push(item);
|
||||
addToSet(i ? splitValues[i] : modifiedFirstItem, set, options.partial && i === lastIndex);
|
||||
}
|
||||
addToResult(set, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
// Add as is
|
||||
const keywords: Set<string> = new Set();
|
||||
const test: Set<string> = new Set();
|
||||
const set: ResultsSet = {
|
||||
keywords: new Set(),
|
||||
test: new Set(),
|
||||
};
|
||||
for (let i = 0; i <= lastIndex; i++) {
|
||||
add(splitValues[i], keywords, test, options.partial && i === lastIndex);
|
||||
addToSet(splitValues[i], set, options.partial && i === lastIndex);
|
||||
}
|
||||
addToResult(set);
|
||||
|
||||
if (keywords.size || results.partial) {
|
||||
const item: SplitResultItem = {
|
||||
keywords: Array.from(keywords),
|
||||
};
|
||||
if (test.size) {
|
||||
item.test = Array.from(test);
|
||||
// Merge values
|
||||
if (splitValues.length > 1) {
|
||||
// Check which items can be used for merge
|
||||
// Merge only simple keywords
|
||||
const validIndexes: Set<number> = new Set();
|
||||
for (let i = 0; i <= lastIndex; i++) {
|
||||
const item = splitValues[i];
|
||||
if (item.length === 1 && !item[0].empty) {
|
||||
validIndexes.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (validIndexes.size > 1) {
|
||||
for (let startIndex = 0; startIndex < lastIndex; startIndex++) {
|
||||
if (!validIndexes.has(startIndex)) {
|
||||
continue;
|
||||
}
|
||||
for (let endIndex = startIndex + 1; endIndex <= lastIndex; endIndex++) {
|
||||
if (!validIndexes.has(endIndex)) {
|
||||
// Break loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Generate new values list
|
||||
const newSplitValues: Entry[][] = [
|
||||
...splitValues.slice(0, startIndex),
|
||||
[
|
||||
{
|
||||
value: splitValues
|
||||
.slice(startIndex, endIndex + 1)
|
||||
.map((item) => item[0].value)
|
||||
.join(''),
|
||||
empty: false,
|
||||
},
|
||||
],
|
||||
...splitValues.slice(endIndex + 1),
|
||||
];
|
||||
const newLastIndex = newSplitValues.length - 1;
|
||||
const set: ResultsSet = {
|
||||
keywords: new Set(),
|
||||
test: new Set(),
|
||||
};
|
||||
for (let i = 0; i <= newLastIndex; i++) {
|
||||
addToSet(newSplitValues[i], set, options.partial && i === newLastIndex);
|
||||
}
|
||||
addToResult(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
results.searches.push(item);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
|
@ -425,7 +464,7 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
|
|||
return;
|
||||
}
|
||||
|
||||
const searches: SearchKeywordsEntry[] = entries.searches.map((item) => {
|
||||
const searches: SearchKeywordsEntry[] = entries.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
prefixes: item.prefix
|
||||
|
|
@ -446,6 +485,5 @@ export function splitKeyword(keyword: string, allowPartial = true): SearchKeywor
|
|||
return {
|
||||
searches,
|
||||
params,
|
||||
partial: entries.partial,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { MemoryStorageItem, MemoryStorageCallback } from '../../types/storage';
|
||||
import type { MemoryStorageItem, MemoryStorageCallback } from '../../types/storage.js';
|
||||
|
||||
/**
|
||||
* Run all callbacks from storage
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage';
|
||||
import { runStorageCallbacks } from './callbacks';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage.js';
|
||||
import { runStorageCallbacks } from './callbacks.js';
|
||||
|
||||
/**
|
||||
* Stop timer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { appConfig } from '../../config/app';
|
||||
import type { MemoryStorage, MemoryStorageConfig, MemoryStorageItem } from '../../types/storage';
|
||||
import { cleanupStoredItem } from './cleanup';
|
||||
import { writeStoredItem } from './write';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type { MemoryStorage, MemoryStorageConfig, MemoryStorageItem } from '../../types/storage.js';
|
||||
import { cleanupStoredItem } from './cleanup.js';
|
||||
import { writeStoredItem } from './write.js';
|
||||
|
||||
/**
|
||||
* Create storage
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { MemoryStorageItem, MemoryStorageCallback, MemoryStorage } from '../../types/storage';
|
||||
import { loadStoredItem } from './load';
|
||||
import type { MemoryStorageItem, MemoryStorageCallback, MemoryStorage } from '../../types/storage.js';
|
||||
import { loadStoredItem } from './load.js';
|
||||
|
||||
/**
|
||||
* Get storage data when ready
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { readFile, readFileSync } from 'node:fs';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage';
|
||||
import { runStorageCallbacks } from './callbacks';
|
||||
import { addStorageToCleanup } from './cleanup';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage.js';
|
||||
import { runStorageCallbacks } from './callbacks.js';
|
||||
import { addStorageToCleanup } from './cleanup.js';
|
||||
|
||||
/**
|
||||
* Load data
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { SplitDataTree, SplitRecord, SplitRecordCallback } from '../../types/split';
|
||||
import type { SplitDataTree, SplitRecord, SplitRecordCallback } from '../../types/split.js';
|
||||
|
||||
/**
|
||||
* Split records into `count` chunks
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { rm } from 'node:fs/promises';
|
||||
import { appConfig } from '../../config/app';
|
||||
import type { MemoryStorage } from '../../types/storage';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type { MemoryStorage } from '../../types/storage.js';
|
||||
|
||||
/**
|
||||
* Remove old cache
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { writeFile, mkdir } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage';
|
||||
import { addStorageToCleanup } from './cleanup';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../../types/storage.js';
|
||||
import { addStorageToCleanup } from './cleanup.js';
|
||||
|
||||
const createdDirs: Set<string> = new Set();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DownloaderStatus, DownloaderType } from '../types/downloaders/base';
|
||||
import type { DownloaderStatus, DownloaderType } from '../types/downloaders/base.js';
|
||||
|
||||
/**
|
||||
* loadDataFromDirectory()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BaseDownloader } from './base';
|
||||
import { BaseDownloader } from './base.js';
|
||||
|
||||
/**
|
||||
* Custom downloader
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { directoryExists, hashFiles, listFilesInDirectory } from '../misc/files';
|
||||
import { BaseDownloader } from './base';
|
||||
import { directoryExists, hashFiles, listFilesInDirectory } from '../misc/files.js';
|
||||
import { BaseDownloader } from './base.js';
|
||||
|
||||
/**
|
||||
* Directory downloader
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { directoryExists } from '../misc/files';
|
||||
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../types/downloaders/remote';
|
||||
import { BaseDownloader } from './base';
|
||||
import { downloadRemoteArchive } from './remote/download';
|
||||
import { getRemoteDownloaderCacheKey } from './remote/key';
|
||||
import { getDownloaderVersion, saveDownloaderVersion } from './remote/versions';
|
||||
import { directoryExists } from '../misc/files.js';
|
||||
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../types/downloaders/remote.js';
|
||||
import { BaseDownloader } from './base.js';
|
||||
import { downloadRemoteArchive } from './remote/download.js';
|
||||
import { getRemoteDownloaderCacheKey } from './remote/key.js';
|
||||
import { getDownloaderVersion, saveDownloaderVersion } from './remote/versions.js';
|
||||
|
||||
/**
|
||||
* Remote downloader
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { execAsync } from '@iconify/tools/lib/misc/exec';
|
|||
import { getGitHubRepoHash } from '@iconify/tools/lib/download/github/hash';
|
||||
import { getGitLabRepoHash } from '@iconify/tools/lib/download/gitlab/hash';
|
||||
import { getNPMVersion, getPackageVersion } from '@iconify/tools/lib/download/npm/version';
|
||||
import { directoryExists } from '../../misc/files';
|
||||
import { directoryExists } from '../../misc/files.js';
|
||||
import type {
|
||||
GitDownloaderOptions,
|
||||
GitDownloaderVersion,
|
||||
|
|
@ -12,7 +12,7 @@ import type {
|
|||
GitLabDownloaderVersion,
|
||||
NPMDownloaderOptions,
|
||||
NPMDownloaderVersion,
|
||||
} from '../../types/downloaders/remote';
|
||||
} from '../../types/downloaders/remote.js';
|
||||
|
||||
/**
|
||||
* Check git repo for update
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ import { downloadGitRepo } from '@iconify/tools/lib/download/git';
|
|||
import { downloadGitHubRepo } from '@iconify/tools/lib/download/github';
|
||||
import { downloadGitLabRepo } from '@iconify/tools/lib/download/gitlab';
|
||||
import { downloadNPMPackage } from '@iconify/tools/lib/download/npm';
|
||||
import { appConfig } from '../../config/app';
|
||||
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../../types/downloaders/remote';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type { RemoteDownloaderOptions, RemoteDownloaderVersion } from '../../types/downloaders/remote.js';
|
||||
import {
|
||||
isGitHubUpdateAvailable,
|
||||
isGitLabUpdateAvailable,
|
||||
isGitUpdateAvailable,
|
||||
isNPMUpdateAvailable,
|
||||
} from './check-update';
|
||||
import { getDownloadDirectory } from './target';
|
||||
} from './check-update.js';
|
||||
import { getDownloadDirectory } from './target.js';
|
||||
|
||||
/**
|
||||
* Download files from remote archive
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { hashString } from '../../misc/hash';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote';
|
||||
import { hashString } from '../../misc/hash.js';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
|
||||
|
||||
/**
|
||||
* Get cache key
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { appConfig } from '../../config/app';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote';
|
||||
import { getRemoteDownloaderCacheKey } from './key';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type { RemoteDownloaderOptions } from '../../types/downloaders/remote.js';
|
||||
import { getRemoteDownloaderCacheKey } from './key.js';
|
||||
|
||||
/**
|
||||
* Get directory
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
||||
import { dirname } from 'node:path';
|
||||
import { appConfig } from '../../config/app';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import type {
|
||||
RemoteDownloaderType,
|
||||
RemoteDownloaderVersion,
|
||||
RemoteDownloaderVersionMixin,
|
||||
} from '../../types/downloaders/remote';
|
||||
} from '../../types/downloaders/remote.js';
|
||||
|
||||
// Storage
|
||||
type StoredVersions = Record<string, RemoteDownloaderVersion>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
export function errorText(code: number) {
|
||||
switch (code) {
|
||||
case 404:
|
||||
return 'Not found';
|
||||
|
||||
case 400:
|
||||
return 'Bad request';
|
||||
}
|
||||
return 'Internal server error';
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Basic cleanup for parameters
|
||||
*/
|
||||
export function cleanupQueryValue(value: string | undefined) {
|
||||
return value ? value.replace(/['"<>&]/g, '') : undefined;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { checkJSONPQuery, sendJSONResponse } from './json.js';
|
||||
import { createIconsDataResponse } from '../responses/icons.js';
|
||||
|
||||
type CallbackResult = object | number;
|
||||
|
||||
/**
|
||||
* Handle icons data API response
|
||||
*/
|
||||
export function handleIconsDataResponse(
|
||||
prefix: string,
|
||||
wrapJS: boolean,
|
||||
query: FastifyRequest['query'],
|
||||
res: FastifyReply
|
||||
) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
|
||||
// Check for JSONP
|
||||
const wrap = checkJSONPQuery(q, wrapJS, 'SimpleSVG._loaderCallback');
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
// Function to send response
|
||||
const respond = (result: CallbackResult) => {
|
||||
if (typeof result === 'number') {
|
||||
res.send(result);
|
||||
} else {
|
||||
sendJSONResponse(result, q, wrap, res);
|
||||
}
|
||||
};
|
||||
|
||||
// Get result
|
||||
const result = createIconsDataResponse(prefix, q);
|
||||
if (result instanceof Promise) {
|
||||
result.then(respond).catch((err) => {
|
||||
console.error(err);
|
||||
respond(500);
|
||||
});
|
||||
} else {
|
||||
respond(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { checkJSONPQuery, sendJSONResponse } from './json.js';
|
||||
import { errorText } from './errors.js';
|
||||
|
||||
type CallbackResult = object | number;
|
||||
|
||||
/**
|
||||
* Handle JSON API response generated by a callback
|
||||
*/
|
||||
export function handleJSONResponse(
|
||||
req: FastifyRequest,
|
||||
res: FastifyReply,
|
||||
callback: (query: Record<string, string>) => CallbackResult | Promise<CallbackResult>
|
||||
) {
|
||||
const q = (req.query || {}) as Record<string, string>;
|
||||
|
||||
// Check for JSONP
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.code(400).send(errorText(400));
|
||||
return;
|
||||
}
|
||||
|
||||
// Function to send response
|
||||
const respond = (result: CallbackResult) => {
|
||||
if (typeof result === 'number') {
|
||||
res.code(result).send(errorText(result));
|
||||
} else {
|
||||
sendJSONResponse(result, q, wrap, res);
|
||||
}
|
||||
};
|
||||
|
||||
// Get result
|
||||
const result = callback(q);
|
||||
if (result instanceof Promise) {
|
||||
result.then(respond).catch((err) => {
|
||||
console.error(err);
|
||||
respond(500);
|
||||
});
|
||||
} else {
|
||||
respond(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
import fastify from 'fastify';
|
||||
import fastifyFormBody from '@fastify/formbody';
|
||||
import { appConfig, httpHeaders } from '../config/app';
|
||||
import { runWhenLoaded } from '../data/loading';
|
||||
import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '../misc/name';
|
||||
import { generateAPIv1IconsListResponse } from './responses/collection-v1';
|
||||
import { generateAPIv2CollectionResponse } from './responses/collection-v2';
|
||||
import { generateCollectionsListResponse } from './responses/collections';
|
||||
import { generateIconsDataResponse } from './responses/icons';
|
||||
import { generateKeywordsResponse } from './responses/keywords';
|
||||
import { generateLastModifiedResponse } from './responses/modified';
|
||||
import { generateAPIv2SearchResponse } from './responses/search';
|
||||
import { generateSVGResponse } from './responses/svg';
|
||||
import { generateUpdateResponse } from './responses/update';
|
||||
import { initVersionResponse, versionResponse } from './responses/version';
|
||||
import { generateIconsStyleResponse } from './responses/css';
|
||||
import { appConfig, httpHeaders } from '../config/app.js';
|
||||
import { runWhenLoaded } from '../data/loading.js';
|
||||
import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '../misc/name.js';
|
||||
import { createAPIv1IconsListResponse } from './responses/collection-v1.js';
|
||||
import { createAPIv2CollectionResponse } from './responses/collection-v2.js';
|
||||
import { createCollectionsListResponse } from './responses/collections.js';
|
||||
import { handleIconsDataResponse } from './helpers/send-icons.js';
|
||||
import { createKeywordsResponse } from './responses/keywords.js';
|
||||
import { createLastModifiedResponse } from './responses/modified.js';
|
||||
import { createAPIv2SearchResponse } from './responses/search.js';
|
||||
import { generateSVGResponse } from './responses/svg.js';
|
||||
import { generateUpdateResponse } from './responses/update.js';
|
||||
import { initVersionResponse, versionResponse } from './responses/version.js';
|
||||
import { generateIconsStyleResponse } from './responses/css.js';
|
||||
import { handleJSONResponse } from './helpers/send.js';
|
||||
import { errorText } from './helpers/errors.js';
|
||||
|
||||
/**
|
||||
* Start HTTP server
|
||||
|
|
@ -21,7 +23,9 @@ import { generateIconsStyleResponse } from './responses/css';
|
|||
export async function startHTTPServer() {
|
||||
// Create HTP server
|
||||
const server = fastify({
|
||||
caseSensitive: true,
|
||||
routerOptions: {
|
||||
caseSensitive: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Support `application/x-www-form-urlencoded`
|
||||
|
|
@ -81,19 +85,19 @@ export async function startHTTPServer() {
|
|||
generateSVGResponse(name.prefix, name.name, req.query, res);
|
||||
});
|
||||
} else {
|
||||
res.send(404);
|
||||
res.code(404).send(errorText(404));
|
||||
}
|
||||
});
|
||||
|
||||
// Icons data: /prefix/icons.json, /prefix.json
|
||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.json', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
||||
handleIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
||||
});
|
||||
});
|
||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').json', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
||||
handleIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -107,19 +111,19 @@ export async function startHTTPServer() {
|
|||
// Icons data: /prefix/icons.js, /prefix.js
|
||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.js', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
||||
handleIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
||||
});
|
||||
});
|
||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').js', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
||||
handleIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
||||
});
|
||||
});
|
||||
|
||||
// Last modification time
|
||||
server.get('/last-modified', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateLastModifiedResponse(req.query, res);
|
||||
handleJSONResponse(req, res, createLastModifiedResponse);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -127,26 +131,26 @@ export async function startHTTPServer() {
|
|||
// Icon sets list
|
||||
server.get('/collections', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateCollectionsListResponse(req.query, res);
|
||||
handleJSONResponse(req, res, createCollectionsListResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// Icons list, API v2
|
||||
server.get('/collection', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateAPIv2CollectionResponse(req.query, res);
|
||||
handleJSONResponse(req, res, createAPIv2CollectionResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// Icons list, API v1
|
||||
server.get('/list-icons', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateAPIv1IconsListResponse(req.query, res, false);
|
||||
handleJSONResponse(req, res, (q) => createAPIv1IconsListResponse(q, false));
|
||||
});
|
||||
});
|
||||
server.get('/list-icons-categorized', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateAPIv1IconsListResponse(req.query, res, true);
|
||||
handleJSONResponse(req, res, (q) => createAPIv1IconsListResponse(q, true));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -154,14 +158,14 @@ export async function startHTTPServer() {
|
|||
// Search, currently version 2
|
||||
server.get('/search', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateAPIv2SearchResponse(req.query, res);
|
||||
handleJSONResponse(req, res, createAPIv2SearchResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// Keywords
|
||||
server.get('/keywords', (req, res) => {
|
||||
runWhenLoaded(() => {
|
||||
generateKeywordsResponse(req.query, res);
|
||||
handleJSONResponse(req, res, createKeywordsResponse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -177,7 +181,7 @@ export async function startHTTPServer() {
|
|||
|
||||
// Options
|
||||
server.options('/*', (req, res) => {
|
||||
res.send(200);
|
||||
res.code(204).header('Content-Length', '0').send();
|
||||
});
|
||||
|
||||
// Robots
|
||||
|
|
@ -197,21 +201,21 @@ export async function startHTTPServer() {
|
|||
|
||||
// Redirect
|
||||
server.get('/', (req, res) => {
|
||||
res.redirect(301, appConfig.redirectIndex);
|
||||
res.redirect(appConfig.redirectIndex, 301);
|
||||
});
|
||||
|
||||
// Error handling
|
||||
server.setDefaultRoute((req, res) => {
|
||||
server.setNotFoundHandler((req, res) => {
|
||||
res.statusCode = 404;
|
||||
console.log('404:', req.url);
|
||||
|
||||
// Need to set custom headers because hooks don't work here
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const header = headers[i];
|
||||
res.setHeader(header.key, header.value);
|
||||
res.header(header.key, header.value);
|
||||
}
|
||||
|
||||
res.end();
|
||||
res.send();
|
||||
});
|
||||
|
||||
// Start it
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { getPrefixes, iconSets } from '../../data/icon-sets';
|
||||
import type { IconSetAPIv2IconsList } from '../../types/icon-set/extra';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage';
|
||||
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
||||
import type { IconSetAPIv2IconsList } from '../../types/icon-set/extra.js';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||
import type {
|
||||
APIv1ListIconsBaseResponse,
|
||||
APIv1ListIconsCategorisedResponse,
|
||||
APIv1ListIconsResponse,
|
||||
} from '../../types/server/v1';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { filterPrefixesByPrefix } from '../helpers/prefixes';
|
||||
} from '../../types/server/v1.js';
|
||||
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||
|
||||
// Response results, depends on `categorised` option
|
||||
type PossibleResults = APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse;
|
||||
|
||||
/**
|
||||
* Send API v2 response
|
||||
* Create API v1 response
|
||||
*
|
||||
* This response ignores the following parameters:
|
||||
* - `aliases` -> always enabled
|
||||
|
|
@ -19,27 +20,15 @@ import { filterPrefixesByPrefix } from '../helpers/prefixes';
|
|||
*
|
||||
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
|
||||
*/
|
||||
export function generateAPIv1IconsListResponse(
|
||||
query: FastifyRequest['query'],
|
||||
res: FastifyReply,
|
||||
export function createAPIv1IconsListResponse(
|
||||
query: Record<string, string>,
|
||||
categorised: boolean
|
||||
) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
): PossibleResults | Record<string, PossibleResults> | number {
|
||||
function parse(
|
||||
prefix: string,
|
||||
iconSet: StoredIconSet,
|
||||
v2Cache: IconSetAPIv2IconsList
|
||||
): APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse {
|
||||
const icons = iconSet.icons;
|
||||
|
||||
// Generate common data
|
||||
const base: APIv1ListIconsBaseResponse = {
|
||||
prefix,
|
||||
|
|
@ -48,13 +37,13 @@ export function generateAPIv1IconsListResponse(
|
|||
if (v2Cache.title) {
|
||||
base.title = v2Cache.title;
|
||||
}
|
||||
if (q.info && v2Cache.info) {
|
||||
if (query.info && v2Cache.info) {
|
||||
base.info = v2Cache.info;
|
||||
}
|
||||
if (q.aliases && v2Cache.aliases) {
|
||||
if (query.aliases && v2Cache.aliases) {
|
||||
base.aliases = v2Cache.aliases;
|
||||
}
|
||||
if (q.chars && v2Cache.chars) {
|
||||
if (query.chars && v2Cache.chars) {
|
||||
base.chars = v2Cache.chars;
|
||||
}
|
||||
|
||||
|
|
@ -81,22 +70,20 @@ export function generateAPIv1IconsListResponse(
|
|||
return result;
|
||||
}
|
||||
|
||||
if (q.prefix) {
|
||||
const prefix = q.prefix;
|
||||
if (query.prefix) {
|
||||
const prefix = query.prefix;
|
||||
const iconSet = iconSets[prefix]?.item;
|
||||
if (!iconSet || !iconSet.apiV2IconsCache) {
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
sendJSONResponse(parse(prefix, iconSet, iconSet.apiV2IconsCache), q, wrap, res);
|
||||
return;
|
||||
return parse(prefix, iconSet, iconSet.apiV2IconsCache);
|
||||
}
|
||||
|
||||
if (q.prefixes) {
|
||||
if (query.prefixes) {
|
||||
const prefixes = filterPrefixesByPrefix(
|
||||
getPrefixes(),
|
||||
{
|
||||
prefixes: q.prefixes,
|
||||
prefixes: query.prefixes,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
@ -125,20 +112,18 @@ export function generateAPIv1IconsListResponse(
|
|||
|
||||
if (!items.length) {
|
||||
// Empty list
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
|
||||
// Get all items
|
||||
const result = Object.create(null) as Record<string, ReturnType<typeof parse>>;
|
||||
const result = Object.create(null) as Record<string, PossibleResults>;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
result[item.prefix] = parse(item.prefix, item.iconSet, item.v2Cache);
|
||||
}
|
||||
sendJSONResponse(result, q, wrap, res);
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Invalid
|
||||
res.send(400);
|
||||
return 400;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { iconSets } from '../../data/icon-sets';
|
||||
import type { APIv2CollectionResponse } from '../../types/server/v2';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { iconSets } from '../../data/icon-sets.js';
|
||||
import type { APIv2CollectionResponse } from '../../types/server/v2.js';
|
||||
|
||||
/**
|
||||
* Send API v2 response
|
||||
|
|
@ -12,29 +10,18 @@ import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
|||
*
|
||||
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
|
||||
*/
|
||||
export function generateAPIv2CollectionResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
export function createAPIv2CollectionResponse(q: Record<string, string>): APIv2CollectionResponse | number {
|
||||
// Get icon set
|
||||
const prefix = q.prefix;
|
||||
if (!prefix || !iconSets[prefix]) {
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
|
||||
const iconSet = iconSets[prefix].item;
|
||||
const apiV2IconsCache = iconSet.apiV2IconsCache;
|
||||
if (!apiV2IconsCache) {
|
||||
// Disabled
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
|
||||
// Generate response
|
||||
|
|
@ -52,5 +39,5 @@ export function generateAPIv2CollectionResponse(query: FastifyRequest['query'],
|
|||
delete response.chars;
|
||||
}
|
||||
|
||||
sendJSONResponse(response, q, wrap, res);
|
||||
return response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { getPrefixes, iconSets } from '../../data/icon-sets';
|
||||
import type { APIv2CollectionsResponse } from '../../types/server/v2';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { filterPrefixesByPrefix } from '../helpers/prefixes';
|
||||
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
||||
import type { APIv2CollectionsResponse } from '../../types/server/v2.js';
|
||||
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||
|
||||
/**
|
||||
* Send response
|
||||
|
|
@ -12,15 +10,7 @@ import { filterPrefixesByPrefix } from '../helpers/prefixes';
|
|||
* Ignored parameters:
|
||||
* - hidden (always enabled)
|
||||
*/
|
||||
export function generateCollectionsListResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
export function createCollectionsListResponse(q: Record<string, string>): APIv2CollectionsResponse {
|
||||
// Filter prefixes
|
||||
const prefixes = filterPrefixesByPrefix(getPrefixes('info'), q, false);
|
||||
const response = Object.create(null) as APIv2CollectionsResponse;
|
||||
|
|
@ -33,5 +23,5 @@ export function generateCollectionsListResponse(query: FastifyRequest['query'],
|
|||
}
|
||||
}
|
||||
|
||||
sendJSONResponse(response, q, wrap, res);
|
||||
return response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { stringToColor } from '@iconify/utils/lib/colors';
|
||||
import { getIconsCSS } from '@iconify/utils/lib/css/icons';
|
||||
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons';
|
||||
import { iconSets } from '../../data/icon-sets';
|
||||
import type { IconCSSIconSetOptions } from '@iconify/utils/lib/css/types';
|
||||
import { paramToBoolean } from '../../misc/bool';
|
||||
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons.js';
|
||||
import { iconSets } from '../../data/icon-sets.js';
|
||||
import { paramToBoolean } from '../../misc/bool.js';
|
||||
import { errorText } from '../helpers/errors.js';
|
||||
import { cleanupQueryValue } from '../helpers/query.js';
|
||||
|
||||
/**
|
||||
* Check selector for weird stuff
|
||||
|
|
@ -26,7 +28,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
|
|||
|
||||
if (!names || !names.length) {
|
||||
// Missing or invalid icons parameter
|
||||
res.send(404);
|
||||
res.code(404).send(errorText(404));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
|
|||
const iconSet = iconSets[prefix];
|
||||
if (!iconSet) {
|
||||
// No such icon set
|
||||
res.send(404);
|
||||
res.code(404).send(errorText(404));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +58,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
|
|||
|
||||
// 'color': string
|
||||
// Sets color for monotone images
|
||||
const color = qOptions.color;
|
||||
const color = cleanupQueryValue(qOptions.color);
|
||||
if (typeof color === 'string' && stringToColor(color)) {
|
||||
options.color = color;
|
||||
}
|
||||
|
|
@ -97,7 +99,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
|
|||
// 'commonSelector': string
|
||||
// Common selector for all requested icons
|
||||
// Alias: 'common'
|
||||
const commonSelector = qOptions.commonSelector || q.common;
|
||||
const commonSelector = cleanupQueryValue(qOptions.commonSelector || q.common);
|
||||
if (checkSelector(commonSelector)) {
|
||||
options.commonSelector = commonSelector;
|
||||
}
|
||||
|
|
@ -105,7 +107,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
|
|||
// 'iconSelector': string
|
||||
// Icon selector
|
||||
// Alias: 'selector'
|
||||
const iconSelector = qOptions.iconSelector || q.selector;
|
||||
const iconSelector = cleanupQueryValue(qOptions.iconSelector || q.selector);
|
||||
if (checkSelector(iconSelector)) {
|
||||
options.iconSelector = iconSelector;
|
||||
}
|
||||
|
|
@ -113,7 +115,7 @@ export function generateIconsStyleResponse(prefix: string, query: FastifyRequest
|
|||
// 'overrideSelector': string
|
||||
// Selector for rules in icon that override common rules
|
||||
// Alias: 'override'
|
||||
const overrideSelector = qOptions.overrideSelector || q.override;
|
||||
const overrideSelector = cleanupQueryValue(qOptions.overrideSelector || q.override);
|
||||
if (checkSelector(overrideSelector)) {
|
||||
options.overrideSelector = overrideSelector;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +1,63 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons';
|
||||
import { iconSets } from '../../data/icon-sets';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { IconifyJSON } from '@iconify/types';
|
||||
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons.js';
|
||||
import { iconSets } from '../../data/icon-sets.js';
|
||||
|
||||
/**
|
||||
* Generate icons data
|
||||
*/
|
||||
export function generateIconsDataResponse(
|
||||
export function createIconsDataResponse(
|
||||
prefix: string,
|
||||
wrapJS: boolean,
|
||||
query: FastifyRequest['query'],
|
||||
res: FastifyReply
|
||||
) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
const names = q.icons?.split(',');
|
||||
q: Record<string, string | string[]>
|
||||
): number | IconifyJSON | Promise<IconifyJSON | number> {
|
||||
const iconNames = q.icons;
|
||||
const names = typeof iconNames === 'string' ? iconNames.split(',') : iconNames;
|
||||
|
||||
if (!names || !names.length) {
|
||||
// Missing or invalid icons parameter
|
||||
res.send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for JSONP
|
||||
const wrap = checkJSONPQuery(q, wrapJS, 'SimpleSVG._loaderCallback');
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
|
||||
// Get icon set
|
||||
const iconSet = iconSets[prefix];
|
||||
if (!iconSet) {
|
||||
// No such icon set
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
|
||||
// Get icons
|
||||
// Get icons, possibly sync
|
||||
let syncData: IconifyJSON | undefined;
|
||||
let resolveData: undefined | ((data: IconifyJSON) => void);
|
||||
|
||||
getStoredIconsData(iconSet.item, names, (data) => {
|
||||
// Send data
|
||||
sendJSONResponse(data, q, wrap, res);
|
||||
if (resolveData) {
|
||||
resolveData(data);
|
||||
} else {
|
||||
syncData = data;
|
||||
}
|
||||
});
|
||||
|
||||
if (syncData) {
|
||||
return syncData;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
resolveData = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaitable version of createIconsDataResponse()
|
||||
*/
|
||||
export function createIconsDataResponseAsync(
|
||||
prefix: string,
|
||||
q: Record<string, string | string[]>
|
||||
): Promise<IconifyJSON | number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const result = createIconsDataResponse(prefix, q);
|
||||
if (result instanceof Promise) {
|
||||
result.then(resolve).catch(reject);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,16 @@
|
|||
import { matchIconName } from '@iconify/utils';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { searchIndex } from '../../data/search';
|
||||
import { getPartialKeywords } from '../../data/search/partial';
|
||||
import type { APIv3KeywordsQuery, APIv3KeywordsResponse } from '../../types/server/keywords';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import { searchIndex } from '../../data/search.js';
|
||||
import { getPartialKeywords } from '../../data/search/partial.js';
|
||||
import type { APIv3KeywordsQuery, APIv3KeywordsResponse } from '../../types/server/keywords.js';
|
||||
|
||||
/**
|
||||
* Generate icons data
|
||||
* Find full keywords for partial keyword
|
||||
*/
|
||||
export function generateKeywordsResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
export function createKeywordsResponse(q: Record<string, string>): number | APIv3KeywordsResponse {
|
||||
// Check if search data is available
|
||||
const searchIndexData = searchIndex.data;
|
||||
if (!searchIndexData) {
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
const keywords = searchIndexData.keywords;
|
||||
|
||||
|
|
@ -32,15 +21,16 @@ export function generateKeywordsResponse(query: FastifyRequest['query'], res: Fa
|
|||
let failed = false;
|
||||
|
||||
if (typeof q.prefix === 'string') {
|
||||
// Keywords should start with prefix
|
||||
test = q.prefix;
|
||||
suffixes = false;
|
||||
} else if (typeof q.keyword === 'string') {
|
||||
// All keywords that contain keyword
|
||||
test = q.keyword;
|
||||
suffixes = true;
|
||||
} else {
|
||||
// Invalid query
|
||||
res.send(400);
|
||||
return;
|
||||
return 400;
|
||||
}
|
||||
test = test.toLowerCase().trim();
|
||||
|
||||
|
|
@ -71,5 +61,5 @@ export function generateKeywordsResponse(query: FastifyRequest['query'], res: Fa
|
|||
matches: failed || invalid ? [] : getPartialKeywords(test, suffixes, searchIndexData)?.slice(0) || [],
|
||||
};
|
||||
|
||||
sendJSONResponse(response, q, wrap, res);
|
||||
return response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,11 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { getPrefixes, iconSets } from '../../data/icon-sets';
|
||||
import type { APIv3LastModifiedResponse } from '../../types/server/modified';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { filterPrefixesByPrefix } from '../helpers/prefixes';
|
||||
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
||||
import type { APIv3LastModifiedResponse } from '../../types/server/modified.js';
|
||||
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||
|
||||
/**
|
||||
* Generate icons data
|
||||
* Get last modified time for all icon sets
|
||||
*/
|
||||
export function generateLastModifiedResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
export function createLastModifiedResponse(q: Record<string, string>): number | APIv3LastModifiedResponse {
|
||||
// Filter prefixes
|
||||
const prefixes = filterPrefixesByPrefix(getPrefixes(), q, false);
|
||||
|
||||
|
|
@ -36,5 +26,5 @@ export function generateLastModifiedResponse(query: FastifyRequest['query'], res
|
|||
}
|
||||
}
|
||||
|
||||
sendJSONResponse(response, q, wrap, res);
|
||||
return response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { iconSets } from '../../data/icon-sets';
|
||||
import { searchIndex } from '../../data/search';
|
||||
import { search } from '../../data/search/index';
|
||||
import { paramToBoolean } from '../../misc/bool';
|
||||
import type { SearchParams } from '../../types/search';
|
||||
import type { APIv2SearchParams, APIv2SearchResponse } from '../../types/server/v2';
|
||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json';
|
||||
import { iconSets } from '../../data/icon-sets.js';
|
||||
import { searchIndex } from '../../data/search.js';
|
||||
import { search } from '../../data/search/index.js';
|
||||
import { paramToBoolean } from '../../misc/bool.js';
|
||||
import type { SearchParams } from '../../types/search.js';
|
||||
import type { APIv2SearchParams, APIv2SearchResponse } from '../../types/server/v2.js';
|
||||
|
||||
const minSearchLimit = 32;
|
||||
const maxSearchLimit = 999;
|
||||
|
|
@ -14,28 +12,17 @@ const defaultSearchLimit = minSearchLimit * 2;
|
|||
/**
|
||||
* Send API v2 response
|
||||
*/
|
||||
export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
||||
const q = (query || {}) as Record<string, string>;
|
||||
|
||||
const wrap = checkJSONPQuery(q);
|
||||
if (!wrap) {
|
||||
// Invalid JSONP callback
|
||||
res.send(400);
|
||||
return;
|
||||
}
|
||||
|
||||
export function createAPIv2SearchResponse(q: Record<string, string>): number | APIv2SearchResponse {
|
||||
// Check if search data is available
|
||||
const searchIndexData = searchIndex.data;
|
||||
if (!searchIndexData) {
|
||||
res.send(404);
|
||||
return;
|
||||
return 404;
|
||||
}
|
||||
|
||||
// Get query
|
||||
const keyword = q.query;
|
||||
if (!keyword) {
|
||||
res.send(400);
|
||||
return;
|
||||
return 400;
|
||||
}
|
||||
|
||||
// Convert to params
|
||||
|
|
@ -49,18 +36,24 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
|
|||
if (v2Query.limit) {
|
||||
const limit = parseInt(v2Query.limit);
|
||||
if (!limit) {
|
||||
res.send(400);
|
||||
return;
|
||||
return 400;
|
||||
}
|
||||
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
|
||||
}
|
||||
if (v2Query.min) {
|
||||
const limit = parseInt(v2Query.min);
|
||||
if (!limit) {
|
||||
return 400;
|
||||
}
|
||||
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
|
||||
params.softLimit = true;
|
||||
}
|
||||
|
||||
let start = 0;
|
||||
if (v2Query.start) {
|
||||
start = parseInt(v2Query.start);
|
||||
if (isNaN(start) || start < 0 || start >= params.limit) {
|
||||
res.send(400);
|
||||
return;
|
||||
return 400;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,5 +114,5 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
|
|||
};
|
||||
}
|
||||
|
||||
sendJSONResponse(response, q, wrap, res);
|
||||
return response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import { defaultIconDimensions, flipFromString, iconToHTML, iconToSVG, rotateFromString } from '@iconify/utils';
|
||||
import { iconToHTML } from '@iconify/utils/lib/svg/html';
|
||||
import { iconToSVG } from '@iconify/utils/lib/svg/build';
|
||||
import { flipFromString } from '@iconify/utils/lib/customisations/flip';
|
||||
import { rotateFromString } from '@iconify/utils/lib/customisations/rotate';
|
||||
import { defaultIconDimensions } from '@iconify/utils/lib/icon/defaults';
|
||||
import { defaultIconCustomisations, IconifyIconCustomisations } from '@iconify/utils/lib/customisations/defaults';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { getStoredIconData } from '../../data/icon-set/utils/get-icon';
|
||||
import { iconSets } from '../../data/icon-sets';
|
||||
import { getStoredIconData } from '../../data/icon-set/utils/get-icon.js';
|
||||
import { iconSets } from '../../data/icon-sets.js';
|
||||
import { errorText } from '../helpers/errors.js';
|
||||
import { cleanupQueryValue } from '../helpers/query.js';
|
||||
|
||||
/**
|
||||
* Generate SVG
|
||||
|
|
@ -12,7 +18,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
|
|||
const iconSetItem = iconSets[prefix]?.item;
|
||||
if (!iconSetItem) {
|
||||
// No such icon set
|
||||
res.send(404);
|
||||
res.code(404).send(errorText(404));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +26,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
|
|||
const icons = iconSetItem.icons;
|
||||
if (!(icons.visible[name] || icons.hidden[name]) && !iconSetItem.icons.chars?.[name]) {
|
||||
// No such icon
|
||||
res.send(404);
|
||||
res.code(404).send(errorText(404));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -28,7 +34,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
|
|||
getStoredIconData(iconSetItem, name, (data) => {
|
||||
if (!data) {
|
||||
// Invalid icon
|
||||
res.send(404);
|
||||
res.code(404).send(errorText(404));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -38,8 +44,8 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
|
|||
const customisations: IconifyIconCustomisations = {};
|
||||
|
||||
// Dimensions
|
||||
customisations.width = q.width || defaultIconCustomisations.width;
|
||||
customisations.height = q.height || defaultIconCustomisations.height;
|
||||
customisations.width = cleanupQueryValue(q.width) || defaultIconCustomisations.width;
|
||||
customisations.height = cleanupQueryValue(q.height) || defaultIconCustomisations.height;
|
||||
|
||||
// Rotation
|
||||
customisations.rotate = q.rotate ? rotateFromString(q.rotate, 0) : 0;
|
||||
|
|
@ -70,7 +76,7 @@ export function generateSVGResponse(prefix: string, name: string, query: Fastify
|
|||
let html = iconToHTML(body, svg.attributes);
|
||||
|
||||
// Change color
|
||||
const color = q.color;
|
||||
const color = cleanupQueryValue(q.color);
|
||||
if (color && html.indexOf('currentColor') !== -1 && color.indexOf('"') === -1) {
|
||||
html = html.split('currentColor').join(color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { appConfig } from '../../config/app';
|
||||
import { triggerIconSetsUpdate } from '../../data/icon-sets';
|
||||
import { runWhenLoaded } from '../../data/loading';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
import { triggerIconSetsUpdate } from '../../data/icon-sets.js';
|
||||
import { runWhenLoaded } from '../../data/loading.js';
|
||||
|
||||
let pendingUpdate = false;
|
||||
let lastError = 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { appConfig } from '../../config/app';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
|
||||
let version: string | undefined;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import { maybeAwait } from '../../misc/async';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import { maybeAwait } from '../../misc/async.js';
|
||||
import type {
|
||||
BaseCollectionsImporter,
|
||||
CreateIconSetImporter,
|
||||
CreateIconSetImporterResult,
|
||||
} from '../../types/importers/collections';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
} from '../../types/importers/collections.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
|
||||
/**
|
||||
* Base collections list importer
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import { createBaseCollectionsListImporter } from './base';
|
||||
import type { IconifyInfo } from '@iconify/types';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import { createBaseCollectionsListImporter } from './base.js';
|
||||
|
||||
interface JSONCollectionsListImporterOptions {
|
||||
// File to load
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { CustomDownloader } from '../../downloaders/custom';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import { createBaseCollectionsListImporter } from './base';
|
||||
import { CustomDownloader } from '../../downloaders/custom.js';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import { createBaseCollectionsListImporter } from './base.js';
|
||||
|
||||
/**
|
||||
* Create importer for hardcoded list of icon sets
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { quicklyValidateIconSet } from '@iconify/utils/lib/icon-set/validate-basic';
|
||||
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage';
|
||||
import { prependSlash } from '../../misc/files';
|
||||
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage.js';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||
import { prependSlash } from '../../misc/files.js';
|
||||
|
||||
export interface IconSetJSONOptions {
|
||||
// Ignore bad prefix?
|
||||
// false -> skip icon sets with mismatched prefix
|
||||
// true -> import icon set with mismatched prefix
|
||||
ignoreInvalidPrefix?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -26,9 +28,12 @@ export async function importIconSetFromJSON(
|
|||
}
|
||||
if (data.prefix !== prefix) {
|
||||
if (!options.ignoreInvalidPrefix) {
|
||||
console.error(
|
||||
`Error loading "${prefix}" icon set: bad prefix (enable ignoreInvalidPrefix option in importer to skip this check)`
|
||||
);
|
||||
if (options.ignoreInvalidPrefix === void 0) {
|
||||
// Show warning if option is not set
|
||||
console.error(
|
||||
`Error loading "${prefix}" icon set: bad prefix (enable ignoreInvalidPrefix option in importer to import icon set)`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
data.prefix = prefix;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { quicklyValidateIconSet } from '@iconify/utils/lib/icon-set/validate-basic';
|
||||
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage';
|
||||
import { appConfig } from '../../config/app';
|
||||
import { asyncStoreLoadedIconSet } from '../../data/icon-set/store/storage.js';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||
import { appConfig } from '../../config/app.js';
|
||||
|
||||
export interface IconSetJSONPackageOptions {
|
||||
// Ignore bad prefix?
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { readdir, stat } from 'node:fs/promises';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import { DirectoryDownloader } from '../../downloaders/directory';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import { createJSONIconSetImporter } from '../icon-set/json';
|
||||
import { createBaseCollectionsListImporter } from '../collections/base';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import { DirectoryDownloader } from '../../downloaders/directory.js';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import { createJSONIconSetImporter } from '../icon-set/json.js';
|
||||
import { createBaseCollectionsListImporter } from '../collections/base.js';
|
||||
|
||||
interface JSONDirectoryImporterOptions {
|
||||
// Icon set filter
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import { DirectoryDownloader } from '../../downloaders/directory';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import { createJSONIconSetImporter } from '../icon-set/json';
|
||||
import { createBaseCollectionsListImporter } from '../collections/base';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import { DirectoryDownloader } from '../../downloaders/directory.js';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||
import type { BaseCollectionsImporter, CreateIconSetImporter } from '../../types/importers/collections.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import { createJSONIconSetImporter } from '../icon-set/json.js';
|
||||
import { createBaseCollectionsListImporter } from '../collections/base.js';
|
||||
|
||||
interface IconSetsPackageImporterOptions {
|
||||
// Icon set filter
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import type { BaseFullImporter } from '../../types/importers/full';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import type { BaseFullImporter } from '../../types/importers/full.js';
|
||||
|
||||
/**
|
||||
* Base full importer
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { readdir, stat } from 'node:fs/promises';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import type { BaseFullImporter } from '../../types/importers/full';
|
||||
import { createBaseImporter } from './base';
|
||||
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import type { BaseFullImporter } from '../../types/importers/full.js';
|
||||
import { createBaseImporter } from './base.js';
|
||||
import { type IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json.js';
|
||||
|
||||
interface JSONDirectoryImporterOptions extends IconSetJSONOptions {
|
||||
// Icon set filter
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { ImportedData } from '../../types/importers/common';
|
||||
import type { BaseFullImporter } from '../../types/importers/full';
|
||||
import { createBaseImporter } from './base';
|
||||
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json';
|
||||
import type { IconifyInfo } from '@iconify/types';
|
||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { ImportedData } from '../../types/importers/common.js';
|
||||
import type { BaseFullImporter } from '../../types/importers/full.js';
|
||||
import { createBaseImporter } from './base.js';
|
||||
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json.js';
|
||||
|
||||
interface IconSetsPackageImporterOptions extends IconSetJSONOptions {
|
||||
// Icon set filter
|
||||
|
|
@ -23,6 +23,17 @@ export function createIconSetsPackageImporter<Downloader extends BaseDownloader<
|
|||
|
||||
// Load collections list
|
||||
obj._loadCollectionsListFromDirectory = async (path: string) => {
|
||||
// Log version
|
||||
try {
|
||||
const packageJSON = JSON.parse(await readFile(path + '/package.json', 'utf8'));
|
||||
if (packageJSON.name && packageJSON.version) {
|
||||
console.log(`Loading ${packageJSON.name} ${packageJSON.version}`);
|
||||
}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
// Get prefixes
|
||||
let prefixes: string[];
|
||||
let data: Record<string, IconifyInfo>;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { BaseIconSetImporter } from '../../types/importers/icon-set';
|
||||
import type { IconSetImportedData } from '../../types/importers/common';
|
||||
import { IconSetJSONPackageOptions, importIconSetFromJSONPackage } from '../common/json-package';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { BaseIconSetImporter } from '../../types/importers/icon-set.js';
|
||||
import type { IconSetImportedData } from '../../types/importers/common.js';
|
||||
import { IconSetJSONPackageOptions, importIconSetFromJSONPackage } from '../common/json-package.js';
|
||||
|
||||
interface JSONPackageIconSetImporterOptions extends IconSetJSONPackageOptions {
|
||||
// Icon set prefix
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { BaseIconSetImporter } from '../../types/importers/icon-set';
|
||||
import type { IconSetImportedData } from '../../types/importers/common';
|
||||
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { BaseIconSetImporter } from '../../types/importers/icon-set.js';
|
||||
import type { IconSetImportedData } from '../../types/importers/common.js';
|
||||
import { IconSetJSONOptions, importIconSetFromJSON } from '../common/icon-set-json.js';
|
||||
|
||||
interface JSONIconSetImporterOptions extends IconSetJSONOptions {
|
||||
// Icon set prefix
|
||||
|
|
|
|||
23
src/index.ts
23
src/index.ts
|
|
@ -1,30 +1,19 @@
|
|||
import { config } from 'dotenv';
|
||||
import { getImporters } from './config/icon-sets';
|
||||
import { iconSetsStorage } from './data/icon-set/store/storage';
|
||||
import { setImporters, updateIconSets } from './data/icon-sets';
|
||||
import { loaded } from './data/loading';
|
||||
import { cleanupStorageCache } from './data/storage/startup';
|
||||
import { startHTTPServer } from './http';
|
||||
import { loadEnvConfig } from './misc/load-config';
|
||||
import { loaded } from './data/loading.js';
|
||||
import { startHTTPServer } from './http/index.js';
|
||||
import { loadEnvConfig } from './misc/load-config.js';
|
||||
import { initAPI } from './init.js';
|
||||
|
||||
(async () => {
|
||||
// Configure environment
|
||||
config();
|
||||
loadEnvConfig();
|
||||
|
||||
// Reset old cache
|
||||
await cleanupStorageCache(iconSetsStorage);
|
||||
|
||||
// Start HTTP server
|
||||
startHTTPServer();
|
||||
|
||||
// Get all importers and load data
|
||||
const importers = await getImporters();
|
||||
for (let i = 0; i < importers.length; i++) {
|
||||
await importers[i].init();
|
||||
}
|
||||
setImporters(importers);
|
||||
updateIconSets();
|
||||
// Init API
|
||||
await initAPI();
|
||||
|
||||
// Loaded
|
||||
loaded();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { getImporters } from './config/icon-sets.js';
|
||||
import { iconSetsStorage } from './data/icon-set/store/storage.js';
|
||||
import { setImporters, updateIconSets } from './data/icon-sets.js';
|
||||
import { cleanupStorageCache } from './data/storage/startup.js';
|
||||
import { Importer } from './types/importers.js';
|
||||
|
||||
interface InitOptions {
|
||||
// Cleanup storage cache
|
||||
cleanup?: boolean;
|
||||
|
||||
// Importers
|
||||
importers?: Importer[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Init API
|
||||
*/
|
||||
export async function initAPI(options: InitOptions = {}) {
|
||||
// Reset old cache
|
||||
if (options.cleanup !== false) {
|
||||
await cleanupStorageCache(iconSetsStorage);
|
||||
}
|
||||
|
||||
// Get all importers and load data
|
||||
let importers = options.importers;
|
||||
if (!importers) {
|
||||
importers = await getImporters();
|
||||
}
|
||||
for (let i = 0; i < importers.length; i++) {
|
||||
await importers[i].init();
|
||||
}
|
||||
|
||||
// Update
|
||||
setImporters(importers);
|
||||
updateIconSets();
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { stat } from 'node:fs/promises';
|
||||
import { scanDirectory } from '@iconify/tools/lib/misc/scan';
|
||||
import type { FileEntry } from '../types/files';
|
||||
import { hashString } from './hash';
|
||||
import type { FileEntry } from '../types/files.js';
|
||||
import { hashString } from './hash.js';
|
||||
|
||||
/**
|
||||
* List all files in directory
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { appConfig, splitIconSetConfig, storageConfig } from '../config/app';
|
||||
import { paramToBoolean } from './bool';
|
||||
import { appConfig, splitIconSetConfig, storageConfig } from '../config/app.js';
|
||||
import { paramToBoolean } from './bool.js';
|
||||
|
||||
interface ConfigurableItem {
|
||||
config: unknown;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { StoredIconSet } from '../icon-set/storage';
|
||||
import type { StoredIconSet } from '../icon-set/storage.js';
|
||||
|
||||
/**
|
||||
* Generated data
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ export type IconStyle = 'fill' | 'stroke';
|
|||
* Extra props added to icons
|
||||
*/
|
||||
export interface ExtraIconSetIconNamesProps {
|
||||
// Icon style
|
||||
_is?: IconStyle;
|
||||
|
||||
// Name length without prefix
|
||||
_l?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import type { IconifyIcons, IconifyInfo, IconifyJSON } from '@iconify/types';
|
||||
import type { SplitDataTree } from '../split';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../storage';
|
||||
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from './extra';
|
||||
import type { SplitIconifyJSONMainData } from './split';
|
||||
import type { IconifyIcons, IconifyInfo, IconifyMetaData } from '@iconify/types';
|
||||
import type { SplitDataTree } from '../split.js';
|
||||
import type { MemoryStorage, MemoryStorageItem } from '../storage.js';
|
||||
import type { IconSetIconsListIcons, IconSetAPIv2IconsList } from './extra.js';
|
||||
import type { SplitIconifyJSONMainData } from './split.js';
|
||||
|
||||
/**
|
||||
* Themes
|
||||
*/
|
||||
export interface StorageIconSetThemes {
|
||||
themes?: IconifyJSON['themes'];
|
||||
prefixes?: IconifyJSON['prefixes'];
|
||||
suffixes?: IconifyJSON['suffixes'];
|
||||
}
|
||||
export type StorageIconSetThemes = Pick<IconifyMetaData, 'prefixes' | 'suffixes'>;
|
||||
|
||||
/**
|
||||
* Generated data
|
||||
|
|
@ -36,6 +32,7 @@ export interface StoredIconSet {
|
|||
|
||||
// Themes
|
||||
themes?: StorageIconSetThemes;
|
||||
themeParts?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { BaseDownloader } from '../downloaders/base';
|
||||
import type { StoredIconSet } from './icon-set/storage';
|
||||
import type { ImportedData } from './importers/common';
|
||||
import type { BaseDownloader } from '../downloaders/base.js';
|
||||
import type { StoredIconSet } from './icon-set/storage.js';
|
||||
import type { ImportedData } from './importers/common.js';
|
||||
|
||||
/**
|
||||
* Importer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { BaseDownloader } from '../../downloaders/base';
|
||||
import type { MaybeAsync } from '../async';
|
||||
import type { BaseMainImporter, IconSetImportedData } from './common';
|
||||
import type { BaseIconSetImporter } from './icon-set';
|
||||
import type { BaseDownloader } from '../../downloaders/base.js';
|
||||
import type { MaybeAsync } from '../async.js';
|
||||
import type { BaseMainImporter, IconSetImportedData } from './common.js';
|
||||
import type { BaseIconSetImporter } from './icon-set.js';
|
||||
|
||||
/**
|
||||
* Loader for child element
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { DownloaderType } from '../downloaders/base';
|
||||
import type { StoredIconSet } from '../icon-set/storage';
|
||||
import type { DownloaderType } from '../downloaders/base.js';
|
||||
import type { StoredIconSet } from '../icon-set/storage.js';
|
||||
|
||||
/**
|
||||
* Base icon set importer interface
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { BaseMainImporter, IconSetImportedData } from './common';
|
||||
import type { BaseMainImporter, IconSetImportedData } from './common.js';
|
||||
|
||||
/**
|
||||
* Base full importer
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { BaseImporter, IconSetImportedData } from './common';
|
||||
import type { BaseImporter, IconSetImportedData } from './common.js';
|
||||
|
||||
/**
|
||||
* Base icon set importer interface
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { IconStyle } from './icon-set/extra';
|
||||
import type { IconStyle } from './icon-set/extra.js';
|
||||
|
||||
/**
|
||||
* List of keywords that can be used to autocomplete keyword
|
||||
|
|
@ -50,6 +50,7 @@ export interface SearchParams {
|
|||
|
||||
// Search results limit
|
||||
limit: number;
|
||||
softLimit?: boolean; // True if limit can be exceeded
|
||||
|
||||
// Toggle partial matches
|
||||
partial?: boolean;
|
||||
|
|
@ -67,6 +68,9 @@ export interface SearchKeywordsEntry {
|
|||
|
||||
// Strings to test icon value
|
||||
test?: string[];
|
||||
|
||||
// Partial keyword
|
||||
partial?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,9 +80,6 @@ export interface SearchKeywords {
|
|||
// List of searches
|
||||
searches: SearchKeywordsEntry[];
|
||||
|
||||
// Partial keyword, used in all matches
|
||||
partial?: string;
|
||||
|
||||
// Params extracted from keywords
|
||||
params: Partial<SearchParams>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,9 @@ export interface APIv2SearchParams extends APIv2CommonParams {
|
|||
query: SearchQuery;
|
||||
|
||||
// Maximum number of items in response
|
||||
limit?: number;
|
||||
// If `min` is set, `limit` is ignored
|
||||
limit?: number; // Hard limit. Number of results will not exceed `limit`.
|
||||
min?: number; // Soft limit. Number of results can exceed `limit` if function already retrieved more icons.
|
||||
|
||||
// Start index for results
|
||||
start?: number;
|
||||
|
|
|
|||
|
|
@ -79,21 +79,21 @@ describe('Searching icons', () => {
|
|||
'mdi-test-prefix:hand-cycle',
|
||||
'mdi-test-prefix:power-cycle',
|
||||
'mdi-test-prefix:bicycle',
|
||||
'mdi-test-prefix:bicycle-basket',
|
||||
'mdi-test-prefix:bicycle-cargo',
|
||||
'mdi-test-prefix:bicycle-electric',
|
||||
'mdi-test-prefix:bicycle-penny-farthing',
|
||||
'emojione-v1:bicycle',
|
||||
'mdi-test-prefix:battery-recycle',
|
||||
'mdi-test-prefix:battery-recycle-outline',
|
||||
'mdi-test-prefix:recycle',
|
||||
'mdi-test-prefix:recycle-variant',
|
||||
'mdi-test-prefix:water-recycle',
|
||||
'mdi-test-prefix:unicycle',
|
||||
'mdi-test-prefix:motorcycle',
|
||||
'mdi-test-prefix:motorcycle-electric',
|
||||
'mdi-test-prefix:motorcycle-off',
|
||||
'emojione-v1:motorcycle',
|
||||
'mdi-test-prefix:bicycle-cargo',
|
||||
'mdi-test-prefix:water-recycle',
|
||||
'mdi-test-prefix:bicycle-basket',
|
||||
'mdi-test-prefix:motorcycle-off',
|
||||
'mdi-test-prefix:battery-recycle',
|
||||
'mdi-test-prefix:battery-recycle-outline',
|
||||
'mdi-test-prefix:recycle-variant',
|
||||
'mdi-test-prefix:bicycle-electric',
|
||||
'mdi-test-prefix:motorcycle-electric',
|
||||
'mdi-test-prefix:bicycle-penny-farthing',
|
||||
],
|
||||
hasMore: false,
|
||||
});
|
||||
|
|
@ -116,19 +116,19 @@ describe('Searching icons', () => {
|
|||
'mdi-test-prefix:hand-cycle',
|
||||
'mdi-test-prefix:power-cycle',
|
||||
'mdi-test-prefix:bicycle',
|
||||
'mdi-test-prefix:bicycle-basket',
|
||||
'mdi-test-prefix:bicycle-cargo',
|
||||
'mdi-test-prefix:bicycle-electric',
|
||||
'mdi-test-prefix:bicycle-penny-farthing',
|
||||
'mdi-test-prefix:battery-recycle',
|
||||
'mdi-test-prefix:battery-recycle-outline',
|
||||
'mdi-test-prefix:recycle',
|
||||
'mdi-test-prefix:recycle-variant',
|
||||
'mdi-test-prefix:water-recycle',
|
||||
'mdi-test-prefix:unicycle',
|
||||
'mdi-test-prefix:motorcycle',
|
||||
'mdi-test-prefix:motorcycle-electric',
|
||||
'mdi-test-prefix:bicycle-cargo',
|
||||
'mdi-test-prefix:water-recycle',
|
||||
'mdi-test-prefix:bicycle-basket',
|
||||
'mdi-test-prefix:motorcycle-off',
|
||||
'mdi-test-prefix:battery-recycle',
|
||||
'mdi-test-prefix:battery-recycle-outline',
|
||||
'mdi-test-prefix:recycle-variant',
|
||||
'mdi-test-prefix:bicycle-electric',
|
||||
'mdi-test-prefix:motorcycle-electric',
|
||||
'mdi-test-prefix:bicycle-penny-farthing',
|
||||
],
|
||||
hasMore: false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,40 +37,34 @@ describe('Splitting keywords', () => {
|
|||
prefix: false,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['home'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['home'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['home'], {
|
||||
prefix: true,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['home'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['home'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['home'], {
|
||||
prefix: true,
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Multiple simple entries', () => {
|
||||
|
|
@ -79,48 +73,73 @@ describe('Splitting keywords', () => {
|
|||
prefix: false,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdihome'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi', 'home', 'outline'], {
|
||||
prefix: false,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['mdi', 'home', 'outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdihome', 'outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdihomeoutline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'homeoutline'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi', 'home'], {
|
||||
prefix: true,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['home'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['home'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdihome'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi', 'home'], {
|
||||
prefix: true,
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: [],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi'],
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
{
|
||||
keywords: ['mdi'],
|
||||
partial: 'home',
|
||||
},
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'mdihome',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Incomplete prefix', () => {
|
||||
|
|
@ -129,51 +148,46 @@ describe('Splitting keywords', () => {
|
|||
prefix: false,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
test: ['mdi-'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
test: ['mdi-'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-', 'home'], {
|
||||
prefix: true,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi-',
|
||||
keywords: ['home'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
test: ['mdi-'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi-',
|
||||
keywords: ['home'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
test: ['mdi-'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-', 'home'], {
|
||||
prefix: true,
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi-',
|
||||
keywords: [],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi'],
|
||||
test: ['mdi-'],
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi-',
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
{
|
||||
keywords: ['mdi'],
|
||||
partial: 'home',
|
||||
test: ['mdi-'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Long entry', () => {
|
||||
|
|
@ -182,53 +196,48 @@ describe('Splitting keywords', () => {
|
|||
prefix: false,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['mdi', 'home', 'outline'],
|
||||
test: ['mdi-home-outline'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['mdi', 'home', 'outline'],
|
||||
test: ['mdi-home-outline'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-home-outline'], {
|
||||
prefix: true,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['home', 'outline'],
|
||||
test: ['home-outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home', 'outline'],
|
||||
test: ['mdi-home-outline'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['home', 'outline'],
|
||||
test: ['home-outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home', 'outline'],
|
||||
test: ['mdi-home-outline'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-home-outline'], {
|
||||
prefix: true,
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['home'],
|
||||
test: ['home-outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
test: ['mdi-home-outline'],
|
||||
},
|
||||
],
|
||||
partial: 'outline',
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['home'],
|
||||
partial: 'outline',
|
||||
test: ['home-outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'home'],
|
||||
partial: 'outline',
|
||||
test: ['mdi-home-outline'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Complex entries', () => {
|
||||
|
|
@ -237,77 +246,71 @@ describe('Splitting keywords', () => {
|
|||
prefix: false,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow', 'left'],
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow', 'left'],
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-light', 'arrow-left'], {
|
||||
prefix: true,
|
||||
partial: false,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi-light',
|
||||
keywords: ['arrow', 'left'],
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['light', 'arrow', 'left'],
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow', 'left'],
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
],
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi-light',
|
||||
keywords: ['arrow', 'left'],
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['light', 'arrow', 'left'],
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow', 'left'],
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-light', 'arrow-left'], {
|
||||
prefix: false,
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow'],
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
],
|
||||
partial: 'left',
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow'],
|
||||
partial: 'left',
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(
|
||||
splitKeywordEntries(['mdi-light', 'arrow-left'], {
|
||||
prefix: true,
|
||||
partial: true,
|
||||
})
|
||||
).toEqual({
|
||||
searches: [
|
||||
{
|
||||
prefix: 'mdi-light',
|
||||
keywords: ['arrow'],
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['light', 'arrow'],
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow'],
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
],
|
||||
partial: 'left',
|
||||
});
|
||||
).toEqual([
|
||||
{
|
||||
prefix: 'mdi-light',
|
||||
keywords: ['arrow'],
|
||||
partial: 'left',
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
prefix: 'mdi',
|
||||
keywords: ['light', 'arrow'],
|
||||
partial: 'left',
|
||||
test: ['arrow-left'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'light', 'arrow'],
|
||||
partial: 'left',
|
||||
test: ['mdi-light', 'arrow-left'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,13 +20,14 @@ describe('Splitting keywords', () => {
|
|||
prefixes: ['mdi'],
|
||||
prefix: 'mdi', // leftover from internal function
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
{
|
||||
keywords: ['mdi'],
|
||||
partial: 'home',
|
||||
test: ['mdi-home'],
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('mdi-home', false)).toEqual({
|
||||
|
|
@ -50,9 +51,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['mdi'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('mdi:home', false)).toEqual({
|
||||
|
|
@ -71,9 +72,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['mdi'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('prefix:mdi home', false)).toEqual({
|
||||
|
|
@ -92,9 +93,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['mdi'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('prefix=mdi home', false)).toEqual({
|
||||
|
|
@ -113,9 +114,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['mdi'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('prefixes:mdi home', false)).toEqual({
|
||||
|
|
@ -134,9 +135,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['fa6-', 'mdi-'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('prefixes:fa6-,mdi- home', false)).toEqual({
|
||||
|
|
@ -155,9 +156,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['mdi', 'mdi-'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('prefixes=mdi* home', false)).toEqual({
|
||||
|
|
@ -177,18 +178,20 @@ describe('Splitting keywords', () => {
|
|||
prefixes: ['mdi-light'],
|
||||
prefix: 'mdi-light',
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
{
|
||||
prefixes: ['mdi'],
|
||||
prefix: 'mdi',
|
||||
keywords: ['light'],
|
||||
partial: 'home',
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'light'],
|
||||
test: ['mdi-light'],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('mdi-light home', false)).toEqual({
|
||||
|
|
@ -218,20 +221,22 @@ describe('Splitting keywords', () => {
|
|||
prefixes: ['mdi-light'],
|
||||
prefix: 'mdi-light',
|
||||
keywords: ['home'],
|
||||
partial: 'outline',
|
||||
test: ['home-outline'],
|
||||
},
|
||||
{
|
||||
prefixes: ['mdi'],
|
||||
prefix: 'mdi',
|
||||
keywords: ['light', 'home'],
|
||||
partial: 'outline',
|
||||
test: ['home-outline'],
|
||||
},
|
||||
{
|
||||
keywords: ['mdi', 'light', 'home'],
|
||||
partial: 'outline',
|
||||
test: ['mdi-light', 'home-outline'],
|
||||
},
|
||||
],
|
||||
partial: 'outline',
|
||||
params: {},
|
||||
});
|
||||
expect(splitKeyword('mdi-light home-outline', false)).toEqual({
|
||||
|
|
@ -262,9 +267,9 @@ describe('Splitting keywords', () => {
|
|||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {
|
||||
palette: true,
|
||||
},
|
||||
|
|
@ -274,9 +279,9 @@ describe('Splitting keywords', () => {
|
|||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {
|
||||
palette: false,
|
||||
},
|
||||
|
|
@ -287,9 +292,9 @@ describe('Splitting keywords', () => {
|
|||
{
|
||||
prefixes: ['mdi', 'mdi-', 'fa6-'],
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {},
|
||||
});
|
||||
|
||||
|
|
@ -309,9 +314,9 @@ describe('Splitting keywords', () => {
|
|||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {
|
||||
style: 'fill',
|
||||
},
|
||||
|
|
@ -321,9 +326,9 @@ describe('Splitting keywords', () => {
|
|||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {
|
||||
style: 'stroke',
|
||||
},
|
||||
|
|
@ -333,9 +338,9 @@ describe('Splitting keywords', () => {
|
|||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {
|
||||
style: 'fill',
|
||||
},
|
||||
|
|
@ -345,9 +350,9 @@ describe('Splitting keywords', () => {
|
|||
searches: [
|
||||
{
|
||||
keywords: [],
|
||||
partial: 'home',
|
||||
},
|
||||
],
|
||||
partial: 'home',
|
||||
params: {
|
||||
style: 'stroke',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"module": "CommonJS",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue