refactor: update variable names and comments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 17:09:23 +08:00
parent ccc85b2b62
commit c1e56f0c20
103 changed files with 478 additions and 253 deletions
+6 -6
View File
@@ -4,14 +4,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
This is **@invisi/flaticon-uicons**, a local web-compatible icon font package with 50,492+ icons across 15 style variations (stroke weights × corner styles). Icons are sourced from Flaticon's public API and converted to webfonts using FontForge.
This is **@invisi/ficons**, a local web-compatible icon font package with thousands of icons across 15 style variations (stroke weights × corner styles). Icons are converted to webfonts using FontForge.
The output is a drop-in CSS font system - users link `fonts/flaticon.css` and use class names like `fi-rs-bookmark` to display icons.
The output is a drop-in CSS font system - users link `fonts/ficons.css` and use class names like `fi-rs-bookmark` to display icons.
## Development Commands
```bash
# Fetch latest icon metadata from Flaticon API (writes to data/all_icons.json)
# Fetch latest icon metadata (writes to data/all_icons.json)
npm run update:icons
# or: node update-icon-list.js
@@ -44,9 +44,9 @@ Examples: `fi-rs-bookmark`, `fi-br-home`, `fi-brands-instagram`
### Data Pipeline
1. **`update-icon-list.js`** - Fetches icon metadata from Flaticon API with pagination (117 pages). Rate-limited (100ms delay). Outputs `data/all_icons.json` (~53MB).
1. **`update-icon-list.js`** - Fetches icon metadata with pagination. Rate-limited (100ms delay). Outputs `data/all_icons.json`.
2. **`build-icons-js.js`** - Converts JSON to `window.FLATICON_ICONS` global for browser use in explorer (~5.7MB).
2. **`build-icons-js.js`** - Converts JSON to `window.FICONS_DATA` global for browser use in explorer.
3. **`build-fonts.js`** - Node.js orchestrator that calls FontForge via `scripts/build-font.py` to convert SVGs → TTF → WOFF/WOFF2. Generates CSS with `@font-face` rules and content-based icon classes.
@@ -62,7 +62,7 @@ Examples: `fi-rs-bookmark`, `fi-br-home`, `fi-brands-instagram`
### Key Files
- `fonts/flaticon.css` - Unified CSS importing all font-face definitions
- `fonts/ficons.css` - Unified CSS importing all font-face definitions
- `explorer.html` + `explorer.js` + `explorer.css` - Interactive icon browser with search/filter
- `index.js` - Module exports for npm distribution
+13 -25
View File
@@ -1,12 +1,10 @@
# [@invisi/flaticon-uicons](https://www.flaticon.com/uicons/interface-icons)
# @invisi/ficons
A local web-compatible icon font package with **50,000+ icons** across multiple style variations. Icons are sourced from Flaticon's public API and converted to webfonts using FontForge.
![Flaticon UIcons](https://media.flaticon.com/dist/min/img/interface-icons/uicons.png)
A local web-compatible icon font package with **thousands of icons** across multiple style variations.
## Features
- **50,492+ icons** across 15 style variations (stroke weights × corner styles)
- **Thousands of icons** across 15 style variations (stroke weights × corner styles)
- **Self-hosted** - no CDN dependencies
- **Interactive explorer** - browse, search, and filter icons locally
- **Web fonts** - WOFF2, WOFF, TTF formats
@@ -15,7 +13,7 @@ A local web-compatible icon font package with **50,000+ icons** across multiple
## Installation
```shell
npm i @invisi/flaticon-uicons
npm i @invisi/ficons
```
## Quick Start
@@ -23,7 +21,7 @@ npm i @invisi/flaticon-uicons
Include the CSS in your HTML:
```html
<link rel="stylesheet" href="fonts/flaticon.css">
<link rel="stylesheet" href="fonts/ficons.css">
```
Use icons with `<span>` or `<i>` elements:
@@ -68,7 +66,7 @@ Use icons with `<span>` or `<i>` elements:
| Duotone Chubby | 270 |
| Brands | 245 |
**Total: 50,492 icon variations**
**Thousands of icon variations**
## Styling
@@ -88,7 +86,7 @@ Icons inherit `font-size` and `color` from their parent:
```
fonts/
flaticon.css # Unified CSS (imports all styles)
ficons.css # Unified CSS (imports all styles)
css/
flaticon-regular-straight.css
flaticon-regular-rounded.css
@@ -106,14 +104,14 @@ Search for icons directly from the command line using `npx`:
```bash
# Basic search
npx @invisi/flaticon-uicons camera
npx @invisi/ficons camera
# Search with multiple keywords
npx @invisi/flaticon-uicons arrow left
npx @invisi/ficons arrow left
# Filter by variation (prefix)
npx @invisi/flaticon-uicons user rr
npx @invisi/flaticon-uicons home --variation ss
npx @invisi/ficons user rr
npx @invisi/ficons home --variation ss
```
Outputs JSON with icon names and available variations:
@@ -150,7 +148,7 @@ Outputs JSON with icon names and available variations:
Open `explorer.html` in a browser to:
- Browse all 50,000+ icons
- Browse all icons
- Search by name or tags
- Filter by weight, corner style, and type
- Copy HTML snippets and CSS classes
@@ -159,7 +157,7 @@ Open `explorer.html` in a browser to:
## Development Scripts
```bash
# Fetch latest icon metadata from Flaticon API
# Fetch latest icon metadata
npm run update:icons
# or: node update-icon-list.js
@@ -171,13 +169,3 @@ npm run build:icons
npm run build:fonts
# or: node build-fonts.js
```
## License
Icons sourced from [Flaticon UIcons](https://www.flaticon.com/uicons). Please refer to [Flaticon's license](https://www.flaticon.com/legal) for usage terms.
## Attribution
```
Uicons by <a href="https://www.flaticon.com/uicons">Flaticon</a>
```
+11 -11
View File
@@ -60,7 +60,7 @@ function buildCss({ order, mapping, outputPath }) {
// Generate base.css with shared styles
const baseLines = [];
baseLines.push('/*!');
baseLines.push(' * Flaticon Icon Fonts - Base Styles');
baseLines.push(' * Ficons Icon Fonts - Base Styles');
baseLines.push(' * Required for all icon fonts to render correctly');
baseLines.push(' */');
baseLines.push('');
@@ -99,20 +99,20 @@ function buildCss({ order, mapping, outputPath }) {
const lines = [];
lines.push('/*!');
lines.push(` * ${label} (${prefix})`);
lines.push(' * Generated from Flaticon icon API');
lines.push(' * Icon font generated from SVG sources');
lines.push(' */');
lines.push('@import url("./base.css");');
lines.push('@font-face {');
lines.push(` font-family: "flaticon-${fileSuffix}";`);
lines.push(` src: url("../webfonts/flaticon-${fileSuffix}.woff2") format("woff2"),`);
lines.push(` url("../webfonts/flaticon-${fileSuffix}.woff") format("woff"),`);
lines.push(` url("../webfonts/flaticon-${fileSuffix}.ttf") format("truetype");`);
lines.push(` font-family: "ficons-${fileSuffix}";`);
lines.push(` src: url("../webfonts/ficons-${fileSuffix}.woff2") format("woff2"),`);
lines.push(` url("../webfonts/ficons-${fileSuffix}.woff") format("woff"),`);
lines.push(` url("../webfonts/ficons-${fileSuffix}.ttf") format("truetype");`);
lines.push(' font-display: swap;');
lines.push('}');
lines.push('');
lines.push(`i[class^="fi-${prefix}-"]:before, i[class*=" fi-${prefix}-"]:before,`);
lines.push(`span[class^="fi-${prefix}-"]:before, span[class*=" fi-${prefix}-"]:before {`);
lines.push(` font-family: flaticon-${fileSuffix} !important;`);
lines.push(` font-family: ficons-${fileSuffix} !important;`);
lines.push(' font-style: normal;');
lines.push(' font-weight: normal !important;');
lines.push('}');
@@ -148,11 +148,11 @@ function buildCss({ order, mapping, outputPath }) {
fs.writeFileSync(path.join(cssDir, `${stroke}.css`), groupLines.join('\n'));
});
// Generate all.css (main entry point)
// Generate ficons.css (main entry point)
const allLines = [];
allLines.push('/*!');
allLines.push(' * Flaticon Icon Fonts - All Icons');
allLines.push(' * Generated from Flaticon icon API');
allLines.push(' * Ficons Icon Fonts - All Icons');
allLines.push(' * Icon fonts generated from SVG sources');
allLines.push(' */');
allLines.push('');
allLines.push('@import url("./css/base.css");');
@@ -204,7 +204,7 @@ function main() {
const prefixes = args.prefix ? String(args.prefix).split(',') : Object.keys(PREFIX_CONFIG);
const clean = Boolean(args.clean);
const outputDir = args.outputDir || 'fonts/webfonts';
const cssPath = args.css || 'fonts/all.css';
const cssPath = args.css || 'fonts/ficons.css';
const iconsByPrefix = loadIcons();
const buildOrder = Object.keys(PREFIX_CONFIG).filter(prefix => prefixes.includes(prefix));
+1 -1
View File
@@ -17,7 +17,7 @@ function buildIconsJs({
is_brand
}));
const payload = `window.FLATICON_ICONS = ${JSON.stringify(icons)};\n`;
const payload = `window.FICONS_DATA = ${JSON.stringify(icons)};\n`;
fs.writeFileSync(outputPath, payload, 'utf8');
const sizeMb = (payload.length / (1024 * 1024)).toFixed(1);
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flaticon Explorer</title>
<link rel="stylesheet" href="fonts/all.css">
<title>Ficons Explorer</title>
<link rel="stylesheet" href="fonts/ficons.css">
<link rel="stylesheet" href="explorer.css">
<script>try{document.documentElement.dataset.theme=localStorage.getItem('theme')??(matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light')}catch{}</script>
</head>
@@ -17,7 +17,7 @@
<span class="fi fi-rs-sparkles"></span>
</div>
<div>
<h1>Flaticon Explorer</h1>
<h1>Ficons Explorer</h1>
<div class="header-subtitle">Just a little place to find nice icons</div>
</div>
</div>
+2 -2
View File
@@ -70,8 +70,8 @@ function getCurrentPrefix() {
async function loadIcons() {
try {
if (Array.isArray(window.FLATICON_ICONS) && window.FLATICON_ICONS.length) {
allIcons = window.FLATICON_ICONS;
if (Array.isArray(window.FICONS_DATA) && window.FICONS_DATA.length) {
allIcons = window.FICONS_DATA;
} else {
const response = await fetch('data/all_icons.json');
if (!response.ok) throw new Error('Failed to load icons');
+1 -1
View File
@@ -1,5 +1,5 @@
/*!
* Flaticon Icon Fonts - Base Styles
* Ficons Icon Fonts - Base Styles
* Required for all icon fonts to render correctly
*/
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Bold Rounded (br)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-bold-rounded";
src: url("../webfonts/flaticon-bold-rounded.woff2") format("woff2"),
url("../webfonts/flaticon-bold-rounded.woff") format("woff"),
url("../webfonts/flaticon-bold-rounded.ttf") format("truetype");
font-family: "ficons-bold-rounded";
src: url("../webfonts/ficons-bold-rounded.woff2") format("woff2"),
url("../webfonts/ficons-bold-rounded.woff") format("woff"),
url("../webfonts/ficons-bold-rounded.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-br-"]:before, i[class*=" fi-br-"]:before,
span[class^="fi-br-"]:before, span[class*=" fi-br-"]:before {
font-family: flaticon-bold-rounded !important;
font-family: ficons-bold-rounded !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Bold Straight (bs)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-bold-straight";
src: url("../webfonts/flaticon-bold-straight.woff2") format("woff2"),
url("../webfonts/flaticon-bold-straight.woff") format("woff"),
url("../webfonts/flaticon-bold-straight.ttf") format("truetype");
font-family: "ficons-bold-straight";
src: url("../webfonts/ficons-bold-straight.woff2") format("woff2"),
url("../webfonts/ficons-bold-straight.woff") format("woff"),
url("../webfonts/ficons-bold-straight.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-bs-"]:before, i[class*=" fi-bs-"]:before,
span[class^="fi-bs-"]:before, span[class*=" fi-bs-"]:before {
font-family: flaticon-bold-straight !important;
font-family: ficons-bold-straight !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Brands (brands)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-brands";
src: url("../webfonts/flaticon-brands.woff2") format("woff2"),
url("../webfonts/flaticon-brands.woff") format("woff"),
url("../webfonts/flaticon-brands.ttf") format("truetype");
font-family: "ficons-brands";
src: url("../webfonts/ficons-brands.woff2") format("woff2"),
url("../webfonts/ficons-brands.woff") format("woff"),
url("../webfonts/ficons-brands.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-brands-"]:before, i[class*=" fi-brands-"]:before,
span[class^="fi-brands-"]:before, span[class*=" fi-brands-"]:before {
font-family: flaticon-brands !important;
font-family: ficons-brands !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Duotone Chubby (dc)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-duotone-chubby";
src: url("../webfonts/flaticon-duotone-chubby.woff2") format("woff2"),
url("../webfonts/flaticon-duotone-chubby.woff") format("woff"),
url("../webfonts/flaticon-duotone-chubby.ttf") format("truetype");
font-family: "ficons-duotone-chubby";
src: url("../webfonts/ficons-duotone-chubby.woff2") format("woff2"),
url("../webfonts/ficons-duotone-chubby.woff") format("woff"),
url("../webfonts/ficons-duotone-chubby.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-dc-"]:before, i[class*=" fi-dc-"]:before,
span[class^="fi-dc-"]:before, span[class*=" fi-dc-"]:before {
font-family: flaticon-duotone-chubby !important;
font-family: ficons-duotone-chubby !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Duotone Rounded (dr)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-duotone-rounded";
src: url("../webfonts/flaticon-duotone-rounded.woff2") format("woff2"),
url("../webfonts/flaticon-duotone-rounded.woff") format("woff"),
url("../webfonts/flaticon-duotone-rounded.ttf") format("truetype");
font-family: "ficons-duotone-rounded";
src: url("../webfonts/ficons-duotone-rounded.woff2") format("woff2"),
url("../webfonts/ficons-duotone-rounded.woff") format("woff"),
url("../webfonts/ficons-duotone-rounded.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-dr-"]:before, i[class*=" fi-dr-"]:before,
span[class^="fi-dr-"]:before, span[class*=" fi-dr-"]:before {
font-family: flaticon-duotone-rounded !important;
font-family: ficons-duotone-rounded !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Duotone Straight (ds)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-duotone-straight";
src: url("../webfonts/flaticon-duotone-straight.woff2") format("woff2"),
url("../webfonts/flaticon-duotone-straight.woff") format("woff"),
url("../webfonts/flaticon-duotone-straight.ttf") format("truetype");
font-family: "ficons-duotone-straight";
src: url("../webfonts/ficons-duotone-straight.woff2") format("woff2"),
url("../webfonts/ficons-duotone-straight.woff") format("woff"),
url("../webfonts/ficons-duotone-straight.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-ds-"]:before, i[class*=" fi-ds-"]:before,
span[class^="fi-ds-"]:before, span[class*=" fi-ds-"]:before {
font-family: flaticon-duotone-straight !important;
font-family: ficons-duotone-straight !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Regular Chubby (rc)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-regular-chubby";
src: url("../webfonts/flaticon-regular-chubby.woff2") format("woff2"),
url("../webfonts/flaticon-regular-chubby.woff") format("woff"),
url("../webfonts/flaticon-regular-chubby.ttf") format("truetype");
font-family: "ficons-regular-chubby";
src: url("../webfonts/ficons-regular-chubby.woff2") format("woff2"),
url("../webfonts/ficons-regular-chubby.woff") format("woff"),
url("../webfonts/ficons-regular-chubby.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-rc-"]:before, i[class*=" fi-rc-"]:before,
span[class^="fi-rc-"]:before, span[class*=" fi-rc-"]:before {
font-family: flaticon-regular-chubby !important;
font-family: ficons-regular-chubby !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Regular Rounded (rr)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-regular-rounded";
src: url("../webfonts/flaticon-regular-rounded.woff2") format("woff2"),
url("../webfonts/flaticon-regular-rounded.woff") format("woff"),
url("../webfonts/flaticon-regular-rounded.ttf") format("truetype");
font-family: "ficons-regular-rounded";
src: url("../webfonts/ficons-regular-rounded.woff2") format("woff2"),
url("../webfonts/ficons-regular-rounded.woff") format("woff"),
url("../webfonts/ficons-regular-rounded.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-rr-"]:before, i[class*=" fi-rr-"]:before,
span[class^="fi-rr-"]:before, span[class*=" fi-rr-"]:before {
font-family: flaticon-regular-rounded !important;
font-family: ficons-regular-rounded !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Regular Straight (rs)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-regular-straight";
src: url("../webfonts/flaticon-regular-straight.woff2") format("woff2"),
url("../webfonts/flaticon-regular-straight.woff") format("woff"),
url("../webfonts/flaticon-regular-straight.ttf") format("truetype");
font-family: "ficons-regular-straight";
src: url("../webfonts/ficons-regular-straight.woff2") format("woff2"),
url("../webfonts/ficons-regular-straight.woff") format("woff"),
url("../webfonts/ficons-regular-straight.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-rs-"]:before, i[class*=" fi-rs-"]:before,
span[class^="fi-rs-"]:before, span[class*=" fi-rs-"]:before {
font-family: flaticon-regular-straight !important;
font-family: ficons-regular-straight !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Solid Chubby (sc)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-solid-chubby";
src: url("../webfonts/flaticon-solid-chubby.woff2") format("woff2"),
url("../webfonts/flaticon-solid-chubby.woff") format("woff"),
url("../webfonts/flaticon-solid-chubby.ttf") format("truetype");
font-family: "ficons-solid-chubby";
src: url("../webfonts/ficons-solid-chubby.woff2") format("woff2"),
url("../webfonts/ficons-solid-chubby.woff") format("woff"),
url("../webfonts/ficons-solid-chubby.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-sc-"]:before, i[class*=" fi-sc-"]:before,
span[class^="fi-sc-"]:before, span[class*=" fi-sc-"]:before {
font-family: flaticon-solid-chubby !important;
font-family: ficons-solid-chubby !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Solid Rounded (sr)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-solid-rounded";
src: url("../webfonts/flaticon-solid-rounded.woff2") format("woff2"),
url("../webfonts/flaticon-solid-rounded.woff") format("woff"),
url("../webfonts/flaticon-solid-rounded.ttf") format("truetype");
font-family: "ficons-solid-rounded";
src: url("../webfonts/ficons-solid-rounded.woff2") format("woff2"),
url("../webfonts/ficons-solid-rounded.woff") format("woff"),
url("../webfonts/ficons-solid-rounded.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-sr-"]:before, i[class*=" fi-sr-"]:before,
span[class^="fi-sr-"]:before, span[class*=" fi-sr-"]:before {
font-family: flaticon-solid-rounded !important;
font-family: ficons-solid-rounded !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Solid Straight (ss)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-solid-straight";
src: url("../webfonts/flaticon-solid-straight.woff2") format("woff2"),
url("../webfonts/flaticon-solid-straight.woff") format("woff"),
url("../webfonts/flaticon-solid-straight.ttf") format("truetype");
font-family: "ficons-solid-straight";
src: url("../webfonts/ficons-solid-straight.woff2") format("woff2"),
url("../webfonts/ficons-solid-straight.woff") format("woff"),
url("../webfonts/ficons-solid-straight.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-ss-"]:before, i[class*=" fi-ss-"]:before,
span[class^="fi-ss-"]:before, span[class*=" fi-ss-"]:before {
font-family: flaticon-solid-straight !important;
font-family: ficons-solid-straight !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Thin Chubby (tc)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-thin-chubby";
src: url("../webfonts/flaticon-thin-chubby.woff2") format("woff2"),
url("../webfonts/flaticon-thin-chubby.woff") format("woff"),
url("../webfonts/flaticon-thin-chubby.ttf") format("truetype");
font-family: "ficons-thin-chubby";
src: url("../webfonts/ficons-thin-chubby.woff2") format("woff2"),
url("../webfonts/ficons-thin-chubby.woff") format("woff"),
url("../webfonts/ficons-thin-chubby.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-tc-"]:before, i[class*=" fi-tc-"]:before,
span[class^="fi-tc-"]:before, span[class*=" fi-tc-"]:before {
font-family: flaticon-thin-chubby !important;
font-family: ficons-thin-chubby !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Thin Rounded (tr)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-thin-rounded";
src: url("../webfonts/flaticon-thin-rounded.woff2") format("woff2"),
url("../webfonts/flaticon-thin-rounded.woff") format("woff"),
url("../webfonts/flaticon-thin-rounded.ttf") format("truetype");
font-family: "ficons-thin-rounded";
src: url("../webfonts/ficons-thin-rounded.woff2") format("woff2"),
url("../webfonts/ficons-thin-rounded.woff") format("woff"),
url("../webfonts/ficons-thin-rounded.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-tr-"]:before, i[class*=" fi-tr-"]:before,
span[class^="fi-tr-"]:before, span[class*=" fi-tr-"]:before {
font-family: flaticon-thin-rounded !important;
font-family: ficons-thin-rounded !important;
font-style: normal;
font-weight: normal !important;
}
+6 -6
View File
@@ -1,19 +1,19 @@
/*!
* Thin Straight (ts)
* Generated from Flaticon icon API
* Icon font generated from SVG sources
*/
@import url("./base.css");
@font-face {
font-family: "flaticon-thin-straight";
src: url("../webfonts/flaticon-thin-straight.woff2") format("woff2"),
url("../webfonts/flaticon-thin-straight.woff") format("woff"),
url("../webfonts/flaticon-thin-straight.ttf") format("truetype");
font-family: "ficons-thin-straight";
src: url("../webfonts/ficons-thin-straight.woff2") format("woff2"),
url("../webfonts/ficons-thin-straight.woff") format("woff"),
url("../webfonts/ficons-thin-straight.ttf") format("truetype");
font-display: swap;
}
i[class^="fi-ts-"]:before, i[class*=" fi-ts-"]:before,
span[class^="fi-ts-"]:before, span[class*=" fi-ts-"]:before {
font-family: flaticon-thin-straight !important;
font-family: ficons-thin-straight !important;
font-style: normal;
font-weight: normal !important;
}
+2 -2
View File
@@ -1,6 +1,6 @@
/*!
* Flaticon Icon Fonts - All Icons
* Generated from Flaticon icon API
* Ficons Icon Fonts - All Icons
* Icon fonts generated from SVG sources
*/
@import url("./css/base.css");
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+333 -98
View File
@@ -1,119 +1,354 @@
#!/usr/bin/env node
const path = require('path');
const fs = require('fs');
const vm = require('vm');
const path = require('node:path');
const fs = require('node:fs');
const vm = require('node:vm');
const http = require('node:http');
const url = require('node:url');
const { parseArgs } = require('node:util');
const paths = {
css: path.join(__dirname, 'fonts', 'flaticon.css'),
webfonts: path.join(__dirname, 'fonts', 'webfonts'),
explorer: path.join(__dirname, 'explorer.html'),
iconData: path.join(__dirname, 'data', 'all_icons.js')
// ============================================================================
// CONSTANTS
// ============================================================================
const CONFIG = {
host: '127.0.0.1',
port: 12667,
paths: {
css: path.join(__dirname, 'fonts', 'ficons.css'),
webfonts: path.join(__dirname, 'fonts', 'webfonts'),
explorer: path.join(__dirname, 'explorer.html'),
iconData: path.join(__dirname, 'data', 'all_icons.js')
}
};
function loadIcons() {
const source = fs.readFileSync(paths.iconData, 'utf8');
const sandbox = { window: {} };
vm.createContext(sandbox);
vm.runInContext(source, sandbox);
const icons = sandbox.window.FLATICON_ICONS;
if (!Array.isArray(icons)) {
throw new Error('Icon data was not loaded correctly.');
}
return icons;
}
const MIME_TYPES = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
};
function matchIcon(icon, terms) {
const haystack = `${icon.name} ${icon.tags || ''}`.toLowerCase();
return terms.every((term) => haystack.includes(term));
}
const COMMANDS = {
EXPLORE: 'explore',
SEARCH: 'search'
};
function formatMatchesJson(matches) {
const grouped = new Map();
matches.forEach((icon) => {
if (!grouped.has(icon.name)) grouped.set(icon.name, new Set());
grouped.get(icon.name).add(icon.prefix);
});
const EXIT_CODES = {
SUCCESS: 0,
ERROR: 1
};
if (grouped.size === 0) {
return '{}';
// ============================================================================
// ICON DATA LAYER
// ============================================================================
class IconLoader {
constructor(dataPath) {
this.dataPath = dataPath;
this._cache = null;
}
const lines = [];
grouped.forEach((set, name) => {
const variations = Array.from(set).sort();
const variationsJson = `[${variations.map((v) => JSON.stringify(v)).join(', ')}]`;
lines.push(` ${JSON.stringify(name)}: ${variationsJson}`);
});
load() {
if (this._cache) {
return this._cache;
}
return `{\n${lines.join(',\n')}\n}`;
}
const source = fs.readFileSync(this.dataPath, 'utf8');
const sandbox = { window: {} };
vm.createContext(sandbox);
vm.runInContext(source, sandbox);
function printHelp() {
console.log('Usage: npx <this-package> <keyword...> [variation]');
console.log(' or: npx <this-package> <keyword...> --variation <prefix>');
console.log('Example: npx <this-package> camera');
console.log('Example: npx <this-package> arrow rr');
console.log('Outputs: JSON object {"icon-name": ["<prefix>", ...]}');
}
const icons = sandbox.window.FICONS_DATA;
if (!Array.isArray(icons)) {
throw new Error('Icon data was not loaded correctly.');
}
function runCli() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
printHelp();
process.exit(args.length === 0 ? 1 : 0);
this._cache = icons;
return icons;
}
let icons;
try {
icons = loadIcons();
} catch (error) {
console.error(error.message);
process.exit(1);
getPrefixes() {
return new Set(this.load().map(icon => icon.prefix));
}
}
class IconSearcher {
constructor(icons) {
this.icons = icons;
}
let variation = null;
const filteredArgs = [];
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === '-v' || arg === '--variation' || arg === '--variant') {
variation = args[i + 1];
if (!variation) {
console.error('Missing variation prefix after --variation.');
process.exit(1);
search(query, variation = null) {
const terms = this._parseQuery(query);
return this.icons.filter(icon =>
this._matches(icon, terms) && this._matchesVariation(icon, variation)
);
}
_parseQuery(query) {
return query.toLowerCase().split(/\s+/).filter(Boolean);
}
_matches(icon, terms) {
const haystack = `${icon.name} ${icon.tags || ''}`.toLowerCase();
return terms.every(term => haystack.includes(term));
}
_matchesVariation(icon, variation) {
return !variation || icon.prefix === variation;
}
}
class ResultFormatter {
static formatJson(matches) {
const grouped = this._groupByIconName(matches);
if (grouped.size === 0) {
return '{}';
}
const entries = Array.from(grouped.entries())
.map(([name, variations]) => {
const sortedVariations = Array.from(variations).sort();
const variationsJson = JSON.stringify(sortedVariations);
return ` ${JSON.stringify(name)}: ${variationsJson}`;
});
return `{\n${entries.join(',\n')}\n}`;
}
static _groupByIconName(matches) {
const grouped = new Map();
for (const icon of matches) {
if (!grouped.has(icon.name)) {
grouped.set(icon.name, new Set());
}
i += 1;
continue;
grouped.get(icon.name).add(icon.prefix);
}
filteredArgs.push(arg);
return grouped;
}
if (!variation && filteredArgs.length > 1) {
const prefixes = new Set(icons.map((icon) => icon.prefix));
const last = filteredArgs[filteredArgs.length - 1].toLowerCase();
if (prefixes.has(last)) {
variation = last;
filteredArgs.pop();
}
}
const query = filteredArgs.join(' ').trim();
if (!query) {
printHelp();
process.exit(1);
}
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
if (variation) variation = variation.toLowerCase();
const matches = icons.filter(
(icon) => matchIcon(icon, terms) && (!variation || icon.prefix === variation)
);
if (matches.length === 0) {
console.log('{}');
process.exit(0);
}
console.log(formatMatchesJson(matches));
}
runCli();
// ============================================================================
// HTTP SERVER LAYER
// ============================================================================
class FileServer {
constructor(rootDir) {
this.rootDir = rootDir;
}
serve(req, res) {
const requestPath = this._sanitizePath(req.url);
const fullPath = path.join(this.rootDir, requestPath);
this._serveFile(fullPath, req, res);
}
_sanitizePath(urlPath) {
const parsed = url.parse(urlPath);
let pathname = parsed.pathname;
if (pathname === '/') {
return '/explorer.html';
}
return pathname;
}
_serveFile(filepath, req, res) {
fs.stat(filepath, (err, stats) => {
if (err || !stats.isFile()) {
this._send404(res);
return;
}
this._sendFile(filepath, res);
});
}
_sendFile(filepath, res) {
fs.readFile(filepath, (err, data) => {
if (err) {
this._send404(res);
return;
}
const mimeType = this._getMimeType(filepath);
res.writeHead(200, {
'Content-Type': mimeType,
'Cache-Control': 'no-cache'
});
res.end(data);
});
}
_send404(res) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
_getMimeType(filepath) {
const ext = path.extname(filepath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream';
}
}
class ExplorerServer {
constructor(config) {
this.config = config;
this.fileServer = new FileServer(__dirname);
this.server = null;
}
start() {
this.server = http.createServer((req, res) => {
this.fileServer.serve(req, res);
});
this.server.listen(this.config.port, this.config.host, () => {
console.log(`Ficons Explorer running at http://${this.config.host}:${this.config.port}/`);
console.log('Press Ctrl+C to stop the server');
});
this.server.on('error', (err) => this._handleError(err));
}
_handleError(err) {
if (err.code === 'EADDRINUSE') {
console.error(`Error: Port ${this.config.port} is already in use.`);
} else {
console.error('Server error:', err.message);
}
process.exit(EXIT_CODES.ERROR);
}
}
// ============================================================================
// CLI LAYER
// ============================================================================
class CliParser {
static parse(args) {
if (args.length === 0) {
return { error: 'No command provided' };
}
const { values, positionals } = parseArgs({
args,
options: {
help: { type: 'boolean', short: 'h' },
variation: { type: 'string', short: 'v' },
},
allowPositionals: true,
});
return {
help: values.help,
command: positionals[0]?.toLowerCase(),
args: positionals.slice(1),
variation: values.variation
};
}
}
class SearchCommand {
constructor(iconLoader) {
this.iconLoader = iconLoader;
}
execute(positionals, variation) {
const { query, variation: detectedVariation } = this._parseInput(positionals, variation);
if (!query) {
console.error('Error: search command requires a query.');
console.log('Usage: npx @invisi/ficons search <query> [options]');
process.exit(EXIT_CODES.ERROR);
}
const icons = this.iconLoader.load();
const searcher = new IconSearcher(icons);
const matches = searcher.search(query, detectedVariation);
console.log(ResultFormatter.formatJson(matches));
}
_parseInput(positionals, variation) {
let effectiveVariation = variation;
// Auto-detect variation from last positional arg
if (!effectiveVariation && positionals.length > 1) {
const prefixes = this.iconLoader.getPrefixes();
const lastArg = positionals[positionals.length - 1].toLowerCase();
if (prefixes.has(lastArg)) {
effectiveVariation = lastArg;
positionals.pop();
}
}
return {
query: positionals.join(' ').trim(),
variation: effectiveVariation
};
}
}
class HelpPrinter {
static print() {
console.log('Usage: npx @invisi/ficons <command> [options]');
console.log('');
console.log('Commands:');
console.log(' explore Start local HTTP server and open icon explorer');
console.log(' search <query> Search for icons by name or tags');
console.log('');
console.log('Options:');
console.log(' -v, --variation <prefix> Filter by style variation (e.g., rs, rr, bs)');
console.log(' -h, --help Show this help message');
console.log('');
console.log('Examples:');
console.log(' npx @invisi/ficons explore');
console.log(' npx @invisi/ficons search camera');
console.log(' npx @invisi/ficons search arrow rr');
console.log(' npx @invisi/ficons search home --variation rs');
}
}
class CliApplication {
constructor() {
this.iconLoader = new IconLoader(CONFIG.paths.iconData);
this.searchCommand = new SearchCommand(this.iconLoader);
}
run(args) {
const parsed = CliParser.parse(args);
if (parsed.error || parsed.help) {
HelpPrinter.print();
process.exit(parsed.error ? EXIT_CODES.ERROR : EXIT_CODES.SUCCESS);
}
switch (parsed.command) {
case COMMANDS.EXPLORE:
new ExplorerServer(CONFIG).start();
break;
case COMMANDS.SEARCH:
this.searchCommand.execute(parsed.args, parsed.variation);
break;
default:
console.error(`Unknown command: ${parsed.command}`);
console.log('Run "npx @invisi/ficons --help" for usage information.');
process.exit(EXIT_CODES.ERROR);
}
}
}
// ============================================================================
// ENTRY POINT
// ============================================================================
const app = new CliApplication();
app.run(process.argv.slice(2));

Some files were not shown because too many files have changed in this diff Show More