@rip-lang/server 1.4.7 → 1.4.9

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/browse.rip CHANGED
@@ -5,15 +5,15 @@
5
5
  # Built-in file browser when no index.rip or index.ts exists in the target
6
6
  # directory. Activated automatically by `rip server` when no app entry is found.
7
7
  #
8
- # Single-click serves files natively (HTML renders, images display, etc.).
9
- # Double-click (appends ?) shows syntax-highlighted source for text files.
10
- # Markdown always renders. Directories show a listing with index file detection.
8
+ # Text files get syntax-highlighted by default. HTML files render natively
9
+ # (double-click adds ?view to see source). Markdown renders. Binary files
10
+ # (images, PDFs) serve natively. Directories show a listing.
11
11
  # ==============================================================================
12
12
 
13
13
  import { use, start, notFound } from '@rip-lang/server'
14
14
  import { statSync } from 'node:fs'
15
15
  import { join, resolve, basename } from 'node:path'
16
- import { renderDirectoryListing, renderMarkdown, renderTextFile, isTextFile, serveRipHighlightGrammar, findIndexFile } from './serving/static.rip'
16
+ import { renderDirectoryListing, serveBrowseFile, serveRipHighlightGrammar, findIndexFile } from './serving/static.rip'
17
17
 
18
18
  root = resolve(process.env.APP_BASE_DIR or process.cwd())
19
19
  rootSlash = root + '/'
@@ -27,7 +27,6 @@ notFound ->
27
27
  url = new URL(@req.url)
28
28
  reqPath = try decodeURIComponent(url.pathname) catch then url.pathname
29
29
  path = resolve(root, reqPath.slice(1))
30
- viewSource = url.search is '?view'
31
30
 
32
31
  unless path is root or path.startsWith(rootSlash)
33
32
  return new Response 'Not Found', { status: 404 }
@@ -40,14 +39,8 @@ notFound ->
40
39
  if stat.isFile()
41
40
  accept = @req.header('accept') or ''
42
41
  if accept.includes('text/html')
43
- if path.endsWith('.md') and not viewSource
44
- try
45
- html = renderMarkdown(path)
46
- return new Response html, { headers: { 'Content-Type': 'text/html; charset=UTF-8' } }
47
- if viewSource and isTextFile(path)
48
- try
49
- html = renderTextFile(path)
50
- return new Response html, { headers: { 'Content-Type': 'text/html; charset=UTF-8' } }
42
+ res = serveBrowseFile(path, url)
43
+ return res if res
51
44
  return @send path
52
45
 
53
46
  if stat.isDirectory()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/server",
3
- "version": "1.4.7",
3
+ "version": "1.4.9",
4
4
  "description": "Bun-native content server: static sites, apps, HTTP proxy, and TCP/TLS passthrough",
5
5
  "type": "module",
6
6
  "main": "api.rip",
@@ -189,7 +189,7 @@ export renderDirectoryListing = (reqPath, fsPath, rootName) ->
189
189
  </table>
190
190
  </div></div>
191
191
  <script>
