diff --git a/config/config.go b/config/config.go index 0a258cd..ed9c544 100644 --- a/config/config.go +++ b/config/config.go @@ -58,6 +58,7 @@ type UserPreferences struct { DisableDownloads bool `yaml:"disable_downloads"` DisableNotifications bool `yaml:"disable_notifications"` DisableShowURLs bool `yaml:"disable_show_urls"` + InlineURLs bool `yaml:"inline_urls"` AltEnterToSend bool `yaml:"alt_enter_to_send"` } diff --git a/go.mod b/go.mod index ab9dbe4..83c9ccf 100644 --- a/go.mod +++ b/go.mod @@ -17,8 +17,8 @@ require ( github.com/zyedidia/clipboard v1.0.3 go.etcd.io/bbolt v1.3.6 go.mau.fi/cbind v0.0.0-20220415094356-e1d579b7925e - go.mau.fi/mauview v0.1.4-0.20220415181713-aa8cd644bf1d - go.mau.fi/tcell v0.0.0-20220415093808-07c67d224693 + go.mau.fi/mauview v0.1.4-0.20220415185926-d3913ee0f2b4 + go.mau.fi/tcell v0.0.0-20220415185117-592f364693a2 golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 golang.org/x/net v0.0.0-20220412020605-290c469a71a5 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 diff --git a/go.sum b/go.sum index 93583d9..1401761 100644 --- a/go.sum +++ b/go.sum @@ -56,10 +56,11 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.mau.fi/cbind v0.0.0-20220415094356-e1d579b7925e h1:zY4TZmHAaUhrMFJQfh02dqxDYSfnnXlw/qRoFanxZTw= go.mau.fi/cbind v0.0.0-20220415094356-e1d579b7925e/go.mod h1:9nnzlslhUo/xO+8tsQgkFqG/W+SgD+r0iTYAuglzlmA= -go.mau.fi/mauview v0.1.4-0.20220415181713-aa8cd644bf1d h1:3HkNVjYG4ht8D80cd0jKCEYXloRULAVwnEjDKFn70U4= -go.mau.fi/mauview v0.1.4-0.20220415181713-aa8cd644bf1d/go.mod h1:CPqlQWgiHEJHLNyD8vjMXotnPluMz0eDpKKCimjxFYE= -go.mau.fi/tcell v0.0.0-20220415093808-07c67d224693 h1:pCfn8BS6fiGG1inpebysWQxeRmK/nsgKPMF9TupqYNQ= +go.mau.fi/mauview v0.1.4-0.20220415185926-d3913ee0f2b4 h1:fciW2Q2Gl/E24nXJ1xWeV4x75pFtEBFa8cBkwoIAATs= +go.mau.fi/mauview v0.1.4-0.20220415185926-d3913ee0f2b4/go.mod h1:1rzvl7kqQ9lv8EVZeAwUlxR4/Q8LM3y2Xogg0yNx0qU= go.mau.fi/tcell v0.0.0-20220415093808-07c67d224693/go.mod h1:HQLPCz9v8YfYewMetOKrg9pe87XEyNcIfCYYq8VxQbU= +go.mau.fi/tcell v0.0.0-20220415185117-592f364693a2 h1:UocCXayiOk0dzu7GeTyiH6UdpJnp5d645NRDwr6RjLY= +go.mau.fi/tcell v0.0.0-20220415185117-592f364693a2/go.mod h1:HQLPCz9v8YfYewMetOKrg9pe87XEyNcIfCYYq8VxQbU= golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM= golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= diff --git a/ui/commands.go b/ui/commands.go index ecf8978..4d9c531 100644 --- a/ui/commands.go +++ b/ui/commands.go @@ -762,6 +762,20 @@ func (stm SimpleToggleMessage) Name() string { return string(unicode.ToUpper(rune(stm[0]))) + string(stm[1:]) } +type InvertedToggleMessage string + +func (itm InvertedToggleMessage) Format(state bool) string { + if state { + return "Enabled " + string(itm) + } else { + return "Disabled " + string(itm) + } +} + +func (itm InvertedToggleMessage) Name() string { + return string(unicode.ToUpper(rune(itm[0]))) + string(itm[1:]) +} + type NewlineKeybindMessage string func (nkm NewlineKeybindMessage) Format(state bool) string { @@ -790,6 +804,7 @@ var toggleMsg = map[string]ToggleMessage{ "notifications": SimpleToggleMessage("desktop notifications"), "unverified": SimpleToggleMessage("sending messages to unverified devices"), "showurls": SimpleToggleMessage("show URLs in text format"), + "inlineurls": InvertedToggleMessage("use fancy terminal features to render URLs inside text"), "newline": NewlineKeybindMessage("should make a new line or send the message"), } @@ -837,6 +852,8 @@ func cmdToggle(cmd *Command) { val = &cmd.Config.SendToVerifiedOnly case "showurls": val = &cmd.Config.Preferences.DisableShowURLs + case "inlineurls": + val = &cmd.Config.Preferences.InlineURLs case "newline": val = &cmd.Config.Preferences.AltEnterToSend default: diff --git a/ui/messages/html/parser.go b/ui/messages/html/parser.go index b0f39de..d706bf6 100644 --- a/ui/messages/html/parser.go +++ b/ui/messages/html/parser.go @@ -34,17 +34,18 @@ import ( "maunium.net/go/mautrix/id" "maunium.net/go/gomuks/config" + "maunium.net/go/gomuks/matrix/muksevt" "maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/ui/widget" ) -var matrixToURL = regexp.MustCompile("^(?:https?://)?(?:www\\.)?matrix\\.to/#/([#@!].*)") - type htmlParser struct { prefs *config.UserPreferences room *rooms.Room + evt *muksevt.Event preserveWhitespace bool + linkIDCounter int } func AdjustStyleBold(style tcell.Style) tcell.Style { @@ -63,18 +64,24 @@ func AdjustStyleStrikethrough(style tcell.Style) tcell.Style { return style.StrikeThrough(true) } -func AdjustStyleTextColor(color tcell.Color) func(tcell.Style) tcell.Style { +func AdjustStyleTextColor(color tcell.Color) AdjustStyleFunc { return func(style tcell.Style) tcell.Style { return style.Foreground(color) } } -func AdjustStyleBackgroundColor(color tcell.Color) func(tcell.Style) tcell.Style { +func AdjustStyleBackgroundColor(color tcell.Color) AdjustStyleFunc { return func(style tcell.Style) tcell.Style { return style.Background(color) } } +func AdjustStyleLink(url, id string) AdjustStyleFunc { + return func(style tcell.Style) tcell.Style { + return style.Hyperlink(url, id) + } +} + func (parser *htmlParser) maybeGetAttribute(node *html.Node, attribute string) (string, bool) { for _, attr := range node.Attr { if attr.Key == attribute { @@ -198,7 +205,7 @@ func (parser *htmlParser) linkToEntity(node *html.Node) Entity { Children: parser.nodeToEntities(node.FirstChild), } - if len(href) == 0 || parser.hasAttribute(node, "data-mautrix-exclude-plaintext") { + if len(href) == 0 { return entity } @@ -209,28 +216,25 @@ func (parser *htmlParser) linkToEntity(node *html.Node) Entity { } } - if !parser.prefs.DisableShowURLs && !parser.hasAttribute(node, "data-mautrix-no-link") && !sameURL { - entity.Children = append(entity.Children, NewTextEntity(fmt.Sprintf(" (%s)", href))) - } - - match := matrixToURL.FindStringSubmatch(href) - if len(match) == 2 { - pillTarget := match[1] - text := NewTextEntity(pillTarget) - if pillTarget[0] == '@' { - if member := parser.room.GetMember(id.UserID(pillTarget)); member != nil { + matrixURI, _ := id.ParseMatrixURIOrMatrixToURL(href) + if matrixURI != nil && (matrixURI.Sigil1 == '@' || matrixURI.Sigil1 == '#') && matrixURI.Sigil2 == 0 { + text := NewTextEntity(matrixURI.PrimaryIdentifier()) + if matrixURI.Sigil1 == '@' { + if member := parser.room.GetMember(matrixURI.UserID()); member != nil { text.Text = member.Displayname - text.Style = text.Style.Foreground(widget.GetHashColor(pillTarget)) + text.Style = text.Style.Foreground(widget.GetHashColor(matrixURI.UserID())) } entity.Children = []Entity{text} - /*} else if slash := strings.IndexRune(pillTarget, '/'); slash != -1 { - room := pillTarget[:slash] - event := pillTarget[slash+1:]*/ - } else if pillTarget[0] == '#' { + } else if matrixURI.Sigil1 == '#' { entity.Children = []Entity{text} } + } else if parser.prefs.InlineURLs { + linkID := fmt.Sprintf("%s-%d", parser.evt.ID, parser.linkIDCounter) + parser.linkIDCounter++ + entity.AdjustStyle(AdjustStyleLink(href, linkID), AdjustStyleReasonNormal) + } else if !sameURL && !parser.prefs.DisableShowURLs && !parser.hasAttribute(node, "data-mautrix-exclude-plaintext") { + entity.Children = append(entity.Children, NewTextEntity(fmt.Sprintf(" (%s)", href))) } - // TODO add click action and underline on hover for links return entity } @@ -447,7 +451,7 @@ func (parser *htmlParser) Parse(htmlData string) Entity { const TabLength = 4 // Parse parses a HTML-formatted Matrix event into a UIMessage. -func Parse(prefs *config.UserPreferences, room *rooms.Room, content *event.MessageEventContent, sender id.UserID, senderDisplayname string) Entity { +func Parse(prefs *config.UserPreferences, room *rooms.Room, content *event.MessageEventContent, evt *muksevt.Event, senderDisplayname string) Entity { htmlData := content.FormattedBody if content.Format != event.FormatHTML { @@ -455,7 +459,7 @@ func Parse(prefs *config.UserPreferences, room *rooms.Room, content *event.Messa } htmlData = strings.Replace(htmlData, "\t", strings.Repeat(" ", TabLength), -1) - parser := htmlParser{room: room, prefs: prefs} + parser := htmlParser{room: room, prefs: prefs, evt: evt} root := parser.Parse(htmlData) beRoot := root.(*ContainerEntity) beRoot.Block = false @@ -474,7 +478,7 @@ func Parse(prefs *config.UserPreferences, room *rooms.Room, content *event.Messa }, Children: []Entity{ NewTextEntity("* "), - NewTextEntity(senderDisplayname).AdjustStyle(AdjustStyleTextColor(widget.GetHashColor(sender)), AdjustStyleReasonNormal), + NewTextEntity(senderDisplayname).AdjustStyle(AdjustStyleTextColor(widget.GetHashColor(evt.Sender)), AdjustStyleReasonNormal), NewTextEntity(" "), root, }, diff --git a/ui/messages/parser.go b/ui/messages/parser.go index f498110..2800701 100644 --- a/ui/messages/parser.go +++ b/ui/messages/parser.go @@ -205,7 +205,7 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *muksevt.Eve switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: if content.Format == event.FormatHTML { - return NewHTMLMessage(evt, displayname, html.Parse(matrix.Preferences(), room, content, evt.Sender, displayname)) + return NewHTMLMessage(evt, displayname, html.Parse(matrix.Preferences(), room, content, evt, displayname)) } content.Body = strings.Replace(content.Body, "\t", " ", -1) return NewTextMessage(evt, displayname, content.Body)