@kntic/links 0.2.0 → 0.3.1
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/package.json +3 -3
- package/src/generator.js +34 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kntic/links",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "CLI for building and deploying self-hosted link-in-bio pages. No accounts, no tracking — just HTML.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"homepage": "https://kntic.ai",
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "https://
|
|
31
|
+
"url": "https://github.com/thomasrobak/kntic-links.git"
|
|
32
32
|
},
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://
|
|
34
|
+
"url": "https://github.com/thomasrobak/kntic-links/issues"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"prepublishOnly": "node -e \"import('./src/config.js').then(() => console.log('prepublish check passed'))\""
|
package/src/generator.js
CHANGED
|
@@ -28,6 +28,15 @@ function esc(str) {
|
|
|
28
28
|
.replace(/'/g, ''');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Extract the --accent-color value from a CSS string.
|
|
33
|
+
* Falls back to '#6366f1' (indigo) if not found.
|
|
34
|
+
*/
|
|
35
|
+
export function extractAccentColor(css) {
|
|
36
|
+
const match = css.match(/--accent-color:\s*([^;]+);/);
|
|
37
|
+
return match ? match[1].trim() : '#6366f1';
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
/** MIME type lookup for common image formats. */
|
|
32
41
|
function imageMime(filePath) {
|
|
33
42
|
const ext = extname(filePath).toLowerCase();
|
|
@@ -104,16 +113,37 @@ export function generatePage(config, options = {}) {
|
|
|
104
113
|
|
|
105
114
|
// Avatar handling
|
|
106
115
|
let avatarHTML = '';
|
|
116
|
+
let avatarDataUri = null;
|
|
107
117
|
if (config.avatar && config.avatar.trim().length > 0) {
|
|
108
118
|
try {
|
|
109
|
-
|
|
110
|
-
avatarHTML = `<img class="profile__avatar" src="${
|
|
119
|
+
avatarDataUri = inlineImage(config.avatar, configDir);
|
|
120
|
+
avatarHTML = `<img class="profile__avatar" src="${avatarDataUri}" alt="${esc(name)}" width="88" height="88">`;
|
|
111
121
|
} catch {
|
|
112
122
|
// Fallback: reference the file directly (it will be copied to output dir)
|
|
113
123
|
avatarHTML = `<img class="profile__avatar" src="${esc(config.avatar)}" alt="${esc(name)}" width="88" height="88">`;
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
// Favicon handling — Strategy A (avatar) or Strategy B (SVG letter)
|
|
128
|
+
let faviconHTML = '';
|
|
129
|
+
if (avatarDataUri) {
|
|
130
|
+
// Strategy A: reuse the already-computed avatar data URI
|
|
131
|
+
const avatarMime = avatarDataUri.startsWith('data:image/svg') ? 'image/svg+xml' : 'image/png';
|
|
132
|
+
faviconHTML = ` <link rel="icon" type="${avatarMime}" href="${avatarDataUri}">\n`
|
|
133
|
+
+ ` <link rel="icon" type="${avatarMime}" sizes="32x32" href="${avatarDataUri}">`;
|
|
134
|
+
} else {
|
|
135
|
+
// Strategy B: generate SVG favicon from first letter of name
|
|
136
|
+
const firstChar = name[0].toUpperCase();
|
|
137
|
+
const accent = extractAccentColor(css);
|
|
138
|
+
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'>`
|
|
139
|
+
+ `<rect width='64' height='64' rx='12' fill='${accent}'/>`
|
|
140
|
+
+ `<text x='32' y='32' text-anchor='middle' dominant-baseline='central' font-family='system-ui,sans-serif' font-size='32' font-weight='700' fill='white'>${firstChar}</text>`
|
|
141
|
+
+ `</svg>`;
|
|
142
|
+
const svgDataUri = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
|
|
143
|
+
faviconHTML = ` <link rel="icon" type="image/svg+xml" href="${svgDataUri}">\n`
|
|
144
|
+
+ ` <link rel="icon" type="image/svg+xml" sizes="32x32" href="${svgDataUri}">`;
|
|
145
|
+
}
|
|
146
|
+
|
|
117
147
|
// Build link list HTML
|
|
118
148
|
const linksHTML = links.map((link) => {
|
|
119
149
|
const icon = link.icon ? `<span class="links__icon">${esc(link.icon)}</span>` : '';
|
|
@@ -142,6 +172,7 @@ export function generatePage(config, options = {}) {
|
|
|
142
172
|
<meta property="og:title" content="${esc(name)}">
|
|
143
173
|
<meta property="og:description" content="${esc(ogDescription)}">
|
|
144
174
|
<meta property="og:type" content="website">
|
|
175
|
+
${faviconHTML}
|
|
145
176
|
<style>
|
|
146
177
|
${css}
|
|
147
178
|
</style>
|
|
@@ -157,7 +188,7 @@ ${linksHTML}
|
|
|
157
188
|
</ul>
|
|
158
189
|
|
|
159
190
|
<footer class="footer">
|
|
160
|
-
<a class="footer__link" href="https://kntic
|
|
191
|
+
<a class="footer__link" href="https://github.com/thomasrobak/kntic-links">Powered by KNTIC Links</a>
|
|
161
192
|
</footer>
|
|
162
193
|
</body>
|
|
163
194
|
</html>
|