@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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/generator.js +34 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kntic/links",
3
- "version": "0.2.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://gitlab.kommune7.wien/kommune7/apps/links.git"
31
+ "url": "https://github.com/thomasrobak/kntic-links.git"
32
32
  },
33
33
  "bugs": {
34
- "url": "https://gitlab.kommune7.wien/kommune7/apps/links/-/issues"
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
- const dataUri = inlineImage(config.avatar, configDir);
110
- avatarHTML = `<img class="profile__avatar" src="${dataUri}" alt="${esc(name)}" width="88" height="88">`;
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.ai">Powered by KNTIC Links</a>
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>