commit e6d6b9e

delthas  ·  2024-10-30 20:03:18 +0000 UTC
parent a8c3f1a
Support links to channels

Clicking on #foo will now jump to it or try joining it.
4 files changed,  +85, -11
M app.go
M app.go
+12, -0
 1@@ -539,6 +539,8 @@ func (app *App) handleUIEvent(ev interface{}) bool {
 2 		app.handleNickEvent(ev)
 3 	case *events.EventClickLink:
 4 		app.handleLinkEvent(ev)
 5+	case *events.EventClickChannel:
 6+		app.handleChannelEvent(ev)
 7 	case *events.EventImageLoaded:
 8 		app.win.ShowImage(ev.Image)
 9 		if ev.Image == nil {
10@@ -905,6 +907,16 @@ func (app *App) handleNickEvent(ev *events.EventClickNick) {
11 	}
12 }
13 
14+func (app *App) handleChannelEvent(ev *events.EventClickChannel) {
15+	s := app.sessions[ev.NetID]
16+	if s == nil {
17+		return
18+	}
19+	if !app.win.JumpBufferNetwork(ev.NetID, ev.Channel) {
20+		s.Join(ev.Channel, "")
21+	}
22+}
23+
24 var patternOpenGraphImage = regexp.MustCompile(`<meta property="og:image" content="(.*?)"/>`)
25 var patternOpenGraphVideo = regexp.MustCompile(`<meta property="og:video"`)
26 
+5, -0
 1@@ -31,6 +31,11 @@ type EventClickLink struct {
 2 	Mouse bool
 3 }
 4 
 5+type EventClickChannel struct {
 6+	EventClick
 7+	Channel string
 8+}
 9+
10 type EventImageLoaded struct {
11 	Image image.Image // nil if error
12 }
+28, -2
 1@@ -974,7 +974,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
 2 				st = nextStyles[0].Style
 3 				nextStyles = nextStyles[1:]
 4 
 5-				if bs.ui.mouseLinks && st.Hyperlink != "" && st.UnderlineStyle == 0 {
 6+				if (bs.ui.mouseLinks || st.Hyperlink == "") && st.HyperlinkParams != "" && st.UnderlineStyle == 0 {
 7 					st.UnderlineStyle = vaxis.UnderlineDotted
 8 				}
 9 			}
10@@ -997,6 +997,19 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
11 						Mouse: ui.mouseLinks,
12 					},
13 				})
14+			} else if _, channel, ok := strings.Cut(st.HyperlinkParams, "="); ok {
15+				ui.clickEvents = append(ui.clickEvents, clickEvent{
16+					xb: xTopic - dx,
17+					xe: xTopic,
18+					y:  y0,
19+					event: &events.EventClickChannel{
20+						EventClick: events.EventClick{
21+							NetID:  b.netID,
22+							Buffer: b.title,
23+						},
24+						Channel: channel,
25+					},
26+				})
27 			}
28 		}
29 	}
30@@ -1100,7 +1113,7 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
31 				style = nextStyles[0].Style
32 				nextStyles = nextStyles[1:]
33 
34-				if bs.ui.mouseLinks && style.Hyperlink != "" && style.UnderlineStyle == 0 {
35+				if (bs.ui.mouseLinks || style.Hyperlink == "") && style.HyperlinkParams != "" && style.UnderlineStyle == 0 {
36 					style.UnderlineStyle = vaxis.UnderlineDotted
37 				}
38 			}
39@@ -1146,6 +1159,19 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
40 						Mouse: ui.mouseLinks,
41 					},
42 				})
43+			} else if _, channel, ok := strings.Cut(style.HyperlinkParams, "="); ok {
44+				ui.clickEvents = append(ui.clickEvents, clickEvent{
45+					xb: xb,
46+					xe: x,
47+					y:  y,
48+					event: &events.EventClickChannel{
49+						EventClick: events.EventClick{
50+							NetID:  b.netID,
51+							Buffer: b.title,
52+						},
53+						Channel: channel,
54+					},
55+				})
56 			}
57 		}
58 	}
+40, -9
  1@@ -4,8 +4,10 @@ import (
  2 	"fmt"
  3 	"math/rand"
  4 	"net/url"
  5+	"regexp"
  6 	"strconv"
  7 	"strings"
  8+	"unicode"
  9 	"unicode/utf8"
 10 
 11 	"git.sr.ht/~rockorager/vaxis"
 12@@ -87,11 +89,17 @@ func (s StyledString) String() string {
 13 	return s.string
 14 }
 15 
 16-var urlRegex, _ = xurls.StrictMatchingScheme(xurls.AnyScheme)
 17+var urlRegex *regexp.Regexp
 18+
 19+func init() {
 20+	urlRegex, _ = xurls.StrictMatchingScheme(xurls.AnyScheme)
 21+	urlRegex = regexp.MustCompile(urlRegex.String() + `|#[\p{L}0-9#.-]*[\p{L}0-9]`)
 22+	urlRegex.Longest()
 23+}
 24 
 25 func (s StyledString) ParseURLs() StyledString {
 26-	if !strings.ContainsRune(s.string, '.') {
 27-		// fast path: no dot means no URL
 28+	if !strings.ContainsAny(s.string, ".#") {
 29+		// fast path: no URL
 30 		return s
 31 	}
 32 
 33@@ -106,10 +114,22 @@ func (s StyledString) ParseURLs() StyledString {
 34 		u := urls[i]
 35 		ub, ue := u[0], u[1]
 36 		link := s.string[u[0]:u[1]]
 37-		if u, err := url.Parse(link); err != nil || u.Scheme == "" {
 38-			link = "https://" + link
 39+		var params string
 40+		if link[0] == '#' {
 41+			if prev := lastRuneBefore(s.string, u[0]); !(prev == 0 || unicode.IsSpace(prev) || prev == '(' || prev == '[') {
 42+				// channel link preceded by a non-space character: eg a#a: drop
 43+				continue
 44+			}
 45+			// store channel in hyperlink params, but create no link.
 46+			// this allows us to save the link data without actually creating a link in the terminal
 47+			params = fmt.Sprintf("channel=%v", link)
 48+			link = ""
 49+		} else {
 50+			if u, err := url.Parse(link); err != nil || u.Scheme == "" {
 51+				link = "https://" + link
 52+			}
 53+			params = fmt.Sprintf("id=_%010d", rand.Int31())
 54 		}
 55-		id := fmt.Sprintf("_%010d", rand.Int31())
 56 		// find last style starting before or at url begin
 57 		for ; j < len(s.styles); j++ {
 58 			st := s.styles[j]
 59@@ -119,7 +139,7 @@ func (s StyledString) ParseURLs() StyledString {
 60 			if st.Start == ub {
 61 				// a style already starts at this position, edit it
 62 				st.Style.Hyperlink = link
 63-				st.Style.HyperlinkParams = fmt.Sprintf("id=%v", id)
 64+				st.Style.HyperlinkParams = params
 65 			}
 66 			lastStyle = st
 67 			styles = append(styles, st)
 68@@ -128,7 +148,7 @@ func (s StyledString) ParseURLs() StyledString {
 69 			// no style existed at this position, add one from the last style
 70 			st := lastStyle.Style
 71 			st.Hyperlink = link
 72-			st.HyperlinkParams = fmt.Sprintf("id=%v", id)
 73+			st.HyperlinkParams = params
 74 			styles = append(styles, rangedStyle{
 75 				Start: ub,
 76 				Style: st,
 77@@ -142,7 +162,7 @@ func (s StyledString) ParseURLs() StyledString {
 78 			}
 79 			if st.Start < ue {
 80 				st.Style.Hyperlink = link
 81-				st.Style.HyperlinkParams = fmt.Sprintf("id=%v", id)
 82+				st.Style.HyperlinkParams = params
 83 			}
 84 			lastStyle = st
 85 			styles = append(styles, st)
 86@@ -242,6 +262,17 @@ func parseHexColor(raw string) (fg, bg vaxis.Color, n int) {
 87 	return fg, bg, n
 88 }
 89 
 90+func lastRuneBefore(s string, i int) rune {
 91+	var r rune
 92+	for ri, rr := range s {
 93+		if ri >= i {
 94+			break
 95+		}
 96+		r = rr
 97+	}
 98+	return r
 99+}
100+
101 func IRCString(raw string) StyledString {
102 	var formatted strings.Builder
103 	var styles []rangedStyle