omni-viewer-core 0.3.0 → 0.4.0

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.
@@ -13,4 +13,5 @@ export interface PptxParserDeps {
13
13
  export { openPptxZip } from './zip-reader.js';
14
14
  import { parsePptxVscode } from './vscode.js';
15
15
  export { parsePptxVscode };
16
+ export declare function parsePptxLegacy(input: Uint8Array, deps: PptxParserDeps, options?: ParseOptions): Promise<ParseOutcome<SlideDeck>>;
16
17
  export declare function parsePptx(input: Uint8Array, deps: PptxParserDeps, options?: ParseOptions): Promise<ParseOutcome<SlideDeck>>;
@@ -55,7 +55,7 @@ async function parseSlide(zip, path, n, size) { const xml = await zip.text(path)
55
55
  else
56
56
  elements.push({ ...common, type: 'shape', presetGeom: geom, fillColor: color(block, 'transparent'), borderColor: color(/<a:ln\b[\s\S]*?<\/a:ln>/.exec(block)?.[0] ?? '') });
57
57
  } return { slideNumber: n, ...size, backgroundColor: color(/<p:bg\b[\s\S]*?<\/p:bg>/.exec(xml)?.[0] ?? '', '#ffffff') ?? '#ffffff', elements }; }
58
- async function parsePptxLegacy(input, deps, options = {}) { const started = Date.now(), diagnostics = []; if (input.byteLength > (options.limits?.maxInputBytes ?? 50 * 1024 * 1024))
58
+ export async function parsePptxLegacy(input, deps, options = {}) { const started = Date.now(), diagnostics = []; if (input.byteLength > (options.limits?.maxInputBytes ?? 50 * 1024 * 1024))
59
59
  return { result: { status: 'failed', failure: { code: 'limit-exceeded', retryable: false, messageKey: 'diag.limit-exceeded.input' }, diagnostics }, execution: { workerUsed: false, hardLimitEnforced: false, elapsedMillis: Date.now() - started } }; let zip; try {
60
60
  zip = await deps.openZip(input, options);
61
61
  const presentation = await zip.text('ppt/presentation.xml');
@@ -4,7 +4,7 @@
4
4
  .omni-viewer--archive *{box-sizing:border-box}.omni-viewer--archive{padding:28px 20px 40px}.omni-archive__hero,.omni-archive__controls,.omni-archive__stats,.omni-archive__table-wrap,.omni-archive__preview-panel{background:var(--omni-bg-secondary,#252526);border:1px solid var(--omni-border,#3c3c3c);border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.12)}
5
5
  .omni-archive__hero{display:flex;justify-content:space-between;gap:16px;padding:24px;align-items:flex-start}.omni-archive__eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--omni-accent,#d97706);font-weight:700}.omni-archive__hero h1{margin:8px 0 6px;font-size:clamp(26px,4vw,40px);line-height:1.1}.omni-archive__subtitle,.omni-archive__preview-meta{color:var(--omni-muted-fg,#9d9d9d);line-height:1.5}.omni-archive__pills{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.omni-archive__pill{padding:10px 14px;border-radius:999px;background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent);border:1px solid color-mix(in srgb,var(--omni-accent,#d97706) 35%,transparent);font-weight:600;white-space:nowrap}
6
6
  .omni-archive__controls,.omni-archive__stats,.omni-archive__workspace{margin-top:16px}.omni-archive__controls{padding:16px}.omni-archive__search-label{display:block;font-weight:600}.omni-archive__search{display:block;width:100%;margin-top:8px;padding:12px 14px;border-radius:12px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-input-bg,#3c3c3c);color:var(--omni-fg,#d4d4d4);font:inherit}.omni-archive__stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;padding:16px}.omni-archive__stat{padding:14px;border-radius:14px;background:color-mix(in srgb,var(--omni-bg-secondary,#252526) 76%,#000);border:1px solid var(--omni-border,#3c3c3c)}.omni-archive__stat-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__stat-value{font-size:24px;font-weight:700}
7
- .omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}
7
+ .omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh;overflow-anchor:none}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}
8
8
  .omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}
9
9
  .omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}
10
10
  .omni-archive__preview-media{flex:1;display:flex;align-items:center;justify-content:center;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;overflow:auto}.omni-archive__audio{width:100%}.omni-archive__video{max-width:100%;max-height:100%}.omni-archive__image{max-width:100%;max-height:100%;object-fit:contain}
@@ -294,20 +294,57 @@ export async function mountArchiveViewer(inputOrSource, container, ctx, depsOrOp
294
294
  const ROW_HEIGHT = 43;
295
295
  const OVERSCAN = 8;
296
296
  const FALLBACK_VIEWPORT_HEIGHT = 600;
297
- const render = () => {
297
+ let lastStart = -1;
298
+ let lastEnd = -1;
299
+ let lastLength = -1;
300
+ let lastQuery;
301
+ let lastExpanded;
302
+ /** `force` covers state changes (search/select/expand) that must repaint the
303
+ * rows; scroll events pass `false` so an unchanged window is a no-op. */
304
+ const render = (force = true) => {
298
305
  const visible = controller.visibleEntries();
306
+ // Measure before mutating: emptying the tbody first would collapse the
307
+ // scroll height, so the layout flush from reading clientHeight clamps
308
+ // scrollTop to 0 and every scroll snaps back to the top of the list.
309
+ const viewportHeight = tableWrap.clientHeight || FALLBACK_VIEWPORT_HEIGHT;
310
+ const scrollTop = tableWrap.scrollTop;
311
+ const start = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN);
312
+ const count = Math.ceil(viewportHeight / ROW_HEIGHT) + OVERSCAN * 2;
313
+ const end = Math.min(visible.length, start + count);
314
+ // Most scroll ticks land inside the overscan margin. Rebuilding the rows
315
+ // anyway would destroy the focused row on every tick, and the resulting
316
+ // focus restore plus scroll anchoring drags the viewport around.
317
+ // The rendered window is fully determined by these; the controller hands
318
+ // out a fresh `expanded` set per toggle, so identity comparison is enough.
319
+ const sameWindow = start === lastStart && end === lastEnd && visible.length === lastLength
320
+ && controller.state.query === lastQuery && controller.state.expanded === lastExpanded;
321
+ // Most scroll ticks land inside the overscan margin. Rebuilding the rows
322
+ // anyway would destroy the focused row on every tick, and the resulting
323
+ // focus restore plus scroll anchoring drags the viewport around.
324
+ if (!force && sameWindow)
325
+ return;
326
+ // Selecting an entry only flips a class. Rebuilding every row for that is
327
+ // what threw the viewport back to the top the moment a row was clicked,
328
+ // so patch the existing rows in place and leave the scroll state alone.
329
+ if (sameWindow) {
330
+ for (const row of tbody.querySelectorAll('.omni-archive__entry'))
331
+ row.classList.toggle('is-selected', Number(row.dataset.entryId) === controller.state.selectedId);
332
+ return;
333
+ }
334
+ lastStart = start;
335
+ lastEnd = end;
336
+ lastLength = visible.length;
337
+ lastQuery = controller.state.query;
338
+ lastExpanded = controller.state.expanded;
299
339
  visibleStat[1] = visible.length;
300
340
  stats.querySelectorAll('.omni-archive__stat-value')[3].textContent = visible.length.toLocaleString();
301
- tbody.replaceChildren();
302
341
  empty.hidden = visible.length !== 0;
303
342
  table.hidden = visible.length === 0;
304
- const viewportHeight = tableWrap.clientHeight || FALLBACK_VIEWPORT_HEIGHT;
305
- const start = Math.max(0, Math.floor(tableWrap.scrollTop / ROW_HEIGHT) - OVERSCAN);
306
- const count = Math.ceil(viewportHeight / ROW_HEIGHT) + OVERSCAN * 2;
307
- const end = Math.min(visible.length, start + count);
308
343
  const spacer = (height) => { const row = element('tr', 'omni-archive__spacer'); const cell = element('td'); cell.colSpan = 5; cell.style.height = `${height}px`; row.append(cell); return row; };
344
+ // Build off-DOM and swap once, so the tbody is never laid out empty.
345
+ const rows = [];
309
346
  if (start > 0)
310
- tbody.append(spacer(start * ROW_HEIGHT));
347
+ rows.push(spacer(start * ROW_HEIGHT));
311
348
  for (const entry of visible.slice(start, end)) {
312
349
  const row = element('tr', `omni-archive__entry${controller.state.selectedId === entry.entryId ? ' is-selected' : ''}`);
313
350
  row.tabIndex = 0;
@@ -320,14 +357,26 @@ export async function mountArchiveViewer(inputOrSource, container, ctx, depsOrOp
320
357
  event.preventDefault();
321
358
  void select(entry);
322
359
  } });
323
- tbody.append(row);
360
+ rows.push(row);
324
361
  }
325
362
  if (end < visible.length)
326
- tbody.append(spacer((visible.length - end) * ROW_HEIGHT));
363
+ rows.push(spacer((visible.length - end) * ROW_HEIGHT));
364
+ // Recycling rows destroys the focused one; hand focus back to its
365
+ // replacement so keyboard navigation survives a scroll.
366
+ const active = (typeof ShadowRoot !== 'undefined' && root instanceof ShadowRoot ? root.activeElement : document.activeElement);
367
+ const focusedId = active instanceof HTMLElement && tbody.contains(active) ? active.dataset.entryId : undefined;
368
+ tbody.replaceChildren(...rows);
369
+ // Swapping the rows can still make the browser adjust the offset (focus
370
+ // fixup, anchoring, a transient height change). The list is virtualized,
371
+ // so the pre-swap offset is authoritative — put it back.
372
+ if (tableWrap.scrollTop !== scrollTop)
373
+ tableWrap.scrollTop = scrollTop;
374
+ if (focusedId !== undefined)
375
+ rows.find(row => row.dataset.entryId === focusedId)?.focus({ preventScroll: true });
327
376
  };
