opentwig 1.0.0 → 1.0.3

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 CHANGED
@@ -8,7 +8,7 @@ OpenTwig is an open source personal link page generator that creates beautiful,
8
8
  - 📱 **Mobile Responsive**: Optimized for all devices with mobile-first design
9
9
  - 🚀 **Fast & Lightweight**: Generates static HTML/CSS with minimal dependencies
10
10
  - 🔗 **Easy Link Management**: Simple JSON configuration for all your links
11
- - 🖼️ **Avatar Support**: Custom profile pictures with automatic processing
11
+ - 🖼️ **Optional Avatar Support**: Custom profile pictures with automatic processing (completely optional)
12
12
  - 📊 **Open Graph Images**: Auto-generated social media preview images
13
13
  - 📱 **QR Code Generation**: Built-in QR codes for easy mobile sharing
14
14
  - 🎭 **Modal Dialogs**: Support for rich content in footer links
@@ -38,6 +38,7 @@ npx opentwig
38
38
 
39
39
  OpenTwig uses a simple JSON configuration file (`config.json`) to define your page. Here's the complete configuration structure:
40
40
 
41
+ ### With Avatar (Optional)
41
42
  ```json
42
43
  {
43
44
  "theme": "default",
@@ -77,6 +78,39 @@ OpenTwig uses a simple JSON configuration file (`config.json`) to define your pa
77
78
  }
78
79
  ```
79
80
 
81
+ ### Without Avatar (Minimal Configuration)
82
+ ```json
83
+ {
84
+ "theme": "default",
85
+ "url": "https://links.yourwebsite.com",
86
+ "title": "Your Name - opentwig 🌿",
87
+ "name": "Your Name",
88
+ "content": "Hello World! Here is my bio.",
89
+ "minify": true,
90
+ "links": [
91
+ {
92
+ "url": "https://twitter.com",
93
+ "title": "Twitter"
94
+ },
95
+ {
96
+ "url": "https://instagram.com",
97
+ "title": "Instagram"
98
+ }
99
+ ],
100
+ "footerLinks": [
101
+ {
102
+ "title": "Contact",
103
+ "url": "mailto:mail@mail.com"
104
+ }
105
+ ],
106
+ "share": {
107
+ "title": "Your Name - opentwig 🌿",
108
+ "url": "https://links.yourwebsite.com",
109
+ "text": "Share"
110
+ }
111
+ }
112
+ ```
113
+
80
114
  ### Configuration Options
81
115
 
82
116
  | Option | Type | Description |
@@ -87,12 +121,38 @@ OpenTwig uses a simple JSON configuration file (`config.json`) to define your pa
87
121
  | `name` | string | Your display name |
88
122
  | `content` | string | Bio/description text |
89
123
  | `minify` | boolean | Enable CSS minification (default: `true`) |
90
- | `avatar` | object | Avatar image configuration |
91
- | `avatar.path` | string | Path to your avatar image |
124
+ | `avatar` | object | **Optional** Avatar image configuration |
125
+ | `avatar.path` | string | **Optional** Path to your avatar image (supports PNG, JPG, JPEG, WebP) |
92
126
  | `links` | array | Array of link objects with `url` and `title` |
93
127
  | `footerLinks` | array | Footer links (can be URLs or modal dialogs) |
94
128
  | `share` | object | Web Share API configuration |
95
129
 
130
+ ### 🖼️ Avatar Configuration
131
+
132
+ The avatar feature is completely optional. If you don't include the `avatar` object in your configuration, no avatar will be displayed on your page.
133
+
134
+ **Supported image formats:**
135
+ - PNG
136
+ - JPG/JPEG
137
+ - WebP
138
+
139
+ **Avatar processing:**
140
+ - Images are automatically optimized and resized
141
+ - Processed avatar is saved as `avatar.png` in the output directory
142
+ - Original aspect ratio is preserved
143
+ - Images are compressed for optimal web performance
144
+
145
+ **Example avatar configuration:**
146
+ ```json
147
+ {
148
+ "avatar": {
149
+ "path": "./my-photo.jpg"
150
+ }
151
+ }
152
+ ```
153
+
154
+ **Note:** If you don't want an avatar, simply omit the `avatar` object from your configuration entirely.
155
+
96
156
  ## 🎨 Themes
97
157
 
98
158
  OpenTwig includes 4 beautiful themes:
@@ -103,7 +163,7 @@ OpenTwig includes 4 beautiful themes:
103
163
  - **Colorful**: Vibrant color scheme
104
164
 
105
165
  All themes are mobile-responsive and include:
106
- - Custom avatar display
166
+ - Optional custom avatar display
107
167
  - Link buttons with hover effects
108
168
  - Modal dialogs for rich content
109
169
  - QR code integration
@@ -128,7 +188,7 @@ OpenTwig generates the following files in the `dist/` directory:
128
188
 
129
189
  - `index.html` - Main HTML page
130
190
  - `style.css` - Processed and optimized CSS
131
- - `avatar.png` - Processed avatar image
191
+ - `avatar.png` - Processed avatar image *(only generated if avatar is configured)*
132
192
  - `og-image.jpg` - Open Graph image for social sharing
133
193
  - `qr.svg` - QR code for mobile sharing
