Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(landing): declutter homescreen with + New dropdown and More menu

Collapses the seven large create cards into a single compact
`+ New ▾` dropdown menu (Document, Spreadsheet, Presentation, Form,
Diagram, Calendar) and keeps `Today's Note` as a separate accent
button. Drops the marketing tagline so the brand header flows
straight into the primary action.

Consolidates the document toolbar: `New Folder`, `Import file`,
`Export backup`, and `Restore backup` move into a `⋯ More` overflow
menu so only Search / Sort / View-toggle are visible by default.

Updates e2e tests to drive the new menu for doc/sheet creation and
renames the tagline assertion to a footer-bar assertion.

Refs: #605

+348 -124
+7
CHANGELOG.md
··· 7 7 8 8 ## [Unreleased] 9 9 10 + ## [0.32.0] — 2026-04-10 11 + 12 + ### Changed 13 + - Landing: collapsed the seven large create cards into a compact `+ New ▾` dropdown menu (Document / Spreadsheet / Presentation / Form / Diagram / Calendar) with `Today's Note` kept as a separate accent button — dramatically less clutter above the fold (#605) 14 + - Landing: dropped the "AI/Human first collaborative office suite" marketing tagline so the brand header flows straight into the primary action — E2EE status still appears in the footer bar (#605) 15 + - Landing: consolidated the document toolbar — `New Folder`, `Import file`, `Export backup`, and `Restore backup` moved into a `⋯ More` overflow menu so only Search / Sort / View-toggle stay visible by default (#605) 16 + 10 17 ## [0.31.0] — 2026-04-10 11 18 12 19 ### Added
+1 -1
e2e/daily-notes.spec.ts
··· 7 7 8 8 const btn = page.locator('#daily-note'); 9 9 await expect(btn).toBeVisible(); 10 - await expect(btn.locator('.create-card-title')).toHaveText("Today's Note"); 10 + await expect(btn).toContainText("Today's Note"); 11 11 }); 12 12 13 13 test('clicking daily note creates a new document', async ({ page }) => {
+4 -2
e2e/dark-mode.spec.ts
··· 183 183 const landingTheme = await page.locator('html').getAttribute('data-theme'); 184 184 185 185 // Navigate to a new doc 186 - await page.click('#new-doc'); 186 + await page.click('#new-btn'); 187 + await page.click('.new-menu-item[data-new="doc"]'); 187 188 await page.waitForSelector('.tiptap', { timeout: 15000 }); 188 189 189 190 // Theme should carry over ··· 199 200 const landingTheme = await page.locator('html').getAttribute('data-theme'); 200 201 201 202 // Navigate to a new sheet 202 - await page.click('#new-sheet'); 203 + await page.click('#new-btn'); 204 + await page.click('.new-menu-item[data-new="sheet"]'); 203 205 await page.waitForSelector('#sheet-grid tbody tr td[data-id]', { timeout: 15000 }); 204 206 205 207 // Theme should carry over
+14 -8
e2e/landing.spec.ts
··· 11 11 await expect(page.locator('.brand-badge')).toHaveText('E2EE'); 12 12 }); 13 13 14 - test('shows tagline', async ({ page }) => { 15 - await expect(page.locator('.brand-tagline')).toContainText('end-to-end encrypted', { ignoreCase: true }); 14 + test('shows E2EE footer notice', async ({ page }) => { 15 + await expect(page.locator('.encryption-bar')).toContainText('end-to-end encrypted', { ignoreCase: true }); 16 16 }); 17 17 18 18 test('create new document navigates to docs editor', async ({ page }) => { 19 - await page.click('#new-doc'); 19 + await page.click('#new-btn'); 20 + await page.click('.new-menu-item[data-new="doc"]'); 20 21 await page.waitForURL(/\/docs\/[^/]+#/); 21 22 // Editor should mount 22 23 await expect(page.locator('.tiptap')).toBeVisible({ timeout: 15000 }); 23 24 }); 24 25 25 26 test('create new spreadsheet navigates to sheets editor', async ({ page }) => { 26 - await page.click('#new-sheet'); 27 + await page.click('#new-btn'); 28 + await page.click('.new-menu-item[data-new="sheet"]'); 27 29 await page.waitForURL(/\/sheets\/[^/]+#/); 28 30 // Grid should render 29 31 await expect(page.locator('#sheet-grid')).toBeVisible({ timeout: 15000 }); ··· 31 33 32 34 test('new document appears in document list after returning to landing', async ({ page }) => { 33 35 // Create a doc 34 - await page.click('#new-doc'); 36 + await page.click('#new-btn'); 37 + await page.click('.new-menu-item[data-new="doc"]'); 35 38 await page.waitForURL(/\/docs\//); 36 39 await page.waitForSelector('.tiptap', { timeout: 15000 }); 37 40 ··· 46 49 47 50 test('rename document via title input in editor', async ({ page }) => { 48 51 // Create a doc and rename it 49 - await page.click('#new-doc'); 52 + await page.click('#new-btn'); 53 + await page.click('.new-menu-item[data-new="doc"]'); 50 54 await page.waitForSelector('.tiptap', { timeout: 15000 }); 51 55 const titleInput = page.locator('#doc-title'); 52 56 await titleInput.fill('My Test Document'); ··· 63 67 64 68 test('trash and restore document', async ({ page }) => { 65 69 // Create a doc first 66 - await page.click('#new-doc'); 70 + await page.click('#new-btn'); 71 + await page.click('.new-menu-item[data-new="doc"]'); 67 72 await page.waitForURL(/\/docs\//); 68 73 await page.waitForSelector('.tiptap', { timeout: 15000 }); 69 74 ··· 107 112 108 113 test('search filters documents', async ({ page }) => { 109 114 // Create a doc with a known name 110 - await page.click('#new-doc'); 115 + await page.click('#new-btn'); 116 + await page.click('.new-menu-item[data-new="doc"]'); 111 117 await page.waitForSelector('.tiptap', { timeout: 15000 }); 112 118 await page.locator('#doc-title').fill('SearchableDoc'); 113 119 await page.locator('#doc-title').press('Enter');
+14 -7
e2e/mobile-landing.spec.ts
··· 16 16 }); 17 17 18 18 test('create buttons are visible and tappable', async ({ page }) => { 19 - const newDoc = page.locator('#new-doc'); 20 - const newSheet = page.locator('#new-sheet'); 21 - await expect(newDoc).toBeVisible(); 22 - await expect(newSheet).toBeVisible(); 19 + const newBtn = page.locator('#new-btn'); 20 + const dailyNote = page.locator('#daily-note'); 21 + await expect(newBtn).toBeVisible(); 22 + await expect(dailyNote).toBeVisible(); 23 23 24 24 // Verify minimum touch target size (44x44) 25 - const docBox = await newDoc.boundingBox(); 26 - expect(docBox!.width).toBeGreaterThanOrEqual(44); 27 - expect(docBox!.height).toBeGreaterThanOrEqual(44); 25 + const newBox = await newBtn.boundingBox(); 26 + expect(newBox!.width).toBeGreaterThanOrEqual(44); 27 + expect(newBox!.height).toBeGreaterThanOrEqual(44); 28 + 29 + // Menu opens on click and items are tappable 30 + await newBtn.click(); 31 + const docItem = page.locator('.new-menu-item[data-new="doc"]'); 32 + await expect(docItem).toBeVisible(); 33 + const itemBox = await docItem.boundingBox(); 34 + expect(itemBox!.height).toBeGreaterThanOrEqual(32); 28 35 }); 29 36 30 37 test('search input is full width on mobile', async ({ page }) => {
+1 -1
package.json
··· 1 1 { 2 2 "name": "tools", 3 - "version": "0.31.0", 3 + "version": "0.32.0", 4 4 "private": true, 5 5 "type": "module", 6 6 "main": "electron/main.js",
+169 -50
src/css/app.css
··· 423 423 .comment-popover, 424 424 .toolbar-dropdown-menu, 425 425 .toolbar-overflow-menu, 426 - .create-card, 426 + .new-menu, 427 + .more-menu-panel, 427 428 .doc-item, 428 429 .folder-card, 429 430 .sort-menu, ··· 650 651 border-radius: var(--radius-sm); 651 652 } 652 653 653 - .brand-tagline { 654 - font-family: var(--font-display); 655 - font-style: italic; 654 + .landing-actions { 655 + display: flex; 656 + flex-wrap: wrap; 657 + gap: var(--space-sm); 658 + margin-top: var(--space-lg); 659 + } 660 + 661 + .new-dropdown { 662 + position: relative; 663 + } 664 + 665 + .new-btn { 666 + display: inline-flex; 667 + align-items: center; 668 + gap: 0.5rem; 669 + padding: 0.55rem 1rem; 670 + background: var(--color-accent); 671 + color: var(--color-btn-primary-text); 672 + border: 1px solid var(--color-accent); 673 + border-radius: var(--radius-md); 674 + font: inherit; 675 + font-size: 0.95rem; 676 + font-weight: 600; 677 + cursor: pointer; 678 + transition: background var(--transition-fast), border-color var(--transition-fast); 679 + } 680 + .new-btn:hover { 681 + background: var(--color-accent-hover); 682 + border-color: var(--color-accent-hover); 683 + } 684 + .new-btn-plus { 656 685 font-size: 1.15rem; 657 - color: var(--color-text-muted); 658 - max-width: 36ch; 686 + line-height: 1; 687 + } 688 + .new-btn-caret { 689 + font-size: 0.7rem; 690 + opacity: 0.85; 659 691 } 660 692 661 - .create-actions { 693 + .new-menu { 694 + display: none; 695 + position: absolute; 696 + top: calc(100% + 4px); 697 + left: 0; 698 + min-width: 200px; 699 + background: var(--color-surface); 700 + border: 1px solid var(--color-border); 701 + border-radius: var(--radius-md); 702 + box-shadow: var(--shadow-md); 703 + z-index: var(--z-overlay); 704 + padding: var(--space-xs); 705 + flex-direction: column; 706 + } 707 + .new-dropdown.open .new-menu { 662 708 display: flex; 663 - flex-wrap: wrap; 664 - gap: var(--space-md); 665 - margin-top: var(--space-xl); 666 709 } 667 710 668 - .create-card { 669 - flex: 1; 711 + .new-menu-item { 670 712 display: flex; 671 - flex-direction: column; 713 + align-items: center; 672 714 gap: var(--space-sm); 673 - padding: var(--space-lg) var(--space-lg); 674 - background: var(--color-surface); 675 - border: 1px solid var(--color-border); 676 - border-radius: var(--radius-md); 715 + padding: var(--space-sm) var(--space-md); 716 + background: none; 717 + border: none; 718 + border-radius: var(--radius-sm); 719 + color: var(--color-text); 720 + font: inherit; 721 + font-size: 0.9rem; 722 + text-align: left; 677 723 cursor: pointer; 678 - transition: all var(--transition-med); 679 - text-decoration: none; 680 - color: inherit; 724 + transition: background var(--transition-fast); 681 725 } 682 - .create-card:hover { 683 - border-color: var(--color-border-strong); 726 + .new-menu-item:hover { 684 727 background: var(--color-hover); 685 - transform: translateY(-1px); 686 - box-shadow: var(--shadow-md); 687 - color: inherit; 688 728 } 689 729 690 - .create-card-icon { 691 - font-size: 1.8rem; 692 - line-height: 1; 693 - } 694 - 695 - .create-card-title { 696 - font-family: var(--font-body); 697 - font-weight: 600; 730 + .new-menu-icon { 698 731 font-size: 1rem; 732 + width: 1.25rem; 733 + text-align: center; 734 + color: var(--color-text-muted); 699 735 } 700 736 701 - .create-card-desc { 702 - font-size: 0.85rem; 703 - color: var(--color-text-muted); 704 - line-height: 1.4; 737 + .daily-note-btn { 738 + display: inline-flex; 739 + align-items: center; 740 + gap: 0.5rem; 741 + padding: 0.55rem 1rem; 742 + background: transparent; 743 + color: var(--color-text); 744 + border: 1px solid var(--color-border-strong); 745 + border-radius: var(--radius-md); 746 + font: inherit; 747 + font-size: 0.95rem; 748 + font-weight: 500; 749 + cursor: pointer; 750 + transition: background var(--transition-fast), border-color var(--transition-fast); 705 751 } 706 - 707 - .create-card-accent { 708 - border-color: #5194d566; 709 - border-color: oklch(0.65 0.12 250 / 0.4); 710 - background: #5194d50d; 711 - background: oklch(0.65 0.12 250 / 0.05); 752 + .daily-note-btn:hover { 753 + background: var(--color-hover); 754 + border-color: var(--color-text-muted); 712 755 } 713 - .create-card-accent:hover { 714 - border-color: #5194d5b3; 715 - border-color: oklch(0.65 0.12 250 / 0.7); 716 - background: #5194d51a; 717 - background: oklch(0.65 0.12 250 / 0.1); 756 + .daily-note-icon { 757 + color: var(--color-accent); 758 + font-size: 1rem; 759 + line-height: 1; 718 760 } 719 761 720 762 /* Document list */ ··· 1234 1276 background: var(--color-hover); 1235 1277 } 1236 1278 1279 + /* More menu (overflow actions: folder / import / backup) */ 1280 + .more-menu { 1281 + position: relative; 1282 + } 1283 + 1284 + .more-btn { 1285 + display: inline-flex; 1286 + align-items: center; 1287 + justify-content: center; 1288 + padding: var(--space-xs) var(--space-sm); 1289 + border: 1px solid var(--color-border); 1290 + border-radius: var(--radius-sm); 1291 + background: var(--color-surface); 1292 + color: var(--color-text-muted); 1293 + font: inherit; 1294 + cursor: pointer; 1295 + transition: border-color var(--transition-fast), color var(--transition-fast); 1296 + } 1297 + .more-btn:hover { 1298 + border-color: var(--color-border-strong); 1299 + color: var(--color-text); 1300 + } 1301 + 1302 + .more-menu-panel { 1303 + display: none; 1304 + position: absolute; 1305 + top: 100%; 1306 + right: 0; 1307 + margin-top: 4px; 1308 + min-width: 200px; 1309 + background: var(--color-surface); 1310 + border: 1px solid var(--color-border); 1311 + border-radius: var(--radius-md); 1312 + box-shadow: var(--shadow-md); 1313 + z-index: var(--z-overlay); 1314 + padding: var(--space-xs); 1315 + flex-direction: column; 1316 + } 1317 + .more-menu.open .more-menu-panel { 1318 + display: flex; 1319 + } 1320 + 1321 + .more-menu-item { 1322 + display: flex; 1323 + align-items: center; 1324 + gap: var(--space-sm); 1325 + padding: var(--space-sm) var(--space-md); 1326 + background: none; 1327 + border: none; 1328 + border-radius: var(--radius-sm); 1329 + color: var(--color-text); 1330 + font: inherit; 1331 + font-size: 0.85rem; 1332 + text-align: left; 1333 + cursor: pointer; 1334 + transition: background var(--transition-fast); 1335 + white-space: nowrap; 1336 + } 1337 + .more-menu-item:hover { 1338 + background: var(--color-hover); 1339 + } 1340 + 1341 + .more-menu-icon { 1342 + width: 1.25rem; 1343 + text-align: center; 1344 + color: var(--color-text-muted); 1345 + } 1346 + 1347 + .more-menu-sep { 1348 + height: 1px; 1349 + margin: var(--space-xs) 0; 1350 + background: var(--color-border); 1351 + } 1352 + 1237 1353 /* Folder grid */ 1238 1354 .folder-grid { 1239 1355 display: grid; ··· 3727 3843 3728 3844 /* --- Responsive --- */ 3729 3845 @media (max-width: 640px) { 3730 - .create-actions { flex-direction: column; } 3846 + .landing-actions { flex-direction: column; align-items: stretch; } 3847 + .new-dropdown, .new-btn, .daily-note-btn { width: 100%; justify-content: center; } 3848 + .new-menu { left: 0; right: 0; } 3731 3849 .toolbar { flex-wrap: nowrap; } 3732 3850 .editor-container { padding: var(--space-md); } 3733 3851 .shortcuts-modal { max-width: 95vw; } ··· 5708 5826 padding: var(--space-lg) var(--space-md); 5709 5827 } 5710 5828 5711 - .create-actions { 5829 + .landing-actions { 5712 5830 flex-direction: column; 5831 + align-items: stretch; 5713 5832 } 5714 5833 5715 5834 .doc-toolbar {
+58 -42
src/index.html
··· 42 42 <span class="user-badge" id="user-badge" title="Click to change name"></span> 43 43 <button class="theme-toggle" id="theme-toggle" title="Toggle dark mode"></button> 44 44 </div> 45 - <p class="brand-tagline">AI/Human first collaborative office suite. End-to-end encrypted.</p> 46 - 47 - <div class="create-actions"> 48 - <a class="create-card" id="new-doc" href="#"> 49 - <span class="create-card-icon">&#9998;</span> 50 - <span class="create-card-title">New Document</span> 51 - <span class="create-card-desc">Rich text with formatting, tables, images, and task lists</span> 52 - </a> 53 - <a class="create-card" id="new-sheet" href="#"> 54 - <span class="create-card-icon">&#9638;</span> 55 - <span class="create-card-title">New Spreadsheet</span> 56 - <span class="create-card-desc">Formulas, formatting, multiple sheets, and real-time collaboration</span> 57 - </a> 58 - <a class="create-card" id="new-form" href="#"> 59 - <span class="create-card-icon">&#9783;</span> 60 - <span class="create-card-title">New Form</span> 61 - <span class="create-card-desc">E2EE form builder with responses pipeline to sheets</span> 62 - </a> 63 - <a class="create-card" id="new-slide" href="#"> 64 - <span class="create-card-icon">&#9707;</span> 65 - <span class="create-card-title">New Presentation</span> 66 - <span class="create-card-desc">Slide decks with themes, transitions, and presenter mode</span> 67 - </a> 68 - <a class="create-card" id="new-diagram" href="#"> 69 - <span class="create-card-icon">&#9683;</span> 70 - <span class="create-card-title">New Diagram</span> 71 - <span class="create-card-desc">Freeform whiteboard with shapes, arrows, and freehand drawing</span> 72 - </a> 73 - <a class="create-card" id="new-calendar" href="#"> 74 - <span class="create-card-icon">&#9776;</span> 75 - <span class="create-card-title">Calendar</span> 76 - <span class="create-card-desc">E2EE calendar with month, week, day, and agenda views</span> 77 - </a> 78 - <a class="create-card create-card-accent" id="daily-note" href="#"> 79 - <span class="create-card-icon">&#9830;</span> 80 - <span class="create-card-title">Today's Note</span> 81 - <span class="create-card-desc">Open or create today's journal entry</span> 82 - </a> 45 + <div class="landing-actions"> 46 + <div class="new-dropdown" id="new-dropdown"> 47 + <button class="new-btn" id="new-btn" aria-haspopup="true" aria-expanded="false"> 48 + <span class="new-btn-plus">+</span> 49 + <span>New</span> 50 + <span class="new-btn-caret">&#9662;</span> 51 + </button> 52 + <div class="new-menu" id="new-menu" role="menu"> 53 + <button class="new-menu-item" data-new="doc" role="menuitem"> 54 + <span class="new-menu-icon">&#9998;</span> 55 + <span class="new-menu-label">Document</span> 56 + </button> 57 + <button class="new-menu-item" data-new="sheet" role="menuitem"> 58 + <span class="new-menu-icon">&#9638;</span> 59 + <span class="new-menu-label">Spreadsheet</span> 60 + </button> 61 + <button class="new-menu-item" data-new="slide" role="menuitem"> 62 + <span class="new-menu-icon">&#9707;</span> 63 + <span class="new-menu-label">Presentation</span> 64 + </button> 65 + <button class="new-menu-item" data-new="form" role="menuitem"> 66 + <span class="new-menu-icon">&#9783;</span> 67 + <span class="new-menu-label">Form</span> 68 + </button> 69 + <button class="new-menu-item" data-new="diagram" role="menuitem"> 70 + <span class="new-menu-icon">&#9683;</span> 71 + <span class="new-menu-label">Diagram</span> 72 + </button> 73 + <button class="new-menu-item" data-new="calendar" role="menuitem"> 74 + <span class="new-menu-icon">&#9776;</span> 75 + <span class="new-menu-label">Calendar</span> 76 + </button> 77 + </div> 78 + </div> 79 + <button class="daily-note-btn" id="daily-note"> 80 + <span class="daily-note-icon">&#9830;</span> 81 + <span>Today's Note</span> 82 + </button> 83 83 </div> 84 84 </header> 85 85 ··· 106 106 <svg class="view-icon view-icon-grid" viewBox="0 0 16 16" width="16" height="16"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg> 107 107 <svg class="view-icon view-icon-list" viewBox="0 0 16 16" width="16" height="16"><rect x="1" y="2" width="14" height="2.5" rx="0.5"/><rect x="1" y="6.75" width="14" height="2.5" rx="0.5"/><rect x="1" y="11.5" width="14" height="2.5" rx="0.5"/></svg> 108 108 </button> 109 - <button class="btn-secondary" id="new-folder-btn" title="New Folder">+ Folder</button> 110 - <button class="btn-secondary" id="file-import-btn" title="Import .docx, .xlsx, .csv, or .md file">&#8679; Import</button> 109 + <div class="more-menu" id="more-menu"> 110 + <button class="more-btn" id="more-btn" title="More actions" aria-label="More actions" aria-haspopup="true" aria-expanded="false"> 111 + <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><circle cx="3.5" cy="8" r="1.5"/><circle cx="8" cy="8" r="1.5"/><circle cx="12.5" cy="8" r="1.5"/></svg> 112 + </button> 113 + <div class="more-menu-panel" id="more-menu-panel" role="menu"> 114 + <button class="more-menu-item" id="new-folder-btn" role="menuitem"> 115 + <span class="more-menu-icon">&#128193;</span> New Folder 116 + </button> 117 + <button class="more-menu-item" id="file-import-btn" role="menuitem"> 118 + <span class="more-menu-icon">&#8679;</span> Import file&hellip; 119 + </button> 120 + <div class="more-menu-sep"></div> 121 + <button class="more-menu-item" id="backup-export-btn" role="menuitem"> 122 + <span class="more-menu-icon">&#8681;</span> Export backup 123 + </button> 124 + <button class="more-menu-item" id="backup-import-btn" role="menuitem"> 125 + <span class="more-menu-icon">&#8679;</span> Restore backup&hellip; 126 + </button> 127 + </div> 128 + </div> 111 129 <input type="file" id="file-import-input" accept=".docx,.xlsx,.xls,.csv,.tsv,.md,.txt" style="display:none"> 112 - <button class="btn-secondary" id="backup-export-btn" title="Export backup">&#8681; Backup</button> 113 - <button class="btn-secondary" id="backup-import-btn" title="Import backup">&#8679; Restore</button> 114 130 <input type="file" id="backup-import-input" accept=".json" style="display:none"> 115 131 </div> 116 132 </div>
+80 -13
src/landing.ts
··· 23 23 const docListEl = document.getElementById('doc-list') as HTMLElement; 24 24 const folderListEl = document.getElementById('folder-list') as HTMLElement; 25 25 const noResultsEl = document.getElementById('no-results') as HTMLElement; 26 - const newDocBtn = document.getElementById('new-doc') as HTMLElement; 27 - const newSheetBtn = document.getElementById('new-sheet') as HTMLElement; 28 - const newFormBtn = document.getElementById('new-form') as HTMLElement; 29 - const newSlideBtn = document.getElementById('new-slide') as HTMLElement; 30 - const newDiagramBtn = document.getElementById('new-diagram') as HTMLElement; 31 - const newCalendarBtn = document.getElementById('new-calendar') as HTMLElement; 26 + const newBtn = document.getElementById('new-btn') as HTMLElement; 27 + const newDropdown = document.getElementById('new-dropdown') as HTMLElement; 28 + const newMenu = document.getElementById('new-menu') as HTMLElement; 32 29 const dailyNoteBtn = document.getElementById('daily-note') as HTMLElement; 30 + const moreBtn = document.getElementById('more-btn') as HTMLElement; 31 + const moreMenu = document.getElementById('more-menu') as HTMLElement; 32 + const moreMenuPanel = document.getElementById('more-menu-panel') as HTMLElement; 33 33 const searchInput = document.getElementById('search-input') as HTMLInputElement; 34 34 const searchClear = document.getElementById('search-clear') as HTMLElement; 35 35 const sortBtn = document.getElementById('sort-btn') as HTMLElement; ··· 169 169 setupDelegatedListeners(eventDeps); 170 170 setupOneOffListeners(eventDeps); 171 171 172 - // --- New document buttons --- 173 - newDocBtn.addEventListener('click', (e) => { e.preventDefault(); createDocument(createDeps, 'doc'); }); 174 - newSheetBtn.addEventListener('click', (e) => { e.preventDefault(); createDocument(createDeps, 'sheet'); }); 175 - newFormBtn.addEventListener('click', (e) => { e.preventDefault(); createDocument(createDeps, 'form'); }); 176 - newSlideBtn.addEventListener('click', (e) => { e.preventDefault(); createDocument(createDeps, 'slide'); }); 177 - newDiagramBtn.addEventListener('click', (e) => { e.preventDefault(); createDocument(createDeps, 'diagram'); }); 178 - newCalendarBtn.addEventListener('click', (e) => { e.preventDefault(); openCalendar(createDeps); }); 172 + // --- New document menu --- 173 + function handleNewAction(kind: string): void { 174 + switch (kind) { 175 + case 'doc': 176 + case 'sheet': 177 + case 'form': 178 + case 'slide': 179 + case 'diagram': 180 + createDocument(createDeps, kind); 181 + break; 182 + case 'calendar': 183 + openCalendar(createDeps); 184 + break; 185 + } 186 + } 187 + 188 + function closeNewMenu(): void { 189 + newDropdown.classList.remove('open'); 190 + newBtn.setAttribute('aria-expanded', 'false'); 191 + } 192 + function toggleNewMenu(): void { 193 + const open = newDropdown.classList.toggle('open'); 194 + newBtn.setAttribute('aria-expanded', open ? 'true' : 'false'); 195 + if (open) closeMoreMenu(); 196 + } 197 + newBtn.addEventListener('click', (e) => { 198 + e.preventDefault(); 199 + e.stopPropagation(); 200 + toggleNewMenu(); 201 + }); 202 + newMenu.addEventListener('click', (e) => { 203 + const target = (e.target as HTMLElement).closest('.new-menu-item') as HTMLElement | null; 204 + if (!target) return; 205 + const kind = target.dataset.new; 206 + if (!kind) return; 207 + closeNewMenu(); 208 + handleNewAction(kind); 209 + }); 210 + 179 211 dailyNoteBtn.addEventListener('click', (e) => { e.preventDefault(); openDailyNote(createDeps); }); 212 + 213 + // --- More menu (folder / import / backup) --- 214 + function closeMoreMenu(): void { 215 + moreMenu.classList.remove('open'); 216 + moreBtn.setAttribute('aria-expanded', 'false'); 217 + } 218 + function toggleMoreMenu(): void { 219 + const open = moreMenu.classList.toggle('open'); 220 + moreBtn.setAttribute('aria-expanded', open ? 'true' : 'false'); 221 + if (open) closeNewMenu(); 222 + } 223 + moreBtn.addEventListener('click', (e) => { 224 + e.preventDefault(); 225 + e.stopPropagation(); 226 + toggleMoreMenu(); 227 + }); 228 + // Clicking any item inside the more menu closes it afterward. 229 + // The item's own handler (wired in landing-events.ts) still runs. 230 + moreMenuPanel.addEventListener('click', (e) => { 231 + const target = (e.target as HTMLElement).closest('.more-menu-item'); 232 + if (target) closeMoreMenu(); 233 + }); 234 + 235 + // Dismiss menus on outside click / Escape 236 + document.addEventListener('click', (e) => { 237 + const t = e.target as HTMLElement; 238 + if (!newDropdown.contains(t)) closeNewMenu(); 239 + if (!moreMenu.contains(t)) closeMoreMenu(); 240 + }); 241 + document.addEventListener('keydown', (e) => { 242 + if (e.key === 'Escape') { 243 + closeNewMenu(); 244 + closeMoreMenu(); 245 + } 246 + }); 180 247 181 248 // --- Drag-and-drop file import --- 182 249 setupDragAndDrop({