@wipcomputer/markdown-viewer
Advanced tools
+13
-10
@@ -1041,12 +1041,15 @@ <!DOCTYPE html> | ||
| // Render math equations with KaTeX | ||
| renderMathInElement(document.getElementById('markdown-content'), { | ||
| delimiters: [ | ||
| {left: '$$', right: '$$', display: true}, | ||
| {left: '$', right: '$', display: false}, | ||
| {left: '\\[', right: '\\]', display: true}, | ||
| {left: '\\(', right: '\\)', display: false} | ||
| ], | ||
| throwOnError: false | ||
| }); | ||
| // Render math equations with KaTeX (only if math delimiters are present) | ||
| const contentText = document.getElementById('markdown-content').textContent; | ||
| const hasMath = /\$\$/.test(contentText) || /\\\[/.test(contentText) || /\\\(/.test(contentText); | ||
| if (hasMath) { | ||
| renderMathInElement(document.getElementById('markdown-content'), { | ||
| delimiters: [ | ||
| {left: '$$', right: '$$', display: true}, | ||
| {left: '\\[', right: '\\]', display: true}, | ||
| {left: '\\(', right: '\\)', display: false} | ||
| ], | ||
| throwOnError: false | ||
| }); | ||
| } | ||
@@ -1053,0 +1056,0 @@ // Generate Table of Contents and show it |
+1
-1
| { | ||
| "name": "@wipcomputer/markdown-viewer", | ||
| "version": "1.1.0", | ||
| "version": "1.1.1", | ||
| "description": "Live markdown viewer for AI pair-editing. When you collaborate, the updates render instantly. Works with any AI agent and web browser.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+40
-15
@@ -13,3 +13,3 @@ #!/usr/bin/env node | ||
| import { createServer } from "node:http"; | ||
| import { readFileSync, watchFile, unwatchFile, existsSync, statSync } from "node:fs"; | ||
| import { readFileSync, watch, existsSync, statSync } from "node:fs"; | ||
| import { resolve, basename, dirname, join, extname } from "node:path"; | ||
@@ -66,5 +66,15 @@ import { exec } from "node:child_process"; | ||
| // Map<absolutePath, { clients: Set<res>, lastMtime: number }> | ||
| // Map<absolutePath, { clients: Set<res>, watcher: FSWatcher, lastMtime: number }> | ||
| const watchers = new Map(); | ||
| // SSE keepalive: ping all clients every 30s so connections don't go stale | ||
| setInterval(() => { | ||
| for (const [, entry] of watchers) { | ||
| for (const client of entry.clients) { | ||
| try { client.write(`:keepalive\n\n`); } | ||
| catch { entry.clients.delete(client); } | ||
| } | ||
| } | ||
| }, 30_000); | ||
| function startWatching(filePath) { | ||
@@ -76,15 +86,28 @@ if (watchers.has(filePath)) return; | ||
| const entry = { clients: new Set(), lastMtime }; | ||
| const entry = { clients: new Set(), watcher: null, lastMtime }; | ||
| watchers.set(filePath, entry); | ||
| watchFile(filePath, { interval: 500 }, (curr) => { | ||
| if (curr.mtimeMs > entry.lastMtime) { | ||
| entry.lastMtime = curr.mtimeMs; | ||
| console.log(`File changed: ${basename(filePath)}`); | ||
| for (const client of entry.clients) { | ||
| try { client.write(`data: reload\n\n`); } | ||
| catch { entry.clients.delete(client); } | ||
| } | ||
| } | ||
| }); | ||
| // Use fs.watch (native OS events) instead of fs.watchFile (polling). | ||
| // Debounce to avoid duplicate events (common on macOS). | ||
| let debounce = null; | ||
| try { | ||
| entry.watcher = watch(filePath, () => { | ||
| if (debounce) return; | ||
| debounce = setTimeout(() => { | ||
| debounce = null; | ||
| let currMtime = 0; | ||
| try { currMtime = statSync(filePath).mtimeMs; } catch {} | ||
| if (currMtime > entry.lastMtime) { | ||
| entry.lastMtime = currMtime; | ||
| console.log(`File changed: ${basename(filePath)}`); | ||
| for (const client of entry.clients) { | ||
| try { client.write(`data: reload\n\n`); } | ||
| catch { entry.clients.delete(client); } | ||
| } | ||
| } | ||
| }, 200); | ||
| }); | ||
| } catch (err) { | ||
| console.error(`Watch failed for ${filePath}: ${err.message}`); | ||
| } | ||
| } | ||
@@ -95,3 +118,3 @@ | ||
| if (entry && entry.clients.size === 0) { | ||
| unwatchFile(filePath); | ||
| if (entry.watcher) entry.watcher.close(); | ||
| watchers.delete(filePath); | ||
@@ -357,5 +380,7 @@ } | ||
| process.on("SIGINT", () => { | ||
| for (const [path] of watchers) { unwatchFile(path); } | ||
| for (const [, entry] of watchers) { | ||
| if (entry.watcher) entry.watcher.close(); | ||
| } | ||
| server.close(); | ||
| process.exit(0); | ||
| }); |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
5014986
0.02%16275
0.15%28
-3.45%