Skip to content

Commit 3acf3a9

Browse files
authored
Merge pull request #108 from shinokada/fix/footer-nav
* **New Features** * Added global navigation shortcuts: `g` (Top) and `G` (End). * New "Import from URL" option. * Dedicated help overlay for the "I Feel Lucky" screen; press `?` to toggle it. * **Documentation** * Updated keyboard shortcut listings and in-app help text to include new keys. * **Refactor** * Help text now appears in a bottom-aligned help bar across multiple list and selection views for consistent layout.
2 parents 01e7751 + 8aab598 commit 3acf3a9

File tree

7 files changed

+111
-51
lines changed

7 files changed

+111
-51
lines changed

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ You can edit this file directly or use the Settings menu.
660660
| Key | Action |
661661
| ----------- | ------------- |
662662
| `↑↓` / `jk` | Navigate |
663+
| `g` / `G` | Top / End |
663664
| `Enter` | Select / Play |
664665
| `Esc` | Back / Stop |
665666
| `0` | Main Menu |

v3/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ You can edit this file directly or use the Settings menu.
571571
| Key | Action |
572572
| ----------- | ------------- |
573573
| `↑↓` / `jk` | Navigate |
574+
| `g` / `G` | Top / End |
574575
| `Enter` | Select / Play |
575576
| `Esc` | Back / Stop |
576577
| `0` | Main Menu |

v3/internal/ui/components/help.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,56 @@ func CreateTagsPlayingHelp() []HelpSection {
287287
}
288288
}
289289

