@hirokisakabe/pom-cli 0.2.3 → 0.2.4
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/README.md +2 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +181 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ pom preview slides.pom.md
|
|
|
21
21
|
|
|
22
22
|
Open http://localhost:3000 in your browser. The page updates automatically when the file is saved.
|
|
23
23
|
|
|
24
|
+
Use the zoom buttons in the toolbar or press `+` / `-` to zoom in and out. The current zoom level is saved across sessions.
|
|
25
|
+
|
|
24
26
|
### Build
|
|
25
27
|
|
|
26
28
|
Converts a pom file to a PPTX file.
|
package/dist/preview.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AA8WA,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAyGlD"}
|
package/dist/preview.js
CHANGED
|
@@ -66,78 +66,188 @@ async function generateSvgs(inputFile) {
|
|
|
66
66
|
const svgs = slides.map((s) => s.svg);
|
|
67
67
|
return { type: "success", svgs, slideWidth };
|
|
68
68
|
}
|
|
69
|
-
function
|
|
69
|
+
function escapeHtml(s) {
|
|
70
|
+
return s
|
|
71
|
+
.replace(/&/g, "&")
|
|
72
|
+
.replace(/</g, "<")
|
|
73
|
+
.replace(/>/g, ">")
|
|
74
|
+
.replace(/"/g, """);
|
|
75
|
+
}
|
|
76
|
+
function buildPreviewHtml(filename) {
|
|
77
|
+
const safeFilename = escapeHtml(filename);
|
|
70
78
|
return `<!DOCTYPE html>
|
|
71
79
|
<html>
|
|
72
80
|
<head>
|
|
73
81
|
<meta charset="UTF-8">
|
|
74
|
-
<title>pom
|
|
82
|
+
<title>pom — ${safeFilename}</title>
|
|
75
83
|
<style>
|
|
76
|
-
* { box-sizing: border-box; }
|
|
77
|
-
body {
|
|
84
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
85
|
+
body {
|
|
86
|
+
background: #0f0f13;
|
|
87
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
88
|
+
color: #e2e2e8;
|
|
89
|
+
min-height: 100vh;
|
|
90
|
+
}
|
|
78
91
|
.toolbar {
|
|
79
92
|
position: sticky; top: 0; z-index: 100;
|
|
80
|
-
display: flex; align-items: center; gap:
|
|
81
|
-
padding:
|
|
82
|
-
|
|
93
|
+
display: flex; align-items: center; gap: 12px;
|
|
94
|
+
padding: 0 20px; height: 48px;
|
|
95
|
+
background: #1a1a2e;
|
|
96
|
+
border-bottom: 1px solid #2d2d4e;
|
|
97
|
+
}
|
|
98
|
+
.app-name {
|
|
99
|
+
font-size: 13px; font-weight: 700;
|
|
100
|
+
color: #a78bfa; letter-spacing: 0.06em;
|
|
101
|
+
flex-shrink: 0;
|
|
83
102
|
}
|
|
103
|
+
.filename {
|
|
104
|
+
font-size: 12px; color: #94a3b8;
|
|
105
|
+
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
|
|
106
|
+
background: #0f172a; border: 1px solid #2d2d4e;
|
|
107
|
+
padding: 2px 8px; border-radius: 4px;
|
|
108
|
+
flex-shrink: 1; min-width: 0; overflow: hidden;
|
|
109
|
+
text-overflow: ellipsis; white-space: nowrap;
|
|
110
|
+
}
|
|
111
|
+
.zoom-controls { display: flex; gap: 4px; flex-shrink: 0; }
|
|
84
112
|
.zoom-btn {
|
|
85
|
-
padding:
|
|
86
|
-
border: 1px solid #
|
|
87
|
-
background: #
|
|
113
|
+
padding: 4px 10px; font-size: 11px;
|
|
114
|
+
border: 1px solid #3d3d5e; border-radius: 4px;
|
|
115
|
+
background: #2d2d4e; color: #c4c4d4; cursor: pointer;
|
|
116
|
+
transition: background 0.15s, color 0.15s;
|
|
117
|
+
}
|
|
118
|
+
.zoom-btn:hover { background: #3d3d6e; color: #e2e2f2; }
|
|
119
|
+
.zoom-btn.active { background: #7c3aed; color: #fff; border-color: #7c3aed; }
|
|
120
|
+
.zoom-hint { font-size: 11px; color: #3d3d5e; flex-shrink: 0; }
|
|
121
|
+
.status-group {
|
|
122
|
+
margin-left: auto; display: flex; align-items: center;
|
|
123
|
+
gap: 6px; flex-shrink: 0;
|
|
124
|
+
}
|
|
125
|
+
.status-dot {
|
|
126
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
127
|
+
background: #f59e0b;
|
|
128
|
+
transition: background 0.3s, box-shadow 0.3s;
|
|
88
129
|
}
|
|
89
|
-
.
|
|
90
|
-
.
|
|
91
|
-
.status
|
|
92
|
-
.
|
|
93
|
-
.
|
|
94
|
-
|
|
130
|
+
.status-dot.connected { background: #22c55e; box-shadow: 0 0 6px #22c55e88; }
|
|
131
|
+
.status-dot.warning { background: #f59e0b; box-shadow: 0 0 6px #f59e0b88; }
|
|
132
|
+
.status-dot.error { background: #ef4444; box-shadow: 0 0 6px #ef444488; }
|
|
133
|
+
.status-text { font-size: 12px; color: #94a3b8; }
|
|
134
|
+
.slides-container {
|
|
135
|
+
padding: 32px 20px;
|
|
136
|
+
display: flex; flex-direction: column;
|
|
137
|
+
align-items: center; gap: 32px;
|
|
138
|
+
}
|
|
139
|
+
.slide-wrapper { width: 100%; display: flex; justify-content: center; }
|
|
95
140
|
.slide-frame {
|
|
96
|
-
|
|
97
|
-
overflow: hidden; background: #fff;
|
|
141
|
+
position: relative;
|
|
142
|
+
border-radius: 8px; overflow: hidden; background: #fff;
|
|
143
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.4);
|
|
98
144
|
}
|
|
99
145
|
.slide-frame svg { display: block; }
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
146
|
+
.slide-number {
|
|
147
|
+
position: absolute; bottom: 10px; right: 12px;
|
|
148
|
+
font-size: 11px; font-weight: 500;
|
|
149
|
+
color: rgba(255,255,255,0.8);
|
|
150
|
+
background: rgba(0,0,0,0.5);
|
|
151
|
+
padding: 2px 8px; border-radius: 3px;
|
|
152
|
+
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
|
|
153
|
+
pointer-events: none; user-select: none;
|
|
154
|
+
}
|
|
155
|
+
.loading-screen {
|
|
156
|
+
display: flex; flex-direction: column;
|
|
157
|
+
align-items: center; justify-content: center;
|
|
158
|
+
height: calc(100vh - 48px); gap: 16px;
|
|
159
|
+
}
|
|
160
|
+
.spinner {
|
|
161
|
+
width: 32px; height: 32px;
|
|
162
|
+
border: 3px solid #2d2d4e;
|
|
163
|
+
border-top-color: #7c3aed;
|
|
164
|
+
border-radius: 50%;
|
|
165
|
+
animation: spin 0.8s linear infinite;
|
|
103
166
|
}
|
|
104
|
-
|
|
167
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
168
|
+
.loading-text { font-size: 13px; color: #555578; }
|
|
169
|
+
.empty-screen {
|
|
105
170
|
display: flex; align-items: center; justify-content: center;
|
|
106
|
-
height: calc(100vh -
|
|
171
|
+
height: calc(100vh - 48px);
|
|
172
|
+
font-size: 13px; color: #555578;
|
|
173
|
+
}
|
|
174
|
+
.error-screen { padding: 32px; display: flex; justify-content: center; }
|
|
175
|
+
.error-block {
|
|
176
|
+
max-width: 720px; width: 100%;
|
|
177
|
+
background: #1a0a0a; border: 1px solid #5c1a1a;
|
|
178
|
+
border-radius: 8px; overflow: hidden;
|
|
179
|
+
}
|
|
180
|
+
.error-header {
|
|
181
|
+
padding: 10px 16px; background: #2a0a0a;
|
|
182
|
+
border-bottom: 1px solid #5c1a1a;
|
|
183
|
+
font-size: 12px; font-weight: 600; color: #f87171;
|
|
184
|
+
}
|
|
185
|
+
.error-body {
|
|
186
|
+
padding: 14px 16px;
|
|
187
|
+
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
|
|
188
|
+
font-size: 12px; color: #fca5a5; line-height: 1.6;
|
|
189
|
+
white-space: pre-wrap; word-break: break-all;
|
|
107
190
|
}
|
|
108
191
|
</style>
|
|
109
192
|
</head>
|
|
110
193
|
<body>
|
|
111
194
|
<div class="toolbar">
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
195
|
+
<span class="app-name">pom</span>
|
|
196
|
+
<span class="filename">${safeFilename}</span>
|
|
197
|
+
<div class="zoom-controls">
|
|
198
|
+
<button class="zoom-btn" data-zoom="fit">Fit</button>
|
|
199
|
+
<button class="zoom-btn" data-zoom="50">50%</button>
|
|
200
|
+
<button class="zoom-btn" data-zoom="75">75%</button>
|
|
201
|
+
<button class="zoom-btn" data-zoom="100">100%</button>
|
|
202
|
+
<button class="zoom-btn" data-zoom="150">150%</button>
|
|
203
|
+
</div>
|
|
204
|
+
<span class="zoom-hint">+ / −</span>
|
|
205
|
+
<div class="status-group">
|
|
206
|
+
<span class="status-dot warning" id="statusDot"></span>
|
|
207
|
+
<span class="status-text" id="statusText">Connecting...</span>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
<div id="content">
|
|
211
|
+
<div class="loading-screen">
|
|
212
|
+
<div class="spinner"></div>
|
|
213
|
+
<span class="loading-text">Building preview...</span>
|
|
214
|
+
</div>
|
|
118
215
|
</div>
|
|
119
|
-
<div id="content"><div class="loading-message"><span>Building preview...</span></div></div>
|
|
120
216
|
|
|
121
217
|
<script>
|
|
122
218
|
(function() {
|
|
123
|
-
var
|
|
219
|
+
var ZOOM_STEPS = ['fit', '50', '75', '100', '150'];
|
|
124
220
|
var currentZoom = localStorage.getItem('pom-zoom') || 'fit';
|
|
125
221
|
var currentSlideWidth = 1280;
|
|
126
222
|
|
|
223
|
+
if (ZOOM_STEPS.indexOf(currentZoom) === -1) currentZoom = 'fit';
|
|
127
224
|
applyZoom(currentZoom);
|
|
128
225
|
|
|
129
226
|
document.querySelectorAll('.zoom-btn').forEach(function(btn) {
|
|
130
227
|
btn.addEventListener('click', function() {
|
|
131
|
-
|
|
132
|
-
applyZoom(zoom);
|
|
133
|
-
localStorage.setItem('pom-zoom', zoom);
|
|
228
|
+
setZoom(this.getAttribute('data-zoom'));
|
|
134
229
|
});
|
|
135
230
|
});
|
|
136
231
|
|
|
232
|
+
document.addEventListener('keydown', function(e) {
|
|
233
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
234
|
+
if (e.key === '+' || e.key === '=') {
|
|
235
|
+
var idx = ZOOM_STEPS.indexOf(currentZoom);
|
|
236
|
+
if (idx < ZOOM_STEPS.length - 1) setZoom(ZOOM_STEPS[idx + 1]);
|
|
237
|
+
} else if (e.key === '-') {
|
|
238
|
+
var idx = ZOOM_STEPS.indexOf(currentZoom);
|
|
239
|
+
if (idx > 0) setZoom(ZOOM_STEPS[idx - 1]);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
function setZoom(zoom) {
|
|
244
|
+
localStorage.setItem('pom-zoom', zoom);
|
|
245
|
+
applyZoom(zoom);
|
|
246
|
+
}
|
|
247
|
+
|
|
137
248
|
function applyZoom(zoom) {
|
|
138
|
-
if (
|
|
249
|
+
if (ZOOM_STEPS.indexOf(zoom) === -1) zoom = 'fit';
|
|
139
250
|
currentZoom = zoom;
|
|
140
|
-
document.body.setAttribute('data-zoom', zoom);
|
|
141
251
|
document.querySelectorAll('.zoom-btn').forEach(function(b) {
|
|
142
252
|
b.classList.toggle('active', b.getAttribute('data-zoom') === zoom);
|
|
143
253
|
});
|
|
@@ -149,35 +259,45 @@ function buildPreviewHtml() {
|
|
|
149
259
|
function applySvgZoom(svg, zoom, slideWidth) {
|
|
150
260
|
var frame = svg.closest('.slide-frame');
|
|
151
261
|
if (zoom === 'fit') {
|
|
262
|
+
frame.style.width = '100%';
|
|
263
|
+
frame.style.maxWidth = slideWidth + 'px';
|
|
152
264
|
svg.style.width = '100%';
|
|
153
265
|
svg.style.height = 'auto';
|
|
154
|
-
frame.style.display = 'block';
|
|
155
266
|
} else {
|
|
156
267
|
var scale = parseInt(zoom) / 100;
|
|
268
|
+
frame.style.width = (slideWidth * scale) + 'px';
|
|
269
|
+
frame.style.maxWidth = '';
|
|
157
270
|
svg.style.width = (slideWidth * scale) + 'px';
|
|
158
271
|
svg.style.height = 'auto';
|
|
159
|
-
frame.style.display = 'inline-block';
|
|
160
272
|
}
|
|
161
273
|
}
|
|
162
274
|
|
|
163
|
-
var
|
|
275
|
+
var statusDot = document.getElementById('statusDot');
|
|
276
|
+
var statusText = document.getElementById('statusText');
|
|
164
277
|
var content = document.getElementById('content');
|
|
165
278
|
|
|
279
|
+
function setStatus(state, text) {
|
|
280
|
+
statusDot.className = 'status-dot ' + state;
|
|
281
|
+
statusText.textContent = text;
|
|
282
|
+
}
|
|
283
|
+
|
|
166
284
|
var es = new EventSource('/_sse');
|
|
167
285
|
|
|
168
286
|
es.addEventListener('open', function() {
|
|
169
|
-
|
|
287
|
+
setStatus('connected', 'Connected');
|
|
170
288
|
});
|
|
171
289
|
|
|
172
290
|
es.addEventListener('update', function(e) {
|
|
173
291
|
var data = JSON.parse(e.data);
|
|
174
292
|
if (data.type === 'success') {
|
|
175
293
|
currentSlideWidth = data.slideWidth;
|
|
176
|
-
|
|
294
|
+
setStatus('connected', 'Updated ' + new Date().toLocaleTimeString());
|
|
295
|
+
var total = data.svgs.length;
|
|
177
296
|
var slideHtml = data.svgs.map(function(svg, i) {
|
|
178
297
|
return '<div class="slide-wrapper">' +
|
|
179
|
-
'<div class="slide-
|
|
180
|
-
|
|
298
|
+
'<div class="slide-frame">' + svg +
|
|
299
|
+
'<span class="slide-number">' + (i + 1) + ' / ' + total + '</span>' +
|
|
300
|
+
'</div>' +
|
|
181
301
|
'</div>';
|
|
182
302
|
}).join('');
|
|
183
303
|
content.innerHTML = '<div class="slides-container">' + slideHtml + '</div>';
|
|
@@ -185,21 +305,31 @@ function buildPreviewHtml() {
|
|
|
185
305
|
applySvgZoom(svgEl, currentZoom, currentSlideWidth);
|
|
186
306
|
});
|
|
187
307
|
} else if (data.type === 'error') {
|
|
188
|
-
|
|
308
|
+
setStatus('error', 'Error');
|
|
189
309
|
var escaped = data.message
|
|
190
310
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
191
|
-
content.innerHTML =
|
|
311
|
+
content.innerHTML =
|
|
312
|
+
'<div class="error-screen">' +
|
|
313
|
+
'<div class="error-block">' +
|
|
314
|
+
'<div class="error-header">⚠ Build Error</div>' +
|
|
315
|
+
'<pre class="error-body">' + escaped + '</pre>' +
|
|
316
|
+
'</div>' +
|
|
317
|
+
'</div>';
|
|
192
318
|
} else if (data.type === 'empty') {
|
|
193
|
-
|
|
194
|
-
content.innerHTML = '<div class="empty-
|
|
319
|
+
setStatus('connected', 'No slides');
|
|
320
|
+
content.innerHTML = '<div class="empty-screen">No slides to preview</div>';
|
|
195
321
|
} else if (data.type === 'building') {
|
|
196
|
-
|
|
197
|
-
content.innerHTML =
|
|
322
|
+
setStatus('warning', 'Building...');
|
|
323
|
+
content.innerHTML =
|
|
324
|
+
'<div class="loading-screen">' +
|
|
325
|
+
'<div class="spinner"></div>' +
|
|
326
|
+
'<span class="loading-text">Building preview...</span>' +
|
|
327
|
+
'</div>';
|
|
198
328
|
}
|
|
199
329
|
});
|
|
200
330
|
|
|
201
331
|
es.addEventListener('error', function() {
|
|
202
|
-
|
|
332
|
+
setStatus('error', 'Disconnected — retrying...');
|
|
203
333
|
});
|
|
204
334
|
})();
|
|
205
335
|
</script>
|
|
@@ -257,7 +387,7 @@ export function runPreview(inputFile) {
|
|
|
257
387
|
clearTimeout(debounceTimer);
|
|
258
388
|
debounceTimer = setTimeout(refresh, 100);
|
|
259
389
|
});
|
|
260
|
-
const html = buildPreviewHtml();
|
|
390
|
+
const html = buildPreviewHtml(path.basename(absInput));
|
|
261
391
|
const server = http.createServer((req, res) => {
|
|
262
392
|
if (req.url === "/_sse") {
|
|
263
393
|
res.writeHead(200, {
|