@rip-lang/server 1.3.80 → 1.3.81
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/default.rip +189 -4
- package/package.json +1 -1
package/default.rip
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
# - Serves static files with auto-detected MIME types
|
|
10
10
|
# - Directories with index.html serve the index.html
|
|
11
11
|
# - Directories without index.html show a browsable file listing
|
|
12
|
+
# - Hover preview for images and PDFs
|
|
12
13
|
# - Rip browser bundle available at /rip/rip.min.js
|
|
13
14
|
# - Hot-reload via SSE for .rip, .html, and .css changes
|
|
14
15
|
#
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
|
|
17
18
|
import { use, start, notFound } from '@rip-lang/server'
|
|
18
19
|
import { serve } from '@rip-lang/server/middleware'
|
|
19
|
-
import { statSync, readdirSync } from 'node:fs'
|
|
20
|
+
import { statSync, readdirSync, readFileSync } from 'node:fs'
|
|
20
21
|
import { join, resolve, basename } from 'node:path'
|
|
21
22
|
|
|
22
23
|
root = resolve(process.env.APP_BASE_DIR or process.cwd())
|
|
@@ -56,11 +57,17 @@ EXT_ICONS =
|
|
|
56
57
|
mp3: '🎵', ogg: '🎵', wav: '🎵', mp4: '🎬', webm: '🎬'
|
|
57
58
|
woff: '🔤', woff2: '🔤', ttf: '🔤', otf: '🔤'
|
|
58
59
|
|
|
60
|
+
IMG_EXTS = new Set ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif']
|
|
61
|
+
|
|
59
62
|
fileIcon = (name) ->
|
|
60
63
|
EXT_ICONS[name.slice(name.lastIndexOf('.') + 1).toLowerCase()] or '📄'
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
previewAttr = (name, href) ->
|
|
66
|
+
ext = name.slice(name.lastIndexOf('.') + 1).toLowerCase()
|
|
67
|
+
if IMG_EXTS.has(ext) then " data-pv=\"image\" data-src=\"#{esc href}\"" else ''
|
|
68
|
+
|
|
69
|
+
row = (ic, href, label, size = '', date = '', extra = '') ->
|
|
70
|
+
"<tr><td class=\"ic\">#{ic}</td><td><a href=\"#{esc href}\"#{extra}>#{esc label}</a></td><td class=\"sz\">#{size}</td><td class=\"dt\">#{date}</td></tr>"
|
|
64
71
|
|
|
65
72
|
breadcrumbs = (reqPath) ->
|
|
66
73
|
parts = reqPath.split('/').filter((p) -> p)
|
|
@@ -90,7 +97,7 @@ renderIndex = (reqPath, fsPath) ->
|
|
|
90
97
|
for f in files
|
|
91
98
|
try
|
|
92
99
|
s = statSync(join(fsPath, f))
|
|
93
|
-
rows.push row(fileIcon(f), f, f, fmtSize(s.size), fmtDate(s.mtimeMs))
|
|
100
|
+
rows.push row(fileIcon(f), f, f, fmtSize(s.size), fmtDate(s.mtimeMs), previewAttr(f, f))
|
|
94
101
|
catch
|
|
95
102
|
rows.push row(fileIcon(f), f, f)
|
|
96
103
|
|
|
@@ -194,11 +201,29 @@ renderIndex = (reqPath, fsPath) ->
|
|
|
194
201
|
.sz { width: 6rem; text-align: right; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
195
202
|
.dt { color: var(--muted); white-space: nowrap; }
|
|
196
203
|
th.sz { text-align: right; }
|
|
204
|
+
.pop {
|
|
205
|
+
position: fixed; z-index: 9999; pointer-events: none;
|
|
206
|
+
width: 320px; background: var(--card);
|
|
207
|
+
border: 1px solid var(--border); border-radius: 12px;
|
|
208
|
+
box-shadow: 0 10px 30px rgba(0,0,0,.12);
|
|
209
|
+
overflow: hidden; opacity: 0;
|
|
210
|
+
transform: translateY(6px) scale(.985);
|
|
211
|
+
transition: opacity 150ms ease, transform 150ms ease;
|
|
212
|
+
}
|
|
213
|
+
.pop.on { opacity: 1; transform: none; }
|
|
214
|
+
.pop-h {
|
|
215
|
+
padding: 8px 14px; font-size: .6875rem; font-weight: 700;
|
|
216
|
+
letter-spacing: .06em; color: var(--muted); text-transform: uppercase;
|
|
217
|
+
border-bottom: 1px solid var(--border-row); background: var(--head-bg);
|
|
218
|
+
}
|
|
219
|
+
.pop-b { display: flex; align-items: center; justify-content: center; min-height: 100px; }
|
|
220
|
+
.pop-b img { display: block; max-width: 100%; max-height: 240px; }
|
|
197
221
|
@media (max-width: 640px) {
|
|
198
222
|
.wrap { margin: 1rem auto; }
|
|
199
223
|
td, th { padding: .5rem .75rem; }
|
|
200
224
|
.dt, th.dt { display: none; }
|
|
201
225
|
.bar { padding: .75rem 1rem; }
|
|
226
|
+
.pop { display: none; }
|
|
202
227
|
}
|
|
203
228
|
</style>
|
|
204
229
|
</head>
|
|
@@ -217,6 +242,162 @@ renderIndex = (reqPath, fsPath) ->
|
|
|
217
242
|
</table>
|
|
218
243
|
</div>
|
|
219
244
|
</div>
|
|
245
|
+
<script>
|
|
246
|
+
(function(){
|
|
247
|
+
var pop,cur,ox=16,oy=16;
|
|
248
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
|
249
|
+
function base(s){return decodeURIComponent(s.split('?')[0].split('/').pop())}
|
|
250
|
+
function mk(){
|
|
251
|
+
if(pop)return pop;
|
|
252
|
+
pop=document.createElement('div');pop.className='pop';
|
|
253
|
+
pop.innerHTML='<div class="pop-h"></div><div class="pop-b"></div>';
|
|
254
|
+
document.body.appendChild(pop);return pop;
|
|
255
|
+
}
|
|
256
|
+
function show(a,e){
|
|
257
|
+
var t=a.dataset.pv,s=a.dataset.src;if(!t||!s)return;
|
|
258
|
+
var el=mk(),h=el.firstChild,b=el.lastChild;
|
|
259
|
+
h.textContent='PREVIEW';b.innerHTML='';
|
|
260
|
+
var img=new Image();img.src=s;img.alt=base(s);b.appendChild(img);
|
|
261
|
+
cur=a;el.classList.add('on');place(e.clientX,e.clientY);
|
|
262
|
+
}
|
|
263
|
+
function hide(){cur=null;if(pop)pop.classList.remove('on')}
|
|
264
|
+
function place(x,y){
|
|
265
|
+
if(!pop)return;var r=pop.getBoundingClientRect(),p=12,l=x+ox,t=y+oy;
|
|
266
|
+
if(l+r.width>innerWidth-p)l=x-r.width-ox;
|
|
267
|
+
if(t+r.height>innerHeight-p)t=innerHeight-r.height-p;
|
|
268
|
+
if(l<p)l=p;if(t<p)t=p;
|
|
269
|
+
pop.style.left=l+'px';pop.style.top=t+'px';
|
|
270
|
+
}
|
|
271
|
+
document.addEventListener('mouseover',function(e){var a=e.target.closest('[data-pv]');if(a)show(a,e)});
|
|
272
|
+
document.addEventListener('mousemove',function(e){if(cur)place(e.clientX,e.clientY)});
|
|
273
|
+
document.addEventListener('mouseout',function(e){var a=e.target.closest('[data-pv]');if(a&&a===cur)hide()});
|
|
274
|
+
document.addEventListener('scroll',hide,{passive:true});
|
|
275
|
+
window.addEventListener('blur',hide);
|
|
276
|
+
})();
|
|
277
|
+
</script>
|
|
278
|
+
</body>
|
|
279
|
+
</html>
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
renderMarkdown = (reqPath, fsPath) ->
|
|
283
|
+
md = readFileSync(fsPath, 'utf8')
|
|
284
|
+
body = Bun.markdown.html(md)
|
|
285
|
+
name = esc(basename(fsPath))
|
|
286
|
+
"""
|
|
287
|
+
<!DOCTYPE html>
|
|
288
|
+
<html lang="en">
|
|
289
|
+
<head>
|
|
290
|
+
<meta charset="UTF-8">
|
|
291
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
292
|
+
<title>#{name}</title>
|
|
293
|
+
<style>
|
|
294
|
+
:root {
|
|
295
|
+
--bg: #f3f6f7; --card: #fff; --border: #d1d9e0; --border-subtle: #d1d9e0b3;
|
|
296
|
+
--text: #1f2328; --muted: #59636e; --link: #0969da;
|
|
297
|
+
--head-bg: #fafbfc; --sep: #cbd5e1;
|
|
298
|
+
--code-bg: #f6f8fa; --code-inline: #818b981f;
|
|
299
|
+
--table-alt: #f6f8fa; --hr: #d1d9e0;
|
|
300
|
+
--mark-bg: #fff8c5; --mark-text: #1f2328;
|
|
301
|
+
}
|
|
302
|
+
@media (prefers-color-scheme: dark) {
|
|
303
|
+
:root {
|
|
304
|
+
--bg: #0d1117; --card: #161b22; --border: #30363d; --border-subtle: #30363db3;
|
|
305
|
+
--text: #e6edf3; --muted: #8b949e; --link: #58a6ff;
|
|
306
|
+
--head-bg: #1c2129; --sep: #484f58;
|
|
307
|
+
--code-bg: #161b22; --code-inline: #b1bac41f;
|
|
308
|
+
--table-alt: #161b22; --hr: #3d444d;
|
|
309
|
+
--mark-bg: #bb800926; --mark-text: #e6edf3;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
313
|
+
body {
|
|
314
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
|
|
315
|
+
font-size: 16px; line-height: 1.5; color: var(--text); background: var(--bg);
|
|
316
|
+
word-wrap: break-word;
|
|
317
|
+
}
|
|
318
|
+
.wrap { max-width: 820px; margin: 2.5rem auto; padding: 0 1rem; }
|
|
319
|
+
.card {
|
|
320
|
+
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
|
|
321
|
+
overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
|
|
322
|
+
}
|
|
323
|
+
.bar {
|
|
324
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
325
|
+
padding: .875rem 1.25rem; border-bottom: 1px solid var(--border);
|
|
326
|
+
}
|
|
327
|
+
.crumbs { font-size: .9375rem; font-weight: 500; }
|
|
328
|
+
.crumbs a { color: var(--link); text-decoration: none; }
|
|
329
|
+
.crumbs a:hover { text-decoration: underline; }
|
|
330
|
+
.sep { color: var(--sep); margin: 0 .3rem; font-weight: 400; }
|
|
331
|
+
article { padding: 2rem 2.5rem; }
|
|
332
|
+
article > *:first-child { margin-top: 0 !important; }
|
|
333
|
+
article > *:last-child { margin-bottom: 0 !important; }
|
|
334
|
+
article h1, article h2, article h3, article h4, article h5, article h6 {
|
|
335
|
+
margin-top: 1.5rem; margin-bottom: 1rem; font-weight: 600; line-height: 1.25;
|
|
336
|
+
}
|
|
337
|
+
article h1 { font-size: 2em; padding-bottom: .3em; border-bottom: 1px solid var(--border-subtle); }
|
|
338
|
+
article h2 { font-size: 1.5em; padding-bottom: .3em; border-bottom: 1px solid var(--border-subtle); }
|
|
339
|
+
article h3 { font-size: 1.25em; }
|
|
340
|
+
article h4 { font-size: 1em; }
|
|
341
|
+
article h5 { font-size: .875em; }
|
|
342
|
+
article h6 { font-size: .85em; color: var(--muted); }
|
|
343
|
+
article p, article blockquote, article ul, article ol, article dl, article table, article pre, article details {
|
|
344
|
+
margin-top: 0; margin-bottom: 1rem;
|
|
345
|
+
}
|
|
346
|
+
article a { color: var(--link); text-decoration: none; }
|
|
347
|
+
article a:hover { text-decoration: underline; }
|
|
348
|
+
article strong { font-weight: 600; }
|
|
349
|
+
article img { max-width: 100%; box-sizing: content-box; border-style: none; }
|
|
350
|
+
article ul, article ol { padding-left: 2em; }
|
|
351
|
+
article ol ol, article ul ol { list-style-type: lower-roman; }
|
|
352
|
+
article ul ul ol, article ul ol ol, article ol ul ol, article ol ol ol { list-style-type: lower-alpha; }
|
|
353
|
+
article ul ul, article ul ol, article ol ol, article ol ul { margin-top: 0; margin-bottom: 0; }
|
|
354
|
+
article li + li { margin-top: .25em; }
|
|
355
|
+
article li > p { margin-top: 1rem; }
|
|
356
|
+
article li > input[type="checkbox"] { margin-right: .5rem; }
|
|
357
|
+
article blockquote { padding: 0 1em; color: var(--muted); border-left: .25em solid var(--border); }
|
|
358
|
+
article blockquote > :first-child { margin-top: 0; }
|
|
359
|
+
article blockquote > :last-child { margin-bottom: 0; }
|
|
360
|
+
article mark { background-color: var(--mark-bg); color: var(--mark-text); }
|
|
361
|
+
article code, article tt {
|
|
362
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
363
|
+
font-size: 85%; padding: .2em .4em; margin: 0;
|
|
364
|
+
white-space: break-spaces; background-color: var(--code-inline); border-radius: 6px;
|
|
365
|
+
}
|
|
366
|
+
article pre {
|
|
367
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
368
|
+
font-size: 85%; line-height: 1.45; padding: 1rem;
|
|
369
|
+
overflow: auto; background-color: var(--code-bg); border: 1px solid var(--border); border-radius: 6px;
|
|
370
|
+
}
|
|
371
|
+
article pre code, article pre tt {
|
|
372
|
+
display: inline; padding: 0; margin: 0; overflow: visible;
|
|
373
|
+
line-height: inherit; word-wrap: normal; background-color: transparent;
|
|
374
|
+
border: 0; font-size: 100%; white-space: pre;
|
|
375
|
+
}
|
|
376
|
+
article table { display: block; width: max-content; max-width: 100%; overflow: auto; border-spacing: 0; border-collapse: collapse; }
|
|
377
|
+
article table th { font-weight: 600; }
|
|
378
|
+
article table th, article table td { padding: 6px 13px; border: 1px solid var(--border); }
|
|
379
|
+
article table tr { background-color: var(--card); border-top: 1px solid var(--border-subtle); }
|
|
380
|
+
article table tr:nth-child(2n) { background-color: var(--table-alt); }
|
|
381
|
+
article table td > :last-child { margin-bottom: 0; }
|
|
382
|
+
article hr {
|
|
383
|
+
height: .25em; padding: 0; margin: 1.5rem 0;
|
|
384
|
+
background-color: var(--hr); border: 0; overflow: hidden;
|
|
385
|
+
}
|
|
386
|
+
article del { color: var(--muted); }
|
|
387
|
+
article dl dt { padding: 0; margin-top: 1rem; font-size: 1em; font-style: italic; font-weight: 600; }
|
|
388
|
+
article dl dd { padding: 0 1rem; margin-bottom: 1rem; }
|
|
389
|
+
@media (max-width: 640px) { article { padding: 1.25rem 1rem; } .wrap { margin: 1rem auto; } }
|
|
390
|
+
</style>
|
|
391
|
+
</head>
|
|
392
|
+
<body>
|
|
393
|
+
<div class="wrap">
|
|
394
|
+
<div class="card">
|
|
395
|
+
<div class="bar">
|
|
396
|
+
<div class="crumbs">#{breadcrumbs reqPath}</div>
|
|
397
|
+
</div>
|
|
398
|
+
<article>#{body}</article>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
220
401
|
</body>
|
|
221
402
|
</html>
|
|
222
403
|
"""
|
|
@@ -236,6 +417,10 @@ notFound ->
|
|
|
236
417
|
return new Response 'Not Found', { status: 404 }
|
|
237
418
|
|
|
238
419
|
if stat.isFile()
|
|
420
|
+
if path.endsWith('.md')
|
|
421
|
+
try
|
|
422
|
+
html = renderMarkdown(reqPath, path)
|
|
423
|
+
return new Response html, { headers: { 'Content-Type': 'text/html; charset=UTF-8' } }
|
|
239
424
|
return @send path
|
|
240
425
|
|
|
241
426
|
if stat.isDirectory()
|