lobsterboard 0.8.5 → 0.8.6
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/CHANGELOG.md +8 -0
- package/app.html +6 -0
- package/dist/lobsterboard.css +1 -1
- package/dist/lobsterboard.esm.js +189 -1
- package/dist/lobsterboard.esm.js.map +1 -1
- package/dist/lobsterboard.esm.min.js +2 -2
- package/dist/lobsterboard.esm.min.js.map +1 -1
- package/dist/lobsterboard.umd.js +189 -1
- package/dist/lobsterboard.umd.js.map +1 -1
- package/dist/lobsterboard.umd.min.js +2 -2
- package/dist/lobsterboard.umd.min.js.map +1 -1
- package/js/widgets/finance.js +146 -0
- package/js/widgets/search.js +119 -0
- package/package.json +1 -1
- package/server/routes/finance.cjs +92 -0
- package/server.cjs +4 -0
- package/src/widgets.js +188 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.6] - 2026-05-06
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Search widget restored** — reinstated the PR27 Search widget in the picker with DuckDuckGo-style `!bangs`, keyboard focus shortcut, and query recall
|
|
7
|
+
- **Finance widgets restored** — reinstated the PR27 Stock Ticker and Crypto Price widgets plus their server-side finance routes
|
|
8
|
+
- **npm package contents** — ensured the published package includes required runtime server files and restored PR27 widget assets
|
|
9
|
+
- **Remote stats cleanup** — improved remote stats poller teardown to avoid stale pollers and overlapping requests
|
|
10
|
+
|
|
3
11
|
## [0.8.5] - 2026-05-06
|
|
4
12
|
|
|
5
13
|
### Fixed
|
package/app.html
CHANGED
|
@@ -336,6 +336,10 @@
|
|
|
336
336
|
<span class="widget-name">GitHub Stats</span>
|
|
337
337
|
<span class="widget-verified" title="Tested & Verified">✓</span>
|
|
338
338
|
</div>
|
|
339
|
+
<div class="widget-item" draggable="true" data-widget="search">
|
|
340
|
+
<span class="widget-icon">🔍</span>
|
|
341
|
+
<span class="widget-name">Search</span>
|
|
342
|
+
</div>
|
|
339
343
|
</div>
|
|
340
344
|
</div>
|
|
341
345
|
|
|
@@ -761,6 +765,8 @@
|
|
|
761
765
|
<script src="js/widgets/layout.js"></script>
|
|
762
766
|
<script src="js/widgets/releases.js"></script>
|
|
763
767
|
<script src="js/widgets/misc.js"></script>
|
|
768
|
+
<script src="js/widgets/search.js"></script>
|
|
769
|
+
<script src="js/widgets/finance.js"></script>
|
|
764
770
|
<!-- Editor modules (order matters: state first, index last) -->
|
|
765
771
|
<script src="js/editor/state.js"></script>
|
|
766
772
|
<script src="js/editor/canvas.js"></script>
|
package/dist/lobsterboard.css
CHANGED
package/dist/lobsterboard.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* LobsterBoard v0.8.
|
|
2
|
+
* LobsterBoard v0.8.6
|
|
3
3
|
* Dashboard builder with customizable widgets
|
|
4
4
|
* https://github.com/curbob/LobsterBoard
|
|
5
5
|
* @license MIT
|
|
@@ -619,6 +619,194 @@ const WIDGETS = {
|
|
|
619
619
|
async function update_${props.id.replace(/-/g,'_')}() { const body = document.getElementById('${props.id}-body'); const subEl = document.getElementById('${props.id}-sub'); try { const res = await fetch('/api/pages/claude-usage/usage'); const d = await res.json(); if (d.error) { body.innerHTML='<div style="color:#f85149;">'+d.error+'</div>'; return; } if (subEl) { subEl.textContent = {max:'Max (5×)',pro:'Pro',free:'Free'}[d.subscription]||d.subscription||''; } let html=''; if(d.five_hour) html+=usageBar('5h Session',d.five_hour.utilization,d.five_hour.resets_at); if(d.seven_day) html+=usageBar('7d Weekly',d.seven_day.utilization,d.seven_day.resets_at); if(d.seven_day_opus) html+=usageBar('Opus (7d)',d.seven_day_opus.utilization,d.seven_day_opus.resets_at); if(d.seven_day_sonnet&&d.seven_day_sonnet.utilization>0) html+=usageBar('Sonnet (7d)',d.seven_day_sonnet.utilization,d.seven_day_sonnet.resets_at); if(d.extra_usage&&d.extra_usage.is_enabled){const used=(d.extra_usage.used_credits/100).toFixed(2),limit=d.extra_usage.monthly_limit>0?(d.extra_usage.monthly_limit/100).toFixed(2):'∞';html+='<div style="margin-top:4px;padding-top:6px;border-top:1px solid #30363d;"><div style="display:flex;justify-content:space-between;font-size:11px;"><span style="color:#8b949e;">Extra Usage</span><span style="font-weight:600;">$'+used+' / $'+limit+'</span></div></div>';} if(!html) html='<div style="color:#8b949e;">No usage data</div>'; body.innerHTML=html; } catch(e) { console.error('Claude usage error:',e); body.innerHTML='<div style="color:#f85149;">Failed to load</div>'; } }
|
|
620
620
|
update_${props.id.replace(/-/g,'_')}(); setInterval(update_${props.id.replace(/-/g,'_')}, ${(props.refreshInterval||120)*1000});
|
|
621
621
|
`
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
// ── Finance ──────────────────────────────────────────────────────────────
|
|
625
|
+
|
|
626
|
+
'stock-ticker': {
|
|
627
|
+
name: 'Stock Ticker',
|
|
628
|
+
icon: '📈',
|
|
629
|
+
category: 'large',
|
|
630
|
+
description: 'Live stock prices via Yahoo Finance. No API key required.',
|
|
631
|
+
defaultWidth: 320,
|
|
632
|
+
defaultHeight: 280,
|
|
633
|
+
hasApiKey: false,
|
|
634
|
+
properties: {
|
|
635
|
+
title: 'Stocks',
|
|
636
|
+
symbols: 'AAPL,MSFT,GOOGL,AMZN',
|
|
637
|
+
refreshInterval: 300
|
|
638
|
+
},
|
|
639
|
+
preview: `<div style="padding:6px;font-size:11px;">
|
|
640
|
+
<div style="display:flex;justify-content:space-between;"><span>AAPL</span><span>$182.50 <span style="color:#3fb950;">+1.24%</span></span></div>
|
|
641
|
+
<div style="display:flex;justify-content:space-between;"><span>MSFT</span><span>$375.20 <span style="color:#f85149;">-0.38%</span></span></div>
|
|
642
|
+
</div>`,
|
|
643
|
+
generateHtml: (props) => `
|
|
644
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
645
|
+
<div class="dash-card-head">
|
|
646
|
+
<span class="dash-card-title">📈 ${props.title || 'Stocks'}</span>
|
|
647
|
+
<span class="dash-card-badge" id="${props.id}-badge">—</span>
|
|
648
|
+
</div>
|
|
649
|
+
<div class="dash-card-body" id="${props.id}-list" style="overflow-y:auto;padding:4px 8px;">
|
|
650
|
+
<div style="color:#8b949e;font-size:12px;">Loading...</div>
|
|
651
|
+
</div>
|
|
652
|
+
</div>`,
|
|
653
|
+
generateJs: (props) => `
|
|
654
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
655
|
+
var list = document.getElementById('${props.id}-list');
|
|
656
|
+
var badge = document.getElementById('${props.id}-badge');
|
|
657
|
+
if (!list) return;
|
|
658
|
+
try {
|
|
659
|
+
var res = await fetch('/api/finance/stocks?symbols=' + encodeURIComponent('${props.symbols || 'AAPL,MSFT'}'));
|
|
660
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
661
|
+
var data = await res.json();
|
|
662
|
+
var ok = data.filter(function(d) { return !d.error; });
|
|
663
|
+
list.innerHTML = data.map(function(d) {
|
|
664
|
+
if (d.error) return '<div style="padding:5px 0;border-bottom:1px solid #21262d;color:#8b949e;font-size:12px;">' + _esc(d.symbol) + ' — ' + _esc(d.error) + '</div>';
|
|
665
|
+
var color = d.up ? '#3fb950' : '#f85149';
|
|
666
|
+
return '<div style="display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid #21262d;">'
|
|
667
|
+
+ '<span style="font-weight:600;font-size:13px;">' + _esc(d.symbol) + '</span>'
|
|
668
|
+
+ '<span style="text-align:right;">'
|
|
669
|
+
+ '<span style="font-size:13px;font-weight:700;margin-right:8px;">$' + _esc(d.price) + '</span>'
|
|
670
|
+
+ '<span style="font-size:11px;color:' + color + ';font-weight:600;">' + _esc(d.pctChange) + '%</span>'
|
|
671
|
+
+ '</span></div>';
|
|
672
|
+
}).join('');
|
|
673
|
+
if (badge) badge.textContent = ok.length + ' stocks';
|
|
674
|
+
} catch (e) {
|
|
675
|
+
console.error('Stock ticker error:', e);
|
|
676
|
+
if (list) list.innerHTML = '<div style="color:#f85149;font-size:12px;">Failed to load</div>';
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
680
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 300) * 1000});
|
|
681
|
+
`
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
'crypto-price': {
|
|
685
|
+
name: 'Crypto Price',
|
|
686
|
+
icon: '₿',
|
|
687
|
+
category: 'large',
|
|
688
|
+
description: 'Live crypto prices via CoinGecko. No API key required.',
|
|
689
|
+
defaultWidth: 320,
|
|
690
|
+
defaultHeight: 280,
|
|
691
|
+
hasApiKey: false,
|
|
692
|
+
properties: {
|
|
693
|
+
title: 'Crypto',
|
|
694
|
+
coins: 'bitcoin,ethereum,solana',
|
|
695
|
+
currency: 'usd',
|
|
696
|
+
refreshInterval: 300
|
|
697
|
+
},
|
|
698
|
+
preview: `<div style="padding:6px;font-size:11px;">
|
|
699
|
+
<div style="display:flex;justify-content:space-between;"><span>Bitcoin</span><span>$67,240 <span style="color:#3fb950;">+2.1%</span></span></div>
|
|
700
|
+
<div style="display:flex;justify-content:space-between;"><span>Ethereum</span><span>$3,521 <span style="color:#f85149;">-0.8%</span></span></div>
|
|
701
|
+
</div>`,
|
|
702
|
+
generateHtml: (props) => `
|
|
703
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
704
|
+
<div class="dash-card-head">
|
|
705
|
+
<span class="dash-card-title">₿ ${props.title || 'Crypto'}</span>
|
|
706
|
+
<span class="dash-card-badge" id="${props.id}-badge">—</span>
|
|
707
|
+
</div>
|
|
708
|
+
<div class="dash-card-body" id="${props.id}-list" style="overflow-y:auto;padding:4px 8px;">
|
|
709
|
+
<div style="color:#8b949e;font-size:12px;">Loading...</div>
|
|
710
|
+
</div>
|
|
711
|
+
</div>`,
|
|
712
|
+
generateJs: (props) => `
|
|
713
|
+
async function update_${props.id.replace(/-/g, '_')}() {
|
|
714
|
+
var list = document.getElementById('${props.id}-list');
|
|
715
|
+
var badge = document.getElementById('${props.id}-badge');
|
|
716
|
+
if (!list) return;
|
|
717
|
+
try {
|
|
718
|
+
var res = await fetch('/api/finance/crypto?coins=' + encodeURIComponent('${props.coins || 'bitcoin,ethereum'}') + '¤cy=${props.currency || 'usd'}');
|
|
719
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
720
|
+
var data = await res.json();
|
|
721
|
+
list.innerHTML = data.map(function(d) {
|
|
722
|
+
var color = d.up ? '#3fb950' : '#f85149';
|
|
723
|
+
return '<div style="display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid #21262d;">'
|
|
724
|
+
+ '<span style="font-weight:600;font-size:13px;">' + _esc(d.name) + '</span>'
|
|
725
|
+
+ '<span style="text-align:right;">'
|
|
726
|
+
+ '<span style="font-size:13px;font-weight:700;margin-right:8px;">${(props.currency || 'usd').toUpperCase()} ' + _esc(d.price) + '</span>'
|
|
727
|
+
+ '<span style="font-size:11px;color:' + color + ';font-weight:600;">' + _esc(d.pctChange) + '%</span>'
|
|
728
|
+
+ '</span></div>';
|
|
729
|
+
}).join('');
|
|
730
|
+
if (badge) badge.textContent = data.length + ' coins';
|
|
731
|
+
} catch (e) {
|
|
732
|
+
console.error('Crypto price error:', e);
|
|
733
|
+
if (list) list.innerHTML = '<div style="color:#f85149;font-size:12px;">Failed to load</div>';
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
update_${props.id.replace(/-/g, '_')}();
|
|
737
|
+
setInterval(update_${props.id.replace(/-/g, '_')}, ${(props.refreshInterval || 300) * 1000});
|
|
738
|
+
`
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
// ── Search ────────────────────────────────────────────────────────────────
|
|
742
|
+
|
|
743
|
+
'search': {
|
|
744
|
+
name: 'Search',
|
|
745
|
+
icon: '🔍',
|
|
746
|
+
category: 'large',
|
|
747
|
+
description: 'Search box with DuckDuckGo-style !bangs. Press S to focus, ↑ for last query.',
|
|
748
|
+
defaultWidth: 460,
|
|
749
|
+
defaultHeight: 120,
|
|
750
|
+
hasApiKey: false,
|
|
751
|
+
properties: {
|
|
752
|
+
title: 'Search',
|
|
753
|
+
placeholder: 'Search or use !bangs',
|
|
754
|
+
searchEngine: 'duckduckgo',
|
|
755
|
+
openInNewTab: true,
|
|
756
|
+
showBangHints: true
|
|
757
|
+
},
|
|
758
|
+
preview: `<div style="padding:10px;">
|
|
759
|
+
<input style="width:100%;padding:6px 10px;background:#21262d;border:1px solid #30363d;border-radius:6px;color:#e6edf3;font-size:13px;" placeholder="Search or use !bangs" readonly />
|
|
760
|
+
<div style="font-size:10px;color:#8b949e;margin-top:6px;">!yt YouTube !gh GitHub !r Reddit !so Stack Overflow</div>
|
|
761
|
+
</div>`,
|
|
762
|
+
generateHtml: (props) => `
|
|
763
|
+
<div class="dash-card" id="widget-${props.id}" style="height:100%;">
|
|
764
|
+
<div class="dash-card-head">
|
|
765
|
+
<span class="dash-card-title">🔍 ${props.title || 'Search'}</span>
|
|
766
|
+
</div>
|
|
767
|
+
<div class="dash-card-body" style="padding:8px 10px;">
|
|
768
|
+
<form id="${props.id}-form" style="display:flex;gap:6px;" onsubmit="return false;">
|
|
769
|
+
<input id="${props.id}-input" type="text"
|
|
770
|
+
placeholder="${props.placeholder || 'Search or use !bangs'}"
|
|
771
|
+
autocomplete="off" spellcheck="false"
|
|
772
|
+
style="flex:1;padding:7px 11px;background:#21262d;border:1px solid #30363d;border-radius:6px;color:#e6edf3;font-size:13px;outline:none;" />
|
|
773
|
+
<button type="submit"
|
|
774
|
+
style="padding:7px 14px;background:#238636;border:none;border-radius:6px;color:#fff;font-size:13px;cursor:pointer;">Go</button>
|
|
775
|
+
</form>
|
|
776
|
+
${props.showBangHints !== false ? `<div style="font-size:10px;color:#8b949e;margin-top:5px;"><span style="opacity:.7;">!g Google !yt YouTube !gh GitHub !r Reddit !so Stack Overflow !w Wikipedia !npm npm</span></div>` : ''}
|
|
777
|
+
</div>
|
|
778
|
+
</div>`,
|
|
779
|
+
generateJs: (props) => {
|
|
780
|
+
const providers = { duckduckgo:'https://duckduckgo.com/?q={q}', google:'https://www.google.com/search?q={q}', bing:'https://www.bing.com/search?q={q}', brave:'https://search.brave.com/search?q={q}' };
|
|
781
|
+
const bangs = { g:'https://www.google.com/search?q={q}',b:'https://www.bing.com/search?q={q}',yt:'https://www.youtube.com/results?search_query={q}',gh:'https://github.com/search?q={q}',r:'https://www.reddit.com/search/?q={q}',so:'https://stackoverflow.com/search?q={q}',w:'https://en.wikipedia.org/wiki/Special:Search/{q}',img:'https://www.google.com/search?tbm=isch&q={q}',maps:'https://www.google.com/maps/search/{q}',ddg:'https://duckduckgo.com/?q={q}',npm:'https://www.npmjs.com/search?q={q}',mdn:'https://developer.mozilla.org/en-US/search?q={q}',x:'https://x.com/search?q={q}',tw:'https://x.com/search?q={q}' };
|
|
782
|
+
const defaultUrl = providers[props.searchEngine] || providers.duckduckgo;
|
|
783
|
+
return `
|
|
784
|
+
(function() {
|
|
785
|
+
var BANGS = ${JSON.stringify(bangs)};
|
|
786
|
+
var DEFAULT_URL = '${defaultUrl}';
|
|
787
|
+
var lastQuery = '';
|
|
788
|
+
function navigate(q) {
|
|
789
|
+
q = q.trim(); if (!q) return; lastQuery = q;
|
|
790
|
+
var url = DEFAULT_URL;
|
|
791
|
+
var m = q.match(/^!(\\S+)\\s*(.*)/);
|
|
792
|
+
if (m) { var bang = m[1].toLowerCase(), rest = m[2]; if (BANGS[bang]) { url = BANGS[bang]; q = rest || q; } }
|
|
793
|
+
var target = url.replace('{q}', encodeURIComponent(q));
|
|
794
|
+
if (${props.openInNewTab !== false}) { window.open(target, '_blank', 'noopener'); } else { window.location.href = target; }
|
|
795
|
+
}
|
|
796
|
+
var form = document.getElementById('${props.id}-form');
|
|
797
|
+
var input = document.getElementById('${props.id}-input');
|
|
798
|
+
if (form) form.addEventListener('submit', function() { navigate(input.value); });
|
|
799
|
+
if (input) input.addEventListener('keydown', function(e) { if (e.key === 'ArrowUp') { e.preventDefault(); input.value = lastQuery; } });
|
|
800
|
+
document.addEventListener('keydown', function(e) {
|
|
801
|
+
var tag = document.activeElement && document.activeElement.tagName;
|
|
802
|
+
if (e.key === 's' && tag !== 'INPUT' && tag !== 'TEXTAREA' && !e.ctrlKey && !e.metaKey) {
|
|
803
|
+
var inp = document.getElementById('${props.id}-input');
|
|
804
|
+
if (inp) { inp.focus(); inp.select(); e.preventDefault(); }
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
})();
|
|
808
|
+
`;
|
|
809
|
+
}
|
|
622
810
|
}
|
|
623
811
|
};
|
|
624
812
|
|