instadash-embed 0.1.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/embed.html +298 -0
- package/package.json +20 -0
package/embed.html
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Instadash</title>
|
|
7
|
+
<style>
|
|
8
|
+
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
|
|
9
|
+
|
|
10
|
+
:root{
|
|
11
|
+
--bg:#1B1F24;--bg-1:#1F242A;--bg-2:#252B32;
|
|
12
|
+
--fg:#E8EAED;--fg-2:#9CA3AF;--fg-3:#6B7280;
|
|
13
|
+
--line:rgba(255,255,255,.07);
|
|
14
|
+
--accent:#00E5FF;--lime:#39D353;--warn:#F5A623;
|
|
15
|
+
--mono:'SF Mono','Fira Code','Consolas',monospace;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html,body{height:100%;overflow:hidden}
|
|
19
|
+
body{background:var(--bg);color:var(--fg);font-family:system-ui,-apple-system,sans-serif;font-size:13px;display:flex;flex-direction:column}
|
|
20
|
+
|
|
21
|
+
/* ── Header ─────────────────────────────────────────────────── */
|
|
22
|
+
.bar{
|
|
23
|
+
display:flex;align-items:center;gap:8px;
|
|
24
|
+
padding:7px 12px;
|
|
25
|
+
background:var(--bg-1);
|
|
26
|
+
border-bottom:1px solid var(--line);
|
|
27
|
+
flex-shrink:0;
|
|
28
|
+
}
|
|
29
|
+
.mark{
|
|
30
|
+
width:18px;height:18px;
|
|
31
|
+
display:flex;align-items:center;justify-content:center;
|
|
32
|
+
border:1px solid var(--accent);border-radius:3px;
|
|
33
|
+
color:var(--accent);font-family:var(--mono);font-size:10px;font-weight:700;
|
|
34
|
+
flex-shrink:0;
|
|
35
|
+
}
|
|
36
|
+
.wordmark{font-size:12px;font-weight:700;letter-spacing:-.025em;color:var(--fg);flex-shrink:0}
|
|
37
|
+
.wordmark span{color:var(--accent)}
|
|
38
|
+
.grid-title{
|
|
39
|
+
font-size:12px;color:var(--fg-2);
|
|
40
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
|
|
41
|
+
flex:1;min-width:0;padding-left:4px;
|
|
42
|
+
font-family:var(--mono);
|
|
43
|
+
}
|
|
44
|
+
.open-link{
|
|
45
|
+
color:var(--accent);font-size:11px;font-family:var(--mono);
|
|
46
|
+
text-decoration:none;flex-shrink:0;white-space:nowrap;
|
|
47
|
+
}
|
|
48
|
+
.open-link:hover{text-decoration:underline}
|
|
49
|
+
|
|
50
|
+
/* ── Status bar ─────────────────────────────────────────────── */
|
|
51
|
+
.statusbar{
|
|
52
|
+
display:flex;align-items:center;
|
|
53
|
+
padding:3px 12px;
|
|
54
|
+
border-bottom:1px solid var(--line);
|
|
55
|
+
flex-shrink:0;
|
|
56
|
+
}
|
|
57
|
+
.rowcount{font-size:10px;color:var(--fg-3);font-family:var(--mono)}
|
|
58
|
+
|
|
59
|
+
/* ── Table ──────────────────────────────────────────────────── */
|
|
60
|
+
.wrap{flex:1;overflow:auto}
|
|
61
|
+
|
|
62
|
+
table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:12px}
|
|
63
|
+
|
|
64
|
+
thead th{
|
|
65
|
+
background:var(--bg-1);
|
|
66
|
+
padding:7px 12px;
|
|
67
|
+
text-align:left;font-size:11px;font-weight:600;color:var(--fg-2);
|
|
68
|
+
border-bottom:1px solid var(--line);
|
|
69
|
+
position:sticky;top:0;z-index:1;
|
|
70
|
+
white-space:nowrap;cursor:pointer;user-select:none;
|
|
71
|
+
}
|
|
72
|
+
thead th:hover{color:var(--fg)}
|
|
73
|
+
thead th.sorted{color:var(--accent)}
|
|
74
|
+
thead th.num{text-align:right}
|
|
75
|
+
|
|
76
|
+
tbody td{
|
|
77
|
+
padding:6px 12px;
|
|
78
|
+
border-bottom:1px solid rgba(255,255,255,.03);
|
|
79
|
+
max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
80
|
+
}
|
|
81
|
+
tbody tr:nth-child(even) td{background:var(--bg-1)}
|
|
82
|
+
tbody tr:hover td{background:var(--bg-2)}
|
|
83
|
+
|
|
84
|
+
td.num{text-align:right;color:var(--warn)}
|
|
85
|
+
td.url a{color:var(--accent);text-decoration:none}
|
|
86
|
+
td.url a:hover{text-decoration:underline}
|
|
87
|
+
td.bool .t{color:var(--lime)}
|
|
88
|
+
td.bool .f{color:var(--fg-3)}
|
|
89
|
+
|
|
90
|
+
/* ── Chips ──────────────────────────────────────────────────── */
|
|
91
|
+
.chip{
|
|
92
|
+
display:inline-block;
|
|
93
|
+
padding:1px 6px;border-radius:3px;
|
|
94
|
+
font-size:10px;border:1px solid;white-space:nowrap;
|
|
95
|
+
}
|
|
96
|
+
.c0{color:#00E5FF;border-color:rgba(0,229,255,.3);background:rgba(0,229,255,.06)}
|
|
97
|
+
.c1{color:#39D353;border-color:rgba(57,211,83,.3);background:rgba(57,211,83,.06)}
|
|
98
|
+
.c2{color:#F5A623;border-color:rgba(245,166,35,.3);background:rgba(245,166,35,.06)}
|
|
99
|
+
.c3{color:#BF5AF2;border-color:rgba(191,90,242,.3);background:rgba(191,90,242,.06)}
|
|
100
|
+
.c4{color:#FF6B6B;border-color:rgba(255,107,107,.3);background:rgba(255,107,107,.06)}
|
|
101
|
+
.c5{color:#9CA3AF;border-color:rgba(156,163,175,.3);background:rgba(156,163,175,.06)}
|
|
102
|
+
|
|
103
|
+
/* ── States ─────────────────────────────────────────────────── */
|
|
104
|
+
.state{
|
|
105
|
+
position:fixed;inset:0;display:flex;align-items:center;justify-content:center;
|
|
106
|
+
flex-direction:column;gap:8px;
|
|
107
|
+
background:var(--bg);color:var(--fg-3);font-family:var(--mono);font-size:12px;
|
|
108
|
+
}
|
|
109
|
+
.state .err{color:#FF6B6B}
|
|
110
|
+
.state a{color:var(--accent);margin-top:8px}
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
|
|
115
|
+
<div class="bar">
|
|
116
|
+
<div class="mark">›</div>
|
|
117
|
+
<div class="wordmark">insta<span>dash</span></div>
|
|
118
|
+
<div class="grid-title" id="grid-title"></div>
|
|
119
|
+
<a class="open-link" id="open-link" href="#" target="_blank" rel="noopener">open grid ↗</a>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div id="root"><div class="state">Loading…</div></div>
|
|
123
|
+
|
|
124
|
+
<script>
|
|
125
|
+
(function () {
|
|
126
|
+
var p = new URLSearchParams(location.search)
|
|
127
|
+
var handle = p.get('handle') || ''
|
|
128
|
+
var slug = p.get('slug') || ''
|
|
129
|
+
var base = 'https://instadash.io'
|
|
130
|
+
var gridUrl = base + '/' + handle + '/' + slug
|
|
131
|
+
|
|
132
|
+
document.getElementById('open-link').href = gridUrl
|
|
133
|
+
|
|
134
|
+
if (!handle || !slug) {
|
|
135
|
+
setRoot('<div class="state"><span class="err">Missing ?handle= and ?slug= params</span></div>')
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Type detection ──────────────────────────────────────────
|
|
140
|
+
function detectType(values) {
|
|
141
|
+
var nonNull = values.filter(function(v){ return v !== null && v !== undefined && v !== '' })
|
|
142
|
+
if (!nonNull.length) return 'string'
|
|
143
|
+
if (nonNull.every(function(v){ return v === true || v === false || v === 'true' || v === 'false' })) return 'bool'
|
|
144
|
+
if (nonNull.every(function(v){
|
|
145
|
+
var s = String(v).replace(/[$,%]/g, '')
|
|
146
|
+
return !isNaN(parseFloat(s)) && isFinite(s)
|
|
147
|
+
})) return 'num'
|
|
148
|
+
if (nonNull.every(function(v){ return typeof v === 'string' && /^https?:\/\//.test(v) })) return 'url'
|
|
149
|
+
var distinct = unique(nonNull.map(function(v){ return String(v) }))
|
|
150
|
+
if (distinct.length <= 10 && distinct.every(function(v){ return v.length <= 28 })) return 'chip'
|
|
151
|
+
return 'string'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function unique(arr) {
|
|
155
|
+
return arr.filter(function(v, i, a){ return a.indexOf(v) === i })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Per-column chip color maps
|
|
159
|
+
var colMaps = {}
|
|
160
|
+
function chipCls(col, val) {
|
|
161
|
+
if (!colMaps[col]) colMaps[col] = { m: {}, n: 0 }
|
|
162
|
+
var cm = colMaps[col], k = String(val).toLowerCase()
|
|
163
|
+
if (!(k in cm.m)) cm.m[k] = cm.n++ % 6
|
|
164
|
+
return 'chip c' + cm.m[k]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Sort state ──────────────────────────────────────────────
|
|
168
|
+
var sortCol = null, sortDir = 'asc'
|
|
169
|
+
|
|
170
|
+
function esc(s) {
|
|
171
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderTable(rows, meta) {
|
|
175
|
+
if (!rows.length) {
|
|
176
|
+
setRoot('<div class="state">No rows found</div>')
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
var cols = Object.keys(rows[0])
|
|
181
|
+
var types = {}
|
|
182
|
+
cols.forEach(function(col) {
|
|
183
|
+
types[col] = detectType(rows.map(function(r){ return r[col] }))
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// Pre-seed chip colors for stable ordering
|
|
187
|
+
colMaps = {}
|
|
188
|
+
cols.forEach(function(col) {
|
|
189
|
+
if (types[col] === 'chip') {
|
|
190
|
+
unique(rows.map(function(r){ return String(r[col] ?? '') })).forEach(function(v){ chipCls(col, v) })
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Sort
|
|
195
|
+
var sorted = rows.slice()
|
|
196
|
+
if (sortCol && cols.indexOf(sortCol) >= 0) {
|
|
197
|
+
var t = types[sortCol]
|
|
198
|
+
sorted.sort(function(a, b) {
|
|
199
|
+
var va = a[sortCol] ?? '', vb = b[sortCol] ?? ''
|
|
200
|
+
if (t === 'num') {
|
|
201
|
+
va = parseFloat(String(va).replace(/[$,%]/g, '')) || 0
|
|
202
|
+
vb = parseFloat(String(vb).replace(/[$,%]/g, '')) || 0
|
|
203
|
+
} else {
|
|
204
|
+
va = String(va).toLowerCase()
|
|
205
|
+
vb = String(vb).toLowerCase()
|
|
206
|
+
}
|
|
207
|
+
return va < vb ? (sortDir === 'asc' ? -1 : 1) : va > vb ? (sortDir === 'asc' ? 1 : -1) : 0
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Header
|
|
212
|
+
var ths = cols.map(function(col) {
|
|
213
|
+
var isSorted = sortCol === col
|
|
214
|
+
var cls = (types[col] === 'num' ? ' num' : '') + (isSorted ? ' sorted' : '')
|
|
215
|
+
var icon = isSorted ? (sortDir === 'asc' ? ' ↑' : ' ↓') : ''
|
|
216
|
+
return '<th class="' + cls.trim() + '" data-col="' + esc(col) + '">' + esc(col) + '<span style="opacity:.5;margin-left:3px">' + (icon || ' ↕') + '</span></th>'
|
|
217
|
+
}).join('')
|
|
218
|
+
|
|
219
|
+
// Rows
|
|
220
|
+
var trs = sorted.map(function(row) {
|
|
221
|
+
var tds = cols.map(function(col) {
|
|
222
|
+
var v = row[col]
|
|
223
|
+
var t = types[col]
|
|
224
|
+
var s = v === null || v === undefined ? '' : String(v)
|
|
225
|
+
if (t === 'url' && s) {
|
|
226
|
+
return '<td class="url"><a href="' + esc(s) + '" target="_blank" rel="noopener">' + esc(s) + '</a></td>'
|
|
227
|
+
}
|
|
228
|
+
if (t === 'bool') {
|
|
229
|
+
var isTrue = v === true || v === 'true' || v === 1 || v === '1'
|
|
230
|
+
return '<td class="bool"><span class="' + (isTrue ? 't' : 'f') + '">' + (isTrue ? '✓' : '✗') + '</span></td>'
|
|
231
|
+
}
|
|
232
|
+
if (t === 'num') {
|
|
233
|
+
return '<td class="num">' + esc(s) + '</td>'
|
|
234
|
+
}
|
|
235
|
+
if (t === 'chip' && s) {
|
|
236
|
+
return '<td><span class="' + chipCls(col, s) + '">' + esc(s) + '</span></td>'
|
|
237
|
+
}
|
|
238
|
+
return '<td title="' + esc(s) + '">' + esc(s) + '</td>'
|
|
239
|
+
}).join('')
|
|
240
|
+
return '<tr>' + tds + '</tr>'
|
|
241
|
+
}).join('')
|
|
242
|
+
|
|
243
|
+
var title = meta ? (meta.title || (handle + '/' + slug)) : (handle + '/' + slug)
|
|
244
|
+
var total = meta ? meta.rows : rows.length
|
|
245
|
+
document.getElementById('grid-title').textContent = title
|
|
246
|
+
|
|
247
|
+
setRoot(
|
|
248
|
+
'<div class="statusbar"><span class="rowcount">' +
|
|
249
|
+
sorted.length + (sorted.length < total ? ' of ' + total : '') + ' rows · ' +
|
|
250
|
+
cols.length + ' columns · click header to sort' +
|
|
251
|
+
'</span></div>' +
|
|
252
|
+
'<div class="wrap"><table>' +
|
|
253
|
+
'<thead><tr>' + ths + '</tr></thead>' +
|
|
254
|
+
'<tbody>' + trs + '</tbody>' +
|
|
255
|
+
'</table></div>'
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
document.querySelectorAll('thead th').forEach(function(th) {
|
|
259
|
+
th.addEventListener('click', function() {
|
|
260
|
+
var col = th.dataset.col
|
|
261
|
+
if (sortCol === col) {
|
|
262
|
+
sortDir = sortDir === 'asc' ? 'desc' : 'asc'
|
|
263
|
+
} else {
|
|
264
|
+
sortCol = col; sortDir = 'asc'
|
|
265
|
+
}
|
|
266
|
+
renderTable(rows, meta)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function setRoot(html) {
|
|
272
|
+
document.getElementById('root').innerHTML = html
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── Fetch ───────────────────────────────────────────────────
|
|
276
|
+
Promise.all([
|
|
277
|
+
fetch(gridUrl + '/rows?limit=2000').then(function(r) {
|
|
278
|
+
return r.ok ? r.json() : Promise.reject('HTTP ' + r.status)
|
|
279
|
+
}),
|
|
280
|
+
fetch(gridUrl + '/meta').then(function(r) {
|
|
281
|
+
return r.ok ? r.json() : null
|
|
282
|
+
}).catch(function() { return null }),
|
|
283
|
+
])
|
|
284
|
+
.then(function(res) { renderTable(res[0].rows || [], res[1]) })
|
|
285
|
+
.catch(function(e) {
|
|
286
|
+
setRoot(
|
|
287
|
+
'<div class="state">' +
|
|
288
|
+
'<span class="err">Could not load grid</span>' +
|
|
289
|
+
'<span>' + esc(handle + '/' + slug) + ' · ' + esc(String(e)) + '</span>' +
|
|
290
|
+
'<a href="' + esc(gridUrl) + '" target="_blank" rel="noopener">open in instadash ↗</a>' +
|
|
291
|
+
'</div>'
|
|
292
|
+
)
|
|
293
|
+
})
|
|
294
|
+
})()
|
|
295
|
+
</script>
|
|
296
|
+
|
|
297
|
+
</body>
|
|
298
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "instadash-embed",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Self-contained grid embed widget for instadash.io — works in Claude.ai artifacts via cdn.jsdelivr.net",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"embed.html"
|
|
8
|
+
],
|
|
9
|
+
"keywords": [
|
|
10
|
+
"instadash",
|
|
11
|
+
"embed",
|
|
12
|
+
"grid",
|
|
13
|
+
"data",
|
|
14
|
+
"mcp"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/instadash-io/embed"
|
|
19
|
+
}
|
|
20
|
+
}
|