134
194
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentwig",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "opentwig 🌿 is an open source link in bio page generator.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -16,10 +16,7 @@ const DEFAULT_CONFIG = {
16
16
  content: 'Hello World! Here is my bio.',
17
17
  url: 'https://links.yourwebsite.com',
18
18
 
19
- // Avatar settings
20
- avatar: {
21
- path: './avatar.png'
22
- },
19
+ // Avatar settings - no default, user must explicitly define if they want an avatar
23
20
 
24
21
  // Links settings
25
22
  links: [],
@@ -94,19 +91,15 @@ const SAMPLE_CONFIG = {
94
91
  const applyDefaults = (config) => {
95
92
  const result = { ...config };
96
93
 
97
- // Apply defaults for each key
94
+ // Apply defaults for each key (excluding avatar which has special handling)
98
95
  Object.keys(DEFAULT_CONFIG).forEach(key => {
99
- if (result[key] === undefined || result[key] === null) {
96
+ if (key !== 'avatar' && (result[key] === undefined || result[key] === null)) {
100
97
  result[key] = DEFAULT_CONFIG[key];
101
98
  }
102
99
  });
103
100
 
104
- // Special handling for nested objects
105
- if (result.avatar && typeof result.avatar === 'object') {
106
- result.avatar = { ...DEFAULT_CONFIG.avatar, ...result.avatar };
107
- } else if (!result.avatar) {
108
- result.avatar = { ...DEFAULT_CONFIG.avatar };
109
- }
101
+ // Special handling for avatar - no defaults, user must explicitly define
102
+ // If avatar is undefined, null, false, or an object, leave it as is
110
103
 
111
104
  if (result.share && typeof result.share === 'object') {
112
105
  result.share = { ...DEFAULT_CONFIG.share, ...result.share };
@@ -2,7 +2,7 @@ const sharp = require('sharp');
2
2
  const readImageAsBase64 = require('./readImageAsBase64');
3
3
 
4
4
  module.exports = async function({name, content, avatar}) {
5
- const avatarBase64 = readImageAsBase64(avatar.path);
5
+ const avatarBase64 = avatar && avatar.path ? readImageAsBase64(avatar.path) : null;
6
6
  // Create SVG with the same dimensions and styling as the original HTML
7
7
  const svg = `
8
8
  <svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
@@ -6,11 +6,18 @@ const postcss = require('postcss');
6
6
  const minify = require('postcss-minify');
7
7
 
8
8
  module.exports = async function(config) {
9
- // Check if the theme has a style.css file
10
- const cssPath = path.join(cwd, 'theme', config.theme, 'style.css');
11
-
12
- const hasCss = fs.existsSync(cssPath);
13
- if (!hasCss) {
9
+ // Try package directory first (for NPX), then current directory (for local dev)
10
+ const packageDir = path.dirname(require.main.filename);
11
+ const currentDir = process.cwd();
12
+
13
+ const cssPaths = [
14
+ path.join(packageDir, '..', 'theme', config.theme, 'style.css'), // NPX package
15
+ path.join(currentDir, 'theme', config.theme, 'style.css') // Local development
16
+ ];
17
+
18
+ const cssPath = cssPaths.find(p => fs.existsSync(p));
19
+
20
+ if (!cssPath) {
14
21
  return null;
15
22
  }
16
23
 
@@ -9,8 +9,8 @@ module.exports = function(filePath) {
9
9
 
10
10
  const absolutePath = path.join(cwd, filePath);
11
11
  if (!fs.existsSync(absolutePath)) {
12
- console.error(`File not found: ${absolutePath}`);
13
- process.exit(1);
12
+ console.warn(`Avatar file not found: ${absolutePath}. Continuing without avatar.`);
13
+ return '';
14
14
  }
15
15
 
16
16
  const extension = path.extname(absolutePath).toLowerCase();
@@ -17,8 +17,11 @@ module.exports = function(html, css, avatar,ogImage, qrImage) {
17
17
  fs.writeFileSync(path.join(distDir, 'style.css'), css);
18
18
  }
19
19
 
20
- if (avatar != null && avatar.path) {
21
- fs.copyFileSync(path.join(cwd, avatar.path), path.join(distDir, 'avatar.png'));
20
+ if (avatar && avatar.path && fs.existsSync(path.join(cwd, avatar.path))) {
21
+ // Get the original file extension from the avatar path
22
+ const originalExtension = path.extname(avatar.path);
23
+ const avatarFileName = `avatar${originalExtension}`;
24
+ fs.copyFileSync(path.join(cwd, avatar.path), path.join(distDir, avatarFileName));
22
25
  }
23
26
 
24
27
  // Write the generated OG Image
@@ -1,7 +1,17 @@
1
- module.exports = function() {
1
+ module.exports = function({avatar}) {
2
+ // If no avatar is defined or avatar path is not provided, return empty string
3
+ if (!avatar || !avatar.path) {
4
+ return '';
5
+ }
6
+
7
+ // Get the original file extension from the avatar path
8
+ const path = require('path');
9
+ const originalExtension = path.extname(avatar.path);
10
+ const avatarFileName = `avatar${originalExtension}`;
11
+
2
12
  return `
3
13
  <div class="avatar">
4
- <img src="./avatar.png" alt="Avatar" />
14
+ <img src="./${avatarFileName}" alt="Avatar" />
5
15
  </div>
6
16
  `;
7
17
  }