290+
// CreateLuckyHelp creates help sections for the I Feel Lucky screen
291+
func CreateLuckyHelp() []HelpSection {
292+
return []HelpSection{
293+
{
294+
Title: "Input",
295+
Items: []HelpItem{
296+
{"Tab", "Switch focus (keyword ↔ history)"},
297+
{"Enter", "Search / select history"},
298+
{"ctrl+t", "Toggle shuffle mode"},
299+
{"↑↓/jk", "Navigate history (nav mode)"},
300+
{"1-N", "Quick pick history (nav mode)"},
301+
{"Esc", "Back to main menu"},
302+
},
303+
},
304+
{
305+
Title: "Playback",
306+
Items: []HelpItem{
307+
{"Space", "Pause / Resume"},
308+
{"*", "Volume up (+5%)"},
309+
{"/", "Volume down (-5%)"},
310+
{"m", "Toggle mute"},
311+
{"Esc", "Stop & back to input"},
312+
{"0", "Main menu"},
313+
},
314+
},
315+
{
316+
Title: "Actions",
317+
Items: []HelpItem{
318+
{"f", "Save to Quick Favorites"},
319+
{"s", "Save to another list"},
320+
{"v", "Vote for station"},
321+
{"r", "Rate station (1-5)"},
322+
{"t", "Add tag"},
323+
{"T", "Manage tags"},
324+
{"b", "Block station"},
325+
{"u", "Undo block (5s)"},
326+
},
327+
},
328+
{
329+
Title: "Shuffle",
330+
Items: []HelpItem{
331+
{"n", "Next station"},
332+
{"[", "Previous station"},
333+
{"p", "Pause / resume timer"},
334+
{"h", "Stop shuffle, keep playing"},
335+
},
336+
},
337+
}
338+
}
339+
290340
func CreateAppearanceHelp() []HelpSection {
291341
return []HelpSection{
292342
{

v3/internal/ui/gist.go

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -534,41 +534,42 @@ func (m GistModel) View() string {
534534
return ""
535535
}
536536

537+
h := m.height
537538
switch m.state {
538539
case gistStateMenu:
539-
return RenderPage(PageLayout{
540+
return RenderPageWithBottomHelp(PageLayout{
540541
Title: "Gist Management",
541542
Subtitle: "Select an Option",
542543
Content: m.menuList.View() + "\n" + m.renderMessage(),
543544
Help: "↑↓/jk: Navigate • Enter: Select • 1-7: Quick select • Esc: Back • Ctrl+C: Quit",
544-
})
545+
}, h)
545546
case gistStateCreateVisibility:
546-
return RenderPage(PageLayout{
547+
return RenderPageWithBottomHelp(PageLayout{
547548
Title: "Create Gist",
548549
Subtitle: "Choose Visibility",
549550
Content: m.visibilityMenu.View() + "\n" + m.renderMessage(),
550551
Help: "↑↓/jk: Navigate • Enter: Select • 1-2: Quick select • Esc: Back",
551-
})
552+
}, h)
552553
case gistStateCreateName:
553554
visibility := "Secret"
554555
if m.createPublic {
555556
visibility = "Public"
556557
}
557-
return RenderPage(PageLayout{
558+
return RenderPageWithBottomHelp(PageLayout{
558559
Title: "Create Gist",
559560
Subtitle: fmt.Sprintf("Enter Gist Name (%s)", visibility),
560561
Content: fmt.Sprintf("Enter a name/description for your gist:\n\n%s", m.textInput.View()) + "\n\n" + m.renderMessage(),
561562
Help: "Enter: Create • Esc: Back",
562-
})
563+
}, h)
563564
case gistStateCreate:
564-
return RenderPage(PageLayout{
565+
return RenderPageWithBottomHelp(PageLayout{
565566
Title: "Create Gist",
566567
Subtitle: "Uploading favorites to GitHub",
567568
Content: "Creating gist...\n\nPlease wait while your favorites are uploaded.\n\n" + m.renderMessage(),
568569
Help: "Please wait...",
569-
})
570+
}, h)
570571
case gistStateImportURL:
571-
return RenderPage(PageLayout{
572+
return RenderPageWithBottomHelp(PageLayout{
572573
Title: "Import from URL",
573574
Subtitle: "Paste a public gist URL or ID",
574575
Content: fmt.Sprintf("Enter a gist URL or ID to import favorites:\n\n"+
@@ -577,7 +578,7 @@ func (m GistModel) View() string {
577578
" • Raw gist ID (e.g., abc123def456...)\n\n"+
578579
"%s", m.textInput.View()) + "\n\n" + m.renderMessage(),
579580
Help: "Enter: Import • Esc: Back",
580-
})
581+
}, h)
581582
case gistStateList, gistStateUpdate, gistStateDelete, gistStateRecover:
582583
action := "My Gists"
583584
switch m.state {
@@ -594,26 +595,26 @@ func (m GistModel) View() string {
594595
content = "No gists available.\n\nCreate a gist first from the main menu."
595596
}
596597

597-
return RenderPage(PageLayout{
598+
return RenderPageWithBottomHelp(PageLayout{
598599
Title: action,
599600
Subtitle: "Select a Gist",
600601
Content: content + "\n" + m.renderMessage(),
601602
Help: "↑↓/jk: Navigate • Enter: Select • Esc: Back",
602-
})
603+
}, h)
603604
case gistStateTokenMenu:
604-
return RenderPage(PageLayout{
605+
return RenderPageWithBottomHelp(PageLayout{
605606
Title: "Token Management",
606607
Subtitle: "Manage your GitHub Token",
607608
Content: m.tokenMenuList.View() + "\n" + m.renderMessage(),
608609
Help: "↑↓/jk: Navigate • Enter: Select • 1-4: Quick select • Esc: Back • Ctrl+C: Quit",
609-
})
610+
}, h)
610611
case gistStateTokenSetup:
611-
return RenderPage(PageLayout{
612+
return RenderPageWithBottomHelp(PageLayout{
612613
Title: "Setup Token",
613614
Subtitle: "Paste your GitHub Token",
614615
Content: fmt.Sprintf("Token will be hidden for security.\n\n%s", m.textInput.View()) + "\n\n" + m.renderMessage(),
615616
Help: "Enter: Save • Esc: Cancel",
616-
})
617+
}, h)
617618
case gistStateTokenView:
618619
masked := gist.GetMaskedToken(m.token)
619620
sourceInfo := ""
@@ -634,34 +635,34 @@ func (m GistModel) View() string {
634635
}
635636
}
636637
}
637-
return RenderPage(PageLayout{
638+
return RenderPageWithBottomHelp(PageLayout{
638639
Title: "Current Token",
639640
Subtitle: "View Token Status",
640641
Content: fmt.Sprintf("Token: %s%s", masked, sourceInfo) + "\n\n" + m.renderMessage(),
641642
Help: "Enter/Esc: Back",
642-
})
643+
}, h)
643644
case gistStateUpdateInput:
644-
return RenderPage(PageLayout{
645+
return RenderPageWithBottomHelp(PageLayout{
645646
Title: "Update Gist",
646647
Subtitle: "Enter new description",
647648
Content: fmt.Sprintf("Current: %s\n\nNew Description:\n%s", m.selectedGist.Description, m.textInput.View()) + "\n\n" + m.renderMessage(),
648649
Help: "Enter: Update • Esc: Cancel",
649-
})
650+
}, h)
650651
case gistStateDeleteConfirm:
651-
return RenderPage(PageLayout{
652+
return RenderPageWithBottomHelp(PageLayout{
652653
Title: "Delete Gist",
653654
Subtitle: "Confirm Deletion",
654655
Content: fmt.Sprintf("Are you sure you want to delete this gist?\n%s\n\nType 'yes' to confirm:\n%s", m.selectedGist.Description, m.textInput.View()) + "\n\n" + m.renderMessage(),
655656
Help: "Enter: Confirm • Esc: Cancel",
656-
})
657+
}, h)
657658
case gistStateTokenDelete:
658659
masked := gist.GetMaskedToken(m.token)
659-
return RenderPage(PageLayout{
660+
return RenderPageWithBottomHelp(PageLayout{
660661
Title: "Delete Token",
661662
Subtitle: "⚠️ WARNING",
662663
Content: fmt.Sprintf("This will delete your stored GitHub token!\n\nToken: %s\n\nYou won't be able to use Gist features until you set up a new token.\n\nType 'yes' to confirm deletion:\n%s", masked, m.textInput.View()) + "\n\n" + m.renderMessage(),
663664
Help: "Enter: Confirm • Esc: Cancel",
664-
})
665+
}, h)
665666
}
666667

667668
return ""

v3/internal/ui/lucky.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ const (
3636
luckyStateManageTags
3737
)
3838

39+
// Footer help text constants for the I Feel Lucky screen
40+
const (
41+
luckyHelpInputFooter = "Tab: Switch focus • Enter: Search • ctrl+t: Shuffle • Esc: Back • ?: Help"
42+
luckyHelpPlayingFooter = "Space: Pause • f: Fav • s: List • 0: Main Menu • ?: Help"
43+
luckyHelpShuffleFooter = "Space: Pause • n: Next • [: Prev • f: Fav • h: Stop shuffle • 0: Main Menu • ?: Help"
44+
)
45+
3946
// LuckyModel represents the I Feel Lucky screen
4047
type LuckyModel struct {
4148
state luckyState
@@ -162,7 +169,7 @@ func NewLuckyModel(apiClient *api.Client, favoritePath string, blocklistManager
162169
searchHistory: history,
163170
width: 80,
164171
height: 24,
165-
helpModel: components.NewHelpModel(components.CreatePlayingHelp()),
172+
helpModel: components.NewHelpModel(components.CreateLuckyHelp()),
166173
votedStations: votedStations,
167174
shuffleEnabled: false,
168175
shuffleConfig: shuffleConfig,
@@ -538,6 +545,13 @@ func (m LuckyModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
538545
func (m LuckyModel) updateInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
539546
key := msg.String()
540547

548+
// Handle '?' for help overlay
549+
if key == "?" {
550+
m.helpModel.SetSize(m.width, m.height)
551+
m.helpModel.Toggle()
552+
return m, nil
553+
}
554+
541555
// Handle escape - return to main menu
542556
if key == "esc" {
543557
m.numberBuffer = "" // Clear buffer
@@ -1291,15 +1305,7 @@ func (m LuckyModel) viewInput() string {
12911305
}
12921306
}
12931307

1294-
helpText := "Tab: Switch focus • Enter: Search • ctrl+t: Toggle shuffle"
1295-
if m.searchHistory != nil && len(m.searchHistory.LuckyQueries) > 0 {
1296-
maxItems := len(m.searchHistory.LuckyQueries)
1297-
if maxItems > m.searchHistory.MaxSize {
1298-
maxItems = m.searchHistory.MaxSize
1299-
}
1300-
helpText += fmt.Sprintf(" • 1-%d: Quick pick (nav mode)", maxItems)
1301-
}
1302-
helpText += " • ↑↓: History (nav mode) • Esc: Back"
1308+
helpText := luckyHelpInputFooter
13031309

13041310
return RenderPageWithBottomHelp(PageLayout{
13051311
Content: content.String(),
@@ -1405,7 +1411,7 @@ func (m LuckyModel) viewPlaying() string {
14051411
content.WriteString(msgStyle.Render(m.saveMessage))
14061412
}
14071413

1408-
helpText := "Space: Pause • f: Fav • s: List • v: Vote • b: Block • r: Rate • 0: Main Menu • ?: Help"
1414+
helpText := luckyHelpPlayingFooter
14091415
return RenderPageWithBottomHelp(PageLayout{
14101416
Title: "🎵 Now Playing",
14111417
Content: content.String(),
@@ -1942,7 +1948,7 @@ func (m LuckyModel) viewShufflePlaying() string {
19421948
}
19431949

19441950
title := fmt.Sprintf("🎵 Now Playing (🔀 Shuffle: %s)", m.lastSearchKeyword)
1945-
help := "Space: Pause • n: Next • [: Prev • f: Fav • b: Block • p: Pause timer • h: Stop shuffle • 0: Main Menu • ?: Help"
1951+
help := luckyHelpShuffleFooter
19461952

19471953
return RenderPageWithBottomHelp(PageLayout{
19481954
Title: title,

v3/internal/ui/most_played.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ func (m MostPlayedModel) handleListInput(msg tea.KeyMsg) (MostPlayedModel, tea.C
357357
return m, func() tea.Msg { return navigateMsg{screen: screenMainMenu} }
358358

359359
case "?":
360+
m.helpModel.SetSize(m.width, m.height)
360361
m.helpModel.Show()
361362
return m, nil
362363

@@ -694,11 +695,11 @@ func (m MostPlayedModel) viewList() string {
694695
}
695696
}
696697

697-
return RenderPage(PageLayout{
698+
return RenderPageWithBottomHelp(PageLayout{
698699
Title: "📊 Most Played Stations",
699700
Content: content.String(),
700-
Help: "↑↓/jk: Navigate • Enter: Play • s: Sort • f: Fav • ?: Help • Esc: Back",
701-
})
701+
Help: "↑↓/jk: Navigate • g/G: Top/End • Enter: Play • s: Sort • f: Fav • ?: Help • Esc: Back",
702+
}, m.height)
702703
}
703704

704705
func (m MostPlayedModel) viewPlaying() string {
@@ -800,11 +801,11 @@ func (m MostPlayedModel) viewSavePrompt() string {
800801
content.WriteString("[L] Choose from list\n")
801802
content.WriteString("[N] Cancel")
802803

803-
return RenderPage(PageLayout{
804+
return RenderPageWithBottomHelp(PageLayout{
804805
Title: "💾 Save Station",
805806
Content: content.String(),
806807
Help: "Y: My-favorites • L: Choose list • N: Cancel",
807-
})
808+
}, m.height)
808809
}
809810

810811
func (m MostPlayedModel) viewSelectList() string {
@@ -813,9 +814,9 @@ func (m MostPlayedModel) viewSelectList() string {
813814
content.WriteString("Select a list:\n\n")
814815
content.WriteString(m.listModel.View())
815816

816-
return RenderPage(PageLayout{
817+
return RenderPageWithBottomHelp(PageLayout{
817818
Title: "📁 Select List",
818819
Content: content.String(),
819820
Help: "↑↓: Navigate • Enter: Select • Esc: Cancel",
820-
})
821+
}, m.height)
821822
}

v3/internal/ui/play.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,13 +1245,13 @@ func (m PlayModel) viewListSelection() string {
12451245
content.WriteString(style.Render(m.saveMessage))
12461246
}
12471247

1248-
// Use the consistent page template
1249-
return RenderPage(PageLayout{
1248+
// Use the consistent page template with bottom-aligned help
1249+
return RenderPageWithBottomHelp(PageLayout{
12501250
Title: "Play from Favorites",
12511251
Subtitle: "Select a Favorite List",
12521252
Content: content.String(),
1253-
Help: "↑↓/jk: Navigate • Enter: Select • Esc: Back • Ctrl+C: Quit",
1254-
})
1253+
Help: "↑↓/jk: Navigate • g/G: Top/End • Enter: Select • Esc: Back • Ctrl+C: Quit",
1254+
}, m.height)
12551255
}
12561256

12571257
// viewPlaying renders the playback view
@@ -1424,12 +1424,12 @@ func (m PlayModel) viewStationSelection() string {
14241424
content.WriteString(style.Render(m.saveMessage))
14251425
}
14261426

1427-
// Use the consistent page template
1428-
return RenderPage(PageLayout{
1427+
// Use the consistent page template with bottom-aligned help
1428+
return RenderPageWithBottomHelp(PageLayout{
14291429
Title: "Play from Favorites",
14301430
Content: content.String(),
1431-
Help: "↑↓/jk: Navigate • /: Filter • Enter: Play • d: Delete • Esc: Back • 0: Main Menu • Ctrl+C: Quit",
1432-
})
1431+
Help: "↑↓/jk: Navigate • g/G: Top/End • /: Filter • Enter: Play • d: Delete • Esc: Back • 0: Main Menu • Ctrl+C: Quit",
1432+
}, m.height)
14331433
}
14341434

14351435
// noStationsView renders the view when a list is empty

0 commit comments

Comments
 (0)