vimd 0.5.6 → 0.5.8

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.
@@ -0,0 +1,259 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="generator" content="vimd">
7
+ <title>{{title}} - vimd</title>
8
+ <style>
9
+ {{theme_css}}
10
+ </style>
11
+ <style>
12
+ /* Single file mode styles */
13
+ * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ html, body {
18
+ margin: 0;
19
+ padding: 0;
20
+ width: 100%;
21
+ height: 100%;
22
+ }
23
+
24
+ body {
25
+ overflow: hidden;
26
+ }
27
+
28
+ .vimd-single-container {
29
+ width: 100%;
30
+ height: 100vh;
31
+ overflow-y: auto;
32
+ overflow-x: hidden;
33
+ }
34
+
35
+ .vimd-single-content {
36
+ padding: 32px;
37
+ max-width: 720px;
38
+ margin: 0 auto;
39
+ }
40
+
41
+ .vimd-single-content pre,
42
+ .vimd-single-content table {
43
+ overflow-x: auto;
44
+ }
45
+
46
+ /* Connection status indicator */
47
+ .vimd-connection-status {
48
+ position: fixed;
49
+ bottom: 16px;
50
+ right: 16px;
51
+ padding: 8px 12px;
52
+ background: rgba(0, 0, 0, 0.7);
53
+ color: #fff;
54
+ font-size: 12px;
55
+ border-radius: 4px;
56
+ opacity: 0;
57
+ transition: opacity 0.3s;
58
+ pointer-events: none;
59
+ z-index: 1000;
60
+ }
61
+
62
+ .vimd-connection-status.visible {
63
+ opacity: 1;
64
+ }
65
+
66
+ .vimd-connection-status.error {
67
+ background: rgba(211, 47, 47, 0.9);
68
+ }
69
+ </style>
70
+ {{#if math_enabled}}
71
+ <style>
72
+ /* Block math centering */
73
+ .math-block {
74
+ display: block;
75
+ text-align: center;
76
+ margin: 1em 0;
77
+ }
78
+ mjx-container[display="true"] {
79
+ display: block !important;
80
+ text-align: center !important;
81
+ margin: 1em 0 !important;
82
+ }
83
+ </style>
84
+ <script>
85
+ MathJax = {
86
+ loader: {load: ['[tex]/bussproofs', '[tex]/ams', '[tex]/physics', '[tex]/boldsymbol']},
87
+ tex: {
88
+ packages: {'[+]': ['bussproofs', 'ams', 'physics', 'boldsymbol']},
89
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
90
+ displayMath: [['$$', '$$'], ['\\[', '\\]']],
91
+ tags: 'ams',
92
+ macros: {
93
+ bm: ['\\boldsymbol{#1}', 1]
94
+ }
95
+ }
96
+ };
97
+ </script>
98
+ <script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
99
+ {{/if}}
100
+ </head>
101
+ <body>
102
+ <div class="vimd-single-container" id="container">
103
+ <article class="vimd-single-content markdown-body" id="content">
104
+ <!-- Content will be loaded via WebSocket -->
105
+ </article>
106
+ </div>
107
+
108
+ <div class="vimd-connection-status" id="status"></div>
109
+
110
+ <script>
111
+ (function() {
112
+ 'use strict';
113
+
114
+ var container = document.getElementById('container');
115
+ var content = document.getElementById('content');
116
+ var status = document.getElementById('status');
117
+ var ws = null;
118
+ var reconnectAttempts = 0;
119
+ var maxReconnectAttempts = 10;
120
+ var reconnectDelay = 1000;
121
+
122
+ /**
123
+ * Show connection status message
124
+ */
125
+ function showStatus(message, isError) {
126
+ status.textContent = message;
127
+ status.classList.toggle('error', isError);
128
+ status.classList.add('visible');
129
+
130
+ // Hide after 3 seconds for non-error messages
131
+ if (!isError) {
132
+ setTimeout(function() {
133
+ status.classList.remove('visible');
134
+ }, 3000);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Hide connection status
140
+ */
141
+ function hideStatus() {
142
+ status.classList.remove('visible');
143
+ }
144
+
145
+ /**
146
+ * Connect to WebSocket server
147
+ */
148
+ function connect() {
149
+ var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
150
+ ws = new WebSocket(protocol + '//' + location.host);
151
+
152
+ ws.onopen = function() {
153
+ console.log('[vimd] WebSocket connected');
154
+ reconnectAttempts = 0;
155
+ hideStatus();
156
+ };
157
+
158
+ ws.onmessage = function(event) {
159
+ try {
160
+ var msg = JSON.parse(event.data);
161
+ handleMessage(msg);
162
+ } catch (e) {
163
+ console.error('[vimd] Failed to parse message:', e);
164
+ }
165
+ };
166
+
167
+ ws.onclose = function() {
168
+ console.log('[vimd] WebSocket disconnected');
169
+
170
+ if (reconnectAttempts < maxReconnectAttempts) {
171
+ reconnectAttempts++;
172
+ showStatus('Reconnecting... (' + reconnectAttempts + '/' + maxReconnectAttempts + ')', false);
173
+ setTimeout(connect, reconnectDelay);
174
+ } else {
175
+ showStatus('Connection lost. Please refresh the page.', true);
176
+ }
177
+ };
178
+
179
+ ws.onerror = function(error) {
180
+ console.error('[vimd] WebSocket error:', error);
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Handle incoming WebSocket message
186
+ */
187
+ function handleMessage(msg) {
188
+ switch (msg.type) {
189
+ case 'content':
190
+ updateContent(msg.data.html);
191
+ if (msg.data.title) {
192
+ document.title = msg.data.title + ' - vimd';
193
+ }
194
+ break;
195
+
196
+ case 'error':
197
+ showError(msg.data.message);
198
+ break;
199
+
200
+ default:
201
+ console.warn('[vimd] Unknown message type:', msg.type);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Update content with scroll position preservation
207
+ */
208
+ function updateContent(html) {
209
+ // Save scroll position
210
+ var scrollTop = container.scrollTop;
211
+
212
+ // Update content
213
+ content.innerHTML = html;
214
+
215
+ // Re-render MathJax if available
216
+ if (window.MathJax && window.MathJax.typeset) {
217
+ // MathJax.typeset returns a promise in v3
218
+ var promise = window.MathJax.typeset([content]);
219
+
220
+ // Restore scroll position after MathJax completes
221
+ if (promise && promise.then) {
222
+ promise.then(function() {
223
+ container.scrollTop = scrollTop;
224
+ }).catch(function() {
225
+ // Restore scroll even if MathJax fails
226
+ container.scrollTop = scrollTop;
227
+ });
228
+ } else {
229
+ // Fallback: restore scroll immediately
230
+ container.scrollTop = scrollTop;
231
+ }
232
+ } else {
233
+ // No MathJax: restore scroll immediately
234
+ container.scrollTop = scrollTop;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Show error message
240
+ */
241
+ function showError(message) {
242
+ content.innerHTML = '<div class="vimd-error"><h2>Error</h2><p>' + escapeHtml(message) + '</p></div>';
243
+ }
244
+
245
+ /**
246
+ * Escape HTML special characters
247
+ */
248
+ function escapeHtml(text) {
249
+ var div = document.createElement('div');
250
+ div.textContent = text;
251
+ return div.innerHTML;
252
+ }
253
+
254
+ // Initialize connection
255
+ connect();
256
+ })();
257
+ </script>
258
+ </body>
259
+ </html>
@@ -1,52 +0,0 @@
1
- /**
2
- * WebSocketServer options
3
- * Note: 'open' property is not included (handled by dev.ts)
4
- */
5
- export interface WebSocketServerOptions {
6
- port: number;
7
- root: string;
8
- host?: string;
9
- }
10
- /**
11
- * Result of server start operation
12
- * Same interface as previous LiveServer for compatibility
13
- */
14
- export interface ServerStartResult {
15
- actualPort: number;
16
- requestedPort: number;
17
- portChanged: boolean;
18
- }
19
- /**
20
- * WebSocket server for live reload functionality
21
- * Replaces live-server with direct WebSocket control
22
- */
23
- export declare class WebSocketServer {
24
- private httpServer;
25
- private wsServer;
26
- private clients;
27
- private options;
28
- private _port;
29
- constructor(options: WebSocketServerOptions);
30
- /**
31
- * Get the actual port the server is running on
32
- */
33
- get port(): number;
34
- /**
35
- * Start the HTTP and WebSocket servers
36
- */
37
- start(): Promise<ServerStartResult>;
38
- /**
39
- * Stop the server
40
- * Uses force termination to ensure immediate shutdown
41
- */
42
- stop(): Promise<void>;
43
- /**
44
- * Broadcast a message to all connected clients
45
- */
46
- broadcast(type: string, data?: unknown): void;
47
- /**
48
- * Inject reload script into HTML content
49
- */
50
- private injectReloadScript;
51
- }
52
- //# sourceMappingURL=websocket-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/core/websocket-server.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AA0CD;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,EAAE,sBAAsB;IAQ3C;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IA4GzC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC3B;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAU7C;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAc3B"}
@@ -1,221 +0,0 @@
1
- // src/core/websocket-server.ts
2
- import http from 'http';
3
- import fs from 'fs';
4
- import path from 'path';
5
- import polka from 'polka';
6
- import sirv from 'sirv';
7
- import { WebSocketServer as WSServer, WebSocket } from 'ws';
8
- import { Logger } from '../utils/logger.js';
9
- import { SessionManager } from '../utils/session-manager.js';
10
- /**
11
- * WebSocket reload script injected into HTML
12
- * Features:
13
- * - Reconnection with max 5 retries (prevents infinite loop)
14
- * - Protocol detection for future HTTPS support
15
- */
16
- const RELOAD_SCRIPT = `
17
- <script>
18
- (function() {
19
- var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
20
- var ws = new WebSocket(protocol + '//' + location.host);
21
-
22
- ws.onmessage = function(event) {
23
- var data = JSON.parse(event.data);
24
- if (data.type === 'reload') {
25
- location.reload();
26
- }
27
- };
28
-
29
- ws.onclose = function() {
30
- var retries = 0;
31
- var maxRetries = 5;
32
-
33
- function reconnect() {
34
- if (retries++ >= maxRetries) {
35
- console.log('[vimd] Server disconnected. Please refresh manually.');
36
- return;
37
- }
38
- setTimeout(function() {
39
- var newWs = new WebSocket(protocol + '//' + location.host);
40
- newWs.onopen = function() { location.reload(); };
41
- newWs.onerror = reconnect;
42
- }, 1000);
43
- }
44
- reconnect();
45
- };
46
- })();
47
- </script>
48
- `;
49
- /**
50
- * WebSocket server for live reload functionality
51
- * Replaces live-server with direct WebSocket control
52
- */
53
- export class WebSocketServer {
54
- constructor(options) {
55
- this.httpServer = null;
56
- this.wsServer = null;
57
- this.clients = new Set();
58
- this.options = {
59
- host: 'localhost',
60
- ...options,
61
- };
62
- this._port = options.port;
63
- }
64
- /**
65
- * Get the actual port the server is running on
66
- */
67
- get port() {
68
- return this._port;
69
- }
70
- /**
71
- * Start the HTTP and WebSocket servers
72
- */
73
- async start() {
74
- const requestedPort = this.options.port;
75
- let actualPort = requestedPort;
76
- // Check if port is available, find alternative if not
77
- if (!(await SessionManager.isPortAvailable(requestedPort))) {
78
- actualPort = await SessionManager.findAvailablePort(requestedPort + 1);
79
- Logger.warn(`Port ${requestedPort} was unavailable, using port ${actualPort}`);
80
- }
81
- this._port = actualPort;
82
- // Create static file server with sirv
83
- const serve = sirv(this.options.root, {
84
- dev: true, // Enable dev mode for no caching
85
- });
86
- // Create polka app with HTML injection middleware
87
- const app = polka();
88
- // Middleware to inject reload script into HTML
89
- // For HTML files, read directly to avoid sirv streaming issues
90
- app.use((req, res, next) => {
91
- // Only process GET requests for HTML files
92
- if (req.method !== 'GET' || !req.url?.endsWith('.html')) {
93
- return serve(req, res, next);
94
- }
95
- // Parse the URL and resolve the file path
96
- const urlPath = req.url || '/index.html';
97
- const filePath = path.join(this.options.root, urlPath);
98
- // Check if file exists
99
- if (!fs.existsSync(filePath)) {
100
- // Let sirv handle 404
101
- return serve(req, res, next);
102
- }
103
- try {
104
- // Read HTML file directly
105
- const html = fs.readFileSync(filePath, 'utf8');
106
- // Inject reload script
107
- const injectedHtml = this.injectReloadScript(html);
108
- // Send response with proper headers
109
- res.writeHead(200, {
110
- 'Content-Type': 'text/html; charset=utf-8',
111
- 'Content-Length': Buffer.byteLength(injectedHtml, 'utf8'),
112
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
113
- });
114
- res.end(injectedHtml);
115
- }
116
- catch (error) {
117
- // On error, fall back to sirv
118
- Logger.warn(`Failed to read HTML file: ${filePath}`);
119
- serve(req, res, next);
120
- }
121
- });
122
- // Create HTTP server
123
- this.httpServer = http.createServer(app.handler);
124
- // Create WebSocket server attached to HTTP server
125
- this.wsServer = new WSServer({ server: this.httpServer });
126
- // Handle WebSocket connections
127
- this.wsServer.on('connection', (ws) => {
128
- this.clients.add(ws);
129
- Logger.info(`WebSocket client connected (${this.clients.size} total)`);
130
- ws.on('close', () => {
131
- this.clients.delete(ws);
132
- Logger.info(`WebSocket client disconnected (${this.clients.size} remaining)`);
133
- });
134
- ws.on('error', (error) => {
135
- Logger.warn(`WebSocket error: ${error.message}`);
136
- this.clients.delete(ws);
137
- });
138
- });
139
- // Start listening
140
- await new Promise((resolve, reject) => {
141
- const timeout = setTimeout(() => {
142
- reject(new Error('Server start timeout'));
143
- }, 10000);
144
- this.httpServer.listen(actualPort, this.options.host, () => {
145
- clearTimeout(timeout);
146
- resolve();
147
- });
148
- this.httpServer.on('error', (err) => {
149
- clearTimeout(timeout);
150
- reject(err);
151
- });
152
- });
153
- const url = `http://${this.options.host}:${actualPort}`;
154
- Logger.success(`Server started at ${url}`);
155
- return {
156
- actualPort,
157
- requestedPort,
158
- portChanged: actualPort !== requestedPort,
159
- };
160
- }
161
- /**
162
- * Stop the server
163
- * Uses force termination to ensure immediate shutdown
164
- */
165
- async stop() {
166
- // Force terminate all WebSocket clients (immediate disconnect)
167
- // Use terminate() instead of close() to avoid waiting for graceful handshake
168
- for (const client of this.clients) {
169
- try {
170
- client.terminate();
171
- }
172
- catch {
173
- // Ignore termination errors
174
- }
175
- }
176
- this.clients.clear();
177
- // Close WebSocket server (should be instant now that clients are terminated)
178
- if (this.wsServer) {
179
- await new Promise((resolve) => {
180
- this.wsServer.close(() => resolve());
181
- });
182
- this.wsServer = null;
183
- }
184
- // Force close all HTTP connections before closing server
185
- // closeAllConnections() requires Node.js >= 18.2.0
186
- if (this.httpServer) {
187
- this.httpServer.closeAllConnections();
188
- await new Promise((resolve) => {
189
- this.httpServer.close(() => resolve());
190
- });
191
- this.httpServer = null;
192
- }
193
- Logger.info('Server stopped');
194
- }
195
- /**
196
- * Broadcast a message to all connected clients
197
- */
198
- broadcast(type, data) {
199
- const message = JSON.stringify({ type, data });
200
- for (const client of this.clients) {
201
- if (client.readyState === WebSocket.OPEN) {
202
- client.send(message);
203
- }
204
- }
205
- }
206
- /**
207
- * Inject reload script into HTML content
208
- */
209
- injectReloadScript(html) {
210
- // Try to inject before </body>
211
- if (html.includes('</body>')) {
212
- return html.replace('</body>', `${RELOAD_SCRIPT}</body>`);
213
- }
214
- // Fallback: inject before </html>
215
- if (html.includes('</html>')) {
216
- return html.replace('</html>', `${RELOAD_SCRIPT}</html>`);
217
- }
218
- // Last resort: append to the end
219
- return html + RELOAD_SCRIPT;
220
- }
221
- }
@@ -1,57 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ja">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="generator" content="vimd">
7
- <title>{{title}}</title>
8
- <style>
9
- {{theme_css}}
10
- </style>
11
- {{#if custom_css}}
12
- <style>
13
- {{custom_css}}
14
- </style>
15
- {{/if}}
16
- {{#if math_enabled}}
17
- <style>
18
- /* Block math centering */
19
- .math-block {
20
- display: block;
21
- text-align: center;
22
- margin: 1em 0;
23
- }
24
- mjx-container[display="true"] {
25
- display: block !important;
26
- text-align: center !important;
27
- margin: 1em 0 !important;
28
- }
29
- </style>
30
- <script>
31
- MathJax = {
32
- loader: {load: ['[tex]/bussproofs', '[tex]/ams', '[tex]/physics']},
33
- tex: {
34
- packages: {'[+]': ['bussproofs', 'ams', 'physics']},
35
- inlineMath: [['$', '$'], ['\\(', '\\)']],
36
- displayMath: [['$$', '$$'], ['\\[', '\\]']]
37
- }
38
- };
39
- </script>
40
- <script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
41
- {{/if}}
42
- </head>
43
- <body>
44
- <div class="markdown-body">
45
- {{content}}
46
- </div>
47
-
48
- <!-- vimd metadata -->
49
- <script type="application/json" id="vimd-meta">
50
- {
51
- "generated": "{{timestamp}}",
52
- "theme": "{{theme}}",
53
- "version": "{{version}}"
54
- }
55
- </script>
56
- </body>
57
- </html>
@@ -1,57 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ja">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="generator" content="vimd">
7
- <title>{{title}}</title>
8
- <style>
9
- {{theme_css}}
10
- </style>
11
- {{#if custom_css}}
12
- <style>
13
- {{custom_css}}
14
- </style>
15
- {{/if}}
16
- {{#if math_enabled}}
17
- <style>
18
- /* Block math centering */
19
- .math-block {
20
- display: block;
21
- text-align: center;
22
- margin: 1em 0;
23
- }
24
- mjx-container[display="true"] {
25
- display: block !important;
26
- text-align: center !important;
27
- margin: 1em 0 !important;
28
- }
29
- </style>
30
- <script>
31
- MathJax = {
32
- loader: {load: ['[tex]/bussproofs', '[tex]/ams', '[tex]/physics']},
33
- tex: {
34
- packages: {'[+]': ['bussproofs', 'ams', 'physics']},
35
- inlineMath: [['$', '$'], ['\\(', '\\)']],
36
- displayMath: [['$$', '$$'], ['\\[', '\\]']]
37
- }
38
- };
39
- </script>
40
- <script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
41
- {{/if}}
42
- </head>
43
- <body>
44
- <div class="markdown-body">
45
- {{content}}
46
- </div>
47
-
48
- <!-- vimd metadata -->
49
- <script type="application/json" id="vimd-meta">
50
- {
51
- "generated": "{{timestamp}}",
52
- "theme": "{{theme}}",
53
- "version": "{{version}}"
54
- }
55
- </script>
56
- </body>
57
- </html>