@rip-lang/print 0.1.4 → 0.1.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/print.rip +80 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/print",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "description": "Syntax-highlighted source code printer — Shiki-powered, serves once, auto-opens browser",
5
5
  "type": "module",
6
6
  "main": "print.rip",
package/print.rip CHANGED
@@ -204,7 +204,8 @@ def highlightCode(code, lang)
204
204
  lines = highlighted.split('\n')
205
205
  numbered = lines.map (line, i) ->
206
206
  num = String(i + 1).padStart(4)
207
- "<span class=\"line-num\">#{num}</span> #{line}"
207
+ cls = if i is 0 then 'line-num first' else 'line-num'
208
+ "<span class=\"#{cls}\">#{num}</span> #{line}"
208
209
  numbered.join('\n')
209
210
 
210
211
  sections = []
@@ -273,24 +274,56 @@ for section, i in sections
273
274
 
274
275
  """
275
276
 
276
- # Read highlight.js CSS theme
277
- hljsTheme = if dark then 'github-dark' else 'github'
277
+ # Load highlight.js theme CSS files
278
278
  hljsMain = import.meta.resolve 'highlight.js'
279
279
  hljsDir = join hljsMain.replace('file://', '').replace(/\/[^/]+$/, ''), '..', 'styles'
280
- hljsCss = readFileSync join(hljsDir, "#{hljsTheme}.css"), 'utf-8'
280
+
281
+ themes = [
282
+ { id: 'github', name: 'GitHub Light', dark: false }
283
+ { id: 'atom-one-light', name: 'Atom One Light', dark: false }
284
+ { id: 'vs', name: 'Visual Studio', dark: false }
285
+ { id: 'xcode', name: 'Xcode', dark: false }
286
+ { id: 'intellij-light', name: 'IntelliJ Light', dark: false }
287
+ { id: 'stackoverflow-light', name: 'Stack Overflow', dark: false }
288
+ { id: 'default', name: 'Default Light', dark: false }
289
+ { id: 'github-dark', name: 'GitHub Dark', dark: true }
290
+ { id: 'atom-one-dark', name: 'Atom One Dark', dark: true }
291
+ { id: 'monokai', name: 'Monokai', dark: true }
292
+ { id: 'nord', name: 'Nord', dark: true }
293
+ { id: 'vs2015', name: 'VS 2015 Dark', dark: true }
294
+ { id: 'tokyo-night-dark', name: 'Tokyo Night', dark: true }
295
+ { id: 'night-owl', name: 'Night Owl', dark: true }
296
+ ]
297
+
298
+ defaultTheme = if dark then 'github-dark' else 'github'
299
+
300
+ # Load all theme CSS into a JS object for runtime switching
301
+ defaultCss = readFileSync join(hljsDir, "#{defaultTheme}.css"), 'utf-8'
302
+ themeEntries = []
303
+ themeOptions = ''
304
+ for theme in themes
305
+ css = readFileSync join(hljsDir, "#{theme.id}.css"), 'utf-8'
306
+ encoded = Buffer.from(css).toString('base64')
307
+ themeEntries.push "'#{theme.id}':{d:#{theme.dark},c:atob('#{encoded}')}"
308
+ selected = if theme.id is defaultTheme then ' selected' else ''
309
+ themeOptions += "<option value=\"#{theme.id}\" data-dark=\"#{theme.dark}\"#{selected}>#{theme.name}</option>\n"
310
+ themeData = themeEntries.join(",\n ")
281
311
 
282
312
  html = """
283
313
  <!DOCTYPE html>
284
314
  <html lang="en">
285
315
  <head>
286
316
  <meta charset="utf-8">
287
- <title>rip-print</title>
317
+ <title>Rip Print - #{new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })} at #{new Date().toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }).toLowerCase()}</title>
288
318
  <link rel="icon" href="data:,">
289
- <style>#{hljsCss}</style>
319
+ <style id="hljs-theme">#{defaultCss}</style>
290
320
  <style>
291
321
  * { margin: 0; padding: 0; box-sizing: border-box; }
292
322
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #{bgColor}; color: #{textColor}; }
293
323
 
