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

Configure Feed

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

fix: diagram text editing and reopen-as-spreadsheet bugs (#223)

scott 5747cb27 b5d8217e

+32 -11
+9
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## [0.22.4] — 2026-04-04 9 + 10 + ### Fixed 11 + - Inline text editing in diagrams survives re-renders (textarea no longer destroyed) (#352) 12 + - Diagrams now reopen correctly instead of loading as spreadsheets (#353) 13 + - Landing page routing supports all document types (doc, sheet, form, slide, diagram) 14 + - DocType and DocumentMeta types include all 5 document types 15 + 8 16 ## [0.22.3] — 2026-04-04 9 17 10 18 ### Fixed ··· 15 23 - Arrow selection export includes arrows with at least one selected endpoint (#351) 16 24 17 25 ### Added 26 + - Diagrams polish and test coverage round 2 (#351) 18 27 - 110 new polish tests covering export arrows, rotation, resize, groups, history edge cases 19 28 20 29 ## [0.22.2] — 2026-04-03
+1 -1
package.json
··· 1 1 { 2 2 "name": "tools", 3 - "version": "0.22.3", 3 + "version": "0.22.4", 4 4 "private": true, 5 5 "type": "module", 6 6 "main": "electron/main.js",
+7
src/diagrams/main.ts
··· 262 262 263 263 // --- Rendering --- 264 264 function render() { 265 + // Preserve inline text editing overlay across re-renders 266 + const editOverlay = editingShapeId ? layer.querySelector('.inline-text-edit') : null; 265 267 layer.innerHTML = ''; 266 268 const transform = `translate(${wb.panX}, ${wb.panY}) scale(${wb.zoom})`; 267 269 layer.setAttribute('transform', transform); ··· 459 461 polygon.setAttribute('fill', 'var(--color-text)'); 460 462 marker.appendChild(polygon); 461 463 defs.appendChild(marker); 464 + } 465 + 466 + // Re-attach inline text editing overlay if it was active 467 + if (editOverlay) { 468 + layer.appendChild(editOverlay); 462 469 } 463 470 464 471 zoomLabel.textContent = `${Math.round(wb.zoom * 100)}%`;
+2 -2
src/landing-types.ts
··· 4 4 5 5 export interface DocumentMeta { 6 6 id: string; 7 - type: 'doc' | 'sheet'; 7 + type: 'doc' | 'sheet' | 'form' | 'slide' | 'diagram'; 8 8 name_encrypted: string | null; 9 9 deleted_at: string | null; 10 10 tags: string | null; ··· 55 55 expired: string[]; 56 56 } 57 57 58 - export type DocType = 'doc' | 'sheet'; 58 + export type DocType = 'doc' | 'sheet' | 'form' | 'slide' | 'diagram'; 59 59 export type ImportType = 'docx' | 'xlsx' | 'csv' | 'md'; 60 60 61 61 export interface SortLabels {
+13 -8
src/landing.ts
··· 31 31 buildEditorUrl, 32 32 } from './landing-dragdrop.js'; 33 33 34 + // --- Document type routing --- 35 + const DOC_PATH_MAP: Record<string, string> = { doc: '/docs', sheet: '/sheets', form: '/forms', slide: '/slides', diagram: '/diagrams' }; 36 + const DOC_ICON_MAP: Record<string, string> = { doc: '&#9998;', sheet: '&#9638;', form: '&#9783;', slide: '&#9707;', diagram: '&#9683;' }; 37 + function docPath(type: string): string { return DOC_PATH_MAP[type] || '/sheets'; } 38 + function docIcon(type: string): string { return DOC_ICON_MAP[type] || '&#9638;'; } 39 + 34 40 // --- DOM refs --- 35 41 const docListEl = document.getElementById('doc-list') as HTMLElement; 36 42 const folderListEl = document.getElementById('folder-list') as HTMLElement; ··· 222 228 recentIds = trackRecentDoc(recentIds, id); 223 229 localStorage.setItem('tools-recent', JSON.stringify(recentIds)); 224 230 225 - const pathMap = { doc: '/docs', sheet: '/sheets', form: '/forms', slide: '/slides', diagram: '/diagrams' }; 226 - window.location.href = `${pathMap[type]}/${id}#${keyStr}`; 231 + window.location.href = `${docPath(type)}/${id}#${keyStr}`; 227 232 } 228 233 229 234 async function createFromTemplate(templateId: string): Promise<void> { ··· 529 534 530 535 let html = '<h3 class="recent-heading">Recent</h3><div class="recent-list">'; 531 536 for (const doc of recent) { 532 - const path = doc.type === 'doc' ? '/docs' : '/sheets'; 533 - const icon = doc.type === 'doc' ? '&#9998;' : '&#9638;'; 537 + const path = docPath(doc.type); 538 + const icon = docIcon(doc.type); 534 539 const name = doc._decryptedName || 'Encrypted Document'; 535 540 const href = doc._keyStr ? `${path}/${doc.id}#${doc._keyStr}` : '#'; 536 541 html += `<a class="recent-card" href="${href}" data-doc-id="${doc.id}"> ··· 572 577 573 578 let html = '<h3 class="pinned-heading">Pinned</h3><div class="pinned-list">'; 574 579 for (const doc of pinned) { 575 - const path = doc.type === 'doc' ? '/docs' : '/sheets'; 576 - const icon = doc.type === 'doc' ? '&#9998;' : '&#9638;'; 580 + const path = docPath(doc.type); 581 + const icon = docIcon(doc.type); 577 582 const name = doc._decryptedName || 'Encrypted Document'; 578 583 const href = doc._keyStr ? `${path}/${doc.id}#${doc._keyStr}` : '#'; 579 584 html += `<a class="pinned-card" href="${href}" data-doc-id="${doc.id}"> ··· 1301 1306 return allDocs 1302 1307 .filter(doc => doc._keyStr) 1303 1308 .map(doc => { 1304 - const path = doc.type === 'doc' ? '/docs' : '/sheets'; 1309 + const path = docPath(doc.type); 1305 1310 return { 1306 1311 id: `doc-${doc.id}`, 1307 1312 label: doc._decryptedName || 'Encrypted Document', 1308 1313 category: 'document' as const, 1309 - icon: doc.type === 'doc' ? '\u270e' : '\u25a6', 1314 + icon: doc.type === 'doc' ? '\u270e' : doc.type === 'diagram' ? '\u25c3' : '\u25a6', 1310 1315 action: () => { window.location.href = `${path}/${doc.id}#${doc._keyStr}`; }, 1311 1316 }; 1312 1317 });