Răsfoiți Sursa

feat: open url in messages (#518)

Co-authored-by: ait <aiten@posteo.net>
Co-authored-by: Ayyan <ayn2op@gmail.com>
ait 1 an în urmă
părinte
comite
84415c0a16
1 a modificat fișierele cu 113 adăugiri și 7 ștergeri
  1. 113 7
      cmd/messages_text.go

+ 113 - 7
cmd/messages_text.go

@@ -17,7 +17,10 @@ import (
 	"github.com/gdamore/tcell/v2"
 	"github.com/rivo/tview"
 	"github.com/skratchdot/open-golang/open"
+	"github.com/yuin/goldmark/ast"
+	"github.com/yuin/goldmark/parser"
 	"github.com/yuin/goldmark/renderer"
+	"github.com/yuin/goldmark/text"
 )
 
 type MessagesText struct {
@@ -312,17 +315,120 @@ func (mt *MessagesText) open() {
 		return
 	}
 
-	attachments := msg.Attachments
-	if len(attachments) == 0 {
+	var urls []string
+	if msg.Content != "" {
+		urls = extractURLs(msg.Content)
+	}
+
+	if len(urls) == 0 && len(msg.Attachments) == 0 {
+		return
+	}
+
+	if len(urls)+len(msg.Attachments) == 1 {
+		if len(urls) == 1 {
+			go openURL(urls[0])
+		} else {
+			go openURL(msg.Attachments[0].URL)
+		}
 		return
 	}
 
-	for _, a := range attachments {
-		go func() {
-			if err := open.Start(a.URL); err != nil {
-				slog.Error("failed to open URL", "err", err, "url", a.URL)
+	showSelector(mt, urls, msg.Attachments)
+}
+
+func extractURLs(content string) []string {
+	src := []byte(content)
+	node := parser.NewParser(
+		parser.WithBlockParsers(discordmd.BlockParsers()...),
+		parser.WithInlineParsers(discordmd.InlineParserWithLink()...),
+	).Parse(text.NewReader(src))
+
+	var urls []string
+	ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+		if entering {
+			switch n := n.(type) {
+			case *ast.AutoLink:
+				urls = append(urls, string(n.URL(src)))
+			case *ast.Link:
+				urls = append(urls, string(n.Destination))
 			}
-		}()
+		}
+
+		return ast.WalkContinue, nil
+	})
+	return urls
+}
+
+func showSelector(mt *MessagesText, urls []string, attachments []discord.Attachment) {
+	list := tview.NewList().
+		SetWrapAround(true).
+		SetHighlightFullLine(true).
+		ShowSecondaryText(false)
+
+	for i, a := range attachments {
+		attachment := a
+		list.AddItem(a.Filename, "", rune('a'+i), func() {
+			go openURL(attachment.URL)
+			app.SetRoot(app.flex, true)
+		})
+	}
+
+	for i, url := range urls {
+		urlCopy := url
+		list.AddItem(url, "", rune('1'+i), func() {
+			go openURL(urlCopy)
+			app.SetRoot(app.flex, true)
+		})
+	}
+
+	list.AddItem("Cancel", "", 'q', func() {
+		app.SetRoot(app.flex, true)
+	})
+
+	list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+		switch event.Name() {
+		case mt.cfg.Keys.MessagesText.SelectPrevious:
+			return tcell.NewEventKey(tcell.KeyUp, 0, tcell.ModNone)
+		case mt.cfg.Keys.MessagesText.SelectNext:
+			return tcell.NewEventKey(tcell.KeyDown, 0, tcell.ModNone)
+		case mt.cfg.Keys.MessagesText.SelectFirst:
+			list.SetCurrentItem(0)
+			return nil
+		case mt.cfg.Keys.MessagesText.SelectLast:
+			list.SetCurrentItem(list.GetItemCount() - 1)
+			return nil
+		case "Escape":
+			app.SetRoot(app.flex, true)
+			return nil
+		}
+		return event
+	})
+
+	height := len(urls) + len(attachments) + 1
+	maxHeight := 20
+	if height > maxHeight {
+		height = maxHeight
+	}
+
+	modal := createCenteredModal(list, height, 60)
+
+	app.SetRoot(modal, true)
+	app.SetFocus(list)
+}
+
+func createCenteredModal(p tview.Primitive, height, width int) tview.Primitive {
+	return tview.NewFlex().
+		AddItem(nil, 0, 1, false).
+		AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
+			AddItem(nil, 0, 1, false).
+			AddItem(p, height, 1, true).
+			AddItem(nil, 0, 1, false), width, 1, true).
+		AddItem(nil, 0, 1, false)
+}
+
+func openURL(url string) {
+	if err := open.Start(url); err != nil {
+		slog.Error("failed to open URL", "err", err, "url", url)
 	}
 }