324
+ .toolbar { padding: 8px 16px; border-bottom: 1px solid #{borderColor}; text-align: right; font-size: 13px; }
325
+ .toolbar select { font-size: 13px; padding: 4px 8px; border-radius: 3px; border: 1px solid #{borderColor}; background: #{bgColor}; color: #{textColor}; }
326
+ .toolbar label { color: #888; margin-left: 16px; }
294
327
  .toc { padding: 20px 30px; border-bottom: 1px solid #{borderColor}; }
295
328
  .toc h2 { font-size: 18px; margin-bottom: 10px; }
296
329
  .toc ol { padding-left: 24px; columns: 2; column-gap: 40px; }
@@ -313,22 +346,61 @@ html = """
313
346
  .code-container pre { margin: 0; border-radius: 0; }
314
347
  .code-container code { font-size: 13px; line-height: 1.5; padding: 0 !important; display: block; }
315
348
  .line-num { color: #aaa; background: #{if dark then '#161b22' else '#f4f4f4'}; user-select: none; display: inline-block; min-width: 2em; text-align: right; padding: 0 0.7em; margin-right: 0.7em; border-right: 1px solid #{borderColor}; }
349
+ .line-num.first { padding-top: 4px; }
316
350
 
317
351
  @media print {
318
352
  .toc { page-break-after: always; }
319
353
  .file-header { background: #f0f0f0 !important; color: #000 !important; }
354
+ .toolbar { display: none; }
320
355
  .nav { display: none; }
321
- .file-section { page-break-before: always; }
322
- .file-section:first-of-type { page-break-before: avoid; }
323
356
  body { background: white !important; color: black !important; }
324
357
  code { font-size: 11px !important; }
325
358
  }
326
359
  </style>
327
360
  </head>
328
361
  <body>
362
+ <div class="toolbar">
363
+ <label>Theme: <select id="theme-picker">
364
+ <optgroup label="Light">#{themeOptions.split('\n').filter((o) -> o.includes('data-dark="false"')).join('')}</optgroup>
365
+ <optgroup label="Dark">#{themeOptions.split('\n').filter((o) -> o.includes('data-dark="true"')).join('')}</optgroup>
366
+ </select></label>
367
+ </div>
329
368
  <a name="top"></a>
330
369
  #{toc}
331
370
  #{fileSections}
371
+ <script>
372
+ var themes = {
373
+ #{themeData}
374
+ };
375
+ // Restore saved theme
376
+ var saved = localStorage.getItem('rip-print-theme');
377
+ if (saved && themes[saved]) {
378
+ document.getElementById('theme-picker').value = saved;
379
+ document.getElementById('theme-picker').dispatchEvent(new Event('change'));
380
+ }
381
+ document.getElementById('theme-picker').addEventListener('change', function(e) {
382
+ var id = e.target.value;
383
+ localStorage.setItem('rip-print-theme', id);
384
+ var t = themes[id];
385
+ if (!t) return;
386
+ document.getElementById('hljs-theme').textContent = t.c;
387
+ var d = t.d;
388
+ var bg = d ? '#0d1117' : '#ffffff';
389
+ var fg = d ? '#e6edf3' : '#1f2328';
390
+ var hdr = d ? '#161b22' : '#f6f8fa';
391
+ var brd = d ? '#30363d' : '#d0d7de';
392
+ var gut = d ? '#161b22' : '#f4f4f4';
393
+ document.body.style.background = bg;
394
+ document.body.style.color = fg;
395
+ document.querySelectorAll('.toolbar').forEach(function(e) { e.style.borderColor = brd; });
396
+ document.querySelectorAll('.toolbar select').forEach(function(e) { e.style.background = bg; e.style.color = fg; e.style.borderColor = brd; });
397
+ document.querySelectorAll('.toc').forEach(function(e) { e.style.borderColor = brd; });
398
+ document.querySelectorAll('.file-header').forEach(function(e) { e.style.background = hdr; e.style.borderColor = brd; e.style.color = fg; });
399
+ document.querySelectorAll('.line-num').forEach(function(e) { e.style.background = gut; e.style.borderColor = brd; });
400
+ document.querySelectorAll('.code-container').forEach(function(e) { e.style.borderColor = brd; });
401
+ document.querySelectorAll('.toc a').forEach(function(e) { e.style.color = fg; });
402
+ });
403
+ </script>
332
404
  </body>
333
405
  </html>
334
406
  """