328
- tableWrap.addEventListener('scroll', render, { passive: true });
377
+ tableWrap.addEventListener('scroll', () => render(false), { passive: true });
329
378
  search.addEventListener('input', () => { tableWrap.scrollTop = 0; controller.dispatch({ type: 'set-search', query: search.value }); });
330
- const unsubscribe = controller.subscribe(render);
379
+ const unsubscribe = controller.subscribe(() => render(true));
331
380
  render();
332
381
  return { dispose() { ticket++; extraction?.abort(); saveExtraction?.abort(); clearPreviewMedia(); unsubscribe(); wrap.remove(); void handle?.close(); handle = undefined; } };
333
382
  }
@@ -1 +1 @@
1
- export declare const archiveViewerCss = "\n.omni-viewer--archive{all:initial}.omni-viewer--archive :where(header,section,aside,div,h1,h2,p,span,label,input,table,thead,tbody,tr,th,td,pre){all:revert}\n:host,.omni-viewer--archive{display:block;min-height:100%;box-sizing:border-box;color:var(--omni-fg,#d4d4d4);background:radial-gradient(circle at top left,color-mix(in srgb,var(--omni-accent,#d97706) 14%,transparent),transparent 28%),var(--omni-bg,#1e1e1e);font:13px var(--omni-font,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif)}\n.omni-viewer--archive *{box-sizing:border-box}.omni-viewer--archive{padding:28px 20px 40px}.omni-archive__hero,.omni-archive__controls,.omni-archive__stats,.omni-archive__table-wrap,.omni-archive__preview-panel{background:var(--omni-bg-secondary,#252526);border:1px solid var(--omni-border,#3c3c3c);border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.12)}\n.omni-archive__hero{display:flex;justify-content:space-between;gap:16px;padding:24px;align-items:flex-start}.omni-archive__eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--omni-accent,#d97706);font-weight:700}.omni-archive__hero h1{margin:8px 0 6px;font-size:clamp(26px,4vw,40px);line-height:1.1}.omni-archive__subtitle,.omni-archive__preview-meta{color:var(--omni-muted-fg,#9d9d9d);line-height:1.5}.omni-archive__pills{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.omni-archive__pill{padding:10px 14px;border-radius:999px;background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent);border:1px solid color-mix(in srgb,var(--omni-accent,#d97706) 35%,transparent);font-weight:600;white-space:nowrap}\n.omni-archive__controls,.omni-archive__stats,.omni-archive__workspace{margin-top:16px}.omni-archive__controls{padding:16px}.omni-archive__search-label{display:block;font-weight:600}.omni-archive__search{display:block;width:100%;margin-top:8px;padding:12px 14px;border-radius:12px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-input-bg,#3c3c3c);color:var(--omni-fg,#d4d4d4);font:inherit}.omni-archive__stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;padding:16px}.omni-archive__stat{padding:14px;border-radius:14px;background:color-mix(in srgb,var(--omni-bg-secondary,#252526) 76%,#000);border:1px solid var(--omni-border,#3c3c3c)}.omni-archive__stat-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__stat-value{font-size:24px;font-weight:700}\n.omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}\n.omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}\n.omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}\n.omni-archive__preview-media{flex:1;display:flex;align-items:center;justify-content:center;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;overflow:auto}.omni-archive__audio{width:100%}.omni-archive__video{max-width:100%;max-height:100%}.omni-archive__image{max-width:100%;max-height:100%;object-fit:contain}\n@media(max-width:720px){.omni-viewer--archive{padding:16px 10px 24px}.omni-archive__hero{flex-direction:column}.omni-archive__pills{justify-content:flex-start}.omni-archive__table th:nth-child(n+3),.omni-archive__table td:nth-child(n+3){display:none}.omni-archive__workspace{grid-template-columns:1fr}.omni-archive__preview-panel{min-height:280px}}\n";
1
+ export declare const archiveViewerCss = "\n.omni-viewer--archive{all:initial}.omni-viewer--archive :where(header,section,aside,div,h1,h2,p,span,label,input,table,thead,tbody,tr,th,td,pre){all:revert}\n:host,.omni-viewer--archive{display:block;min-height:100%;box-sizing:border-box;color:var(--omni-fg,#d4d4d4);background:radial-gradient(circle at top left,color-mix(in srgb,var(--omni-accent,#d97706) 14%,transparent),transparent 28%),var(--omni-bg,#1e1e1e);font:13px var(--omni-font,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif)}\n.omni-viewer--archive *{box-sizing:border-box}.omni-viewer--archive{padding:28px 20px 40px}.omni-archive__hero,.omni-archive__controls,.omni-archive__stats,.omni-archive__table-wrap,.omni-archive__preview-panel{background:var(--omni-bg-secondary,#252526);border:1px solid var(--omni-border,#3c3c3c);border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.12)}\n.omni-archive__hero{display:flex;justify-content:space-between;gap:16px;padding:24px;align-items:flex-start}.omni-archive__eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--omni-accent,#d97706);font-weight:700}.omni-archive__hero h1{margin:8px 0 6px;font-size:clamp(26px,4vw,40px);line-height:1.1}.omni-archive__subtitle,.omni-archive__preview-meta{color:var(--omni-muted-fg,#9d9d9d);line-height:1.5}.omni-archive__pills{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.omni-archive__pill{padding:10px 14px;border-radius:999px;background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent);border:1px solid color-mix(in srgb,var(--omni-accent,#d97706) 35%,transparent);font-weight:600;white-space:nowrap}\n.omni-archive__controls,.omni-archive__stats,.omni-archive__workspace{margin-top:16px}.omni-archive__controls{padding:16px}.omni-archive__search-label{display:block;font-weight:600}.omni-archive__search{display:block;width:100%;margin-top:8px;padding:12px 14px;border-radius:12px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-input-bg,#3c3c3c);color:var(--omni-fg,#d4d4d4);font:inherit}.omni-archive__stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;padding:16px}.omni-archive__stat{padding:14px;border-radius:14px;background:color-mix(in srgb,var(--omni-bg-secondary,#252526) 76%,#000);border:1px solid var(--omni-border,#3c3c3c)}.omni-archive__stat-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__stat-value{font-size:24px;font-weight:700}\n.omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh;overflow-anchor:none}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}\n.omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}\n.omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}\n.omni-archive__preview-media{flex:1;display:flex;align-items:center;justify-content:center;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;overflow:auto}.omni-archive__audio{width:100%}.omni-archive__video{max-width:100%;max-height:100%}.omni-archive__image{max-width:100%;max-height:100%;object-fit:contain}\n@media(max-width:720px){.omni-viewer--archive{padding:16px 10px 24px}.omni-archive__hero{flex-direction:column}.omni-archive__pills{justify-content:flex-start}.omni-archive__table th:nth-child(n+3),.omni-archive__table td:nth-child(n+3){display:none}.omni-archive__workspace{grid-template-columns:1fr}.omni-archive__preview-panel{min-height:280px}}\n";
@@ -4,7 +4,7 @@ export const archiveViewerCss = `
4
4
  .omni-viewer--archive *{box-sizing:border-box}.omni-viewer--archive{padding:28px 20px 40px}.omni-archive__hero,.omni-archive__controls,.omni-archive__stats,.omni-archive__table-wrap,.omni-archive__preview-panel{background:var(--omni-bg-secondary,#252526);border:1px solid var(--omni-border,#3c3c3c);border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.12)}
5
5
  .omni-archive__hero{display:flex;justify-content:space-between;gap:16px;padding:24px;align-items:flex-start}.omni-archive__eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--omni-accent,#d97706);font-weight:700}.omni-archive__hero h1{margin:8px 0 6px;font-size:clamp(26px,4vw,40px);line-height:1.1}.omni-archive__subtitle,.omni-archive__preview-meta{color:var(--omni-muted-fg,#9d9d9d);line-height:1.5}.omni-archive__pills{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.omni-archive__pill{padding:10px 14px;border-radius:999px;background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent);border:1px solid color-mix(in srgb,var(--omni-accent,#d97706) 35%,transparent);font-weight:600;white-space:nowrap}
6
6
  .omni-archive__controls,.omni-archive__stats,.omni-archive__workspace{margin-top:16px}.omni-archive__controls{padding:16px}.omni-archive__search-label{display:block;font-weight:600}.omni-archive__search{display:block;width:100%;margin-top:8px;padding:12px 14px;border-radius:12px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-input-bg,#3c3c3c);color:var(--omni-fg,#d4d4d4);font:inherit}.omni-archive__stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;padding:16px}.omni-archive__stat{padding:14px;border-radius:14px;background:color-mix(in srgb,var(--omni-bg-secondary,#252526) 76%,#000);border:1px solid var(--omni-border,#3c3c3c)}.omni-archive__stat-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__stat-value{font-size:24px;font-weight:700}
7
- .omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}
7
+ .omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh;overflow-anchor:none}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}
8
8
  .omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}
9
9
  .omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}
10
10
  .omni-archive__preview-media{flex:1;display:flex;align-items:center;justify-content:center;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;overflow:auto}.omni-archive__audio{width:100%}.omni-archive__video{max-width:100%;max-height:100%}.omni-archive__image{max-width:100%;max-height:100%;object-fit:contain}
@@ -1,5 +1,5 @@
1
1
  import { parsePptBinaryVscode } from '../../parsers/ppt-binary/index.js';
2
- import { openPptxZip, parsePptx, parsePptxVscode } from '../../parsers/pptx/index.js';
2
+ import { openPptxZip, parsePptxLegacy, parsePptxVscode } from '../../parsers/pptx/index.js';
3
3
  import { mountPdfViewer } from '../pdf/index.js';
4
4
  import { MountAbortedError, VIEWER_ROOT_CLASS } from '../types.js';
5
5
  import { createPptController } from './controller.js';
@@ -41,7 +41,7 @@ export async function mountPptViewer(input, container, ctx, deps = {}, options =
41
41
  return mountPdfFallback(input, container, ctx, deps, options, 'diag.ppt.empty');
42
42
  if (!deck.slides.some(slide => slide.elements.length > 0)) {
43
43
  if (isPptx) {
44
- const legacy = await (deps.parseLegacyPptx?.(input.data, options) ?? parsePptx(input.data, { openZip: openPptxZip }, options));
44
+ const legacy = await (deps.parseLegacyPptx?.(input.data, options) ?? parsePptxLegacy(input.data, { openZip: deps.openZip ?? openPptxZip }, options));
45
45
  if (legacy.result.status !== 'failed' && legacy.result.document.slides.some(slide => slide.elements.length > 0))
46
46
  deck = legacy.result.document;
47
47
  else
@@ -64,7 +64,7 @@ export async function mountPptViewer(input, container, ctx, deps = {}, options =
64
64
  next.setAttribute('aria-label', ctx.i18n.t('ppt.next'));
65
65
  jump.setAttribute('aria-label', ctx.i18n.t('ppt.jump'));
66
66
  deck.slides.forEach(slide => { const option = el('option'); option.value = String(slide.slideNumber); const title = slide.elements.find(element => element.isTitle)?.paragraphs?.map(paragraph => paragraph.text).join(' '); option.textContent = `Slide ${slide.slideNumber}${title ? `: ${title}` : ''}`; jump.append(option); });
67
- toolbar.append(prev, next, jump, count, minus, plus, reset, mode);
67
+ toolbar.append(prev, next, jump, count, minus, reset, plus, mode);
68
68
  const viewport = el('div', 'omni-ppt__viewport'), slides = el('div', 'omni-ppt__slides');
69
69
  viewport.append(slides);
70
70
  frame.append(toolbar, viewport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omni-viewer-core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared parsing + rendering core for Omni Viewer (vscode / chrome / obsidian / web)",
5
5
  "type": "module",
6
6
  "license": "MIT",