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
+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