Skip to content

Commit 83d59e0

Browse files
committed
feat: summary view more
1 parent 556cd98 commit 83d59e0

File tree

9 files changed

+123
-48
lines changed

9 files changed

+123
-48
lines changed

.gh-dash.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ defaults:
3333

3434
preview:
3535
open: true
36-
width: 70
36+
width: 84
3737
prsLimit: 20
3838
issuesLimit: 20
3939
repoPaths:

docs/data/schemas/keybindings/entry.yaml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ schematize:
2020
[sref:`command`]: keybindings.entry.command
2121
required:
2222
- key
23-
- command
2423
properties:
2524
key:
2625
title: Bound Key
@@ -51,4 +50,20 @@ properties:
5150
details: |
5251
Specifies the command bound to the [sref:`key`] for an entry.
5352
53+
[sref:`key`]: keybindings.entry.key
54+
builtin:
55+
title: Builtin Command
56+
description: One of gh-dash's builtin commands that will run when you press the key combination
57+
type: string
58+
schematize:
59+
weight: 2
60+
details: |
61+
Specifies the builtin command bound to the the [sref:`key`] for an entry.
62+
63+
For global actions, the available builtin commands are: `up`, `down`, `firstLine`, `lastLine`, `togglePreview`, `openGithub`, `refresh`, `refreshAll`, `pageDown`, `pageUp`, `nextSection`, `prevSection`, `search`, `copyurl`, `copyNumber`, `help`, `quit`.
64+
65+
For PRs, the available builtin commands are: `prevSidebarTab`, `nextSidebarTab`, `approve`, `assign`, `unassign`, `comment`, `diff`, `checkout`, `close`, `ready`, `reopen`, `merge`, `update`, `watchChecks`, `viewIssues`, `summaryViewMore`.
66+
67+
For Issues, the available builtin commands are: `assign`, `unassign`, `comment`, `close`, `reopen`, `viewPrs`.
68+
5469
[sref:`key`]: keybindings.entry.key

imposters/pr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"author": {
1010
"login": "lewis6991"
1111
},
12+
"authorAssociation": "OWNER",
1213
"updatedAt": "2025-01-10T11:42:47Z",
1314
"createdAt": "2025-01-09T11:42:47Z",
1415
"url": "https://github.com/neovim/neovim/pull/31944",

ui/components/issuesidebar/issuesidebar.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func (m Model) View() string {
166166
s.WriteString(m.inputBox.View())
167167
}
168168

169-
return s.String()
169+
return lipgloss.NewStyle().Padding(0, m.ctx.Styles.Sidebar.ContentPadding).Render(s.String())
170170
}
171171

