opentwig 1.1.0 → 1.1.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/AGENTS.md +49 -200
- package/package.json +2 -3
- package/src/live-ui/editor.js +1 -0
- package/src/live-ui/sidebar.js +2 -0
- package/src/utils/favicon.js +20 -0
- package/src/utils/loadConfig.js +3 -1
- package/src/utils/setupWatcher.js +2 -2
- package/src/utils/startLiveServer.js +11 -8
- package/theme/default/index.js +2 -0
- package/vitest.config.js +19 -10
- package/website/README.md +42 -0
- package/website/components.json +16 -0
- package/website/eslint.config.js +36 -0
- package/website/package-lock.json +4136 -0
- package/website/package.json +41 -0
- package/website/shadcn-svelte.md +118 -0
- package/website/src/app.d.ts +13 -0
- package/website/src/lib/components/ui/badge/badge.svelte +50 -0
- package/website/src/lib/components/ui/badge/index.ts +2 -0
- package/website/src/lib/components/ui/button/button.svelte +82 -0
- package/website/src/lib/components/ui/button/index.ts +17 -0
- package/website/src/lib/components/ui/card/card-action.svelte +20 -0
- package/website/src/lib/components/ui/card/card-content.svelte +15 -0
- package/website/src/lib/components/ui/card/card-description.svelte +20 -0
- package/website/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/website/src/lib/components/ui/card/card-header.svelte +23 -0
- package/website/src/lib/components/ui/card/card-title.svelte +20 -0
- package/website/src/lib/components/ui/card/card.svelte +23 -0
- package/website/src/lib/components/ui/card/index.ts +25 -0
- package/website/src/lib/components/ui/separator/index.ts +7 -0
- package/website/src/lib/components/ui/separator/separator.svelte +21 -0
- package/website/src/lib/components/ui/tooltip/index.ts +19 -0
- package/website/src/lib/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/website/src/lib/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/website/src/lib/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/website/src/lib/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/website/src/lib/components/ui/tooltip/tooltip.svelte +7 -0
- package/website/src/lib/index.ts +1 -0
- package/website/src/lib/utils.ts +13 -0
- package/website/src/routes/+layout.svelte +23 -0
- package/website/src/routes/+page.server.ts +82 -0
- package/website/src/routes/+page.svelte +892 -0
- package/website/static/robots.txt +3 -0
- package/website/svelte.config.js +31 -0
- package/website/vite.config.ts +5 -0
- package/test-og.js +0 -40
package/AGENTS.md
CHANGED
|
@@ -7,69 +7,67 @@ This file provides guidelines for agentic coding assistants working on the OpenT
|
|
|
7
7
|
```bash
|
|
8
8
|
# Run the CLI tool
|
|
9
9
|
npm start
|
|
10
|
-
# or: node src/index.js
|
|
11
10
|
|
|
12
|
-
#
|
|
13
|
-
npm
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
npm
|
|
11
|
+
# Run a single test file
|
|
12
|
+
npm test -- src/utils/parseArgs.test.js
|
|
13
|
+
|
|
14
|
+
# Run tests matching a pattern
|
|
15
|
+
npm test -- -t "should validate"
|
|
17
16
|
|
|
18
|
-
#
|
|
19
|
-
npm run test
|
|
17
|
+
# Run tests once (CI mode)
|
|
18
|
+
npm run test:run
|
|
20
19
|
|
|
21
|
-
# Run
|
|
22
|
-
npm test
|
|
23
|
-
npm run test:run # Run tests once
|
|
24
|
-
npm run test:coverage # Run tests with coverage report
|
|
20
|
+
# Run tests with coverage
|
|
21
|
+
npm run test:coverage
|
|
25
22
|
|
|
26
23
|
# Start live preview mode
|
|
27
24
|
npm run live
|
|
25
|
+
|
|
26
|
+
# CLI options
|
|
27
|
+
npm start -- --help
|
|
28
|
+
npm start -- --init
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
## Project Overview
|
|
31
32
|
|
|
32
|
-
OpenTwig is a Node.js CLI tool that generates static "link in bio" pages.
|
|
33
|
+
OpenTwig is a Node.js CLI tool that generates static "link in bio" pages. Uses CommonJS modules. Generates HTML, CSS, QR codes, and Open Graph images from JSON config.
|
|
33
34
|
|
|
34
|
-
**Core technologies:** Node.js (v14+), CommonJS, PostCSS, Sharp,
|
|
35
|
+
**Core technologies:** Node.js (v14+), CommonJS, PostCSS, Sharp, Vitest
|
|
35
36
|
|
|
36
37
|
## Code Style Guidelines
|
|
37
38
|
|
|
38
39
|
### Module System
|
|
39
40
|
- **Use CommonJS exclusively** - no ES6 imports/exports
|
|
40
|
-
- Import: `const
|
|
41
|
+
- Import: `const module = require('./path/to/module');`
|
|
41
42
|
- Export: `module.exports = functionName;` or `module.exports = { name, value };`
|
|
42
|
-
- Main entry point includes shebang: `#! /usr/bin/env node`
|
|
43
|
+
- Main entry point includes shebang: `#! /usr/bin/env node` and `'use strict';`
|
|
43
44
|
|
|
44
45
|
### File Organization
|
|
45
46
|
- **Single function per file** preferred in `src/utils/`
|
|
46
|
-
- Use descriptive filenames in camelCase (e.g., `buildPage.js
|
|
47
|
-
- Theme structure: `theme/{themeName}/index.js`, `style.css`, `components/`
|
|
47
|
+
- Use descriptive filenames in camelCase (e.g., `buildPage.js`)
|
|
48
48
|
- Utilities in `src/utils/`, constants in `src/constants.js`
|
|
49
|
+
- Theme structure: `theme/{themeName}/index.js`, `style.css`, `components/`
|
|
49
50
|
|
|
50
51
|
### Naming Conventions
|
|
51
52
|
- **Variables/Functions**: camelCase (`const buildPage = async (config) => {}`)
|
|
52
53
|
- **Constants**: UPPER_SNAKE_CASE (`const OUTPUT_FILES = { HTML: 'index.html' }`)
|
|
53
54
|
- **Files**: camelCase for JS files (e.g., `loadConfig.js`)
|
|
54
|
-
- **Destructured params**: Use descriptive names (`function({title, url, name})`)
|
|
55
55
|
|
|
56
56
|
### Code Patterns
|
|
57
57
|
|
|
58
58
|
**Function exports:**
|
|
59
59
|
```javascript
|
|
60
|
-
// Pattern: Immediately export a function
|
|
61
60
|
module.exports = async function(config) {
|
|
62
61
|
// implementation
|
|
63
62
|
};
|
|
64
63
|
```
|
|
65
64
|
|
|
66
|
-
**Async/await:**
|
|
65
|
+
**Async/await with error handling:**
|
|
67
66
|
```javascript
|
|
68
67
|
const buildPage = async (config) => {
|
|
69
68
|
try {
|
|
70
69
|
const theme = loadTheme(config);
|
|
71
|
-
|
|
72
|
-
return { html, theme };
|
|
70
|
+
return { html: await generateHTML(config, theme), theme };
|
|
73
71
|
} catch (error) {
|
|
74
72
|
throw new Error(`Failed to build page: ${error.message}`);
|
|
75
73
|
}
|
|
@@ -91,29 +89,18 @@ module.exports = function({title, name}) {
|
|
|
91
89
|
- Always use try-catch for async functions
|
|
92
90
|
- Log errors with `console.error()`
|
|
93
91
|
- Use `process.exit(1)` on fatal errors, `process.exit(0)` on success
|
|
94
|
-
- Prefix error messages with context
|
|
95
|
-
- Centralize error messages in `src/constants.js`
|
|
96
|
-
|
|
97
|
-
```javascript
|
|
98
|
-
try {
|
|
99
|
-
const result = await someAsyncOperation();
|
|
100
|
-
return result;
|
|
101
|
-
} catch (error) {
|
|
102
|
-
throw new Error(`Operation failed: ${error.message}`);
|
|
103
|
-
}
|
|
104
|
-
```
|
|
92
|
+
- Prefix error messages with context from `src/constants.js`
|
|
105
93
|
|
|
106
94
|
### Security
|
|
107
95
|
- Always escape HTML output using `escapeHTML()` utility
|
|
108
|
-
- Always escape XML for OG images using `escapeXml()`
|
|
96
|
+
- Always escape XML for OG images using `escapeXml()` utility
|
|
109
97
|
- Validate config fields in `configDefaults.js`
|
|
110
98
|
- Sanitize all user-generated content
|
|
111
99
|
|
|
112
100
|
### Documentation
|
|
113
101
|
- Add JSDoc comments for functions with @param and @returns
|
|
114
102
|
- Add module-level docstrings describing purpose
|
|
115
|
-
|
|
116
|
-
- Example:
|
|
103
|
+
|
|
117
104
|
```javascript
|
|
118
105
|
/**
|
|
119
106
|
* Load theme configuration from theme directory
|
|
@@ -137,17 +124,6 @@ Centralize all constants in `src/constants.js`:
|
|
|
137
124
|
- CLI options
|
|
138
125
|
- Messages (ERROR_PREFIX, SUCCESS_PREFIX, etc.)
|
|
139
126
|
- Default values
|
|
140
|
-
- Configuration validation requirements
|
|
141
|
-
|
|
142
|
-
### Component Pattern (Themes)
|
|
143
|
-
Theme components in `theme/*/components/` export functions:
|
|
144
|
-
```javascript
|
|
145
|
-
module.exports = function({link}) {
|
|
146
|
-
return `<a href="${link.url}" target="_blank" rel="noopener">
|
|
147
|
-
<span>${link.title}</span>
|
|
148
|
-
</a>`;
|
|
149
|
-
};
|
|
150
|
-
```
|
|
151
127
|
|
|
152
128
|
### File Paths
|
|
153
129
|
- Use `path.join()` for path construction
|
|
@@ -155,169 +131,42 @@ module.exports = function({link}) {
|
|
|
155
131
|
- Use `process.cwd()` for current working directory
|
|
156
132
|
- Check file existence with `fs.existsSync()` before operations
|
|
157
133
|
|
|
134
|
+
## Testing Guidelines
|
|
135
|
+
|
|
136
|
+
Tests use Vitest with ESM imports for test utilities and CommonJS require for modules:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
140
|
+
|
|
141
|
+
describe('functionName', () => {
|
|
142
|
+
it('should do something', () => {
|
|
143
|
+
const module = require('./module');
|
|
144
|
+
expect(result).toBe(expected);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
158
149
|
## Key Files Reference
|
|
159
150
|
|
|
160
|
-
- `src/index.js` - Main CLI entry point
|
|
161
|
-
- `src/constants.js` - Centralized constants
|
|
162
|
-
- `src/utils
|
|
163
|
-
- `
|
|
164
|
-
- `src/
|
|
165
|
-
- `src/utils/processCSS.js` - CSS processing with PostCSS
|
|
166
|
-
- `src/utils/generateOGImage.js` - Open Graph image generation
|
|
167
|
-
- `src/utils/generateQR.js` - QR code generation
|
|
168
|
-
- `src/utils/saveFiles.js` - Save all output files to dist/
|
|
169
|
-
- `src/utils/configDefaults.js` - Default values and validation
|
|
170
|
-
- `src/utils/startLiveServer.js` - Live preview server (Express + WebSocket)
|
|
171
|
-
- `src/utils/websocketServer.js` - WebSocket connection management
|
|
172
|
-
- `src/utils/setupWatcher.js` - Config file change watcher
|
|
173
|
-
- `src/live-ui/index.html` - Live editor main page
|
|
174
|
-
- `src/live-ui/styles.css` - Live editor styles
|
|
175
|
-
- `src/live-ui/preview.js` - Preview iframe management
|
|
176
|
-
- `src/live-ui/editor.js` - Config editor logic
|
|
177
|
-
- `src/live-ui/sidebar.js` - Sidebar components and form rendering
|
|
178
|
-
- `theme/*/index.js` - Theme-specific HTML templates
|
|
179
|
-
- `validateConfig.js` - Config validation utility
|
|
151
|
+
- `src/index.js` - Main CLI entry point
|
|
152
|
+
- `src/constants.js` - Centralized constants
|
|
153
|
+
- `src/utils/` - Utility functions (one function per file)
|
|
154
|
+
- `theme/` - Theme templates
|
|
155
|
+
- `src/live-ui/` - Live editor UI
|
|
180
156
|
|
|
181
157
|
## Output Files
|
|
182
158
|
|
|
183
|
-
All generated files go to `dist
|
|
159
|
+
All generated files go to `dist/`:
|
|
184
160
|
- `index.html` - Main page
|
|
185
161
|
- `style.css` - Processed CSS
|
|
186
|
-
- `avatar.{ext}` - User avatar
|
|
187
|
-
- `og-image.jpg` - Open Graph
|
|
162
|
+
- `avatar.{ext}` - User avatar
|
|
163
|
+
- `og-image.jpg` - Open Graph image
|
|
188
164
|
- `qr.svg` - QR code
|
|
189
165
|
|
|
190
|
-
## Live Mode Architecture
|
|
191
|
-
|
|
192
|
-
### Overview
|
|
193
|
-
Live mode (`--live` flag) provides an interactive development environment with real-time preview and configuration editing.
|
|
194
|
-
|
|
195
|
-
### Components
|
|
196
|
-
|
|
197
|
-
**Backend (Node.js):**
|
|
198
|
-
- `startLiveServer.js` - Express server with WebSocket support
|
|
199
|
-
- HTTP endpoints: `/api/config`, `/api/themes`, `/api/avatar`, `/api/validate`
|
|
200
|
-
- Static file serving for `dist/` and `src/live-ui/`
|
|
201
|
-
- File upload handling (multer)
|
|
202
|
-
- `websocketServer.js` - WebSocket server for real-time updates
|
|
203
|
-
- Broadcast events: `reload`, `config-update`, `theme-change`
|
|
204
|
-
- Client connection management
|
|
205
|
-
- `setupWatcher.js` - Config file watcher
|
|
206
|
-
- Chokidar-based file watching
|
|
207
|
-
- Debounced change detection
|
|
208
|
-
- Auto rebuild on config changes
|
|
209
|
-
|
|
210
|
-
**Frontend (Vanilla JS):**
|
|
211
|
-
- `index.html` - Main editor page (Preview on left, Sidebar on right)
|
|
212
|
-
- `preview.js` - Preview iframe management and WebSocket client
|
|
213
|
-
- `editor.js` - Config API communication and auto-save
|
|
214
|
-
- `sidebar.js` - Dynamic form generation and event handling
|
|
215
|
-
|
|
216
|
-
### Features
|
|
217
|
-
|
|
218
|
-
**Editor Features:**
|
|
219
|
-
- Theme selection (default, dark, minimal, colorful)
|
|
220
|
-
- Profile editing (URL, name, bio, avatar upload)
|
|
221
|
-
- Links management (add, edit, delete, reorder)
|
|
222
|
-
- Footer links management (URL or modal content)
|
|
223
|
-
- Share settings configuration
|
|
224
|
-
- Advanced settings (minify CSS)
|
|
225
|
-
- Auto-save with debounce (500ms)
|
|
226
|
-
- Export config as JSON
|
|
227
|
-
|
|
228
|
-
**Real-time Updates:**
|
|
229
|
-
- Config changes → WebSocket broadcast → Preview reload
|
|
230
|
-
- File watcher → Rebuild → WebSocket broadcast
|
|
231
|
-
- Avatar upload → Save → Rebuild → Preview update
|
|
232
|
-
|
|
233
|
-
### Layout
|
|
234
|
-
|
|
235
|
-
```
|
|
236
|
-
┌─────────────────────────────────────────────┐
|
|
237
|
-
│ Header: OpenTwig Live Editor [Save][Export] │
|
|
238
|
-
├──────────────────────────────┬──────────────┤
|
|
239
|
-
│ │ │
|
|
240
|
-
│ Preview Iframe │ Sidebar │
|
|
241
|
-
│ (dist/index.html embedded) │ │
|
|
242
|
-
│ │ - Theme │
|
|
243
|
-
│ │ - Profile │
|
|
244
|
-
│ │ - Links │
|
|
245
|
-
│ │ - Footer │
|
|
246
|
-
│ │ - Share │
|
|
247
|
-
│ │ - Advanced │
|
|
248
|
-
│ │ │
|
|
249
|
-
├──────────────────────────────┴──────────────┤
|
|
250
|
-
│ Status: Connected | Auto-save: ON │
|
|
251
|
-
└─────────────────────────────────────────────┘
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### WebSocket Events
|
|
255
|
-
|
|
256
|
-
**Server → Client:**
|
|
257
|
-
```json
|
|
258
|
-
{
|
|
259
|
-
"type": "reload"
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
{
|
|
263
|
-
"type": "config-update",
|
|
264
|
-
"config": { ... }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
{
|
|
268
|
-
"type": "theme-change",
|
|
269
|
-
"theme": "dark"
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**API Endpoints:**
|
|
274
|
-
|
|
275
|
-
**GET /api/config**
|
|
276
|
-
- Returns current config from config.json
|
|
277
|
-
- Creates sample config if not exists
|
|
278
|
-
|
|
279
|
-
**POST /api/config**
|
|
280
|
-
- Accepts config JSON
|
|
281
|
-
- Validates and saves to config.json
|
|
282
|
-
- Triggers rebuild and WebSocket broadcast
|
|
283
|
-
|
|
284
|
-
**POST /api/avatar**
|
|
285
|
-
- Accepts multipart form data with avatar file
|
|
286
|
-
- Saves to working directory
|
|
287
|
-
- Returns file path
|
|
288
|
-
|
|
289
|
-
**GET /api/themes**
|
|
290
|
-
- Returns array of available theme names
|
|
291
|
-
|
|
292
|
-
**GET /api/validate?config=...**
|
|
293
|
-
- Validates config JSON
|
|
294
|
-
- Returns errors and warnings
|
|
295
|
-
|
|
296
|
-
**GET /api/status**
|
|
297
|
-
- Returns server status (connected clients, config path, etc.)
|
|
298
|
-
|
|
299
|
-
## Testing Approach
|
|
300
|
-
|
|
301
|
-
The project uses Vitest for automated testing:
|
|
302
|
-
1. Run `npm test` to run tests in watch mode
|
|
303
|
-
2. Run `npm run test:run` to run tests once
|
|
304
|
-
3. Run `npm run test:coverage` to run tests with coverage report
|
|
305
|
-
|
|
306
|
-
For manual testing:
|
|
307
|
-
1. Create a config.json using `npm start -- --init`
|
|
308
|
-
2. Run `npm start` to generate the page
|
|
309
|
-
3. Verify `dist/` output files are generated correctly
|
|
310
|
-
4. Test all themes by modifying config.json
|
|
311
|
-
5. Test edge cases: missing avatar, empty config, long text
|
|
312
|
-
6. Verify HTML is valid (open in browser)
|
|
313
|
-
7. Check CSS renders correctly
|
|
314
|
-
8. Confirm images are processed properly
|
|
315
|
-
|
|
316
166
|
## When Making Changes
|
|
317
167
|
|
|
318
168
|
1. **Always read the file first** - Use the Read tool before editing
|
|
319
169
|
2. **Follow existing patterns** - Mimic code style and structure
|
|
320
|
-
3. **Test thoroughly** -
|
|
170
|
+
3. **Test thoroughly** - Run `npm run test:run` after changes
|
|
321
171
|
4. **Add JSDoc comments** - Document new functions
|
|
322
|
-
5. **
|
|
323
|
-
6. **Keep it simple** - One function per file, focused responsibilities
|
|
172
|
+
5. **Keep it simple** - One function per file, focused responsibilities
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opentwig",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "opentwig 🌿 is an open source link in bio page generator.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"test": "vitest",
|
|
12
12
|
"test:run": "vitest run",
|
|
13
13
|
"test:coverage": "vitest run --coverage",
|
|
14
|
-
"test-og": "node test-og.js",
|
|
15
14
|
"live": "node src/index.js --live"
|
|
16
15
|
},
|
|
17
16
|
"devDependencies": {
|
|
@@ -62,4 +61,4 @@
|
|
|
62
61
|
"browserslist": [
|
|
63
62
|
"ie 9"
|
|
64
63
|
]
|
|
65
|
-
}
|
|
64
|
+
}
|
package/src/live-ui/editor.js
CHANGED
package/src/live-ui/sidebar.js
CHANGED
|
@@ -383,6 +383,8 @@ const renderShareSection = (container, config) => {
|
|
|
383
383
|
|
|
384
384
|
const shareUrlGroup = createFormGroup('Share URL', '', config.share.url, 'text', (value) => {
|
|
385
385
|
config.share.url = value;
|
|
386
|
+
// Sync with main URL so QR code updates correctly
|
|
387
|
+
config.url = value;
|
|
386
388
|
window.configEditor.updateConfig(config);
|
|
387
389
|
window.configEditor.autoSave(config);
|
|
388
390
|
return [];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaf favicon SVG for OpenTwig
|
|
3
|
+
* Used across all themes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const LEAF_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"/></svg>`;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get favicon as data URI for embedding in HTML
|
|
10
|
+
* @returns {string} Data URI for favicon
|
|
11
|
+
*/
|
|
12
|
+
function getFaviconDataURI() {
|
|
13
|
+
const base64 = Buffer.from(LEAF_FAVICON_SVG).toString('base64');
|
|
14
|
+
return `data:image/svg+xml;base64,${base64}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
LEAF_FAVICON_SVG,
|
|
19
|
+
getFaviconDataURI
|
|
20
|
+
};
|
package/src/utils/loadConfig.js
CHANGED
|
@@ -11,7 +11,9 @@ module.exports = function() {
|
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Use fs.readFileSync instead of require to avoid caching issues
|
|
15
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
16
|
+
const config = JSON.parse(configContent);
|
|
15
17
|
|
|
16
18
|
// Apply default values to the loaded configuration
|
|
17
19
|
return applyDefaults(config);
|
|
@@ -12,10 +12,10 @@ const setupWatcher = (configPath, wss, onConfigChange) => {
|
|
|
12
12
|
let watcher = null;
|
|
13
13
|
let isPaused = false;
|
|
14
14
|
|
|
15
|
-
const handleConfigChange = debounce(async (
|
|
15
|
+
const handleConfigChange = debounce(async (path) => {
|
|
16
16
|
if (isPaused) return;
|
|
17
17
|
|
|
18
|
-
console.log(`Config file changed: ${
|
|
18
|
+
console.log(`Config file changed: ${path}`);
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
if (onConfigChange) {
|
|
@@ -50,22 +50,22 @@ const startLiveServer = async (customPort) => {
|
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
app.post('/api/config', (req, res) => {
|
|
53
|
+
app.post('/api/config', async (req, res) => {
|
|
54
54
|
try {
|
|
55
55
|
const newConfig = req.body;
|
|
56
56
|
currentConfig = applyDefaults(newConfig);
|
|
57
57
|
|
|
58
58
|
fs.writeFileSync(configPath, JSON.stringify(currentConfig, null, 4));
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
wss.broadcastConfigUpdate(currentConfig);
|
|
63
|
-
}).catch(error => {
|
|
64
|
-
console.error('Error building page:', error);
|
|
65
|
-
});
|
|
60
|
+
const { html, css, ogImage, qrImage } = await buildPage(currentConfig);
|
|
61
|
+
saveFiles(html, css, currentConfig.avatar, ogImage, qrImage);
|
|
66
62
|
|
|
67
63
|
res.json({ success: true, config: currentConfig });
|
|
64
|
+
|
|
65
|
+
// Broadcast reload after files are saved
|
|
66
|
+
wss.broadcastReload();
|
|
68
67
|
} catch (error) {
|
|
68
|
+
console.error('Error building page:', error);
|
|
69
69
|
res.status(500).json({ error: error.message });
|
|
70
70
|
}
|
|
71
71
|
});
|
|
@@ -201,7 +201,10 @@ const startLiveServer = async (customPort) => {
|
|
|
201
201
|
|
|
202
202
|
const watcher = setupWatcher(configPath, wss, async (changedPath) => {
|
|
203
203
|
try {
|
|
204
|
-
|
|
204
|
+
// Read config directly from file to avoid require cache issues
|
|
205
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
206
|
+
const fileConfig = JSON.parse(configContent);
|
|
207
|
+
currentConfig = applyDefaults(fileConfig);
|
|
205
208
|
await buildPage(currentConfig);
|
|
206
209
|
console.log('Page rebuilt from config change');
|
|
207
210
|
} catch (error) {
|
package/theme/default/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const shareButtonComponent = require('./components/share-button');
|
|
|
5
5
|
const qrComponent = require('./components/qr');
|
|
6
6
|
const dialogComponent = require('./components/dialog');
|
|
7
7
|
const escapeHTML = require('../../src/utils/escapeHTML');
|
|
8
|
+
const { getFaviconDataURI } = require('../../src/utils/favicon');
|
|
8
9
|
|
|
9
10
|
module.exports = function({title, url, name, content, avatar, links, footerLinks, share}) {
|
|
10
11
|
|
|
@@ -20,6 +21,7 @@ module.exports = function({title, url, name, content, avatar, links, footerLinks
|
|
|
20
21
|
<meta property="og:description" content="${escapeHTML(content)}"/>
|
|
21
22
|
<meta property="og:url" content="${escapeHTML(url)}"/>
|
|
22
23
|
<meta property="og:image" content="${escapeHTML(url)}/og-image.jpg"/>
|
|
24
|
+
<link rel="icon" type="image/svg+xml" href="${getFaviconDataURI()}">
|
|
23
25
|
</head>
|
|
24
26
|
<body>
|
|
25
27
|
<div class="app-bg">
|
package/vitest.config.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['tests/**/*.test.js'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html'],
|
|
11
|
+
exclude: [
|
|
12
|
+
'node_modules/',
|
|
13
|
+
'tests/',
|
|
14
|
+
'website/',
|
|
15
|
+
'theme/',
|
|
16
|
+
'dist/'
|
|
17
|
+
]
|
|
18
|
+
}
|
|
9
19
|
}
|
|
10
|
-
|
|
11
|
-
};
|
|
20
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# sv
|
|
2
|
+
|
|
3
|
+
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
|
4
|
+
|
|
5
|
+
## Creating a project
|
|
6
|
+
|
|
7
|
+
If you're seeing this, you've probably already done this step. Congrats!
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
# create a new project
|
|
11
|
+
npx sv create my-app
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
To recreate this project with the same configuration:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
# recreate this project
|
|
18
|
+
npx sv create --template minimal --types ts --add eslint tailwindcss="plugins:typography" sveltekit-adapter="adapter:static" --install npm website
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Developing
|
|
22
|
+
|
|
23
|
+
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm run dev
|
|
27
|
+
|
|
28
|
+
# or start the server and open the app in a new browser tab
|
|
29
|
+
npm run dev -- --open
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Building
|
|
33
|
+
|
|
34
|
+
To create a production version of your app:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
You can preview the production build with `npm run preview`.
|
|
41
|
+
|
|
42
|
+
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://shadcn-svelte.com/schema.json",
|
|
3
|
+
"tailwind": {
|
|
4
|
+
"css": "src/routes/layout.css",
|
|
5
|
+
"baseColor": "slate"
|
|
6
|
+
},
|
|
7
|
+
"aliases": {
|
|
8
|
+
"components": "$lib/components",
|
|
9
|
+
"utils": "$lib/utils",
|
|
10
|
+
"ui": "$lib/components/ui",
|
|
11
|
+
"hooks": "$lib/hooks",
|
|
12
|
+
"lib": "$lib"
|
|
13
|
+
},
|
|
14
|
+
"typescript": true,
|
|
15
|
+
"registry": "https://shadcn-svelte.com/registry"
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { includeIgnoreFile } from '@eslint/compat';
|
|
3
|
+
import js from '@eslint/js';
|
|
4
|
+
import svelte from 'eslint-plugin-svelte';
|
|
5
|
+
import { defineConfig } from 'eslint/config';
|
|
6
|
+
import globals from 'globals';
|
|
7
|
+
import ts from 'typescript-eslint';
|
|
8
|
+
import svelteConfig from './svelte.config.js';
|
|
9
|
+
|
|
10
|
+
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
|
|
11
|
+
|
|
12
|
+
export default defineConfig(
|
|
13
|
+
includeIgnoreFile(gitignorePath),
|
|
14
|
+
js.configs.recommended,
|
|
15
|
+
...ts.configs.recommended,
|
|
16
|
+
...svelte.configs.recommended,
|
|
17
|
+
{
|
|
18
|
+
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
|
19
|
+
rules: {
|
|
20
|
+
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
|
21
|
+
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
|
22
|
+
"no-undef": 'off'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
|
27
|
+
languageOptions: {
|
|
28
|
+
parserOptions: {
|
|
29
|
+
projectService: true,
|
|
30
|
+
extraFileExtensions: ['.svelte'],
|
|
31
|
+
parser: ts.parser,
|
|
32
|
+
svelteConfig
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|