opentwig 1.0.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/LICENSE +21 -0
- package/README.md +191 -0
- package/package.json +49 -0
- package/src/constants.js +58 -0
- package/src/index.js +36 -0
- package/src/utils/buildPage.js +46 -0
- package/src/utils/configDefaults.js +124 -0
- package/src/utils/createSampleConfig.js +23 -0
- package/src/utils/generateHTML.js +21 -0
- package/src/utils/generateOGImage.js +45 -0
- package/src/utils/generateQR.js +10 -0
- package/src/utils/loadConfig.js +18 -0
- package/src/utils/loadTheme.js +23 -0
- package/src/utils/parseArgs.js +21 -0
- package/src/utils/processCSS.js +29 -0
- package/src/utils/readImageAsBase64.js +37 -0
- package/src/utils/saveFiles.js +29 -0
- package/src/utils/showHelp.js +27 -0
- package/theme/colorful/index.js +4 -0
- package/theme/colorful/style.css +378 -0
- package/theme/dark/index.js +4 -0
- package/theme/dark/style.css +313 -0
- package/theme/default/components/avatar.js +7 -0
- package/theme/default/components/dialog.js +20 -0
- package/theme/default/components/footer-link.js +5 -0
- package/theme/default/components/link.js +7 -0
- package/theme/default/components/qr.js +10 -0
- package/theme/default/components/share-button.js +11 -0
- package/theme/default/index.js +53 -0
- package/theme/default/style.css +277 -0
- package/theme/minimal/index.js +4 -0
- package/theme/minimal/style.css +300 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tufan Tunรง
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# OpenTwig ๐ฟ
|
|
2
|
+
|
|
3
|
+
OpenTwig is an open source personal link page generator that creates beautiful, customizable "link in bio" pages. Instead of relying on third-party services, users can define their configuration and instantly create a fully functional static site they own and control.
|
|
4
|
+
|
|
5
|
+
## โจ Features
|
|
6
|
+
|
|
7
|
+
- ๐จ **Multiple Themes**: Choose from 4 built-in themes (default, dark, minimal, colorful)
|
|
8
|
+
- ๐ฑ **Mobile Responsive**: Optimized for all devices with mobile-first design
|
|
9
|
+
- ๐ **Fast & Lightweight**: Generates static HTML/CSS with minimal dependencies
|
|
10
|
+
- ๐ **Easy Link Management**: Simple JSON configuration for all your links
|
|
11
|
+
- ๐ผ๏ธ **Avatar Support**: Custom profile pictures with automatic processing
|
|
12
|
+
- ๐ **Open Graph Images**: Auto-generated social media preview images
|
|
13
|
+
- ๐ฑ **QR Code Generation**: Built-in QR codes for easy mobile sharing
|
|
14
|
+
- ๐ญ **Modal Dialogs**: Support for rich content in footer links
|
|
15
|
+
- ๐ค **Share Functionality**: Native Web Share API integration
|
|
16
|
+
- โก **CSS Optimization**: Automatic CSS minification and autoprefixing
|
|
17
|
+
- ๐ ๏ธ **CLI Interface**: Simple command-line interface with helpful commands
|
|
18
|
+
|
|
19
|
+
## ๐ Quick Start
|
|
20
|
+
|
|
21
|
+
### Installation & Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Create a new project
|
|
25
|
+
npx opentwig --init
|
|
26
|
+
|
|
27
|
+
# Edit the generated config.json with your information
|
|
28
|
+
# Then generate your page
|
|
29
|
+
npx opentwig
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Prerequisites
|
|
33
|
+
|
|
34
|
+
- Node.js (v14 or higher)
|
|
35
|
+
- npm or yarn
|
|
36
|
+
|
|
37
|
+
## ๐ Configuration
|
|
38
|
+
|
|
39
|
+
OpenTwig uses a simple JSON configuration file (`config.json`) to define your page. Here's the complete configuration structure:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"theme": "default",
|
|
44
|
+
"url": "https://links.yourwebsite.com",
|
|
45
|
+
"title": "Your Name - opentwig ๐ฟ",
|
|
46
|
+
"name": "Your Name",
|
|
47
|
+
"content": "Hello World! Here is my bio.",
|
|
48
|
+
"minify": true,
|
|
49
|
+
"avatar": {
|
|
50
|
+
"path": "./avatar.png"
|
|
51
|
+
},
|
|
52
|
+
"links": [
|
|
53
|
+
{
|
|
54
|
+
"url": "https://twitter.com",
|
|
55
|
+
"title": "Twitter"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"url": "https://instagram.com",
|
|
59
|
+
"title": "Instagram"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"footerLinks": [
|
|
63
|
+
{
|
|
64
|
+
"title": "Contact",
|
|
65
|
+
"url": "mailto:mail@mail.com"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"title": "Privacy",
|
|
69
|
+
"content": "Your privacy policy content here..."
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"share": {
|
|
73
|
+
"title": "Your Name - opentwig ๐ฟ",
|
|
74
|
+
"url": "https://links.yourwebsite.com",
|
|
75
|
+
"text": "Share"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Configuration Options
|
|
81
|
+
|
|
82
|
+
| Option | Type | Description |
|
|
83
|
+
|--------|------|-------------|
|
|
84
|
+
| `theme` | string | Theme name (`default`, `dark`, `minimal`, `colorful`) |
|
|
85
|
+
| `url` | string | Your page URL (for Open Graph and QR codes) |
|
|
86
|
+
| `title` | string | Page title (appears in browser tab) |
|
|
87
|
+
| `name` | string | Your display name |
|
|
88
|
+
| `content` | string | Bio/description text |
|
|
89
|
+
| `minify` | boolean | Enable CSS minification (default: `true`) |
|
|
90
|
+
| `avatar` | object | Avatar image configuration |
|
|
91
|
+
| `avatar.path` | string | Path to your avatar image |
|
|
92
|
+
| `links` | array | Array of link objects with `url` and `title` |
|
|
93
|
+
| `footerLinks` | array | Footer links (can be URLs or modal dialogs) |
|
|
94
|
+
| `share` | object | Web Share API configuration |
|
|
95
|
+
|
|
96
|
+
## ๐จ Themes
|
|
97
|
+
|
|
98
|
+
OpenTwig includes 4 beautiful themes:
|
|
99
|
+
|
|
100
|
+
- **Default**: Clean, modern design with subtle shadows and rounded corners
|
|
101
|
+
- **Dark**: Dark mode variant of the default theme
|
|
102
|
+
- **Minimal**: Simplified, minimalist design
|
|
103
|
+
- **Colorful**: Vibrant color scheme
|
|
104
|
+
|
|
105
|
+
All themes are mobile-responsive and include:
|
|
106
|
+
- Custom avatar display
|
|
107
|
+
- Link buttons with hover effects
|
|
108
|
+
- Modal dialogs for rich content
|
|
109
|
+
- QR code integration
|
|
110
|
+
- Share button functionality
|
|
111
|
+
|
|
112
|
+
## ๐ ๏ธ CLI Commands
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Show help information
|
|
116
|
+
npx opentwig --help
|
|
117
|
+
|
|
118
|
+
# Create sample config.json
|
|
119
|
+
npx opentwig --init
|
|
120
|
+
|
|
121
|
+
# Generate page from config.json
|
|
122
|
+
npx opentwig
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## ๐ Output Files
|
|
126
|
+
|
|
127
|
+
OpenTwig generates the following files in the `dist/` directory:
|
|
128
|
+
|
|
129
|
+
- `index.html` - Main HTML page
|
|
130
|
+
- `style.css` - Processed and optimized CSS
|
|
131
|
+
- `avatar.png` - Processed avatar image
|
|
132
|
+
- `og-image.jpg` - Open Graph image for social sharing
|
|
133
|
+
- `qr.svg` - QR code for mobile sharing
|
|
134
|
+
|
|
135
|
+
## ๐ง Development
|
|
136
|
+
|
|
137
|
+
### Project Structure
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
opentwig/
|
|
141
|
+
โโโ src/
|
|
142
|
+
โ โโโ index.js # Main entry point
|
|
143
|
+
โ โโโ constants.js # Application constants
|
|
144
|
+
โ โโโ utils/ # Utility functions
|
|
145
|
+
โ โโโ buildPage.js # Page building logic
|
|
146
|
+
โ โโโ generateHTML.js # HTML generation
|
|
147
|
+
โ โโโ generateOGImage.js # Open Graph image creation
|
|
148
|
+
โ โโโ generateQR.js # QR code generation
|
|
149
|
+
โ โโโ processCSS.js # CSS processing and optimization
|
|
150
|
+
โ โโโ ...
|
|
151
|
+
โโโ theme/
|
|
152
|
+
โ โโโ default/ # Default theme
|
|
153
|
+
โ โ โโโ index.js # Theme template
|
|
154
|
+
โ โ โโโ style.css # Theme styles
|
|
155
|
+
โ โ โโโ components/ # Reusable components
|
|
156
|
+
โ โโโ dark/ # Dark theme
|
|
157
|
+
โ โโโ minimal/ # Minimal theme
|
|
158
|
+
โ โโโ colorful/ # Colorful theme
|
|
159
|
+
โโโ dist/ # Generated output
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Key Features Implementation
|
|
163
|
+
|
|
164
|
+
- **Image Processing**: Uses Sharp for avatar and OG image processing
|
|
165
|
+
- **QR Code Generation**: Uses qrcode library for SVG QR codes
|
|
166
|
+
- **CSS Processing**: PostCSS with autoprefixer and minification
|
|
167
|
+
- **HTML Generation**: Component-based template system
|
|
168
|
+
- **Theme System**: Modular theme architecture with component support
|
|
169
|
+
|
|
170
|
+
## ๐ Deployment
|
|
171
|
+
|
|
172
|
+
Since OpenTwig generates static files, you can deploy to any static hosting service:
|
|
173
|
+
|
|
174
|
+
- **GitHub Pages**: Push `dist/` folder to a repository
|
|
175
|
+
- **Netlify**: Drag and drop the `dist/` folder
|
|
176
|
+
- **Vercel**: Connect your repository
|
|
177
|
+
- **AWS S3**: Upload files to an S3 bucket
|
|
178
|
+
- **Any web server**: Upload the `dist/` folder to your server
|
|
179
|
+
|
|
180
|
+
## ๐ License
|
|
181
|
+
|
|
182
|
+
MIT License - see [LICENSE](LICENSE) file for details
|
|
183
|
+
|
|
184
|
+
## ๐ค Contributing
|
|
185
|
+
|
|
186
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
187
|
+
|
|
188
|
+
## ๐ Links
|
|
189
|
+
|
|
190
|
+
- [GitHub Repository](https://github.com/tufantunc/opentwig)
|
|
191
|
+
- [NPM Package](https://www.npmjs.com/package/opentwig)
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opentwig",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "opentwig ๐ฟ is an open source link in bio page generator.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opentwig": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/tufantunc/opentwig.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"link-in-bio",
|
|
18
|
+
"bio-page",
|
|
19
|
+
"linktree",
|
|
20
|
+
"landing-page",
|
|
21
|
+
"static-site",
|
|
22
|
+
"generator",
|
|
23
|
+
"cli",
|
|
24
|
+
"npx",
|
|
25
|
+
"open-source"
|
|
26
|
+
],
|
|
27
|
+
"author": "Tufan Tunรง",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"type": "commonjs",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=14.0.0"
|
|
32
|
+
},
|
|
33
|
+
"preferGlobal": true,
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/tufantunc/opentwig/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/tufantunc/opentwig#readme",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"autoprefixer": "^10.4.21",
|
|
40
|
+
"html-minifier": "^4.0.0",
|
|
41
|
+
"postcss": "^8.5.6",
|
|
42
|
+
"postcss-minify": "^1.2.0",
|
|
43
|
+
"qrcode": "^1.5.4",
|
|
44
|
+
"sharp": "^0.34.4"
|
|
45
|
+
},
|
|
46
|
+
"browserslist": [
|
|
47
|
+
"ie 9"
|
|
48
|
+
]
|
|
49
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for OpenTwig
|
|
3
|
+
* Centralized constants to avoid magic strings and improve maintainability
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const CONSTANTS = {
|
|
7
|
+
// File paths
|
|
8
|
+
CONFIG_FILE: 'config.json',
|
|
9
|
+
OUTPUT_DIR: 'dist',
|
|
10
|
+
|
|
11
|
+
// Output files
|
|
12
|
+
OUTPUT_FILES: {
|
|
13
|
+
HTML: 'index.html',
|
|
14
|
+
CSS: 'style.css',
|
|
15
|
+
AVATAR: 'avatar.png',
|
|
16
|
+
OG_IMAGE: 'og-image.jpg',
|
|
17
|
+
QR_CODE: 'qr.svg'
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
// Supported themes
|
|
21
|
+
SUPPORTED_THEMES: ['default', 'dark', 'minimal', 'colorful'],
|
|
22
|
+
DEFAULT_THEME: 'default',
|
|
23
|
+
|
|
24
|
+
// CLI options
|
|
25
|
+
CLI_OPTIONS: {
|
|
26
|
+
HELP: ['--help', '-h'],
|
|
27
|
+
INIT: ['--init', '-i']
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Messages
|
|
31
|
+
MESSAGES: {
|
|
32
|
+
CONFIG_NOT_FOUND: 'Config file not found',
|
|
33
|
+
CONFIG_EXISTS: 'config.json already exists. Use --force to overwrite.',
|
|
34
|
+
CONFIG_CREATED: 'Sample config.json created successfully!',
|
|
35
|
+
CONFIG_EDIT_INSTRUCTIONS: 'Edit config.json with your information and run "npx opentwig" to generate your page.',
|
|
36
|
+
PAGE_GENERATED: '๐ Page generated successfully!',
|
|
37
|
+
UNKNOWN_OPTION: 'Unknown option:',
|
|
38
|
+
USE_HELP: 'Use --help for usage information.',
|
|
39
|
+
ERROR_PREFIX: 'โ Error:',
|
|
40
|
+
SUCCESS_PREFIX: 'โ
',
|
|
41
|
+
WARNING_PREFIX: 'โ ๏ธ',
|
|
42
|
+
INFO_PREFIX: 'โน๏ธ'
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// URLs
|
|
46
|
+
GITHUB_URL: 'https://github.com/tufantunc/opentwig',
|
|
47
|
+
|
|
48
|
+
// Default values
|
|
49
|
+
DEFAULT_TITLE: 'OpenTwig ๐ฟ',
|
|
50
|
+
DEFAULT_NAME: 'Your Name',
|
|
51
|
+
DEFAULT_CONTENT: 'Hello World! Here is my bio.',
|
|
52
|
+
DEFAULT_URL: 'https://links.yourwebsite.com',
|
|
53
|
+
|
|
54
|
+
// Required fields for validation
|
|
55
|
+
REQUIRED_FIELDS: ['url', 'name']
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports = CONSTANTS;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const loadConfig = require('./utils/loadConfig');
|
|
5
|
+
const saveFiles = require('./utils/saveFiles');
|
|
6
|
+
const parseArgs = require('./utils/parseArgs');
|
|
7
|
+
const buildPage = require('./utils/buildPage');
|
|
8
|
+
const CONSTANTS = require('./constants');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main application function with proper error handling
|
|
12
|
+
*/
|
|
13
|
+
const main = async () => {
|
|
14
|
+
try {
|
|
15
|
+
// Parse CLI arguments first
|
|
16
|
+
parseArgs();
|
|
17
|
+
|
|
18
|
+
// Load and validate configuration
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
|
|
21
|
+
// Build all page components
|
|
22
|
+
const { html, css, ogImage, qrImage } = await buildPage(config);
|
|
23
|
+
|
|
24
|
+
// Save all generated files
|
|
25
|
+
saveFiles(html, css, config.avatar, ogImage, qrImage);
|
|
26
|
+
|
|
27
|
+
// Success message
|
|
28
|
+
console.log(CONSTANTS.MESSAGES.PAGE_GENERATED);
|
|
29
|
+
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`${CONSTANTS.MESSAGES.ERROR_PREFIX} ${error.message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
main();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build page components
|
|
3
|
+
* Centralizes the page building logic for better organization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const loadTheme = require('./loadTheme');
|
|
7
|
+
const generateHTML = require('./generateHTML');
|
|
8
|
+
const processCSS = require('./processCSS');
|
|
9
|
+
const generateOGImage = require('./generateOGImage');
|
|
10
|
+
const generateQR = require('./generateQR');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build all page components from configuration
|
|
14
|
+
* @param {Object} config - The configuration object
|
|
15
|
+
* @returns {Promise<Object>} - Object containing all generated components
|
|
16
|
+
*/
|
|
17
|
+
const buildPage = async (config) => {
|
|
18
|
+
try {
|
|
19
|
+
// Load theme
|
|
20
|
+
const theme = loadTheme(config);
|
|
21
|
+
|
|
22
|
+
// Generate HTML
|
|
23
|
+
const html = generateHTML(config, theme);
|
|
24
|
+
|
|
25
|
+
// Process CSS (async)
|
|
26
|
+
const css = await processCSS(config);
|
|
27
|
+
|
|
28
|
+
// Generate OG Image (async)
|
|
29
|
+
const ogImage = await generateOGImage(config);
|
|
30
|
+
|
|
31
|
+
// Generate QR Code (async)
|
|
32
|
+
const qrImage = await generateQR(config.url);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
html,
|
|
36
|
+
css,
|
|
37
|
+
ogImage,
|
|
38
|
+
qrImage,
|
|
39
|
+
theme
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`Failed to build page: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = buildPage;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values for OpenTwig
|
|
3
|
+
* This centralizes all default settings to make them easier to maintain
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
// Theme settings
|
|
8
|
+
theme: 'default',
|
|
9
|
+
|
|
10
|
+
// Page settings
|
|
11
|
+
title: 'OpenTwig ๐ฟ',
|
|
12
|
+
minify: true,
|
|
13
|
+
|
|
14
|
+
// Content settings
|
|
15
|
+
name: 'Your Name',
|
|
16
|
+
content: 'Hello World! Here is my bio.',
|
|
17
|
+
url: 'https://links.yourwebsite.com',
|
|
18
|
+
|
|
19
|
+
// Avatar settings
|
|
20
|
+
avatar: {
|
|
21
|
+
path: './avatar.png'
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Links settings
|
|
25
|
+
links: [],
|
|
26
|
+
footerLinks: [],
|
|
27
|
+
|
|
28
|
+
// Share settings
|
|
29
|
+
share: {
|
|
30
|
+
title: 'Your Name - opentwig ๐ฟ',
|
|
31
|
+
url: 'https://links.yourwebsite.com',
|
|
32
|
+
text: 'Share'
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sample configuration for --init command
|
|
38
|
+
* This provides a more complete example with sample data
|
|
39
|
+
*/
|
|
40
|
+
const SAMPLE_CONFIG = {
|
|
41
|
+
theme: 'default',
|
|
42
|
+
url: 'https://links.yourwebsite.com',
|
|
43
|
+
title: 'Your Name - opentwig ๐ฟ',
|
|
44
|
+
name: 'Your Name',
|
|
45
|
+
content: 'Hello World! Here is my bio.',
|
|
46
|
+
minify: true,
|
|
47
|
+
avatar: {
|
|
48
|
+
path: 'avatar.png'
|
|
49
|
+
},
|
|
50
|
+
links: [
|
|
51
|
+
{
|
|
52
|
+
url: 'https://twitter.com',
|
|
53
|
+
title: 'Twitter'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
url: 'https://instagram.com',
|
|
57
|
+
title: 'Instagram'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
url: 'https://linkedin.com',
|
|
61
|
+
title: 'LinkedIn'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
url: 'https://github.com',
|
|
65
|
+
title: 'GitHub'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
url: 'https://youtube.com',
|
|
69
|
+
title: 'YouTube'
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
footerLinks: [
|
|
73
|
+
{
|
|
74
|
+
title: 'Contact',
|
|
75
|
+
url: 'mailto:mail@mail.com'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
title: 'Privacy',
|
|
79
|
+
content: 'When you visit a website, it may store or retrieve information on your browser, mostly in the form of cookies. But its not violating your privacy.'
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
share: {
|
|
83
|
+
title: 'Your Name - opentwig ๐ฟ',
|
|
84
|
+
url: 'https://links.yourwebsite.com',
|
|
85
|
+
text: 'Share'
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Apply default values to a configuration object
|
|
91
|
+
* @param {Object} config - The configuration object to apply defaults to
|
|
92
|
+
* @returns {Object} - Configuration with defaults applied
|
|
93
|
+
*/
|
|
94
|
+
const applyDefaults = (config) => {
|
|
95
|
+
const result = { ...config };
|
|
96
|
+
|
|
97
|
+
// Apply defaults for each key
|
|
98
|
+
Object.keys(DEFAULT_CONFIG).forEach(key => {
|
|
99
|
+
if (result[key] === undefined || result[key] === null) {
|
|
100
|
+
result[key] = DEFAULT_CONFIG[key];
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
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
|
+
}
|
|
110
|
+
|
|
111
|
+
if (result.share && typeof result.share === 'object') {
|
|
112
|
+
result.share = { ...DEFAULT_CONFIG.share, ...result.share };
|
|
113
|
+
} else if (!result.share) {
|
|
114
|
+
result.share = { ...DEFAULT_CONFIG.share };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
DEFAULT_CONFIG,
|
|
122
|
+
SAMPLE_CONFIG,
|
|
123
|
+
applyDefaults
|
|
124
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { SAMPLE_CONFIG } = require('./configDefaults');
|
|
4
|
+
const CONSTANTS = require('../constants');
|
|
5
|
+
|
|
6
|
+
module.exports = function createSampleConfig() {
|
|
7
|
+
const sampleConfig = SAMPLE_CONFIG;
|
|
8
|
+
const configPath = path.join(process.cwd(), CONSTANTS.CONFIG_FILE);
|
|
9
|
+
|
|
10
|
+
if (fs.existsSync(configPath)) {
|
|
11
|
+
console.log(`${CONSTANTS.MESSAGES.WARNING_PREFIX} ${CONSTANTS.MESSAGES.CONFIG_EXISTS}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
fs.writeFileSync(configPath, JSON.stringify(sampleConfig, null, 4));
|
|
17
|
+
console.log(`${CONSTANTS.MESSAGES.SUCCESS_PREFIX} ${CONSTANTS.MESSAGES.CONFIG_CREATED}`);
|
|
18
|
+
console.log(`๐ ${CONSTANTS.MESSAGES.CONFIG_EDIT_INSTRUCTIONS}`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(`${CONSTANTS.MESSAGES.ERROR_PREFIX} creating config.json: ${error.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const htmlMinifier = require('html-minifier');
|
|
2
|
+
|
|
3
|
+
module.exports = function(config, theme) {
|
|
4
|
+
let html = theme(config);
|
|
5
|
+
|
|
6
|
+
if (config.minify) {
|
|
7
|
+
const minifiedHtml = htmlMinifier.minify(html, {
|
|
8
|
+
removeComments: true,
|
|
9
|
+
collapseWhitespace: true,
|
|
10
|
+
minifyCSS: true,
|
|
11
|
+
minifyJS: true
|
|
12
|
+
});
|
|
13
|
+
html = minifiedHtml;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Add created by comment after minification
|
|
17
|
+
const createdByComment = '<!-- Created by OpenTwig ๐ฟ - https://github.com/tufantunc/opentwig -->';
|
|
18
|
+
html = createdByComment + '\n' + html;
|
|
19
|
+
|
|
20
|
+
return html;
|
|
21
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const sharp = require('sharp');
|
|
2
|
+
const readImageAsBase64 = require('./readImageAsBase64');
|
|
3
|
+
|
|
4
|
+
module.exports = async function({name, content, avatar}) {
|
|
5
|
+
const avatarBase64 = readImageAsBase64(avatar.path);
|
|
6
|
+
// Create SVG with the same dimensions and styling as the original HTML
|
|
7
|
+
const svg = `
|
|
8
|
+
<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
|
|
9
|
+
<defs>
|
|
10
|
+
<style>
|
|
11
|
+
.name-text {
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
13
|
+
font-weight: 700;
|
|
14
|
+
font-size: 22px;
|
|
15
|
+
fill: #fdfdfd;
|
|
16
|
+
}
|
|
17
|
+
.content-text {
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
19
|
+
font-size: 14px;
|
|
20
|
+
fill: #fdfdfd;
|
|
21
|
+
}
|
|
22
|
+
</style>
|
|
23
|
+
</defs>
|
|
24
|
+
|
|
25
|
+
<!-- Background -->
|
|
26
|
+
<rect width="1200" height="630" fill="#2d2d2d"/>
|
|
27
|
+
|
|
28
|
+
<!-- Avatar circle background -->
|
|
29
|
+
<circle cx="552" cy="315" r="48" fill="#dedede"/>
|
|
30
|
+
|
|
31
|
+
<!-- Avatar image (if provided) -->
|
|
32
|
+
${avatarBase64 ? `<image href="${avatarBase64}" x="504" y="267" width="96" height="96" clip-path="circle(48px at 48px 48px)"/>` : ''}
|
|
33
|
+
|
|
34
|
+
<!-- Text content -->
|
|
35
|
+
<text x="660" y="300" class="name-text">${name}</text>
|
|
36
|
+
<text x="660" y="325" class="content-text">${content}</text>
|
|
37
|
+
</svg>
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
// Convert SVG to JPG
|
|
41
|
+
const jpgBuffer = await sharp(Buffer.from(svg))
|
|
42
|
+
.jpeg({ quality: 90 })
|
|
43
|
+
.toBuffer();
|
|
44
|
+
return jpgBuffer;
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const QRCode = require('qrcode');
|
|
2
|
+
|
|
3
|
+
module.exports = async function(url) {
|
|
4
|
+
const svgString = await QRCode.toString(url, { type: 'svg' });
|
|
5
|
+
|
|
6
|
+
// Remove the white background fill from the SVG
|
|
7
|
+
const svgWithoutBackground = svgString.replace(/fill="#ffffff"/g, 'fill="transparent"');
|
|
8
|
+
|
|
9
|
+
return svgWithoutBackground;
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const cwd = process.cwd();
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { applyDefaults } = require('./configDefaults');
|
|
5
|
+
const CONSTANTS = require('../constants');
|
|
6
|
+
|
|
7
|
+
module.exports = function() {
|
|
8
|
+
const configPath = path.join(cwd, CONSTANTS.CONFIG_FILE);
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error(`${CONSTANTS.MESSAGES.CONFIG_NOT_FOUND}: ${configPath}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const config = require(configPath);
|
|
15
|
+
|
|
16
|
+
// Apply default values to the loaded configuration
|
|
17
|
+
return applyDefaults(config);
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
module.exports = function(config) {
|
|
5
|
+
// Try package directory first (for NPX), then current directory (for local dev)
|
|
6
|
+
const packageDir = path.dirname(require.main.filename);
|
|
7
|
+
const currentDir = process.cwd();
|
|
8
|
+
|
|
9
|
+
const themePaths = [
|
|
10
|
+
path.join(packageDir, '..', 'theme', config.theme, 'index.js'), // NPX package
|
|
11
|
+
path.join(currentDir, 'theme', config.theme, 'index.js') // Local development
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const themePath = themePaths.find(p => fs.existsSync(p));
|
|
15
|
+
|
|
16
|
+
if (!themePath) {
|
|
17
|
+
console.error(`Theme '${config.theme}' not found in any of these locations:`);
|
|
18
|
+
themePaths.forEach(p => console.error(` - ${p}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return require(themePath);
|
|
23
|
+
}
|