Add basic HTML rendering (ref #16)
This commit is contained in:
parent
69c163cfe9
commit
e7bf5bd59f
@ -94,11 +94,7 @@ func (room *Room) UpdateState(event *gomatrix.Event) {
|
|||||||
room.memberCache = nil
|
room.memberCache = nil
|
||||||
room.firstMemberCache = ""
|
room.firstMemberCache = ""
|
||||||
fallthrough
|
fallthrough
|
||||||
case "m.room.name":
|
case "m.room.name", "m.room.canonical_alias", "m.room.alias":
|
||||||
fallthrough
|
|
||||||
case "m.room.canonical_alias":
|
|
||||||
fallthrough
|
|
||||||
case "m.room.alias":
|
|
||||||
room.nameCache = ""
|
room.nameCache = ""
|
||||||
case "m.room.topic":
|
case "m.room.topic":
|
||||||
room.topicCache = ""
|
room.topicCache = ""
|
||||||
|
@ -141,9 +141,7 @@ func (msg *BaseMessage) TextColor() tcell.Color {
|
|||||||
switch {
|
switch {
|
||||||
case stateColor != tcell.ColorDefault:
|
case stateColor != tcell.ColorDefault:
|
||||||
return stateColor
|
return stateColor
|
||||||
case msg.MsgIsService:
|
case msg.MsgIsService, msg.MsgType == "m.notice":
|
||||||
fallthrough
|
|
||||||
case msg.MsgType == "m.notice":
|
|
||||||
return tcell.ColorGray
|
return tcell.ColorGray
|
||||||
case msg.MsgIsHighlight:
|
case msg.MsgIsHighlight:
|
||||||
return tcell.ColorYellow
|
return tcell.ColorYellow
|
||||||
|
136
ui/messages/htmlparser.go
Normal file
136
ui/messages/htmlparser.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// gomuks - A terminal Matrix client written in Go.
|
||||||
|
// Copyright (C) 2018 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package messages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
"maunium.net/go/gomatrix"
|
||||||
|
"maunium.net/go/gomuks/debug"
|
||||||
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
|
"maunium.net/go/tcell"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TagArray is a reversed queue for remembering what HTML tags are open.
|
||||||
|
type TagArray []string
|
||||||
|
|
||||||
|
// Pushb converts the given byte array into a string and calls Push().
|
||||||
|
func (ta *TagArray) Pushb(tag []byte) {
|
||||||
|
ta.Push(string(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popb converts the given byte array into a string and calls Pop().
|
||||||
|
func (ta *TagArray) Popb(tag []byte) {
|
||||||
|
ta.Pop(string(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hasb converts the given byte array into a string and calls Has().
|
||||||
|
func (ta *TagArray) Hasb(tag []byte) {
|
||||||
|
ta.Has(string(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAfterb converts the given byte array into a string and calls HasAfter().
|
||||||
|
func (ta *TagArray) HasAfterb(tag []byte, after int) {
|
||||||
|
ta.HasAfter(string(tag), after)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push adds the given tag to the array.
|
||||||
|
func (ta *TagArray) Push(tag string) {
|
||||||
|
*ta = append(*ta, "")
|
||||||
|
copy((*ta)[1:], *ta)
|
||||||
|
(*ta)[0] = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop removes the given tag from the array.
|
||||||
|
func (ta *TagArray) Pop(tag string) {
|
||||||
|
if (*ta)[0] == tag {
|
||||||
|
// This is the default case and is lighter than append(), so we handle it separately.
|
||||||
|
*ta = (*ta)[1:]
|
||||||
|
} else if index := ta.Has(tag); index != -1 {
|
||||||
|
*ta = append((*ta)[:index], (*ta)[index+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns the first index where the given tag is, or -1 if it's not in the list.
|
||||||
|
func (ta *TagArray) Has(tag string) int {
|
||||||
|
return ta.HasAfter(tag, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAfter returns the first index after the given index where the given tag is,
|
||||||
|
// or -1 if the given tag is not on the list after the given index.
|
||||||
|
func (ta *TagArray) HasAfter(tag string, after int) int {
|
||||||
|
for i := after + 1; i < len(*ta); i++ {
|
||||||
|
if (*ta)[i] == tag {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHTMLMessage parses a HTML-formatted Matrix event into a UIMessage.
|
||||||
|
func ParseHTMLMessage(evt *gomatrix.Event) tstring.TString {
|
||||||
|
//textData, _ := evt.Content["body"].(string)
|
||||||
|
htmlData, _ := evt.Content["formatted_body"].(string)
|
||||||
|
|
||||||
|
z := html.NewTokenizer(strings.NewReader(htmlData))
|
||||||
|
text := tstring.NewTString("")
|
||||||
|
|
||||||
|
openTags := &TagArray{}
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
tt := z.Next()
|
||||||
|
switch tt {
|
||||||
|
case html.ErrorToken:
|
||||||
|
break Loop
|
||||||
|
case html.TextToken:
|
||||||
|
style := tcell.StyleDefault
|
||||||
|
for _, tag := range *openTags {
|
||||||
|
switch tag {
|
||||||
|
case "b", "strong":
|
||||||
|
style = style.Bold(true)
|
||||||
|
case "i", "em":
|
||||||
|
style = style.Italic(true)
|
||||||
|
case "s", "del":
|
||||||
|
style = style.Strikethrough(true)
|
||||||
|
case "u", "ins":
|
||||||
|
style = style.Underline(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = text.AppendStyle(string(z.Text()), style)
|
||||||
|
case html.SelfClosingTagToken, html.StartTagToken:
|
||||||
|
tagb, _ := z.TagName()
|
||||||
|
tag := string(tagb)
|
||||||
|
switch tag {
|
||||||
|
case "br":
|
||||||
|
debug.Print("BR found")
|
||||||
|
debug.Print(text.String())
|
||||||
|
text = text.Append("\n")
|
||||||
|
default:
|
||||||
|
if tt == html.StartTagToken {
|
||||||
|
openTags.Push(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case html.EndTagToken:
|
||||||
|
tagb, _ := z.TagName()
|
||||||
|
openTags.Popb(tagb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
@ -56,8 +56,14 @@ func ParseMessage(gmx ifc.Gomuks, evt *gomatrix.Event) UIMessage {
|
|||||||
ts := unixToTime(evt.Timestamp)
|
ts := unixToTime(evt.Timestamp)
|
||||||
switch msgtype {
|
switch msgtype {
|
||||||
case "m.text", "m.notice", "m.emote":
|
case "m.text", "m.notice", "m.emote":
|
||||||
|
format, hasFormat := evt.Content["format"].(string)
|
||||||
|
if hasFormat && format == "org.matrix.custom.html" {
|
||||||
|
text := ParseHTMLMessage(evt)
|
||||||
|
return NewExpandedTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
|
||||||
|
} else {
|
||||||
text, _ := evt.Content["body"].(string)
|
text, _ := evt.Content["body"].(string)
|
||||||
return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
|
return NewTextMessage(evt.ID, evt.Sender, msgtype, text, ts)
|
||||||
|
}
|
||||||
case "m.image":
|
case "m.image":
|
||||||
url, _ := evt.Content["url"].(string)
|
url, _ := evt.Content["url"].(string)
|
||||||
data, hs, id, err := gmx.Matrix().Download(url)
|
data, hs, id, err := gmx.Matrix().Download(url)
|
||||||
|
@ -19,8 +19,8 @@ package tstring
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"maunium.net/go/tcell"
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
"maunium.net/go/tcell"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TString []Cell
|
type TString []Cell
|
||||||
@ -49,6 +49,37 @@ func NewStyleTString(str string, style tcell.Style) TString {
|
|||||||
return newStr
|
return newStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (str TString) AppendTString(data TString) TString {
|
||||||
|
return append(str, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (str TString) Append(data string) TString {
|
||||||
|
newStr := make(TString, len(str)+len(data))
|
||||||
|
copy(newStr, str)
|
||||||
|
for i, char := range data {
|
||||||
|
newStr[i+len(str)] = NewCell(char)
|
||||||
|
}
|
||||||
|
return newStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (str TString) AppendColor(data string, color tcell.Color) TString {
|
||||||
|
newStr := make(TString, len(str)+len(data))
|
||||||
|
copy(newStr, str)
|
||||||
|
for i, char := range data {
|
||||||
|
newStr[i+len(str)] = NewColorCell(char, color)
|
||||||
|
}
|
||||||
|
return newStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (str TString) AppendStyle(data string, style tcell.Style) TString {
|
||||||
|
newStr := make(TString, len(str)+len(data))
|
||||||
|
copy(newStr, str)
|
||||||
|
for i, char := range data {
|
||||||
|
newStr[i+len(str)] = NewStyleCell(char, style)
|
||||||
|
}
|
||||||
|
return newStr
|
||||||
|
}
|
||||||
|
|
||||||
func (str TString) Colorize(from, length int, color tcell.Color) {
|
func (str TString) Colorize(from, length int, color tcell.Color) {
|
||||||
for i := from; i < from+length; i++ {
|
for i := from; i < from+length; i++ {
|
||||||
str[i].Style = str[i].Style.Foreground(color)
|
str[i].Style = str[i].Style.Foreground(color)
|
||||||
|
@ -164,9 +164,7 @@ func (view *MainView) HandleCommand(roomView *RoomView, command string, args []s
|
|||||||
view.gmx.Stop()
|
view.gmx.Stop()
|
||||||
case "/panic":
|
case "/panic":
|
||||||
panic("This is a test panic.")
|
panic("This is a test panic.")
|
||||||
case "/part":
|
case "/part", "/leave":
|
||||||
fallthrough
|
|
||||||
case "/leave":
|
|
||||||
debug.Print("Leave room result:", view.matrix.LeaveRoom(roomView.Room.ID))
|
debug.Print("Leave room result:", view.matrix.LeaveRoom(roomView.Room.ID))
|
||||||
case "/join":
|
case "/join":
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user