Hotfix Lucide npm package, fix security issues (#84)

* adjust npmignore

* 📦 Fix security issues

* update font workflow

* identation

* update, package.json

* Add description

* Add shields
This commit is contained in:
Eric Fennis
2020-10-06 23:03:11 +02:00
committed by GitHub
parent 11c6a2e917
commit 4406cbaea0
43 changed files with 598 additions and 1586 deletions

View File

@@ -1,37 +1,28 @@
name: Build Featherity
name: Build Lucide
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Clone 'Featherity'
- name: Clone 'Lucide'
uses: actions/checkout@v2
- name: Install FontForge
run: sudo apt-get install zlib1g-dev fontforge woff2
- name: Clone sfnt2woff-zopfli repo
run: git clone https://github.com/bramstein/sfnt2woff-zopfli.git sfnt2woff-zopfli
- name: Install and move sfnt2woff-zopfli
run: |
cd sfnt2woff-zopfli
make
sudo mv sfnt2woff-zopfli /usr/local/bin/sfnt2woff
- name: Clone woff2
run: git clone --recursive https://github.com/google/woff2.git
@@ -41,23 +32,17 @@ jobs:
sudo make clean all
sudo mv woff2_compress /usr/local/bin/ && sudo mv woff2_decompress /usr/local/bin/
- name: Install Font Custom dependency
run: sudo gem install fontcustom
- name: Build 'Featherity'
- name: Build 'Lucide'
run: echo "Building Featherity font" && fontcustom compile ./icons -h -n Featherity -o build -F
- name: Zip 'Featherity'
- name: Zip 'Lucide'
run: zip -r Featherity.zip build
- name: 'Upload to Artifacts'
uses: actions/upload-artifact@v1.0.0
with:
name: Featherity
path: build
name: Lucide
path: build

View File

@@ -1,4 +1,6 @@
.github
packages
stats
build
node_modules
tests
scripts

View File

@@ -1,5 +1,7 @@
# Lucide
![NPM](https://img.shields.io/npm/l/lucide)
[![npm](https://img.shields.io/npm/v/lucide)](https://www.npmjs.com/package/lucide)
[![Discord](https://img.shields.io/discord/723074157486800936?label=chat&logo=discord&logoColor=%23ffffff&colorB=%237289DA)](https://discord.gg/EH6nSts)
## What is Lucide?

View File

Before

Width:  |  Height:  |  Size: 329 B

After

Width:  |  Height:  |  Size: 329 B

View File

@@ -1,11 +1,12 @@
{
"name": "lucide",
"description": "Lucide is a community-run fork of Feather Icons, open for anyone to contribute icons.",
"version": "0.1.1",
"license": "ISC",
"amdName": "lucide",
"homepage": "https://featherity.netlify.app",
"url": "https://github.com/owner/project/issues",
"repository": "github:lucide-icons/lucide",
"url" : "https://github.com/owner/project/issues",
"license": "ISC",
"version": "0.1.0",
"source": "build/lucide.js",
"main": "dist/cjs/lucide.js",
"main:umd": "dist/umd/lucide.js",

View File

@@ -1,13 +0,0 @@
{
"presets": [
[
"env",
{
"targets": {
"browsers": ["last 2 versions"]
}
}
],
"stage-2"
]
}

View File

@@ -1,2 +0,0 @@
dist
coverage

View File

@@ -1,17 +0,0 @@
{
"extends": ["airbnb-base", "prettier"],
"plugins": ["import", "prettier"],
"rules": {
"no-console": ["error", { "allow": ["warn", "error"] }],
"no-param-reassign": "off",
"no-shadow": "off",
"no-use-before-define": "off",
"prettier/prettier": [
"error",
{
"singleQuote": true,
"trailingComma": "all"
}
]
}
}

View File

@@ -1,8 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`builds object correctly 1`] = `
Object {
"icon1": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line>",
"icon2": "<circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle>",
}
`;

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`builds sprite correctly 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\"><defs><symbol id=\\"icon1\\" viewBox=\\"0 0 24 24\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></symbol><symbol id=\\"icon2\\" viewBox=\\"0 0 24 24\\"><circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle></symbol></defs></svg>"`;

View File

@@ -1,26 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`processes SVG correctly 1`] = `
"<svg
xmlns=\\"http://www.w3.org/2000/svg\\"
width=\\"24\\"
height=\\"24\\"
viewBox=\\"0 0 24 24\\"
fill=\\"none\\"
stroke=\\"currentColor\\"
stroke-width=\\"2\\"
stroke-linecap=\\"round\\"
stroke-linejoin=\\"round\\"
>
<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" />
<line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />
</svg>
"
`;
exports[`rejects when passed unparsable SVG string 1`] = `
[Error: Error in parsing SVG: Unclosed root tag
Line: 0
Column: 10
Char: ]
`;

View File

@@ -1,17 +0,0 @@
/* eslint-env jest */
import buildIconsObject from '../build-icons-object';
const SVG_FILES = {
'icon1.svg':
'<svg\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n>\n <line x1="23" y1="1" x2="1" y2="23" />\n <line x1="1" y1="1" x2="23" y2="23" />\n</svg>',
'icon2.svg':
'<svg\n xmlns="http://www.w3.org/2000/svg"\n width="24"\n height="24"\n viewBox="0 0 24 24"\n>\n <circle cx="12" cy="12" r="11" />\n</svg>',
};
function getSvg(svgFile) {
return SVG_FILES[svgFile];
}
test('builds object correctly', () => {
expect(buildIconsObject(Object.keys(SVG_FILES), getSvg)).toMatchSnapshot();
});

View File

@@ -1,12 +0,0 @@
/* eslint-env jest */
import buildSpriteString from '../build-sprite-string';
const icons = {
icon1:
'<line x1="23" y1="1" x2="1" y2="23"></line><line x1="1" y1="1" x2="23" y2="23"></line>',
icon2: '<circle cx="12" cy="12" r="11"></circle>',
};
test('builds sprite correctly', () => {
expect(buildSpriteString(icons)).toMatchSnapshot();
});

View File

@@ -1,15 +0,0 @@
/* eslint-env jest */
import processSvg from '../process-svg';
test('processes SVG correctly', () => {
const SVG =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>Title</title><line x1="23" y1="1" x2="1" y2="23" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><line x1="1" y1="1" x2="23" y2="23" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>';
expect(processSvg(SVG)).resolves.toMatchSnapshot();
});
test('rejects when passed unparsable SVG string', () => {
const UNPARSABLE_SVG = '<svg></svg';
expect(processSvg(UNPARSABLE_SVG)).rejects.toMatchSnapshot();
});

View File

@@ -1,19 +0,0 @@
import fs from 'fs';
import path from 'path';
import buildIconsObject from './build-icons-object';
const IN_DIR = path.resolve(__dirname, '../icons');
const OUT_FILE = path.resolve(__dirname, '../dist/icons.json');
console.log(`Building ${OUT_FILE}...`);
const svgFiles = fs
.readdirSync(IN_DIR)
.filter(file => path.extname(file) === '.svg');
const getSvg = svgFile => fs.readFileSync(path.join(IN_DIR, svgFile));
const icons = buildIconsObject(svgFiles, getSvg);
fs.writeFileSync(OUT_FILE, JSON.stringify(icons));

View File

@@ -1,35 +0,0 @@
import path from 'path';
import cheerio from 'cheerio';
import { minify } from 'html-minifier';
/**
* Build an object in the format: `{ <name>: <contents> }`.
* @param {string[]} svgFiles - A list of filenames.
* @param {Function} getSvg - A function that returns the contents of an SVG file given a filename.
* @returns {Object}
*/
function buildIconsObject(svgFiles, getSvg) {
return svgFiles
.map(svgFile => {
const name = path.basename(svgFile, '.svg');
const svg = getSvg(svgFile);
const contents = getSvgContents(svg);
return { name, contents };
})
.reduce((icons, icon) => {
icons[icon.name] = icon.contents;
return icons;
}, {});
}
/**
* Get contents between opening and closing `<svg>` tags.
* @param {string} svg
* @returns {string}
*/
function getSvgContents(svg) {
const $ = cheerio.load(svg);
return minify($('svg').html(), { collapseWhitespace: true });
}
export default buildIconsObject;

View File

@@ -1,28 +0,0 @@
import DEFAULT_ATTRS from '../src/default-attrs.json';
/**
* Build an SVG sprite string containing SVG symbols.
* @param {Object} icons
* @returns {string}
*/
function buildSpriteString(icons) {
const symbols = Object.keys(icons)
.map(icon => toSvgSymbol(icon, icons[icon]))
.join('');
return `<svg xmlns="${DEFAULT_ATTRS.xmlns}"><defs>${symbols}</defs></svg>`;
}
/**
* Create an SVG symbol string.
* @param {string} name - Icon name
* @param {string} contents - SVG contents
* @returns {string}
*/
function toSvgSymbol(name, contents) {
return `<symbol id="${name}" viewBox="${DEFAULT_ATTRS.viewBox}">${
contents
}</symbol>`;
}
export default buildSpriteString;

View File

@@ -1,10 +0,0 @@
import fs from 'fs';
import path from 'path';
import icons from '../dist/icons.json';
import buildSpriteString from './build-sprite-string';
const OUT_FILE = path.resolve(__dirname, '../dist/feather-sprite.svg');
console.log(`Building ${OUT_FILE}...`);
fs.writeFileSync(OUT_FILE, buildSpriteString(icons));

View File

@@ -1,13 +0,0 @@
import fs from 'fs';
import path from 'path';
import icons from '../src/icons';
const OUT_DIR = path.resolve(__dirname, '../dist/icons');
console.log(`Building SVGs in ${OUT_DIR}...`);
Object.keys(icons).forEach(name => {
const svg = icons[name].toSvg();
fs.writeFileSync(path.join(OUT_DIR, `${name}.svg`), svg);
});

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# Process SVG files
npx babel-node bin/process-svgs.js
# Create dist directory
npx rimraf dist
mkdir dist
# Build icons.json
npx babel-node bin/build-icons-json.js
# Build SVG sprite
npx babel-node bin/build-sprite.js
# Create dist/icons directory
npx rimraf dist/icons
mkdir dist/icons
# Build SVG icons
npx babel-node bin/build-svgs.js
# Build JavaScript library
npx webpack --output-filename feather.js --mode development
npx webpack --output-filename feather.min.js --mode production

View File

@@ -1,58 +0,0 @@
import Svgo from 'svgo';
import cheerio from 'cheerio';
import { format } from 'prettier';
import DEFAULT_ATTRS from '../src/default-attrs.json';
/**
* Process SVG string.
* @param {string} svg - An SVG string.
* @param {Promise<string>}
*/
function processSvg(svg) {
return (
optimize(svg)
.then(setAttrs)
.then(format)
// remove semicolon inserted by prettier
// because prettier thinks it's formatting JSX not HTML
.then(svg => svg.replace(/;/g, ''))
);
}
/**
* Optimize SVG with `svgo`.
* @param {string} svg - An SVG string.
* @returns {Promise<string>}
*/
function optimize(svg) {
const svgo = new Svgo({
plugins: [
{ convertShapeToPath: false },
{ mergePaths: false },
{ removeAttrs: { attrs: '(fill|stroke.*)' } },
{ removeTitle: true },
],
});
return new Promise(resolve => {
svgo.optimize(svg, ({ data }) => resolve(data));
});
}
/**
* Set default attibutes on SVG.
* @param {string} svg - An SVG string.
* @returns {string}
*/
function setAttrs(svg) {
const $ = cheerio.load(svg);
Object.keys(DEFAULT_ATTRS).forEach(key =>
$('svg').attr(key, DEFAULT_ATTRS[key]),
);
return $('body').html();
}
export default processSvg;

View File

@@ -1,18 +0,0 @@
import fs from 'fs';
import path from 'path';
import processSvg from './process-svg';
const IN_DIR = path.resolve(__dirname, '../icons');
console.log(`Processing SVGs in ${IN_DIR}...`);
fs
.readdirSync(IN_DIR)
.filter(file => path.extname(file) === '.svg')
.forEach(svgFile => {
const svg = fs.readFileSync(path.join(IN_DIR, svgFile));
processSvg(svg).then(svg =>
fs.writeFileSync(path.join(IN_DIR, svgFile), svg),
);
});

View File

@@ -1,73 +0,0 @@
import algolia from 'algoliasearch';
import icons from '../dist/icons.json';
import tags from '../src/tags.json';
const ALGOLIA_APP_ID = '5EEOG744D0';
if (
process.env.TRAVIS_PULL_REQUEST === 'false' &&
process.env.TRAVIS_BRANCH === 'master'
) {
syncAlgolia();
} else {
console.log('Skipped Algolia sync.');
}
function syncAlgolia() {
// ALGOLIA_ADMIN_KEY must be added as an environment variable in Travis CI
const client = algolia(ALGOLIA_APP_ID, process.env.ALGOLIA_ADMIN_KEY);
console.log('Initializing target and temporary indexes...');
const index = client.initIndex('icons');
const indexTmp = client.initIndex('icons_tmp');
console.log(
"Copying target index's settings, synonyms and rules into temporary index...",
);
scopedCopyIndex(client, index.indexName, indexTmp.indexName)
.then(() => {
const objects = Object.keys(icons).map(name => ({
name,
tags: tags[name] || [],
}));
console.log('Adding objects to the temporary index...');
return addObjects(indexTmp, objects);
})
.then(() => {
console.log('Moving temporary index to target index...');
return moveIndex(client, indexTmp.indexName, index.indexName);
});
}
function scopedCopyIndex(
client,
indexNameSrc,
indexNameDest,
scope = ['settings', 'synonyms', 'rules'],
) {
return new Promise((resolve, reject) => {
client.copyIndex(indexNameSrc, indexNameDest, scope, (error, contents) => {
if (error) reject(error);
resolve(contents);
});
});
}
function addObjects(index, objects) {
return new Promise((resolve, reject) => {
index.addObjects(objects, (error, contents) => {
if (error) reject(error);
resolve(contents);
});
});
}
function moveIndex(client, indexNameSrc, indexNameDest) {
return new Promise((resolve, reject) => {
client.moveIndex(indexNameSrc, indexNameDest, (error, contents) => {
if (error) reject(error);
resolve(contents);
});
});
}

View File

@@ -1,67 +0,0 @@
{
"name": "feather-icons",
"version": "0.0.0-development",
"description": "Simply beautiful open source icons",
"main": "dist/feather.js",
"unpkg": "dist/feather.min.js",
"files": [
"dist"
],
"scripts": {
"all": "npm-run-all --sequential build lint test:coverage",
"build": "./bin/build.sh",
"lint": "eslint .",
"test": "jest --watch",
"test:coverage": "jest --coverage",
"cm": "git-cz",
"precommit": "lint-staged",
"commitmsg": "commitlint --edit"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js"
]
},
"dependencies": {
"classnames": "^2.2.5",
"core-js": "^3.1.3"
},
"devDependencies": {
"@commitlint/cli": "^5.2.5",
"@commitlint/config-conventional": "^6.1.3",
"algoliasearch": "^3.27.1",
"babel-cli": "^6.24.1",
"babel-loader": "^7.1.1",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-2": "^6.24.1",
"cheerio": "^1.0.0-rc.2",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.1.0",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.5.0",
"eslint-plugin-prettier": "^2.5.0",
"html-minifier": "^3.5.8",
"husky": "^0.14.3",
"jest": "^22.4.4",
"lint-staged": "^6.0.0",
"npm-run-all": "^4.1.2",
"prettier": "^1.8.2",
"semantic-release": "^12.2.2",
"svgo": "^0.7.2",
"webpack": "^4.8.3",
"webpack-cli": "^2.1.3"
},
"repository": {
"type": "git",
"url": "https://github.com/feathericons/feather.git"
},
"author": "Cole Bemis <cole@colebemis.com> (http://colebemis.com)",
"license": "MIT"
}

View File

@@ -1,54 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`constructs icon object correctly 1`] = `
Icon {
"attrs": Object {
"class": "feather feather-test",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
"name": "test",
"tags": Array [
"hello",
"world",
"foo",
"bar",
],
}
`;
exports[`constructs icon object correctly 2`] = `
Icon {
"attrs": Object {
"class": "feather feather-test",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
"name": "test",
"tags": Array [],
}
`;
exports[`toString() returns correct string 1`] = `"<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />"`;
exports[`toSvg() returns correct string 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`toSvg() returns correct string 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`toSvg() returns correct string 3`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test foo bar\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;

View File

@@ -1,45 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`exports correct object 1`] = `
Object {
"icon1": Icon {
"attrs": Object {
"class": "feather feather-icon1",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
"name": "icon1",
"tags": Array [
"foo",
"bar",
"hello",
"world",
],
},
"icon2": Icon {
"attrs": Object {
"class": "feather feather-icon2",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\" />",
"name": "icon2",
"tags": Array [],
},
}
`;

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`throws an error when run in node environment 1`] = `"\`feather.replace()\` only works in a browser environment."`;

View File

@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`copies placeholder element attributes to <svg> tag 1`] = `"<i data-feather=\\"icon1\\" id=\\"test\\" class=\\"foo bar\\" stroke-width=\\"1\\"></i>"`;
exports[`copies placeholder element attributes to <svg> tag 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar\\" id=\\"test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg>"`;
exports[`replaces [data-feather] elements with SVG markup 1`] = `"<i data-feather=\\"icon1\\"></i><span data-feather=\\"icon2\\"></span>"`;
exports[`replaces [data-feather] elements with SVG markup 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg><svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon2\\"><circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle></svg>"`;
exports[`sets attributes passed as parameters 1`] = `"<i data-feather=\\"icon1\\" id=\\"test\\" class=\\"foo bar\\" stroke-width=\\"1\\"></i>"`;
exports[`sets attributes passed as parameters 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar hello\\" color=\\"salmon\\" id=\\"test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg>"`;

View File

@@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`returns correct string 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`returns correct string 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`returns correct string 3`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;

View File

@@ -1,28 +0,0 @@
/* eslint-env jest */
import Icon from '../icon';
const icon1 = new Icon(
'test',
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
['hello', 'world', 'foo', 'bar'],
);
const icon2 = new Icon(
'test',
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
);
test('constructs icon object correctly', () => {
expect(icon1).toMatchSnapshot();
expect(icon2).toMatchSnapshot();
});
test('toSvg() returns correct string', () => {
expect(icon1.toSvg()).toMatchSnapshot();
expect(icon1.toSvg({ 'stroke-width': 1, color: 'red' })).toMatchSnapshot();
expect(icon1.toSvg({ class: 'foo bar', color: 'red' })).toMatchSnapshot();
});
test('toString() returns correct string', () => {
expect(icon1.toString()).toMatchSnapshot();
});

View File

@@ -1,16 +0,0 @@
/* eslint-env jest */
import icons from '../icons';
jest.mock('../../dist/icons.json', () => ({
icon1:
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
icon2: '<circle cx="12" cy="12" r="11" />',
}));
jest.mock('../tags.json', () => ({
icon1: ['foo', 'bar', 'hello', 'world'],
}));
test('exports correct object', () => {
expect(icons).toMatchSnapshot();
});

View File

@@ -1,8 +0,0 @@
/* eslint-env jest */
import feather from '../index';
test('has correct properties', () => {
expect(feather).toHaveProperty('icons');
expect(feather).toHaveProperty('toSvg');
expect(feather).toHaveProperty('replace');
});

View File

@@ -1,10 +0,0 @@
/**
* @jest-environment node
*/
/* eslint-env jest */
import replace from '../replace';
test('throws an error when run in node environment', () => {
expect(replace).toThrowErrorMatchingSnapshot();
});

View File

@@ -1,32 +0,0 @@
/* eslint-env jest, browser */
import replace from '../replace';
jest.mock('../../dist/icons.json', () => ({
icon1:
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
icon2: '<circle cx="12" cy="12" r="11" />',
}));
test('replaces [data-feather] elements with SVG markup', () => {
document.body.innerHTML =
'<i data-feather="icon1"></i><span data-feather="icon2"></i>';
expect(document.body.innerHTML).toMatchSnapshot();
replace();
expect(document.body.innerHTML).toMatchSnapshot();
});
test('copies placeholder element attributes to <svg> tag', () => {
document.body.innerHTML =
'<i data-feather="icon1" id="test" class="foo bar" stroke-width="1"></i>';
expect(document.body.innerHTML).toMatchSnapshot();
replace();
expect(document.body.innerHTML).toMatchSnapshot();
});
test('sets attributes passed as parameters', () => {
document.body.innerHTML =
'<i data-feather="icon1" id="test" class="foo bar" stroke-width="1"></i>';
expect(document.body.innerHTML).toMatchSnapshot();
replace({ class: 'foo bar hello', 'stroke-width': 1.5, color: 'salmon' });
expect(document.body.innerHTML).toMatchSnapshot();
});

View File

@@ -1,21 +0,0 @@
/* eslint-env jest */
import toSvg from '../to-svg';
jest.mock('../../dist/icons.json', () => ({
icon1:
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
}));
test('returns correct string', () => {
expect(toSvg('icon1')).toMatchSnapshot();
expect(toSvg('icon1', { 'stroke-width': 1, color: 'red' })).toMatchSnapshot();
expect(toSvg('icon1', { class: 'foo bar', color: 'red' })).toMatchSnapshot();
});
test('throws error when `name` parameter is undefined', () => {
expect(() => toSvg()).toThrow();
});
test('throws error when passed unknown icon name', () => {
expect(() => toSvg('foo')).toThrow();
});

View File

@@ -1,11 +0,0 @@
{
"xmlns": "http://www.w3.org/2000/svg",
"width": 24,
"height": 24,
"viewBox": "0 0 24 24",
"fill": "none",
"stroke": "currentColor",
"stroke-width": 2,
"stroke-linecap": "round",
"stroke-linejoin": "round"
}

View File

@@ -1,55 +0,0 @@
import classnames from 'classnames/dedupe';
import DEFAULT_ATTRS from './default-attrs.json';
class Icon {
constructor(name, contents, tags = []) {
this.name = name;
this.contents = contents;
this.tags = tags;
this.attrs = {
...DEFAULT_ATTRS,
...{ class: `feather feather-${name}` },
};
}
/**
* Create an SVG string.
* @param {Object} attrs
* @returns {string}
*/
toSvg(attrs = {}) {
const combinedAttrs = {
...this.attrs,
...attrs,
...{ class: classnames(this.attrs.class, attrs.class) },
};
return `<svg ${attrsToString(combinedAttrs)}>${this.contents}</svg>`;
}
/**
* Return string representation of an `Icon`.
*
* Added for backward compatibility. If old code expects `feather.icons.<name>`
* to be a string, `toString()` will get implicitly called.
*
* @returns {string}
*/
toString() {
return this.contents;
}
}
/**
* Convert attributes object to string of HTML attributes.
* @param {Object} attrs
* @returns {string}
*/
function attrsToString(attrs) {
return Object.keys(attrs)
.map(key => `${key}="${attrs[key]}"`)
.join(' ');
}
export default Icon;

View File

@@ -1,10 +0,0 @@
import Icon from './icon';
import icons from '../dist/icons.json';
import tags from './tags.json';
export default Object.keys(icons)
.map(key => new Icon(key, icons[key], tags[key]))
.reduce((object, icon) => {
object[icon.name] = icon;
return object;
}, {});

View File

@@ -1,5 +0,0 @@
import icons from './icons';
import toSvg from './to-svg';
import replace from './replace';
module.exports = { icons, toSvg, replace };

View File

@@ -1,60 +0,0 @@
/* eslint-env browser */
import classnames from 'classnames/dedupe';
import icons from './icons';
/**
* Replace all HTML elements that have a `data-feather` attribute with SVG markup
* corresponding to the element's `data-feather` attribute value.
* @param {Object} attrs
*/
function replace(attrs = {}) {
if (typeof document === 'undefined') {
throw new Error('`feather.replace()` only works in a browser environment.');
}
const elementsToReplace = document.querySelectorAll('[data-feather]');
Array.from(elementsToReplace).forEach(element =>
replaceElement(element, attrs),
);
}
/**
* Replace a single HTML element with SVG markup
* corresponding to the element's `data-feather` attribute value.
* @param {HTMLElement} element
* @param {Object} attrs
*/
function replaceElement(element, attrs = {}) {
const elementAttrs = getAttrs(element);
const name = elementAttrs['data-feather'];
delete elementAttrs['data-feather'];
const svgString = icons[name].toSvg({
...attrs,
...elementAttrs,
...{ class: classnames(attrs.class, elementAttrs.class) },
});
const svgDocument = new DOMParser().parseFromString(
svgString,
'image/svg+xml',
);
const svgElement = svgDocument.querySelector('svg');
element.parentNode.replaceChild(svgElement, element);
}
/**
* Get the attributes of an HTML element.
* @param {HTMLElement} element
* @returns {Object}
*/
function getAttrs(element) {
return Array.from(element.attributes).reduce((attrs, attr) => {
attrs[attr.name] = attr.value;
return attrs;
}, {});
}
export default replace;

View File

@@ -1,30 +0,0 @@
import icons from './icons';
/**
* Create an SVG string.
* @deprecated
* @param {string} name
* @param {Object} attrs
* @returns {string}
*/
function toSvg(name, attrs = {}) {
console.warn(
'feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead.',
);
if (!name) {
throw new Error('The required `key` (icon name) parameter is missing.');
}
if (!icons[name]) {
throw new Error(
`No icon matching '${
name
}'. See the complete list of icons at https://feathericons.com`,
);
}
return icons[name].toSvg(attrs);
}
export default toSvg;

View File

@@ -1,23 +0,0 @@
const path = require('path');
module.exports = {
entry: ['core-js/es/array/from', path.resolve(__dirname, 'src/index.js')],
output: {
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'umd',
library: 'feather',
// Prevents webpack from referencing `window` in the UMD build
// Source: https://git.io/vppgU
globalObject: "typeof self !== 'undefined' ? self : this",
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
};

1246
yarn.lock

File diff suppressed because it is too large Load Diff