192
- !function(){var p,c,ox=14,oy=14;function mk(){if(p)return p;p=document.createElement('div');p.className='pop';p.innerHTML='<div class="pop-b"></div>';document.body.appendChild(p);return p}function show(a,e){var s=a.dataset.src;if(!s)return;var el=mk(),b=el.firstChild;b.innerHTML='';var img=new Image();img.src=s;b.appendChild(img);c=a;el.classList.add('on');place(e.clientX,e.clientY)}function hide(){c=null;if(p)p.classList.remove('on')}function place(x,y){if(!p)return;var r=p.getBoundingClientRect(),m=12,l=x+ox,t=y+oy;if(l+r.width>innerWidth-m)l=x-r.width-ox;if(t+r.height>innerHeight-m)t=innerHeight-r.height-m;if(l<m)l=m;if(t<m)t=m;p.style.left=l+'px';p.style.top=t+'px'}document.addEventListener('mouseover',function(e){var a=e.target.closest('[data-pv]');if(a)show(a,e)});document.addEventListener('mousemove',function(e){if(c)place(e.clientX,e.clientY)});document.addEventListener('mouseout',function(e){var a=e.target.closest('[data-pv]');if(a&&a===c)hide()});document.addEventListener('scroll',hide,{passive:true});document.addEventListener('keydown',function(e){if(e.key==='Escape')hide()});window.addEventListener('blur',hide);var dt=0;document.addEventListener('click',function(e){var a=e.target.closest('td.nm a');if(!a)return;var h=a.getAttribute('href');if(!h||h.endsWith('/'))return;e.preventDefault();clearTimeout(dt);dt=setTimeout(function(){location.href=h},250)});document.addEventListener('dblclick',function(e){var a=e.target.closest('td.nm a');if(!a)return;var h=a.getAttribute('href');if(!h||h.endsWith('/'))return;e.preventDefault();clearTimeout(dt);location.href=h+'?view'})}();
192
+ !function(){var p,c,ox=14,oy=14;function mk(){if(p)return p;p=document.createElement('div');p.className='pop';p.innerHTML='<div class="pop-b"></div>';document.body.appendChild(p);return p}function show(a,e){var s=a.dataset.src;if(!s)return;var el=mk(),b=el.firstChild;b.innerHTML='';var img=new Image();img.src=s;b.appendChild(img);c=a;el.classList.add('on');place(e.clientX,e.clientY)}function hide(){c=null;if(p)p.classList.remove('on')}function place(x,y){if(!p)return;var r=p.getBoundingClientRect(),m=12,l=x+ox,t=y+oy;if(l+r.width>innerWidth-m)l=x-r.width-ox;if(t+r.height>innerHeight-m)t=innerHeight-r.height-m;if(l<m)l=m;if(t<m)t=m;p.style.left=l+'px';p.style.top=t+'px'}document.addEventListener('mouseover',function(e){var a=e.target.closest('[data-pv]');if(a)show(a,e)});document.addEventListener('mousemove',function(e){if(c)place(e.clientX,e.clientY)});document.addEventListener('mouseout',function(e){var a=e.target.closest('[data-pv]');if(a&&a===c)hide()});document.addEventListener('scroll',hide,{passive:true});document.addEventListener('keydown',function(e){if(e.key==='Escape')hide()});window.addEventListener('blur',hide);var dt=0,hx=/\.html?$/i;document.addEventListener('click',function(e){var a=e.target.closest('td.nm a');if(!a)return;var h=a.getAttribute('href');if(!h||h.endsWith('/'))return;if(!hx.test(h))return;e.preventDefault();clearTimeout(dt);dt=setTimeout(function(){location.href=h},250)});document.addEventListener('dblclick',function(e){var a=e.target.closest('td.nm a');if(!a)return;var h=a.getAttribute('href');if(!h||h.endsWith('/'))return;if(!hx.test(h))return;e.preventDefault();clearTimeout(dt);location.href=h+'?view'})}();
193
193
  </script>
194
194
  </body>
195
195
  </html>
@@ -246,33 +246,33 @@ export renderMarkdown = (filePath) ->
246
246
 
247
247
  # --- Text file rendering with syntax highlighting ---
248
248
 
