refactor: Major hyperscript refactoring and JS elimination

Inline Hyperscript Refactoring:
- Body tag keyboard handlers: 20→8 lines (using helper functions)
- Zoom control handlers: 85→35 lines (using zoom._hs)
- PDF modal card selection: 90→6 lines (3 identical blocks eliminated)

New Hyperscript Files:
- zoom._hs: handleZoomInput, handleZoomReset, initZoomControl
- pdf-modal._hs: selectPdfCard, handlePdfCardKey

JavaScript Elimination (232 lines removed):
- cv-functions.js: REMOVED - hyperscript defs are globally available
- scroll-at-bottom-handler.js: REMOVED - duplicate of handleScroll()
- footer-buttons-interaction.js: REMOVED - moved to hyperscript

Added Tests:
- 32-hyperscript-multi-src.test.mjs: Verifies multi-file loading
- 33-keyboard-shortcuts-refactored.test.mjs: Keyboard shortcuts
- 34-hyperscript-refactor-comprehensive.test.mjs: Full test suite

Key Findings:
- No hyperscript multi-file bug in 0.9.14
- Hyperscript def statements are globally accessible
- Previous refactoring failures were syntax errors, not library bugs
This commit is contained in:
juanatsap
2025-11-30 05:58:44 +00:00
parent 4a02c0a328
commit ba44b435e7
12 changed files with 841 additions and 266 deletions
+37 -35
View File
@@ -1,41 +1,43 @@
-- File: keyboard._hs
-- Purpose: Keyboard shortcut handlers for CV application
-- Last Updated: 2025-11-20
-- Last Updated: 2025-11-30
-- Main keyboard event dispatcher
-- Handles all keyboard shortcuts: '?', 'L', 'I', 'V'
-- NOTE: This file is kept as reference/documentation only.
-- The actual keyboard handler is implemented inline in templates/index.html (body tag)
-- because hyperscript event handlers need direct access to the event context.
def handleGlobalKeydown(event)
set tagName to event.target.tagName
set isInputField to (tagName is 'INPUT' or tagName is 'TEXTAREA')
-- Show shortcuts modal with '?'
if event.key is '?' and not event.ctrlKey and not event.metaKey and not event.altKey and not isInputField
halt the event
set modal to #shortcuts-modal
if modal then call modal.showModal() end
-- ==============================================================================
-- TOGGLE SHORTCUT HELPER
-- ==============================================================================
-- Helper function to toggle a checkbox by ID (tries primary ID, then menu ID)
-- Called from inline keyboard handler in body tag
def handleToggleShortcut(toggleId, menuToggleId)
set toggle to document.getElementById(toggleId)
if toggle is null
set toggle to document.getElementById(menuToggleId)
end
-- Toggle CV length with 'L'
if (event.key is 'l' or event.key is 'L') and not event.ctrlKey and not event.metaKey and not event.altKey and not isInputField
halt the event
set lengthToggle to (#lengthToggle or #lengthToggleMenu)
if lengthToggle then set lengthToggle's checked to (not lengthToggle's checked) then send change to lengthToggle end
end
-- Toggle icons with 'I'
if (event.key is 'i' or event.key is 'I') and not event.ctrlKey and not event.metaKey and not event.altKey and not isInputField
halt the event
set iconToggle to (#iconToggle or #iconToggleMenu)
if iconToggle then set iconToggle's checked to (not iconToggle's checked) then send change to iconToggle end
end
-- Toggle theme with 'V'
if (event.key is 'v' or event.key is 'V') and not event.ctrlKey and not event.metaKey and not event.altKey and not isInputField
halt the event
set themeToggle to (#themeToggle or #themeToggleMenu)
if themeToggle then set themeToggle's checked to (not themeToggle's checked) then send change to themeToggle end
if toggle is not null
set toggle.checked to (not toggle.checked)
send change to toggle
end
end
-- ==============================================================================
-- MODAL SHORTCUT HELPER
-- ==============================================================================
-- Helper function to open a modal dialog by ID
def openModalShortcut(modalId)
set modal to document.getElementById(modalId)
if modal is not null
call modal.showModal()
end
end
-- ==============================================================================
-- REFERENCE: Full keyboard handler logic
-- ==============================================================================
-- NOTE: The actual keydown handler MUST stay inline in body tag because
-- hyperscript event handlers need direct access to the event context.
-- The inline handler uses the helper functions above.
--
-- Keyboard shortcuts:
-- '?' - Open shortcuts modal
-- 'L' - Toggle CV length (short/long)
-- 'I' - Toggle icons visibility
-- 'V' - Toggle visual theme (default/clean)
+45
View File
@@ -0,0 +1,45 @@
-- File: pdf-modal._hs
-- Purpose: PDF modal helper functions
-- Last Updated: 2025-11-30
-- ==============================================================================
-- PDF CARD SELECTION HANDLER
-- ==============================================================================
-- Handles selection of PDF format cards in the modal
-- Called from each card's click event
def selectPdfCard(card)
-- Get the modal container
set modal to document.getElementById('pdf-modal')
if modal is null then exit end
-- Remove selected from all cards
set cards to modal.querySelectorAll('.pdf-option-card')
for c in cards
remove .selected from c
set c's @aria-checked to 'false'
end
-- Add selected to this card
add .selected to card
set card's @aria-checked to 'true'
-- Enable download button
set downloadBtn to modal.querySelector('.pdf-download-btn')
if downloadBtn is not null
remove @disabled from downloadBtn
end
-- Store selected format for download
set window.selectedPdfFormat to card's @data-cv-format
end
-- ==============================================================================
-- PDF CARD KEYDOWN HANDLER
-- ==============================================================================
-- Handles keyboard navigation for PDF cards (Enter/Space to select)
def handlePdfCardKey(card, evt)
if evt.key is 'Enter' or evt.key is ' '
call evt.preventDefault()
call selectPdfCard(card)
end
end
+15
View File
@@ -134,6 +134,21 @@ def handleScroll()
set :lastScroll to currentScroll
end
-- ==============================================================================
-- FOOTER HOVER INTERACTION
-- ==============================================================================
-- Adds/removes footer-hovered class to fixed buttons when hovering footer
def setFooterHover(show)
set buttons to document.querySelectorAll('.download-btn, .print-friendly-btn, .shortcuts-btn, .info-button, .back-to-top, .color-theme-switcher')
for btn in buttons
if show
add .footer-hovered to btn
else
remove .footer-hovered from btn
end
end
end
-- ==============================================================================
-- KEYBOARD SHORTCUTS
-- ==============================================================================
+113
View File
@@ -0,0 +1,113 @@
-- File: zoom._hs
-- Purpose: Zoom control helper functions
-- Last Updated: 2025-11-30
-- ==============================================================================
-- ZOOM SLIDER INPUT HANDLER
-- ==============================================================================
-- Called from zoom-slider input event to update zoom level
def handleZoomInput(slider)
set zoomValue to slider.value as a Number
set zoomLevel to zoomValue / 100
-- Update display
set valueEl to document.getElementById('zoom-value-current')
if valueEl is not null
put zoomValue into valueEl
end
set slider's @aria-valuenow to zoomValue
set slider's @aria-valuetext to `${zoomValue}%`
-- Toggle reset button class
set resetBtn to document.getElementById('zoom-reset')
if resetBtn is not null
if zoomValue is not 100
add .zoom-not-default to resetBtn
else
remove .zoom-not-default from resetBtn
end
end
-- Apply zoom to wrapper
set wrapper to document.getElementById('zoom-wrapper')
if wrapper is not null
set wrapper's *zoom to zoomLevel
-- Handle width for zoom > 100%
if zoomLevel > 1
set wrapper's *width to 'auto'
set wrapper's *minWidth to '100%'
set wrapper's *maxWidth to 'none'
else
set wrapper's *width to ''
set wrapper's *minWidth to ''
set wrapper's *maxWidth to ''
end
end
-- Save to localStorage
set localStorage['cv-zoom'] to zoomValue
end
-- ==============================================================================
-- ZOOM RESET HANDLER
-- ==============================================================================
-- Called from reset button to reset zoom to 100%
def handleZoomReset()
set slider to document.getElementById('zoom-slider')
if slider is not null
set slider.value to 100
call handleZoomInput(slider)
call slider.focus()
end
end
-- ==============================================================================
-- ZOOM CONTROL VISIBILITY
-- ==============================================================================
-- Initialize zoom control visibility on load
def initZoomControl(control)
-- Skip on mobile
if window.innerWidth <= 768
exit
end
-- Load saved zoom level
set savedZoom to localStorage.getItem('cv-zoom')
set slider to document.getElementById('zoom-slider')
if savedZoom and slider
set slider.value to savedZoom
call handleZoomInput(slider)
end
-- Check visibility preference
set isVisible to localStorage.getItem('cv-zoom-visible')
if isVisible is 'true'
remove .zoom-hidden from control
set menuBtn to document.getElementById('show-zoom-menu-btn')
if menuBtn is not null
add .zoom-hidden to menuBtn
end
end
-- Load saved position
set savedPos to localStorage.getItem('cv-zoom-position')
if savedPos
set pos to JSON.parse(savedPos)
set control's *bottom to pos.bottom
set control's *left to pos.left
set control's *transform to 'none'
end
end
-- ==============================================================================
-- ZOOM CLOSE HANDLER
-- ==============================================================================
-- Called when clicking the close button on zoom control
def handleZoomClose(control)
add .zoom-hidden to control
set localStorage['cv-zoom-visible'] to 'false'
set menuBtn to document.getElementById('show-zoom-menu-btn')
if menuBtn is not null
remove .zoom-hidden from menuBtn
end
end