favicon-stealer 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/Favicon.js +105 -0
- package/dist/index.js +8 -0
- package/dist/lib/utils/index.js +17 -0
- package/dist/types/Favicon.d.ts +14 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/lib/utils/index.d.ts +1 -0
- package/package.json +14 -6
- package/src/Favicon.tsx +28 -3
- package/tsconfig.json +7 -3
- /package/{lib → src/lib}/utils/index.ts +0 -0
package/README.md
CHANGED
|
@@ -21,7 +21,14 @@ import { Favicon } from 'favicon-stealer';
|
|
|
21
21
|
| `size` | `number` | The size of the favicon in pixels. Default is 32. |
|
|
22
22
|
| `className` | `string` | A class name to apply to the element. |
|
|
23
23
|
| `timeout` | `number` | The timeout in milliseconds for fetching the favicon. Default is 1000 (1 second). |
|
|
24
|
+
| `lazy` | `boolean` | Whether to load the favicon lazily. Default is false. |
|
|
25
|
+
| `border` | `boolean` | Whether to show a border around the favicon. Default is false. |
|
|
26
|
+
| `padding` | `number` | The padding in pixels.(px) Default is 0. |
|
|
27
|
+
| `background` | `string` | The background color of the favicon. Default is transparent.(in hex) |
|
|
28
|
+
| `borderRadius` | `number` | The border radius in pixels.(px) Default is 0. |
|
|
24
29
|
|
|
30
|
+
# npm package
|
|
31
|
+
https://www.npmjs.com/package/favicon-stealer
|
|
25
32
|
|
|
26
33
|
# license
|
|
27
34
|
MIT License
|
|
@@ -29,4 +36,6 @@ MIT License
|
|
|
29
36
|
# Changelog
|
|
30
37
|
- v1.0.0: Initial release (2025.1.21)
|
|
31
38
|
- v1.0.1: Add README.md (2025.1.21)
|
|
32
|
-
- v1.0.2: Update license to MIT (2025.1.21)
|
|
39
|
+
- v1.0.2: Update license to MIT (2025.1.21)
|
|
40
|
+
- v1.1.0: Fix show bug(2025.2.1)
|
|
41
|
+
- v1.2.0: Add props(lazy, border, padding, background, borderRadius)(2025.2.1)
|
package/dist/Favicon.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const utils_1 = require("./lib/utils");
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const Favicon = ({ url, size = 32, className = "", timeout = 1000, // 1 second
|
|
40
|
+
border = false, padding = 0, background = "transparent", borderRadius = 0, lazy = false, }) => {
|
|
41
|
+
const domain = (0, utils_1.getDomain)(url);
|
|
42
|
+
const [imgSrc, setImgSrc] = (0, react_1.useState)(`https://${domain}/logo.svg`);
|
|
43
|
+
const [fallbackIndex, setFallbackIndex] = (0, react_1.useState)(0);
|
|
44
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(true);
|
|
45
|
+
const [hasError, setHasError] = (0, react_1.useState)(false);
|
|
46
|
+
const fallbackSources = [
|
|
47
|
+
`https://${domain}/logo.svg`,
|
|
48
|
+
`https://${domain}/logo.png`,
|
|
49
|
+
`https://${domain}/apple-touch-icon.png`,
|
|
50
|
+
`https://${domain}/apple-touch-icon-precomposed.png`,
|
|
51
|
+
`https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
|
|
52
|
+
`https://icons.duckduckgo.com/ip3/${domain}.ico`,
|
|
53
|
+
`https://${domain}/favicon.ico`,
|
|
54
|
+
];
|
|
55
|
+
(0, react_1.useEffect)(() => {
|
|
56
|
+
let timeoutId;
|
|
57
|
+
if (isLoading) {
|
|
58
|
+
timeoutId = setTimeout(() => {
|
|
59
|
+
handleError();
|
|
60
|
+
}, timeout);
|
|
61
|
+
}
|
|
62
|
+
return () => {
|
|
63
|
+
if (timeoutId) {
|
|
64
|
+
clearTimeout(timeoutId);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}, [imgSrc, isLoading]);
|
|
68
|
+
const handleError = () => {
|
|
69
|
+
const nextIndex = fallbackIndex + 1;
|
|
70
|
+
if (nextIndex < fallbackSources.length) {
|
|
71
|
+
setFallbackIndex(nextIndex);
|
|
72
|
+
setImgSrc(fallbackSources[nextIndex]);
|
|
73
|
+
setIsLoading(true);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
setHasError(true);
|
|
77
|
+
setIsLoading(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const handleLoad = () => {
|
|
81
|
+
setIsLoading(false);
|
|
82
|
+
setHasError(false);
|
|
83
|
+
};
|
|
84
|
+
return (react_1.default.createElement("div", { className: `relative inline-block
|
|
85
|
+
${className}
|
|
86
|
+
${border ? "border" : ""}
|
|
87
|
+
${hasError ? "opacity-0" : ""}
|
|
88
|
+
${padding ? `p-[${padding}px]` : ""}
|
|
89
|
+
${borderRadius ? `rounded-[${borderRadius}px]` : ""}
|
|
90
|
+
`, style: {
|
|
91
|
+
width: size,
|
|
92
|
+
height: size,
|
|
93
|
+
background: background,
|
|
94
|
+
padding: padding ? `${padding}px` : 0,
|
|
95
|
+
borderRadius: borderRadius ? `${borderRadius}px` : 0,
|
|
96
|
+
} },
|
|
97
|
+
isLoading && (react_1.default.createElement("div", { className: "absolute inset-0 animate-pulse" },
|
|
98
|
+
react_1.default.createElement("div", { className: "w-full h-full rounded-md bg-gray-200/60" }))),
|
|
99
|
+
react_1.default.createElement("img", { src: imgSrc, alt: `${domain} logo`, width: size, height: size, loading: lazy ? "lazy" : "eager", onError: handleError, onLoad: handleLoad, className: `inline-block transition-opacity duration-300 ${isLoading ? "opacity-0" : "opacity-100"}`, style: {
|
|
100
|
+
objectFit: "contain",
|
|
101
|
+
display: hasError ? "none" : "inline-block",
|
|
102
|
+
} }),
|
|
103
|
+
hasError && (react_1.default.createElement("div", { className: "w-full h-full flex items-center justify-center bg-gray-100 rounded-md", style: { fontSize: `${size * 0.5}px` } }, domain.charAt(0).toUpperCase()))));
|
|
104
|
+
};
|
|
105
|
+
exports.default = Favicon;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Favicon = void 0;
|
|
7
|
+
var Favicon_1 = require("./Favicon");
|
|
8
|
+
Object.defineProperty(exports, "Favicon", { enumerable: true, get: function () { return __importDefault(Favicon_1).default; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDomain = void 0;
|
|
4
|
+
const getDomain = (url) => {
|
|
5
|
+
try {
|
|
6
|
+
// Add https:// protocol if not present
|
|
7
|
+
const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;
|
|
8
|
+
const domain = new URL(urlWithProtocol).hostname;
|
|
9
|
+
// Remove 'www.' prefix if exists
|
|
10
|
+
return domain.replace(/^www\./, '');
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
// Return original input if URL parsing fails
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
exports.getDomain = getDomain;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface IProps {
|
|
3
|
+
url: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
border?: boolean;
|
|
8
|
+
padding?: number;
|
|
9
|
+
background?: string;
|
|
10
|
+
borderRadius?: number;
|
|
11
|
+
lazy?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const Favicon: ({ url, size, className, timeout, border, padding, background, borderRadius, lazy, }: IProps) => React.JSX.Element;
|
|
14
|
+
export default Favicon;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Favicon } from './Favicon';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getDomain: (url: string) => string;
|
package/package.json
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "favicon-stealer",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"main": "index.js",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/types/index.d.ts",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
7
8
|
"build": "tsc",
|
|
8
9
|
"prepublishOnly": "npm run build"
|
|
9
10
|
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react",
|
|
13
|
+
"favicon",
|
|
14
|
+
"icon",
|
|
15
|
+
"logo",
|
|
16
|
+
"website",
|
|
17
|
+
"component"
|
|
18
|
+
],
|
|
19
|
+
"author": "Corey Chiu",
|
|
12
20
|
"license": "MIT",
|
|
13
|
-
"description": "",
|
|
21
|
+
"description": "Get clear and consistent favicon of a website easily",
|
|
14
22
|
"dependencies": {
|
|
15
23
|
"react": "^19.0.0",
|
|
16
24
|
"react-dom": "^19.0.0"
|
|
17
25
|
},
|
|
18
26
|
"devDependencies": {
|
|
19
|
-
"@types/react": "^19.0.
|
|
27
|
+
"@types/react": "^19.0.8",
|
|
20
28
|
"@types/react-dom": "^19.0.3",
|
|
21
29
|
"typescript": "^5.7.3"
|
|
22
30
|
}
|
package/src/Favicon.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { getDomain } from "
|
|
3
|
+
import { getDomain } from "./lib/utils";
|
|
4
4
|
import React, { useEffect, useState } from "react";
|
|
5
5
|
|
|
6
6
|
interface IProps {
|
|
@@ -8,6 +8,11 @@ interface IProps {
|
|
|
8
8
|
size?: number;
|
|
9
9
|
className?: string;
|
|
10
10
|
timeout?: number;
|
|
11
|
+
border?: boolean;
|
|
12
|
+
padding?: number;
|
|
13
|
+
background?: string;
|
|
14
|
+
borderRadius?: number;
|
|
15
|
+
lazy?: boolean;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
const Favicon = ({
|
|
@@ -15,6 +20,11 @@ const Favicon = ({
|
|
|
15
20
|
size = 32,
|
|
16
21
|
className = "",
|
|
17
22
|
timeout = 1000, // 1 second
|
|
23
|
+
border = false,
|
|
24
|
+
padding = 0,
|
|
25
|
+
background = "transparent",
|
|
26
|
+
borderRadius = 0,
|
|
27
|
+
lazy = false,
|
|
18
28
|
}: IProps) => {
|
|
19
29
|
const domain = getDomain(url);
|
|
20
30
|
const [imgSrc, setImgSrc] = useState(`https://${domain}/logo.svg`);
|
|
@@ -67,8 +77,22 @@ const Favicon = ({
|
|
|
67
77
|
|
|
68
78
|
return (
|
|
69
79
|
<div
|
|
70
|
-
className={
|
|
71
|
-
|
|
80
|
+
className={
|
|
81
|
+
`relative inline-block
|
|
82
|
+
${className}
|
|
83
|
+
${border ? "border" : ""}
|
|
84
|
+
${hasError ? "opacity-0" : ""}
|
|
85
|
+
${padding ? `p-[${padding}px]` : ""}
|
|
86
|
+
${borderRadius ? `rounded-[${borderRadius}px]` : ""}
|
|
87
|
+
`
|
|
88
|
+
}
|
|
89
|
+
style={{
|
|
90
|
+
width: size,
|
|
91
|
+
height: size,
|
|
92
|
+
background: background,
|
|
93
|
+
padding: padding ? `${padding}px` : 0,
|
|
94
|
+
borderRadius: borderRadius ? `${borderRadius}px` : 0,
|
|
95
|
+
}}
|
|
72
96
|
>
|
|
73
97
|
{/* placeholder */}
|
|
74
98
|
{isLoading && (
|
|
@@ -82,6 +106,7 @@ const Favicon = ({
|
|
|
82
106
|
alt={`${domain} logo`}
|
|
83
107
|
width={size}
|
|
84
108
|
height={size}
|
|
109
|
+
loading={lazy ? "lazy" : "eager"}
|
|
85
110
|
onError={handleError}
|
|
86
111
|
onLoad={handleLoad}
|
|
87
112
|
className={`inline-block transition-opacity duration-300 ${
|
package/tsconfig.json
CHANGED
|
@@ -8,14 +8,18 @@
|
|
|
8
8
|
"declarationDir": "dist/types",
|
|
9
9
|
"strict": true,
|
|
10
10
|
"moduleResolution": "node",
|
|
11
|
-
"baseUrl": "
|
|
11
|
+
"baseUrl": "./src",
|
|
12
|
+
"rootDir": "./src",
|
|
12
13
|
"paths": {
|
|
13
14
|
"@/*": ["./*"]
|
|
14
15
|
},
|
|
15
16
|
"esModuleInterop": true
|
|
16
17
|
},
|
|
17
18
|
"include": [
|
|
18
|
-
"src"
|
|
19
|
-
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist"
|
|
20
24
|
]
|
|
21
25
|
}
|
|
File without changes
|