249
- TEXT_EXTS = new Set [
250
- 'js', 'mjs', 'cjs', 'ts', 'tsx', 'jsx', 'json', 'jsonc'
251
- 'css', 'scss', 'less', 'html', 'htm', 'xml', 'svg'
252
- 'rip', 'coffee', 'rb', 'py', 'go', 'rs', 'c', 'cpp', 'h', 'hpp', 'java', 'kt', 'swift', 'cs'
253
- 'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd'
254
- 'yaml', 'yml', 'toml', 'ini', 'conf', 'cfg', 'env'
255
- 'md', 'txt', 'log', 'csv', 'tsv', 'diff', 'patch'
256
- 'sql', 'graphql', 'gql', 'prisma'
257
- 'dockerfile', 'makefile', 'gemfile', 'rakefile'
258
- 'gitignore', 'gitattributes', 'editorconfig', 'eslintrc', 'prettierrc'
259
- 'lock', 'license', 'licence', 'readme', 'changelog', 'contributing', 'authors'
249
+ BINARY_EXTS = new Set [
250
+ 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'ico', 'bmp', 'tiff', 'tif'
251
+ 'svg'
252
+ 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'rtf'
253
+ 'zip', 'gz', 'tar', 'bz2', 'xz', '7z', 'rar', 'dmg', 'iso'
254
+ 'mp3', 'mp4', 'wav', 'ogg', 'webm', 'flac', 'aac', 'm4a', 'avi', 'mov', 'mkv'
255
+ 'woff', 'woff2', 'ttf', 'otf', 'eot'
256
+ 'wasm', 'bin', 'exe', 'dll', 'so', 'dylib', 'o', 'a'
257
+ 'sqlite', 'db', 'duckdb'
258
+ 'DS_Store'
260
259
  ]
261
260
 
262
261
  HLJS_LANG_MAP =
263
262
  js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'javascript'
264
263
  ts: 'typescript', tsx: 'typescript'
265
264
  rb: 'ruby', py: 'python', rs: 'rust', go: 'go', sh: 'bash', zsh: 'bash'
265
+ cr: 'crystal', ex: 'elixir', exs: 'elixir', elm: 'elm', hs: 'haskell'
266
+ lua: 'lua', nim: 'nim', zig: 'zig', pl: 'perl', r: 'r', m: 'objectivec'
266
267
  yml: 'yaml', toml: 'ini', conf: 'ini', cfg: 'ini'
267
268
  rip: 'rip', coffee: 'coffeescript'
268
- md: 'markdown', htm: 'xml', svg: 'xml'
269
+ md: 'markdown', htm: 'xml'
269
270
  dockerfile: 'dockerfile', makefile: 'makefile'
270
271
  jsonc: 'json', gql: 'graphql'
271
272
 
272
273
  export isTextFile = (filePath) ->
273
- name = basename(filePath).toLowerCase()
274
- ext = name.slice(name.lastIndexOf('.') + 1)
275
- TEXT_EXTS.has(ext) or TEXT_EXTS.has(name)
274
+ ext = basename(filePath).toLowerCase().slice(basename(filePath).lastIndexOf('.') + 1)
275
+ not BINARY_EXTS.has(ext)
276
276
 
277
277
  hljsLang = (filePath) ->
278
278
  name = basename(filePath).toLowerCase()
@@ -319,6 +319,19 @@ export renderTextFile = (filePath) ->
319
319
  </html>
320
320
  """
321
321
 
322
+ # --- Browse file response (shared by browse.rip and serveStaticRoute) ---
323
+
324
+ export serveBrowseFile = (filePath, url) ->
325
+ viewSource = url.search is '?view'
326
+ isHtml = filePath.endsWith('.html') or filePath.endsWith('.htm')
327
+ if filePath.endsWith('.md') and not viewSource
328
+ html = renderMarkdown(filePath)
329
+ return new Response(html, { headers: { 'content-type': 'text/html; charset=UTF-8' } })
330
+ if isTextFile(filePath) and (viewSource or not isHtml)
331
+ html = renderTextFile(filePath)
332
+ return new Response(html, { headers: { 'content-type': 'text/html; charset=UTF-8' } })
333
+ null
334
+
322
335
  # --- Static file serving ---
323
336
 
324
337
  export INDEX_FILES = ['index.html', 'index.rip', 'index.ts', 'index.tsx', 'index.jsx', 'index.js']
@@ -376,13 +389,8 @@ export serveStaticRoute = (req, url, route) ->
376
389
  return new Response(html, { headers: { 'content-type': 'text/html; charset=UTF-8' } })
377
390
  if stat.isFile()
378
391
  if route.browse and acceptsHtml(req)
379
- viewSource = url.search is '?view'
380
- if filePath.endsWith('.md') and not viewSource
381
- html = renderMarkdown(filePath)
382
- return new Response(html, { headers: { 'content-type': 'text/html; charset=UTF-8' } })
383
- if viewSource and isTextFile(filePath)
384
- html = renderTextFile(filePath)
385
- return new Response(html, { headers: { 'content-type': 'text/html; charset=UTF-8' } })
392
+ res = serveBrowseFile(filePath, url)
393
+ return res if res
386
394
  file = Bun.file(filePath)
387
395
  return new Response(file, { headers: { 'content-type': mimeType(filePath) } })
388
396