lobsterboard 0.3.2 โ 0.4.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/app.html +53 -2
- package/dist/lobsterboard.css +1 -1
- package/dist/lobsterboard.esm.js +1 -1
- package/dist/lobsterboard.esm.min.js +1 -1
- package/dist/lobsterboard.umd.js +1 -1
- package/dist/lobsterboard.umd.min.js +1 -1
- package/js/builder.js +10 -7
- package/js/templates.js +24 -17
- package/js/widgets.js +567 -20
- package/package.json +4 -2
- package/server.cjs +1394 -30
package/app.html
CHANGED
|
@@ -223,9 +223,60 @@
|
|
|
223
223
|
<div class="widget-section">
|
|
224
224
|
<h4>๐ค AI / LLM</h4>
|
|
225
225
|
<div class="widget-list">
|
|
226
|
-
<div class="widget-item" draggable="true" data-widget="ai-usage
|
|
226
|
+
<div class="widget-item" draggable="true" data-widget="ai-usage">
|
|
227
|
+
<span class="widget-icon">๐ค</span>
|
|
228
|
+
<span class="widget-name">AI Usage</span>
|
|
229
|
+
<span class="widget-verified" title="Tested & Verified">โ</span>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="widget-item" draggable="true" data-widget="claude-code">
|
|
227
232
|
<span class="widget-icon">๐ฃ</span>
|
|
228
|
-
<span class="widget-name">Claude
|
|
233
|
+
<span class="widget-name">Claude Code</span>
|
|
234
|
+
<span class="widget-verified" title="Tested & Verified">โ</span>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="widget-item" draggable="true" data-widget="codex-cli">
|
|
237
|
+
<span class="widget-icon">๐ข</span>
|
|
238
|
+
<span class="widget-name">Codex CLI</span>
|
|
239
|
+
<span class="widget-verified" title="Tested & Verified">โ</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="widget-item" draggable="true" data-widget="github-copilot">
|
|
242
|
+
<span class="widget-icon">โซ</span>
|
|
243
|
+
<span class="widget-name">GitHub Copilot</span>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="widget-item" draggable="true" data-widget="cursor">
|
|
246
|
+
<span class="widget-icon">๐ต</span>
|
|
247
|
+
<span class="widget-name">Cursor</span>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="widget-item" draggable="true" data-widget="gemini-cli">
|
|
250
|
+
<span class="widget-icon">๐ท</span>
|
|
251
|
+
<span class="widget-name">Gemini CLI</span>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="widget-item" draggable="true" data-widget="amp-code">
|
|
254
|
+
<span class="widget-icon">โก</span>
|
|
255
|
+
<span class="widget-name">Amp Code</span>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="widget-item" draggable="true" data-widget="factory">
|
|
258
|
+
<span class="widget-icon">๐ญ</span>
|
|
259
|
+
<span class="widget-name">Factory</span>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="widget-item" draggable="true" data-widget="kimi-code">
|
|
262
|
+
<span class="widget-icon">๐</span>
|
|
263
|
+
<span class="widget-name">Kimi Code</span>
|
|
264
|
+
</div>
|
|
265
|
+
<div class="widget-item" draggable="true" data-widget="jetbrains-ai">
|
|
266
|
+
<span class="widget-icon">๐ง </span>
|
|
267
|
+
<span class="widget-name">JetBrains AI</span>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="widget-item" draggable="true" data-widget="minimax">
|
|
270
|
+
<span class="widget-icon">๐ถ</span>
|
|
271
|
+
<span class="widget-name">MiniMax</span>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="widget-item" draggable="true" data-widget="zai">
|
|
274
|
+
<span class="widget-icon">๐ฟ</span>
|
|
275
|
+
<span class="widget-name">Z.ai</span>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="widget-item" draggable="true" data-widget="antigravity-local">
|
|
278
|
+
<span class="widget-icon">๐ช</span>
|
|
279
|
+
<span class="widget-name">Antigravity</span>
|
|
229
280
|
</div>
|
|
230
281
|
<div class="widget-item" draggable="true" data-widget="ai-cost-tracker">
|
|
231
282
|
<span class="widget-icon">๐ฐ</span>
|
package/dist/lobsterboard.css
CHANGED
package/dist/lobsterboard.esm.js
CHANGED
package/dist/lobsterboard.umd.js
CHANGED
package/js/builder.js
CHANGED
|
@@ -3290,32 +3290,35 @@ async function openDirBrowser(startDir) {
|
|
|
3290
3290
|
try {
|
|
3291
3291
|
const res = await fetch('/api/browse-dirs?dir=' + encodeURIComponent(dir));
|
|
3292
3292
|
const data = await res.json();
|
|
3293
|
-
if (data.status !== 'ok') { browser.innerHTML = `<span style="color:#f85149;">${data.message}</span>`; return; }
|
|
3294
|
-
let html = `<div style="margin-bottom:6px;color:var(--text-secondary);font-size:11px;word-break:break-all;">${data.path}</div>`;
|
|
3293
|
+
if (data.status !== 'ok') { browser.innerHTML = `<span style="color:#f85149;">${escapeHtml(data.message)}</span>`; return; }
|
|
3294
|
+
let html = `<div style="margin-bottom:6px;color:var(--text-secondary);font-size:11px;word-break:break-all;">${escapeHtml(data.path)}</div>`;
|
|
3295
3295
|
if (data.imageCount > 0) {
|
|
3296
|
-
html += `<div style="margin-bottom:6px;padding:4px 8px;background:var(--bg-secondary);border-radius:4px;color:#3fb950;font-size:11px;">๐ท ${data.imageCount} image${data.imageCount !== 1 ? 's' : ''} in this folder</div>`;
|
|
3296
|
+
html += `<div style="margin-bottom:6px;padding:4px 8px;background:var(--bg-secondary);border-radius:4px;color:#3fb950;font-size:11px;">๐ท ${escapeHtml(String(data.imageCount))} image${data.imageCount !== 1 ? 's' : ''} in this folder</div>`;
|
|
3297
3297
|
}
|
|
3298
3298
|
// Up one level
|
|
3299
3299
|
const parent = data.path.replace(/\/[^/]+\/?$/, '') || '/';
|
|
3300
3300
|
if (data.path !== parent) {
|
|
3301
|
-
html += `<div class="dir-entry" data-path="${parent}" style="cursor:pointer;padding:3px 6px;border-radius:4px;color:var(--text-primary);" onmouseover="this.style.background='var(--bg-secondary)'" onmouseout="this.style.background='none'">๐ ..</div>`;
|
|
3301
|
+
html += `<div class="dir-entry" data-path="${escapeHtml(parent)}" style="cursor:pointer;padding:3px 6px;border-radius:4px;color:var(--text-primary);" onmouseover="this.style.background='var(--bg-secondary)'" onmouseout="this.style.background='none'">๐ ..</div>`;
|
|
3302
3302
|
}
|
|
3303
3303
|
for (const d of data.dirs) {
|
|
3304
3304
|
const full = data.path + '/' + d;
|
|
3305
|
-
html += `<div class="dir-entry" data-path="${full}" style="cursor:pointer;padding:3px 6px;border-radius:4px;color:var(--text-primary);" onmouseover="this.style.background='var(--bg-secondary)'" onmouseout="this.style.background='none'">๐ ${d}</div>`;
|
|
3305
|
+
html += `<div class="dir-entry" data-path="${escapeHtml(full)}" style="cursor:pointer;padding:3px 6px;border-radius:4px;color:var(--text-primary);" onmouseover="this.style.background='var(--bg-secondary)'" onmouseout="this.style.background='none'">๐ ${escapeHtml(d)}</div>`;
|
|
3306
3306
|
}
|
|
3307
3307
|
if (data.dirs.length === 0 && data.imageCount === 0) {
|
|
3308
3308
|
html += `<div style="color:var(--text-muted);font-size:11px;padding:4px;">Empty directory</div>`;
|
|
3309
3309
|
}
|
|
3310
3310
|
html += `<div style="margin-top:8px;display:flex;gap:4px;">`;
|
|
3311
|
-
html += `<button type="button"
|
|
3311
|
+
html += `<button type="button" style="flex:1;padding:4px 8px;background:var(--accent-blue);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;">โ Select this folder</button>`;
|
|
3312
3312
|
html += `<button type="button" onclick="document.getElementById('dir-browser').style.display='none'" style="padding:4px 8px;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;cursor:pointer;font-size:11px;">Cancel</button>`;
|
|
3313
3313
|
html += `</div>`;
|
|
3314
3314
|
browser.innerHTML = html;
|
|
3315
|
+
// Attach select button handler safely (avoid inline onclick with path data)
|
|
3316
|
+
const selectBtn = browser.querySelector('button');
|
|
3317
|
+
if (selectBtn) selectBtn.addEventListener('click', () => selectDir(data.path));
|
|
3315
3318
|
browser.querySelectorAll('.dir-entry').forEach(el => {
|
|
3316
3319
|
el.addEventListener('click', () => openDirBrowser(el.dataset.path));
|
|
3317
3320
|
});
|
|
3318
|
-
} catch (e) { browser.innerHTML = `<span style="color:#f85149;">Error: ${e.message}</span>`; }
|
|
3321
|
+
} catch (e) { browser.innerHTML = `<span style="color:#f85149;">Error: ${escapeHtml(e.message)}</span>`; }
|
|
3319
3322
|
}
|
|
3320
3323
|
|
|
3321
3324
|
function selectDir(dirPath) {
|
package/js/templates.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* LobsterBoard Template Gallery System
|
|
3
3
|
*/
|
|
4
4
|
(function() {
|
|
5
|
+
function _esc(str) {
|
|
6
|
+
if (str == null) return '';
|
|
7
|
+
const div = document.createElement('div');
|
|
8
|
+
div.textContent = String(str);
|
|
9
|
+
return div.innerHTML;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
const galleryModal = document.getElementById('template-gallery-modal');
|
|
6
13
|
const exportModal = document.getElementById('template-export-modal');
|
|
7
14
|
const tplGrid = document.getElementById('tpl-grid');
|
|
@@ -88,19 +95,19 @@
|
|
|
88
95
|
return;
|
|
89
96
|
}
|
|
90
97
|
tplGrid.innerHTML = templates.map(t => `
|
|
91
|
-
<div class="tpl-card" data-id="${t.id}">
|
|
98
|
+
<div class="tpl-card" data-id="${_esc(t.id)}">
|
|
92
99
|
<div class="tpl-card-img">
|
|
93
|
-
<img src="/api/templates/${t.id}/preview" alt="${t.name}" onerror="this.parentElement.innerHTML='<div class=\\'tpl-no-preview\\'>๐ฆ</div>'">
|
|
100
|
+
<img src="/api/templates/${_esc(t.id)}/preview" alt="${_esc(t.name)}" onerror="this.parentElement.innerHTML='<div class=\\'tpl-no-preview\\'>๐ฆ</div>'">
|
|
94
101
|
</div>
|
|
95
102
|
<div class="tpl-card-body">
|
|
96
|
-
<h3>${t.name}</h3>
|
|
97
|
-
<p>${t.description || ''}</p>
|
|
103
|
+
<h3>${_esc(t.name)}</h3>
|
|
104
|
+
<p>${_esc(t.description || '')}</p>
|
|
98
105
|
<div class="tpl-card-meta">
|
|
99
|
-
<span>${t.widgetCount || 0} widgets</span>
|
|
100
|
-
<span>${t.canvasSize || ''}</span>
|
|
106
|
+
<span>${_esc(String(t.widgetCount || 0))} widgets</span>
|
|
107
|
+
<span>${_esc(t.canvasSize || '')}</span>
|
|
101
108
|
</div>
|
|
102
|
-
${(t.widgetTypes || []).length ? `<div style="margin-top:4px;font-size:10px;color:var(--text-muted);">${t.widgetTypes.slice(0,6).map(w => (w.icon || '') + ' ' + w.name).join(' ยท ')}${t.widgetTypes.length > 6 ? ' ยท +' + (t.widgetTypes.length - 6) + ' more' : ''}</div>` : ''}
|
|
103
|
-
<div class="tpl-card-tags">${(t.tags || []).map(tag => `<span class="tpl-tag">${tag}</span>`).join('')}</div>
|
|
109
|
+
${(t.widgetTypes || []).length ? `<div style="margin-top:4px;font-size:10px;color:var(--text-muted);">${t.widgetTypes.slice(0,6).map(w => _esc((w.icon || '') + ' ' + w.name)).join(' ยท ')}${t.widgetTypes.length > 6 ? ' ยท +' + (t.widgetTypes.length - 6) + ' more' : ''}</div>` : ''}
|
|
110
|
+
<div class="tpl-card-tags">${(t.tags || []).map(tag => `<span class="tpl-tag">${_esc(tag)}</span>`).join('')}</div>
|
|
104
111
|
</div>
|
|
105
112
|
</div>
|
|
106
113
|
`).join('');
|
|
@@ -122,13 +129,13 @@
|
|
|
122
129
|
document.getElementById('tpl-detail-name').textContent = selectedTemplate.name;
|
|
123
130
|
document.getElementById('tpl-detail-desc').textContent = selectedTemplate.description || '';
|
|
124
131
|
document.getElementById('tpl-detail-meta').innerHTML = `
|
|
125
|
-
<div><strong>Author:</strong> ${selectedTemplate.author || 'anonymous'}</div>
|
|
126
|
-
<div><strong>Canvas:</strong> ${selectedTemplate.canvasSize || 'unknown'}</div>
|
|
127
|
-
<div><strong>Widgets:</strong> ${selectedTemplate.widgetCount || 0}</div>
|
|
128
|
-
${(selectedTemplate.requiresSetup || []).length ? `<div><strong>Requires:</strong> ${selectedTemplate.requiresSetup.join(', ')}</div>` : ''}
|
|
129
|
-
${(selectedTemplate.widgetTypes || []).length ? `<div style="margin-top:8px;"><strong>Widget Types:</strong><div style="margin-top:4px;">${selectedTemplate.widgetTypes.map(w => `<span style="display:inline-block;padding:2px 8px;margin:2px;background:var(--bg-tertiary);border-radius:4px;font-size:11px;">${w.icon || ''
|
|
132
|
+
<div><strong>Author:</strong> ${_esc(selectedTemplate.author || 'anonymous')}</div>
|
|
133
|
+
<div><strong>Canvas:</strong> ${_esc(selectedTemplate.canvasSize || 'unknown')}</div>
|
|
134
|
+
<div><strong>Widgets:</strong> ${_esc(String(selectedTemplate.widgetCount || 0))}</div>
|
|
135
|
+
${(selectedTemplate.requiresSetup || []).length ? `<div><strong>Requires:</strong> ${(selectedTemplate.requiresSetup || []).map(s => _esc(s)).join(', ')}</div>` : ''}
|
|
136
|
+
${(selectedTemplate.widgetTypes || []).length ? `<div style="margin-top:8px;"><strong>Widget Types:</strong><div style="margin-top:4px;">${selectedTemplate.widgetTypes.map(w => `<span style="display:inline-block;padding:2px 8px;margin:2px;background:var(--bg-tertiary);border-radius:4px;font-size:11px;">${_esc((w.icon || '') + ' ' + w.name)}${w.count > 1 ? ' ร' + _esc(String(w.count)) : ''}</span>`).join('')}</div></div>` : ''}
|
|
130
137
|
`;
|
|
131
|
-
document.getElementById('tpl-detail-tags').innerHTML = (selectedTemplate.tags || []).map(t => `<span class="tpl-tag">${t}</span>`).join('');
|
|
138
|
+
document.getElementById('tpl-detail-tags').innerHTML = (selectedTemplate.tags || []).map(t => `<span class="tpl-tag">${_esc(t)}</span>`).join('');
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
document.getElementById('tpl-back').addEventListener('click', () => {
|
|
@@ -267,16 +274,16 @@
|
|
|
267
274
|
body: JSON.stringify({ data: screenshotData })
|
|
268
275
|
});
|
|
269
276
|
}
|
|
270
|
-
resultEl.innerHTML = `โ
Template exported as <strong>${data.id}</strong>${screenshotData ? '<br>Screenshot captured!' : '<br>โ ๏ธ No screenshot (auto-capture failed).'}`;
|
|
277
|
+
resultEl.innerHTML = `โ
Template exported as <strong>${_esc(data.id)}</strong>${screenshotData ? '<br>Screenshot captured!' : '<br>โ ๏ธ No screenshot (auto-capture failed).'}`;
|
|
271
278
|
resultEl.className = 'tpl-export-result tpl-export-success';
|
|
272
279
|
} else {
|
|
273
|
-
resultEl.innerHTML = `โ ${data.error || 'Export failed'}`;
|
|
280
|
+
resultEl.innerHTML = `โ ${_esc(data.error || 'Export failed')}`;
|
|
274
281
|
resultEl.className = 'tpl-export-result tpl-export-error';
|
|
275
282
|
}
|
|
276
283
|
resultEl.style.display = 'block';
|
|
277
284
|
} catch (e) {
|
|
278
285
|
const resultEl = document.getElementById('tpl-export-result');
|
|
279
|
-
resultEl.innerHTML = `โ Export failed: ${e.message}`;
|
|
286
|
+
resultEl.innerHTML = `โ Export failed: ${_esc(e.message)}`;
|
|
280
287
|
resultEl.className = 'tpl-export-result tpl-export-error';
|
|
281
288
|
resultEl.style.display = 'block';
|
|
282
289
|
}
|