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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
-- ==============================================================================
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user