feat(msgviewer): re-enable composite renderer with scroll support and inline images
Composite mode now handles j/k/g/G/PgUp/PgDown/mouse wheel for scrolling. Text uses the configured default style. Images render inline via Vaxis. Emails without images still use the standard pager path.
This commit is contained in:
+75
-13
@@ -632,18 +632,18 @@ func (pv *PartViewer) attemptCopy() {
|
||||
}
|
||||
// Extract image markers from filter output
|
||||
cleaned, images := parse.ExtractImages(&filterBuf)
|
||||
cleanedBytes, _ := io.ReadAll(cleaned)
|
||||
if len(images) > 0 {
|
||||
pv.imageRefs = images
|
||||
log.Debugf("extracted %d image refs from filter output", len(images))
|
||||
}
|
||||
// Always forward to pager — placeholders are readable text
|
||||
// Image markers become \x00IMG:N\x00 which we replace
|
||||
// with human-readable [image: alt] text for the pager.
|
||||
cleanedBytes, _ := io.ReadAll(cleaned)
|
||||
output := parse.ReplacePlaceholders(cleanedBytes, images)
|
||||
_, copyErr := io.Copy(pv.pagerin, bytes.NewReader(output))
|
||||
if copyErr != nil {
|
||||
log.Errorf("io.Copy: %s", copyErr)
|
||||
pv.composite = newCompositeContent(
|
||||
string(cleanedBytes), images, 80)
|
||||
pv.Invalidate()
|
||||
} else {
|
||||
// No images — forward to pager as normal
|
||||
_, copyErr := io.Copy(pv.pagerin, bytes.NewReader(cleanedBytes))
|
||||
if copyErr != nil {
|
||||
log.Errorf("io.Copy: %s", copyErr)
|
||||
}
|
||||
}
|
||||
err = pv.pagerin.Close()
|
||||
if err != nil {
|
||||
@@ -838,9 +838,14 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
|
||||
if pv.term != nil {
|
||||
pv.term.Draw(ctx)
|
||||
}
|
||||
// NOTE: Composite image rendering (pv.composite) is reserved for
|
||||
// future use. Currently all filter output goes through the pager
|
||||
// with [image: alt] text placeholders for extracted images.
|
||||
// Composite mode: text + inline images from filter output
|
||||
if pv.composite != nil {
|
||||
pv.composite.width = ctx.Width()
|
||||
style := pv.uiConfig.GetStyle(config.STYLE_DEFAULT)
|
||||
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
|
||||
pv.composite.Draw(ctx, pv.scroll, style)
|
||||
return
|
||||
}
|
||||
if pv.image != nil && (pv.resized(ctx) || pv.graphic == nil) {
|
||||
// This path should only occur on resizes or the first pass
|
||||
// after the image is downloaded and could be slow due to
|
||||
@@ -890,12 +895,69 @@ func (pv *PartViewer) resized(ctx *ui.Context) bool {
|
||||
}
|
||||
|
||||
func (pv *PartViewer) Event(event vaxis.Event) bool {
|
||||
if pv.composite != nil {
|
||||
switch ev := event.(type) {
|
||||
case vaxis.Mouse:
|
||||
switch ev.Button {
|
||||
case vaxis.MouseWheelUp:
|
||||
pv.scrollComposite(-3)
|
||||
return true
|
||||
case vaxis.MouseWheelDown:
|
||||
pv.scrollComposite(3)
|
||||
return true
|
||||
}
|
||||
case vaxis.Key:
|
||||
switch {
|
||||
case ev.Matches('j'), ev.Matches(vaxis.KeyDown):
|
||||
pv.scrollComposite(1)
|
||||
return true
|
||||
case ev.Matches('k'), ev.Matches(vaxis.KeyUp):
|
||||
pv.scrollComposite(-1)
|
||||
return true
|
||||
case ev.Matches(vaxis.KeyPgDown), ev.Matches(' '):
|
||||
pv.scrollComposite(pv.height / 2)
|
||||
return true
|
||||
case ev.Matches(vaxis.KeyPgUp):
|
||||
pv.scrollComposite(-pv.height / 2)
|
||||
return true
|
||||
case ev.Matches('g'), ev.Matches(vaxis.KeyHome):
|
||||
pv.scroll = 0
|
||||
pv.Invalidate()
|
||||
return true
|
||||
case ev.Matches('G'), ev.Matches(vaxis.KeyEnd):
|
||||
maxScroll := pv.composite.totalHeight() - pv.height
|
||||
if maxScroll > 0 {
|
||||
pv.scroll = maxScroll
|
||||
}
|
||||
pv.Invalidate()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if pv.term != nil {
|
||||
return pv.term.Event(event)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (pv *PartViewer) scrollComposite(delta int) {
|
||||
pv.scroll += delta
|
||||
if pv.scroll < 0 {
|
||||
pv.scroll = 0
|
||||
}
|
||||
if pv.composite != nil {
|
||||
maxScroll := pv.composite.totalHeight() - pv.height
|
||||
if maxScroll < 0 {
|
||||
maxScroll = 0
|
||||
}
|
||||
if pv.scroll > maxScroll {
|
||||
pv.scroll = maxScroll
|
||||
}
|
||||
}
|
||||
pv.Invalidate()
|
||||
}
|
||||
|
||||
type HeaderView struct {
|
||||
Name string
|
||||
Value string
|
||||
|
||||
Reference in New Issue
Block a user