172172
func (m *Model) renderFullNameAndNumber() string {

ui/components/prsidebar/prsidebar.go

Lines changed: 88 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ var (
2626
lineCleanupRegex = regexp.MustCompile(`((\n)+|^)([^\r\n]*\|[^\r\n]*(\n)?)+`)
2727
commentPrompt = "Leave a comment..."
2828
approvalPrompt = "Approve with comment..."
29+
foldBodyLen = 600
2930
)
3031

3132
type Model struct {
@@ -40,6 +41,7 @@ type Model struct {
4041
isApproving bool
4142
isAssigning bool
4243
isUnassigning bool
44+
summaryViewMore bool
4345

4446
inputBox inputbox.Model
4547
}
@@ -191,68 +193,83 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
191193
}
192194

193195
func (m Model) View() string {
194-
s := strings.Builder{}
195-
196-
s.WriteString(m.renderFullNameAndNumber())
197-
s.WriteString("\n")
198-
199-
s.WriteString(m.renderTitle())
200-
s.WriteString("\n\n")
201-
s.WriteString(m.renderBranches())
202-
s.WriteString("\n\n")
203-
s.WriteString(lipgloss.NewStyle().Width(m.width).
196+
header := strings.Builder{}
197+
198+
header.WriteString(m.renderFullNameAndNumber())
199+
header.WriteString("\n")
200+
201+
header.WriteString(m.renderTitle())
202+
header.WriteString("\n\n")
203+
header.WriteString(m.renderBranches())
204+
header.WriteString("\n\n")
205+
header.WriteString(m.renderAuthor())
206+
header.WriteString("\n\n")
207+
header.WriteString(lipgloss.NewStyle().Width(m.width).
204208
Border(lipgloss.NormalBorder(), false, false, true, false).
205209
BorderForeground(m.ctx.Theme.FaintBorder).
206210
Render(m.carousel.View()),
207211
)
208212

209-
s.WriteString("\n\n")
213+
header.WriteString("\n")
214+
215+
body := strings.Builder{}
210216

211217
switch m.carousel.SelectedItem() {
212218
case tabs[0]:
213219
labels := m.renderLabels()
214220
if labels != "" {
215-
s.WriteString(labels)
216-
s.WriteString("\n\n")
221+
body.WriteString(labels)
222+
body.WriteString("\n\n")
217223
}
218224

219-
s.WriteString(m.renderDescription())
220-
s.WriteString("\n\n")
221-
s.WriteString(m.ctx.Styles.Common.MainTextStyle.MarginBottom(1).Underline(true).Render(" Checks"))
222-
s.WriteString("\n")
223-
s.WriteString(m.renderChecksOverview())
225+
body.WriteString(m.renderSummary())
226+
body.WriteString("\n\n")
227+
body.WriteString(m.ctx.Styles.Common.MainTextStyle.MarginBottom(1).Underline(true).Render(" Checks"))
228+
body.WriteString("\n")
229+
body.WriteString(m.renderChecksOverview())
224230

225231
if m.isCommenting || m.isApproving || m.isAssigning || m.isUnassigning {
226-
s.WriteString(m.inputBox.View())
232+
body.WriteString(m.inputBox.View())
227233
}
228234

229235
case tabs[1]:
230-
s.WriteString(m.renderChecksOverview())
231-
s.WriteString("\n\n")
232-
s.WriteString(m.renderChecks())
236+
body.WriteString(m.renderChecksOverview())
237+
body.WriteString("\n\n")
238+
body.WriteString(m.renderChecks())
233239

234240
case tabs[2]:
235-
s.WriteString(m.renderActivity())
241+
body.WriteString(m.renderActivity())
236242
case tabs[3]:
237-
s.WriteString(m.renderChangedFiles())
243+
body.WriteString(m.renderChangedFiles())
238244
}
239245

240-
return s.String()
246+
return lipgloss.JoinVertical(lipgloss.Left,
247+
header.String(),
248+
lipgloss.NewStyle().Padding(0, m.ctx.Styles.Sidebar.ContentPadding).Render(body.String()),
249+
)
241250
}
242251

243252
func (m *Model) renderFullNameAndNumber() string {
244-
return lipgloss.NewStyle().Foreground(m.ctx.Theme.SecondaryText).Render(fmt.Sprintf("#%d · %s", m.pr.Data.GetNumber(), m.pr.Data.GetRepoNameWithOwner()))
253+
return lipgloss.NewStyle().
254+
PaddingLeft(1).
255+
Width(m.width).
256+
Background(m.ctx.Theme.SelectedBackground).
257+
Foreground(m.ctx.Theme.SecondaryText).
258+
Render(fmt.Sprintf("%s · #%d", m.pr.Data.GetRepoNameWithOwner(), m.pr.Data.GetNumber()))
245259
}
246260

247261
func (m *Model) renderTitle() string {
248-
return lipgloss.JoinHorizontal(
249-
lipgloss.Top,
250-
m.ctx.Styles.Common.MainTextStyle.Width(m.getIndentedContentWidth()).Render(m.pr.Data.Title),
262+
return lipgloss.NewStyle().Height(3).Width(m.width).Background(m.ctx.Theme.SelectedBackground).PaddingLeft(1).Render(
263+
lipgloss.PlaceVertical(3, lipgloss.Center, m.ctx.Styles.Common.MainTextStyle.
264+
Background(m.ctx.Theme.SelectedBackground).
265+
Render(m.pr.Data.Title),
266+
),
251267
)
252268
}
253269

254270
func (m *Model) renderBranches() string {
255271
return lipgloss.JoinHorizontal(lipgloss.Left,
272+
" ",
256273
m.renderStatusPill(),
257274
" ",
258275
lipgloss.NewStyle().
@@ -297,27 +314,35 @@ func (m *Model) renderLabels() string {
297314
)
298315
}
299316

300-
func (m *Model) renderDescription() string {
317+
func (m *Model) renderAuthor() string {
318+
authorAssociation := m.pr.Data.AuthorAssociation
319+
if authorAssociation == "" {
320+
authorAssociation = "unknown role"
321+
}
322+
time := lipgloss.NewStyle().Render(utils.TimeElapsed(m.pr.Data.CreatedAt))
323+
return lipgloss.JoinHorizontal(lipgloss.Top,
324+
" by ",
325+
lipgloss.NewStyle().Foreground(m.ctx.Theme.PrimaryText).Render(lipgloss.NewStyle().Bold(true).Render("@"+m.pr.Data.Author.Login)),
326+
lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Render(lipgloss.JoinHorizontal(lipgloss.Top, " ⋅ ", time, " ago", " ⋅ ")),
327+
lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Render(
328+
lipgloss.JoinHorizontal(lipgloss.Top, data.GetAuthorRoleIcon(m.pr.Data.AuthorAssociation, m.ctx.Theme), " ", lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Render(strings.ToLower(authorAssociation))),
329+
),
330+
)
331+
}
332+
333+
func (m *Model) renderSummary() string {
301334
width := m.getIndentedContentWidth()
302335
// Strip HTML comments from body and cleanup body.
303336
body := htmlCommentRegex.ReplaceAllString(m.pr.Data.Body, "")
304337
body = lineCleanupRegex.ReplaceAllString(body, "")
305338

306-
desc := m.ctx.Styles.Common.MainTextStyle.Bold(true).Underline(true).Render(" Description")
307-
time := lipgloss.NewStyle().Render(utils.TimeElapsed(m.pr.Data.CreatedAt))
339+
desc := m.ctx.Styles.Common.MainTextStyle.Bold(true).Underline(true).Render(" Summary")
308340
title := lipgloss.JoinVertical(
309341
lipgloss.Left,
310342
desc,
311343
"",
312-
lipgloss.JoinHorizontal(lipgloss.Top,
313-
lipgloss.NewStyle().Foreground(m.ctx.Theme.PrimaryText).Render(lipgloss.NewStyle().Bold(true).Render("@"+m.pr.Data.Author.Login)),
314-
" ",
315-
lipgloss.NewStyle().Foreground(m.ctx.Theme.FaintText).Render("commented", time, "ago"),
316-
),
317-
"",
318344
)
319-
sbody := lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).BorderForeground(m.ctx.Theme.FaintBorder).Width(width)
320-
345+
sbody := lipgloss.NewStyle().Width(m.getIndentedContentWidth())
321346
body = strings.TrimSpace(body)
322347
if body == "" {
323348
return lipgloss.JoinVertical(
@@ -333,6 +358,20 @@ func (m *Model) renderDescription() string {
333358
return ""
334359
}
335360

361+
if !m.summaryViewMore && len(rendered) > foldBodyLen {
362+
rendered = rendered[0:foldBodyLen]
363+
rendered = lipgloss.JoinVertical(lipgloss.Left,
364+
rendered,
365+
"",
366+
lipgloss.PlaceHorizontal(m.getIndentedContentWidth(), lipgloss.Center,
367+
lipgloss.JoinHorizontal(lipgloss.Top,
368+
lipgloss.NewStyle().Bold(true).Italic(true).Render("Press "),
369+
lipgloss.NewStyle().Background(m.ctx.Theme.SelectedBackground).Foreground(m.ctx.Theme.PrimaryText).Render("e"),
370+
lipgloss.NewStyle().Bold(true).Italic(true).Render(" to read more...")),
371+
),
372+
)
373+
}
374+
336375
return lipgloss.JoinVertical(lipgloss.Left, title,
337376
lipgloss.NewStyle().
338377
Width(width).
@@ -410,7 +449,7 @@ func (m *Model) SetIsCommenting(isCommenting bool) tea.Cmd {
410449
}
411450

412451
func (m *Model) getIndentedContentWidth() int {
413-
return m.width - 4
452+
return m.width - 3*m.ctx.Styles.Sidebar.ContentPadding
414453
}
415454

416455
func (m *Model) GetIsApproving() bool {
@@ -501,3 +540,11 @@ func (m *Model) prAssignees() []string {
501540
func (m *Model) GoToFirstTab() {
502541
m.carousel.SetCursor(0)
503542
}
543+
544+
func (m *Model) SetSummaryViewMore() {
545+
m.summaryViewMore = true
546+
}
547+
548+
func (m *Model) SetSummaryViewLess() {
549+
m.summaryViewMore = false
550+
}

ui/components/sidebar/sidebar.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
3838
case tea.KeyMsg:
3939
switch {
4040
case key.Matches(msg, keys.Keys.PageDown):
41-
m.viewport.HalfViewDown()
41+
m.viewport.HalfPageDown()
4242

4343
case key.Matches(msg, keys.Keys.PageUp):
44-
m.viewport.HalfViewUp()
44+
m.viewport.HalfPageUp()
4545
}
4646
}
4747

@@ -83,7 +83,7 @@ func (m *Model) GetSidebarContentWidth() int {
8383
if m.ctx.Config == nil {
8484
return 0
8585
}
86-
return m.ctx.Config.Defaults.Preview.Width - 2*m.ctx.Styles.Sidebar.ContentPadding - m.ctx.Styles.Sidebar.BorderWidth
86+
return m.ctx.Config.Defaults.Preview.Width - m.ctx.Styles.Sidebar.BorderWidth
8787
}
8888

8989
func (m *Model) ScrollToTop() {

ui/context/styles.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ func InitStyles(theme theme.Theme) Styles {
156156
s.Sidebar.BorderWidth = 1
157157
s.Sidebar.ContentPadding = 2
158158
s.Sidebar.Root = lipgloss.NewStyle().
159-
Padding(0, s.Sidebar.ContentPadding).
160159
BorderLeft(true).
161160
BorderStyle(lipgloss.Border{
162161
Top: "",

ui/keys/prKeys.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type PRKeyMap struct {
1919
Diff key.Binding
2020
Checkout key.Binding
2121
Close key.Binding
22+
SummaryViewMore key.Binding
2223
Ready key.Binding
2324
Reopen key.Binding
2425
Merge key.Binding
@@ -65,6 +66,10 @@ var PRKeys = PRKeyMap{
6566
key.WithKeys("x"),
6667
key.WithHelp("x", "close"),
6768
),
69+
SummaryViewMore: key.NewBinding(
70+
key.WithKeys("e"),
71+
key.WithHelp("e", "expand description"),
72+
),
6873
Reopen: key.NewBinding(
6974
key.WithKeys("X"),
7075
key.WithHelp("X", "reopen"),
@@ -173,6 +178,8 @@ func rebindPRKeys(keys []config.Keybinding) error {
173178
key = &PRKeys.WatchChecks
174179
case "viewIssues":
175180
key = &PRKeys.ViewIssues
181+
case "summaryViewMore":
182+
key = &PRKeys.SummaryViewMore
176183
default:
177184
return fmt.Errorf("unknown built-in pr key: '%s'", prKey.Builtin)
178185
}

ui/ui.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
439439
cmd = fetchSectionsCmds
440440
}
441441
m.onViewedRowChanged()
442+
443+
case key.Matches(msg, keys.PRKeys.SummaryViewMore):
444+
m.prSidebar.SetSummaryViewMore()
445+
m.syncSidebar()
446+
return m, nil
442447
}
443448
case m.ctx.View == config.IssuesView:
444449
switch {
@@ -681,6 +686,7 @@ func (m *Model) setCurrSectionId(newSectionId int) {
681686
}
682687

683688
func (m *Model) onViewedRowChanged() tea.Cmd {
689+
m.prSidebar.SetSummaryViewLess()
684690
m.prSidebar.GoToFirstTab()
685691
cmd := m.syncSidebar()
686692
m.sidebar.ScrollToTop()

0 commit comments

Comments
 (0)