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

Configure Feed

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

Merge pull request 'fix: remove @ts-nocheck from slides and diagrams, fix real bugs' (#279) from fix/remove-ts-nocheck into main

scott 91b1150b ac1e6822

+45 -46
+32 -32
src/diagrams/main.ts
··· 1 - // @ts-nocheck — DOM entry point; strict typing planned for follow-up 1 + 2 2 /** 3 3 * Tools Diagrams — E2EE collaborative whiteboard/diagrams. 4 4 * Backed by Yjs for real-time collaboration. ··· 375 375 376 376 // Render resize handles + rotation handle for single selection 377 377 if (selectedShapeIds.size === 1 && !editingShapeId) { 378 - const selId = [...selectedShapeIds][0]; 378 + const selId = [...selectedShapeIds][0]!; 379 379 const selShape = wb.shapes.get(selId); 380 380 if (selShape) { 381 381 const handles = getResizeHandles(selShape); ··· 660 660 propsDimensions.style.display = 'none'; 661 661 return; 662 662 } 663 - const shape = wb.shapes.get([...selectedShapeIds][0]); 663 + const shape = wb.shapes.get([...selectedShapeIds][0]!); 664 664 if (!shape) { propsSection.style.display = 'none'; propsDimensions.style.display = 'none'; return; } 665 665 propsSection.style.display = ''; 666 666 propsDimensions.style.display = ''; ··· 676 676 } 677 677 stylePanel.style.display = ''; 678 678 // Show first selected shape's styles 679 - const shape = wb.shapes.get([...selectedShapeIds][0]); 679 + const shape = wb.shapes.get([...selectedShapeIds][0]!); 680 680 if (!shape) return; 681 681 styleFill.value = shape.style?.fill || '#ffffff'; 682 682 styleStroke.value = shape.style?.stroke || '#000000'; ··· 882 882 return; 883 883 } 884 884 const pt = screenToCanvas(e.clientX, e.clientY); 885 - const selShape = wb.shapes.get([...selectedShapeIds][0]); 885 + const selShape = wb.shapes.get([...selectedShapeIds][0]!); 886 886 if (selShape) { 887 887 // Check rotation handle 888 888 const rotX = selShape.x + selShape.width / 2; ··· 932 932 if (activeTool === 'select') { 933 933 // Check rotation handle first (single selection) 934 934 if (selectedShapeIds.size === 1) { 935 - const selId = [...selectedShapeIds][0]; 935 + const selId = [...selectedShapeIds][0]!; 936 936 const selShape = wb.shapes.get(selId); 937 937 if (selShape) { 938 938 const rotX = selShape.x + selShape.width / 2; ··· 1335 1335 // Pinch-to-zoom 1336 1336 e.preventDefault(); 1337 1337 touchPanning = false; 1338 - const dx = e.touches[1].clientX - e.touches[0].clientX; 1339 - const dy = e.touches[1].clientY - e.touches[0].clientY; 1338 + const dx = e.touches[1]!.clientX - e.touches[0]!.clientX; 1339 + const dy = e.touches[1]!.clientY - e.touches[0]!.clientY; 1340 1340 lastTouchDist = Math.sqrt(dx * dx + dy * dy); 1341 1341 lastTouchCenter = { 1342 - x: (e.touches[0].clientX + e.touches[1].clientX) / 2, 1343 - y: (e.touches[0].clientY + e.touches[1].clientY) / 2, 1342 + x: (e.touches[0]!.clientX + e.touches[1]!.clientX) / 2, 1343 + y: (e.touches[0]!.clientY + e.touches[1]!.clientY) / 2, 1344 1344 }; 1345 1345 } else if (e.touches.length === 1 && (activeTool === 'hand' || activeTool === 'select')) { 1346 1346 // Single-finger pan (when hand tool active, or on empty canvas area with select) 1347 - const touch = e.touches[0]; 1347 + const touch = e.touches[0]!; 1348 1348 const pt = screenToCanvas(touch.clientX, touch.clientY); 1349 1349 const hitShape = shapeAtPoint(wb, pt.x, pt.y); 1350 1350 if (!hitShape || activeTool === 'hand') { ··· 1361 1361 // Pinch-to-zoom 1362 1362 e.preventDefault(); 1363 1363 touchPanning = false; 1364 - const dx = e.touches[1].clientX - e.touches[0].clientX; 1365 - const dy = e.touches[1].clientY - e.touches[0].clientY; 1364 + const dx = e.touches[1]!.clientX - e.touches[0]!.clientX; 1365 + const dy = e.touches[1]!.clientY - e.touches[0]!.clientY; 1366 1366 const dist = Math.sqrt(dx * dx + dy * dy); 1367 1367 const scale = dist / lastTouchDist; 1368 1368 wb = setZoom(wb, wb.zoom * scale); 1369 1369 lastTouchDist = dist; 1370 1370 1371 1371 const center = { 1372 - x: (e.touches[0].clientX + e.touches[1].clientX) / 2, 1373 - y: (e.touches[0].clientY + e.touches[1].clientY) / 2, 1372 + x: (e.touches[0]!.clientX + e.touches[1]!.clientX) / 2, 1373 + y: (e.touches[0]!.clientY + e.touches[1]!.clientY) / 2, 1374 1374 }; 1375 1375 wb = { ...wb, panX: wb.panX + (center.x - lastTouchCenter.x), panY: wb.panY + (center.y - lastTouchCenter.y) }; 1376 1376 lastTouchCenter = center; ··· 1378 1378 } else if (e.touches.length === 1 && touchPanning) { 1379 1379 // Single-finger pan 1380 1380 e.preventDefault(); 1381 - const touch = e.touches[0]; 1381 + const touch = e.touches[0]!; 1382 1382 const dx = touch.clientX - touchPanStart.x; 1383 1383 const dy = touch.clientY - touchPanStart.y; 1384 1384 wb = { ...wb, panX: touchPanWbStart.x + dx, panY: touchPanWbStart.y + dy }; ··· 1475 1475 }); 1476 1476 $('btn-ungroup')?.addEventListener('click', () => { 1477 1477 if (selectedShapeIds.size === 0) return; 1478 - const shape = wb.shapes.get([...selectedShapeIds][0]); 1478 + const shape = wb.shapes.get([...selectedShapeIds][0]!); 1479 1479 if (!shape?.groupId) return; 1480 1480 pushHistory(); 1481 1481 wb = ungroupShapes(wb, shape.groupId); ··· 1551 1551 propLabel.addEventListener('change', () => { 1552 1552 if (selectedShapeIds.size === 1) { 1553 1553 pushHistory(); 1554 - const id = [...selectedShapeIds][0]; 1554 + const id = [...selectedShapeIds][0]!; 1555 1555 wb = setShapeLabel(wb, id, propLabel.value); syncToYjs(); render(); 1556 1556 } 1557 1557 }); 1558 1558 propWidth.addEventListener('change', () => { 1559 1559 if (selectedShapeIds.size === 1) { 1560 1560 pushHistory(); 1561 - const id = [...selectedShapeIds][0]; 1561 + const id = [...selectedShapeIds][0]!; 1562 1562 wb = resizeShape(wb, id, Number(propWidth.value), Number(propHeight.value)); syncToYjs(); render(); 1563 1563 } 1564 1564 }); 1565 1565 propHeight.addEventListener('change', () => { 1566 1566 if (selectedShapeIds.size === 1) { 1567 1567 pushHistory(); 1568 - const id = [...selectedShapeIds][0]; 1568 + const id = [...selectedShapeIds][0]!; 1569 1569 wb = resizeShape(wb, id, Number(propWidth.value), Number(propHeight.value)); syncToYjs(); render(); 1570 1570 } 1571 1571 }); ··· 1666 1666 if (selectedShapeIds.size >= 2) { 1667 1667 items.push({ label: 'Group', shortcut: '\u2318G', action: () => { pushHistory(); const r = groupShapes(wb, [...selectedShapeIds]); wb = r.state; syncToYjs(); render(); } }); 1668 1668 } 1669 - const firstShape = wb.shapes.get([...selectedShapeIds][0]); 1669 + const firstShape = wb.shapes.get([...selectedShapeIds][0]!); 1670 1670 if (firstShape?.groupId) { 1671 1671 items.push({ label: 'Ungroup', shortcut: '\u21e7\u2318G', action: () => { pushHistory(); wb = ungroupShapes(wb, firstShape.groupId!); syncToYjs(); render(); } }); 1672 1672 } ··· 1876 1876 if (mod && e.key === 'g' && e.shiftKey) { 1877 1877 e.preventDefault(); 1878 1878 if (selectedShapeIds.size > 0) { 1879 - const shape = wb.shapes.get([...selectedShapeIds][0]); 1879 + const shape = wb.shapes.get([...selectedShapeIds][0]!); 1880 1880 if (shape?.groupId) { 1881 1881 pushHistory(); 1882 1882 wb = ungroupShapes(wb, shape.groupId); ··· 1972 1972 /** Build text summary of current diagram for AI context */ 1973 1973 function getDiagramContextText(): string { 1974 1974 const lines: string[] = []; 1975 - for (const [, shape] of state.shapes) { 1975 + for (const [, shape] of wb.shapes) { 1976 1976 const label = shape.label || '(unlabeled)'; 1977 1977 lines.push(`${shape.kind} "${label}" at (${Math.round(shape.x)},${Math.round(shape.y)}) size ${Math.round(shape.width)}x${Math.round(shape.height)}`); 1978 1978 } 1979 - for (const [, arrow] of state.arrows) { 1979 + for (const [, arrow] of wb.arrows) { 1980 1980 const fromDesc = 'shapeId' in arrow.from 1981 - ? `"${state.shapes.get(arrow.from.shapeId)?.label || 'unknown'}"` 1981 + ? `"${wb.shapes.get(arrow.from.shapeId)?.label || 'unknown'}"` 1982 1982 : `(${arrow.from.x},${arrow.from.y})`; 1983 1983 const toDesc = 'shapeId' in arrow.to 1984 - ? `"${state.shapes.get(arrow.to.shapeId)?.label || 'unknown'}"` 1984 + ? `"${wb.shapes.get(arrow.to.shapeId)?.label || 'unknown'}"` 1985 1985 : `(${arrow.to.x},${arrow.to.y})`; 1986 1986 lines.push(`arrow from ${fromDesc} to ${toDesc}`); 1987 1987 } ··· 2017 2017 2018 2018 // Build selection context from selected shapes 2019 2019 let selectionContext: string | undefined; 2020 - if (selectedIds.size > 0) { 2020 + if (selectedShapeIds.size > 0) { 2021 2021 const selLines: string[] = []; 2022 - for (const id of selectedIds) { 2023 - const shape = state.shapes.get(id); 2022 + for (const id of selectedShapeIds) { 2023 + const shape = wb.shapes.get(id); 2024 2024 if (shape) selLines.push(`${shape.kind} "${shape.label || '(unlabeled)'}"`); 2025 2025 } 2026 2026 selectionContext = `Selected shapes: ${selLines.join(', ')}`; ··· 2033 2033 }); 2034 2034 2035 2035 const diagramDeps = { 2036 - getState: () => state, 2037 - setState: (s: typeof state) => { state = s; syncToYjs(); }, 2036 + getState: () => wb, 2037 + setState: (s: typeof wb) => { wb = s; syncToYjs(); }, 2038 2038 render, 2039 2039 pushHistory, 2040 2040 }; ··· 2136 2136 if (doc.name_encrypted && cryptoKey) { 2137 2137 const bytes = Uint8Array.from(atob(doc.name_encrypted), c => c.charCodeAt(0)); 2138 2138 const { decrypt } = await import('../lib/crypto.js'); 2139 - const plain = await decrypt(bytes.buffer, cryptoKey); 2139 + const plain = await decrypt(new Uint8Array(bytes.buffer), cryptoKey); 2140 2140 diagramTitle.value = new TextDecoder().decode(plain); 2141 2141 } 2142 2142 }
+7 -7
src/sheets/main.ts
··· 1 - // @ts-nocheck — DOM entry point; full strict typing planned for follow-up migration 1 + // @ts-nocheck — 6k-line monolith needs decomposition before strict types (see #409) 2 2 /** 3 3 * Tools Sheets — E2EE collaborative spreadsheet. 4 4 * ··· 380 380 // --- DOM refs --- 381 381 const grid = document.getElementById('sheet-grid'); 382 382 const cellAddressInput = document.getElementById('cell-address'); 383 - const formulaInput = document.getElementById('formula-input'); 383 + const formulaInput = document.getElementById('formula-input') as HTMLInputElement; 384 384 const sheetContainer = document.getElementById('sheet-container'); 385 385 const sheetTabsContainer = document.getElementById('sheet-tabs'); 386 386 ··· 1735 1735 } 1736 1736 else if (key === 'PageDown' || key === 'PageUp') { 1737 1737 e.preventDefault(); 1738 - const pageRows = Math.max(1, Math.floor((sheetContainer?.clientHeight || 600) / bodyRowHeight) - 2); 1738 + const pageRows = Math.max(1, Math.floor((sheetContainer?.clientHeight || 600) / 26) - 2); 1739 1739 const dir = key === 'PageDown' ? pageRows : -pageRows; 1740 1740 if (e.shiftKey) { 1741 1741 extendSelection(0, dir); ··· 1942 1942 // Cell not in DOM (outside virtual range) — scroll to estimated position 1943 1943 if (!sheetContainer) return; 1944 1944 let targetTop = 0; 1945 - for (let r = 1; r < row; r++) targetTop += rh(r); 1945 + for (let r = 1; r < row; r++) targetTop += getRowHeight(r); 1946 1946 let targetLeft = ROW_HEADER_WIDTH; 1947 1947 for (let c = 1; c < col; c++) targetLeft += getColWidth(c); 1948 1948 sheetContainer.scrollTop = Math.max(0, targetTop - sheetContainer.clientHeight / 3); ··· 4332 4332 if (!file) return; 4333 4333 imageUploadInput.value = ''; 4334 4334 4335 - const targetCell = cellId(selCol, selRow); 4335 + const targetCell = cellId(selectedCell.col, selectedCell.row); 4336 4336 try { 4337 4337 const buf = await readFileAsBuffer(file); 4338 4338 const docId = window.location.pathname.split('/').pop() || ''; ··· 4348 4348 // Store image ref in cell data so it persists via Yjs 4349 4349 const existing = getCellData(targetCell) || {}; 4350 4350 setCellData(targetCell, { ...existing, img: result.id }); 4351 - renderViewport(); 4351 + renderGrid(); 4352 4352 }; 4353 4353 img.src = url; 4354 4354 } catch (err) { ··· 5162 5162 ydoc.transact(() => { if (validations.has(cellId)) validations.delete(cellId); }); 5163 5163 }); 5164 5164 } else { 5165 - const rule = { type, value: value1Input.value }; 5165 + const rule: { type: string; value: string; value2?: string } = { type, value: value1Input.value }; 5166 5166 if (type === 'numberBetween' || type === 'textLength') rule.value2 = value2Input.value; 5167 5167 applyToSelectedCells((cellId) => { 5168 5168 ydoc.transact(() => { validations.set(cellId, JSON.stringify(rule)); });
+6 -7
src/slides/main.ts
··· 1 - // @ts-nocheck — DOM entry point; strict typing planned for follow-up 2 1 /** 3 2 * Tools Slides — E2EE collaborative presentations. 4 3 * Backed by Yjs for real-time collaboration. ··· 233 232 const slide = currentSlide(deck); 234 233 const elIdx = slide.elements.findIndex(e => e.id === el.id); 235 234 if (elIdx >= 0) { 236 - slide.elements[elIdx] = { ...slide.elements[elIdx], content: (textEl as HTMLElement).textContent || '' }; 235 + slide.elements[elIdx] = { ...slide.elements[elIdx]!, content: (textEl as HTMLElement).textContent || '' }; 237 236 syncDeckToYjs(); 238 237 } 239 238 }); ··· 266 265 notesInput.value = slide.notes || ''; 267 266 // Update dropdown selections 268 267 if (themedDeck.layouts[deck.currentSlide]) { 269 - layoutSelect.value = themedDeck.layouts[deck.currentSlide]; 268 + layoutSelect.value = themedDeck.layouts[deck.currentSlide]!; 270 269 } 271 270 themeSelect.value = themedDeck.themeId; 272 271 presenter = { ...presenter, totalSlides: slideCount(deck) }; ··· 339 338 }); 340 339 341 340 $('btn-add-shape').addEventListener('click', () => { 342 - deck = addElement(deck, 'shape', 200, 150, 150, 150, '', { fill: getTheme(themedDeck.themeId)?.palette.primary }); 341 + deck = addElement(deck, 'shape', 200, 150, 150, 150, '', { fill: getTheme(themedDeck.themeId)?.palette.primary ?? '' }); 343 342 syncDeckToYjs(); 344 343 renderCanvas(); 345 344 }); ··· 413 412 // Drag handling (touch) 414 413 slideCanvas.addEventListener('touchstart', (e) => { 415 414 if (e.touches.length !== 1) return; 416 - const touch = e.touches[0]; 415 + const touch = e.touches[0]!; 417 416 const target = document.elementFromPoint(touch.clientX, touch.clientY)?.closest('[data-element-id]') as HTMLElement | null; 418 417 if (!target) return; 419 418 const elId = target.dataset.elementId!; ··· 431 430 432 431 document.addEventListener('touchmove', (e) => { 433 432 if (!isDragging || !selectedElementId) return; 434 - const touch = e.touches[0]; 433 + const touch = e.touches[0]!; 435 434 const dx = touch.clientX - dragStartX; 436 435 const dy = touch.clientY - dragStartY; 437 436 deck = moveElement(deck, selectedElementId, dragElStartX + dx, dragElStartY + dy); ··· 619 618 if (doc.name_encrypted && cryptoKey) { 620 619 const bytes = Uint8Array.from(atob(doc.name_encrypted), c => c.charCodeAt(0)); 621 620 const { decrypt } = await import('../lib/crypto.js'); 622 - const plain = await decrypt(bytes.buffer, cryptoKey); 621 + const plain = await decrypt(new Uint8Array(bytes.buffer), cryptoKey); 623 622 deckTitle.value = new TextDecoder().decode(plain); 624 623 } 625 624 }