@@ -393,71 +393,8 @@ <h3>Pro Code Editor</h3>
393393 < h2 class ="section-title "> What's New</ h2 >
394394 < p class ="section-sub "> Every release, every fix, every improvement.</ p >
395395
396- < div class ="changelog-timeline ">
397- < div class ="cl-entry latest ">
398- < div class ="cl-version ">
399- < span class ="v "> v2.2.0</ span >
400- < span class ="badge-new "> Latest</ span >
401- < span class ="date "> March 15, 2026</ span >
402- </ div >
403- < ul class ="cl-list ">
404- < li > < span class ="cl-icon cl-fix "> ●</ span > API Request Builder — params now correctly applied to requests</ li >
405- < li > < span class ="cl-icon cl-improve "> ◆</ span > Port Manager — redesigned kill buttons with outline style, hover reveal</ li >
406- < li > < span class ="cl-icon cl-improve "> ◆</ span > QR Code — shorter correction level picker (L/M/Q/H segments)</ li >
407- < li > < span class ="cl-icon cl-improve "> ◆</ span > All 33 tools use single clean ToolHeader — no more double headers</ li >
408- < li > < span class ="cl-icon cl-fix "> ●</ span > 300 tests, 0 failures across 25 test suites</ li >
409- </ ul >
410- </ div >
411-
412- < div class ="cl-entry ">
413- < div class ="cl-version ">
414- < span class ="v "> v2.1.0</ span >
415- < span class ="date "> March 15, 2026</ span >
416- </ div >
417- < ul class ="cl-list ">
418- < li > < span class ="cl-icon cl-new "> ✦</ span > QR Code Generator — 4 error correction levels, copy/save PNG</ li >
419- < li > < span class ="cl-icon cl-new "> ✦</ span > Image Base64 — encode images to base64/data URI, decode back</ li >
420- < li > < span class ="cl-icon cl-new "> ✦</ span > SQL Formatter — format & minify SQL, 3 indent styles</ li >
421- < li > < span class ="cl-icon cl-new "> ✦</ span > JSON Schema Validator — type, required, min/max, pattern, arrays</ li >
422- < li > < span class ="cl-icon cl-new "> ✦</ span > HTTP Status Codes — searchable reference, color-coded categories</ li >
423- < li > < span class ="cl-icon cl-new "> ✦</ span > Input syntax highlighting — live JSON/SQL coloring in editor</ li >
424- < li > < span class ="cl-icon cl-new "> ✦</ span > History panel — browse & restore past inputs per tool</ li >
425- < li > < span class ="cl-icon cl-new "> ✦</ span > URL handler — < code > dx://tool-name</ code > opens app to specific tool</ li >
426- < li > < span class ="cl-icon cl-new "> ✦</ span > Auto-update checker — checks GitHub Releases on launch</ li >
427- < li > < span class ="cl-icon cl-improve "> ◆</ span > Custom app icon — orange "DX" monogram on dark background</ li >
428- < li > < span class ="cl-icon cl-improve "> ◆</ span > Settings view with font size, indent style, theme toggle</ li >
429- < li > < span class ="cl-icon cl-improve "> ◆</ span > Onboarding — 3-screen first-launch walkthrough</ li >
430- < li > < span class ="cl-icon cl-improve "> ◆</ span > Command Palette — ⌘K overlay with fuzzy search</ li >
431- </ ul >
432- </ div >
433-
434- < div class ="cl-entry ">
435- < div class ="cl-version ">
436- < span class ="v "> v2.0.0</ span >
437- < span class ="date "> March 15, 2026</ span >
438- </ div >
439- < ul class ="cl-list ">
440- < li > < span class ="cl-icon cl-new "> ✦</ span > 23 developer tools in one native macOS app</ li >
441- < li > < span class ="cl-icon cl-new "> ✦</ span > Port Manager, Network Info, URL Encoder, Unix Permissions, Cron Parser</ li >
442- < li > < span class ="cl-icon cl-new "> ✦</ span > SSH Key Generator, Docker Manager, Git Stats, Timestamp Converter, Text Diff</ li >
443- < li > < span class ="cl-icon cl-new "> ✦</ span > API Request Builder, smart clipboard, menu bar, CLI companion</ li >
444- < li > < span class ="cl-icon cl-improve "> ◆</ span > Pro code editor, dark/light theme, favorites system</ li >
445- </ ul >
446- </ div >
447-
448- < div class ="cl-entry ">
449- < div class ="cl-version ">
450- < span class ="v "> v1.0.0</ span >
451- < span class ="date "> Initial Release</ span >
452- </ div >
453- < ul class ="cl-list ">
454- < li > < span class ="cl-icon cl-new "> ✦</ span > JSON Formatter, JSON → Go/Swift/TypeScript, JSON Diff</ li >
455- < li > < span class ="cl-icon cl-new "> ✦</ span > JWT Decoder, Base64, Hash Generator</ li >
456- < li > < span class ="cl-icon cl-new "> ✦</ span > UUID Generator, Color Converter, Epoch Converter</ li >
457- < li > < span class ="cl-icon cl-new "> ✦</ span > Password Generator, Env Manager</ li >
458- < li > < span class ="cl-icon cl-new "> ✦</ span > cURL → Code, Regex Tester, Markdown Preview, Lorem Generator</ li >
459- </ ul >
460- </ div >
396+ < div class ="changelog-timeline " id ="changelog-timeline ">
397+ < div class ="cl-entry "> < div class ="cl-version "> < span class ="v " style ="color:var(--text3) "> Loading releases…</ span > </ div > </ div >
461398 </ div >
462399</ section >
463400
@@ -568,29 +505,88 @@ <h2 class="section-title">Keyboard-First</h2>
568505 setTimeout ( ( ) => toast . classList . remove ( 'show' ) , 2000 ) ;
569506}
570507
571- // Fetch latest release info from GitHub API
572- async function fetchRelease ( ) {
508+ // Classify a changelog line as new/improve/fix
509+ function classifyLine ( line ) {
510+ const l = line . toLowerCase ( ) ;
511+ if ( l . startsWith ( '- feat' ) || l . includes ( 'new tool' ) || l . includes ( 'added' ) || l . match ( / ^ - \* \* \w .* \* \* — .* n e w / ) )
512+ return 'new' ;
513+ if ( l . startsWith ( '- fix' ) || l . includes ( 'bug' ) || l . includes ( 'fixed' ) || l . includes ( 'hotfix' ) )
514+ return 'fix' ;
515+ return 'improve' ;
516+ }
517+
518+ function lineIcon ( type ) {
519+ if ( type === 'new' ) return '<span class="cl-icon cl-new">✦</span>' ;
520+ if ( type === 'fix' ) return '<span class="cl-icon cl-fix">●</span>' ;
521+ return '<span class="cl-icon cl-improve">◆</span>' ;
522+ }
523+
524+ // Parse release body into clean bullet points
525+ function parseBody ( body ) {
526+ if ( ! body ) return [ ] ;
527+ return body . split ( '\n' )
528+ . map ( l => l . trim ( ) )
529+ . filter ( l => l . startsWith ( '- ' ) || l . startsWith ( '* ' ) )
530+ . map ( l => l . replace ( / ^ [ - * ] \s + / , '' ) . replace ( / \* \* / g, '' ) )
531+ . filter ( l => l . length > 0 && ! l . startsWith ( '#' ) && ! l . startsWith ( '```' ) ) ;
532+ }
533+
534+ // Format date
535+ function fmtDate ( dateStr ) {
536+ const d = new Date ( dateStr ) ;
537+ return d . toLocaleDateString ( 'en-US' , { month : 'long' , day : 'numeric' , year : 'numeric' } ) ;
538+ }
539+
540+ // Fetch all releases and render changelog + hero
541+ async function fetchReleases ( ) {
573542 try {
574- const res = await fetch ( 'https://api.github.com/repos/OpenStruct/dx-tools/releases/latest ' ) ;
543+ const res = await fetch ( 'https://api.github.com/repos/OpenStruct/dx-tools/releases?per_page=20 ' ) ;
575544 if ( ! res . ok ) return ;
576- const data = await res . json ( ) ;
545+ const releases = await res . json ( ) ;
546+ if ( ! releases . length ) return ;
577547
578- // Update version badge
548+ // Update hero version
549+ const latest = releases [ 0 ] ;
579550 const ver = document . getElementById ( 'hero-version' ) ;
580- if ( ver && data . tag_name ) ver . textContent = data . tag_name ;
551+ if ( ver ) ver . textContent = latest . tag_name ;
581552
582- // Update download link to DMG asset
583- const dmg = data . assets ?. find ( a => a . name . endsWith ( '.dmg' ) ) ;
553+ // Update download button
554+ const dmg = latest . assets ?. find ( a => a . name . endsWith ( '.dmg' ) ) ;
584555 if ( dmg ) {
585556 const btn = document . getElementById ( 'dl-btn-main' ) ;
586557 if ( btn ) btn . href = dmg . browser_download_url ;
587-
588558 const size = document . getElementById ( 'dl-size' ) ;
589559 if ( size ) size . textContent = ( dmg . size / 1024 / 1024 ) . toFixed ( 1 ) + ' MB' ;
590560 }
591- } catch ( e ) { /* silently fail, defaults work */ }
561+
562+ // Render changelog
563+ const timeline = document . getElementById ( 'changelog-timeline' ) ;
564+ if ( ! timeline ) return ;
565+
566+ timeline . innerHTML = releases . map ( ( rel , i ) => {
567+ const isLatest = i === 0 ;
568+ const lines = parseBody ( rel . body ) ;
569+ const bullets = lines . length > 0
570+ ? lines . map ( l => {
571+ const type = classifyLine ( '- ' + l ) ;
572+ return `<li>${ lineIcon ( type ) } ${ l } </li>` ;
573+ } ) . join ( '' )
574+ : `<li>${ lineIcon ( 'new' ) } ${ rel . name || rel . tag_name } </li>` ;
575+
576+ return `
577+ <div class="cl-entry${ isLatest ? ' latest' : '' } ">
578+ <div class="cl-version">
579+ <span class="v">${ rel . tag_name } </span>
580+ ${ isLatest ? '<span class="badge-new">Latest</span>' : '' }
581+ <span class="date">${ fmtDate ( rel . published_at || rel . created_at ) } </span>
582+ </div>
583+ <ul class="cl-list">${ bullets } </ul>
584+ </div>` ;
585+ } ) . join ( '' ) ;
586+
587+ } catch ( e ) { /* silently fail */ }
592588}
593- fetchRelease ( ) ;
589+ fetchReleases ( ) ;
594590</ script >
595591</ body >
596592</ html >
0 commit comments