Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions __TEST__/e2e/file-menu-submenus.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// #428 — the File menu's long "Download as" and "Export / Import" lists are
// grouped into inline-expanding submenus (DaisyUI <details>) so the menu stays
// short. This guards that the grouping didn't drop any action: every id/target
// is still present, the submenus start collapsed, and the nested label→modal
// wiring still fires after the restructure.
import { test, expect } from '@playwright/test';

test.beforeEach(async ({ page }) => {
await page.goto('/index.html');
await page.waitForSelector('#hypertranscript [data-m]');
});

test('long lists are grouped into collapsed submenus with every action still reachable (#428)', async ({ page }) => {
const state = await page.evaluate(() => {
const dl = document.getElementById('file-download-submenu');
const ei = document.getElementById('file-exportimport-submenu');
const ids = ['download-vtt', 'download-vtt-words', 'download-srt', 'download-html', 'download-hypertranscript'];
const forTargets = ['export-modal', 'interactive-export-modal', 'file-import-deepgram-json-dialog', 'file-import-srt-dialog', 'file-import-vtt-dialog'];
const custom = ['export-json', 'export-ionosphere', 'import-json'];
return {
hasDownloadSubmenu: !!dl,
hasExportImportSubmenu: !!ei,
downloadStartsCollapsed: dl ? !dl.open : null,
exportImportStartsCollapsed: ei ? !ei.open : null,
idsPresent: ids.filter((id) => document.getElementById(id) === null),
labelsPresent: forTargets.filter((t) => document.querySelector(`label[for="${t}"]`) === null),
customPresent: custom.filter((tag) => document.querySelector(`#file-dropdown ${tag}`) === null),
// the download/export items now live UNDER the submenus, not at top level
downloadItemsNested: !!document.querySelector('#file-download-submenu #download-vtt'),
exportItemsNested: !!document.querySelector('#file-exportimport-submenu export-json'),
};
});

expect(state.hasDownloadSubmenu).toBe(true);
expect(state.hasExportImportSubmenu).toBe(true);
expect(state.downloadStartsCollapsed).toBe(true); // collapsed → the menu is short
expect(state.exportImportStartsCollapsed).toBe(true);
expect(state.idsPresent).toEqual([]); // no download action lost
expect(state.labelsPresent).toEqual([]); // no modal/import target lost
expect(state.customPresent).toEqual([]); // custom-element exports/imports kept
expect(state.downloadItemsNested).toBe(true);
expect(state.exportItemsNested).toBe(true);
});

test('a submenu expands on click and its nested actions still fire (#428)', async ({ page }) => {
// clicking the <summary> toggles the native <details> open
const opened = await page.evaluate(() => {
const d = document.getElementById('file-download-submenu');
d.querySelector('summary').click();
return d.open;
});
expect(opened).toBe(true);

// the nested "Interactive Transcript" label still toggles its modal — proving
// the label[for] wiring survives being moved inside the submenu
const modalChecked = await page.evaluate(() => {
document.getElementById('download-hypertranscript').click();
return document.getElementById('interactive-export-modal').checked;
});
expect(modalChecked).toBe(true);
});

test('the menu has a real width so items are not collapsed by a purged width class (#428)', async ({ page }) => {
// The prebuilt tailwind-min.css only ships width utilities used at build time,
// so an unbuilt w-* class collapses the menu to content width and every item
// wraps. Width is set inline instead; guard that the rendered menu stays wide.
const width = await page.evaluate(() => {
const ul = document.getElementById('file-dropdown');
ul.closest('.dropdown').querySelector('label[tabindex="0"]').focus();
return ul.getBoundingClientRect().width;
});
expect(width).toBeGreaterThanOrEqual(288); // 18rem; the collapsed bug rendered ~160px
});
68 changes: 33 additions & 35 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -403,47 +403,45 @@ <h2 id="recents-title">Recents</h2>
<svg id="file-dropdown-symbol" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
<svg id="file-dropdown-symbol-mobile" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
</label>
<ul id="file-dropdown" tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<li class="menu-title">
<span>
Upload
</span>
</li>
<!-- width set inline (not a Tailwind w-* class): the prebuilt/purged
tailwind-min.css only ships width utilities used at build time, so
an unbuilt class silently collapses the menu. Wide enough that the
longest indented submenu item stays on one line. -->
<ul id="file-dropdown" tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box" style="width:20rem">
<li><a id="upload-transcribe-btn">Upload & Transcribe</a></li>
<hr
class="my-2 h-0 border border-t-0 border-solid border-neutral-700 opacity-25 dark:border-neutral-200" />
<li class="menu-title">
<span>
Align
</span>
</li>
<li><a id="upload-align-btn">Add Transcript</a></li>
<hr
class="my-2 h-0 border border-t-0 border-solid border-neutral-700 opacity-25 dark:border-neutral-200" />
<li class="menu-title">
<span>
Download as
</span>
<!-- Long output/input lists live in inline-expanding submenus (DaisyUI
<details>) so the File menu stays short; ids/targets unchanged so
all wiring (download links, modal toggles, custom elements) still
works. Mirrors the Glider native File menu (#428). -->
<li>
<details id="file-download-submenu">
<summary>Download as</summary>
<ul>
<li><a id="download-vtt" href="" download="hyperaudio.vtt">WebVTT (Captions)</a></li>
<li><a id="download-vtt-words" href="" download="hyperaudio.words.vtt">WebVTT (Word-level / Karaoke)</a></li>
<li><a id="download-srt" href="" download="hyperaudio.srt">SRT (Captions)</a></li>
<li><a id="download-html" href="" download="hypertranscript.html">HTML</a></li>
<li><label id="download-hypertranscript" for="interactive-export-modal">Interactive Transcript</label></li>
<li><label for="export-modal">Media (WAV / MP3 / MP4…)</label></li>
</ul>
</details>
</li>
<li><a id="download-vtt" href="" download="hyperaudio.vtt">WebVTT (Captions)</a></li>
<li><a id="download-vtt-words" href="" download="hyperaudio.words.vtt">WebVTT (Word-level / Karaoke)</a></li>
<li><a id="download-srt" href="" download="hyperaudio.srt">SRT (Captions)</a></li>
<li><a id="download-html" href="" download="hypertranscript.html">HTML</a></li>
<li><label id="download-hypertranscript" for="interactive-export-modal">Interactive Transcript</label></li>
<li><label for="export-modal">Media (WAV / MP3 / MP4…)</label></li>
<hr
class="my-2 h-0 border border-t-0 border-solid border-neutral-700 opacity-25 dark:border-neutral-200" />
<li class="menu-title">
<span>
Export / Import
</span>
<li>
<details id="file-exportimport-submenu">
<summary>Export / Import</summary>
<ul>
<li><export-json></export-json></li>
<li><export-ionosphere></export-ionosphere></li>
<li><import-json></import-json></li>
<li><label for="file-import-deepgram-json-dialog">Import Deepgram JSON</label></li>
<li><label for="file-import-srt-dialog">Import SRT</label></li>
<li><label for="file-import-vtt-dialog">Import VTT</label></li>
</ul>
</details>
</li>
<li><export-json></export-json></li>
<li><export-ionosphere></export-ionosphere></li>
<li><import-json></import-json></li>
<li><label for="file-import-deepgram-json-dialog">Import Deepgram JSON</label></li>
<li><label for="file-import-srt-dialog">Import SRT</label></li>
<li><label for="file-import-vtt-dialog">Import VTT</label></li>
</ul>
</div>
<input type="checkbox" id="info-modal" class="modal-toggle" tabindex="-1" aria-hidden="true" />
Expand Down
Loading