Add support for rendering spoilers. Fixes #331
This commit is contained in:
parent
751a158fbf
commit
a5bdba204e
@ -10,6 +10,8 @@
|
|||||||
(thanks to [@tleb] in [#354]).
|
(thanks to [@tleb] in [#354]).
|
||||||
* Added tab-completion support for `/toggle` options
|
* Added tab-completion support for `/toggle` options
|
||||||
(thanks to [@n-peugnet] in [#362]).
|
(thanks to [@n-peugnet] in [#362]).
|
||||||
|
* Added initial support for rendering spoilers in messages.
|
||||||
|
* Fixed mentions being lost when editing messages.
|
||||||
* Fixed date change messages showing the wrong date.
|
* Fixed date change messages showing the wrong date.
|
||||||
* Fixed some whitespace in HTML being rendered even when it shouldn't.
|
* Fixed some whitespace in HTML being rendered even when it shouldn't.
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MessageRenderer interface {
|
type MessageRenderer interface {
|
||||||
Draw(screen mauview.Screen)
|
Draw(screen mauview.Screen, msg *UIMessage)
|
||||||
NotificationContent() string
|
NotificationContent() string
|
||||||
PlainText() string
|
PlainText() string
|
||||||
CalculateBuffer(prefs config.UserPreferences, width int, msg *UIMessage)
|
CalculateBuffer(prefs config.UserPreferences, width int, msg *UIMessage)
|
||||||
@ -324,7 +324,7 @@ func (msg *UIMessage) DrawReactions(screen mauview.Screen) {
|
|||||||
|
|
||||||
func (msg *UIMessage) Draw(screen mauview.Screen) {
|
func (msg *UIMessage) Draw(screen mauview.Screen) {
|
||||||
proxyScreen := msg.DrawReply(screen)
|
proxyScreen := msg.DrawReply(screen)
|
||||||
msg.Renderer.Draw(proxyScreen)
|
msg.Renderer.Draw(proxyScreen, msg)
|
||||||
msg.DrawReactions(proxyScreen)
|
msg.DrawReactions(proxyScreen)
|
||||||
if msg.IsSelected {
|
if msg.IsSelected {
|
||||||
w, h := screen.Size()
|
w, h := screen.Size()
|
||||||
|
@ -83,7 +83,7 @@ func (msg *ExpandedTextMessage) Height() int {
|
|||||||
return len(msg.buffer)
|
return len(msg.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *ExpandedTextMessage) Draw(screen mauview.Screen) {
|
func (msg *ExpandedTextMessage) Draw(screen mauview.Screen, _ *UIMessage) {
|
||||||
for y, line := range msg.buffer {
|
for y, line := range msg.buffer {
|
||||||
line.Draw(screen, 0, y)
|
line.Draw(screen, 0, y)
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ func (msg *FileMessage) Height() int {
|
|||||||
return len(msg.buffer)
|
return len(msg.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) Draw(screen mauview.Screen) {
|
func (msg *FileMessage) Draw(screen mauview.Screen, _ *UIMessage) {
|
||||||
for y, line := range msg.buffer {
|
for y, line := range msg.buffer {
|
||||||
line.Draw(screen, 0, y)
|
line.Draw(screen, 0, y)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ type BaseEntity struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AdjustStyle changes the style of this text entity.
|
// AdjustStyle changes the style of this text entity.
|
||||||
func (be *BaseEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (be *BaseEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
be.Style = fn(be.Style)
|
be.Style = fn(be.Style)
|
||||||
return be
|
return be
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ func (be *BaseEntity) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CalculateBuffer prepares this entity for rendering with the given parameters.
|
// CalculateBuffer prepares this entity for rendering with the given parameters.
|
||||||
func (be *BaseEntity) CalculateBuffer(width, startX int, bare bool) int {
|
func (be *BaseEntity) CalculateBuffer(width, startX int, ctx DrawContext) int {
|
||||||
be.height = be.DefaultHeight
|
be.height = be.DefaultHeight
|
||||||
be.startX = startX
|
be.startX = startX
|
||||||
if be.Block {
|
if be.Block {
|
||||||
@ -96,6 +96,6 @@ func (be *BaseEntity) CalculateBuffer(width, startX int, bare bool) int {
|
|||||||
return be.startX
|
return be.startX
|
||||||
}
|
}
|
||||||
|
|
||||||
func (be *BaseEntity) Draw(screen mauview.Screen) {
|
func (be *BaseEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
panic("Called Draw() of BaseEntity")
|
panic("Called Draw() of BaseEntity")
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,8 @@ func NewBlockquoteEntity(children []Entity) *BlockquoteEntity {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (be *BlockquoteEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (be *BlockquoteEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
be.BaseEntity = be.BaseEntity.AdjustStyle(fn).(*BaseEntity)
|
be.BaseEntity = be.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
|
||||||
return be
|
return be
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ func (be *BlockquoteEntity) Clone() Entity {
|
|||||||
return &BlockquoteEntity{ContainerEntity: be.ContainerEntity.Clone().(*ContainerEntity)}
|
return &BlockquoteEntity{ContainerEntity: be.ContainerEntity.Clone().(*ContainerEntity)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (be *BlockquoteEntity) Draw(screen mauview.Screen) {
|
func (be *BlockquoteEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
be.ContainerEntity.Draw(screen)
|
be.ContainerEntity.Draw(screen, ctx)
|
||||||
for y := 0; y < be.height; y++ {
|
for y := 0; y < be.height; y++ {
|
||||||
screen.SetContent(0, y, BlockQuoteChar, nil, be.Style)
|
screen.SetContent(0, y, BlockQuoteChar, nil, be.Style)
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ func NewBreakEntity() *BreakEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AdjustStyle changes the style of this text entity.
|
// AdjustStyle changes the style of this text entity.
|
||||||
func (be *BreakEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (be *BreakEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
be.BaseEntity = be.BaseEntity.AdjustStyle(fn).(*BaseEntity)
|
be.BaseEntity = be.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
|
||||||
return be
|
return be
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +49,6 @@ func (be *BreakEntity) String() string {
|
|||||||
return "&html.BreakEntity{},\n"
|
return "&html.BreakEntity{},\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (be *BreakEntity) Draw(screen mauview.Screen) {
|
func (be *BreakEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
// No-op, the logic happens in containers
|
// No-op, the logic happens in containers
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,14 @@ func (ce *CodeBlockEntity) Clone() Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ce *CodeBlockEntity) Draw(screen mauview.Screen) {
|
func (ce *CodeBlockEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
screen.Fill(' ', ce.Background)
|
screen.Fill(' ', ce.Background)
|
||||||
ce.ContainerEntity.Draw(screen)
|
ce.ContainerEntity.Draw(screen, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ce *CodeBlockEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (ce *CodeBlockEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
// Don't allow adjusting code block style.
|
if reason != AdjustStyleReasonNormal {
|
||||||
|
ce.ContainerEntity.AdjustStyle(fn, reason)
|
||||||
|
}
|
||||||
return ce
|
return ce
|
||||||
}
|
}
|
||||||
|
@ -61,15 +61,15 @@ func (ce *ContainerEntity) PlainText() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AdjustStyle recursively changes the style of this entity and all its children.
|
// AdjustStyle recursively changes the style of this entity and all its children.
|
||||||
func (ce *ContainerEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (ce *ContainerEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
for _, child := range ce.Children {
|
for _, child := range ce.Children {
|
||||||
child.AdjustStyle(fn)
|
child.AdjustStyle(fn, reason)
|
||||||
}
|
}
|
||||||
ce.Style = fn(ce.Style)
|
ce.Style = fn(ce.Style)
|
||||||
return ce
|
return ce
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone creates a deep copy of this base entity.
|
// Clone creates a deep copy of this base entity.
|
||||||
func (ce *ContainerEntity) Clone() Entity {
|
func (ce *ContainerEntity) Clone() Entity {
|
||||||
children := make([]Entity, len(ce.Children))
|
children := make([]Entity, len(ce.Children))
|
||||||
for i, child := range ce.Children {
|
for i, child := range ce.Children {
|
||||||
@ -98,7 +98,7 @@ func (ce *ContainerEntity) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws this entity onto the given mauview Screen.
|
// Draw draws this entity onto the given mauview Screen.
|
||||||
func (ce *ContainerEntity) Draw(screen mauview.Screen) {
|
func (ce *ContainerEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
if len(ce.Children) == 0 {
|
if len(ce.Children) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ func (ce *ContainerEntity) Draw(screen mauview.Screen) {
|
|||||||
proxyScreen.OffsetY++
|
proxyScreen.OffsetY++
|
||||||
}
|
}
|
||||||
proxyScreen.Height = entity.Height()
|
proxyScreen.Height = entity.Height()
|
||||||
entity.Draw(proxyScreen)
|
entity.Draw(proxyScreen, ctx)
|
||||||
proxyScreen.SetStyle(ce.Style)
|
proxyScreen.SetStyle(ce.Style)
|
||||||
proxyScreen.OffsetY += entity.Height() - 1
|
proxyScreen.OffsetY += entity.Height() - 1
|
||||||
_, isBreak := entity.(*BreakEntity)
|
_, isBreak := entity.(*BreakEntity)
|
||||||
@ -122,8 +122,8 @@ func (ce *ContainerEntity) Draw(screen mauview.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CalculateBuffer prepares this entity and all its children for rendering with the given parameters
|
// CalculateBuffer prepares this entity and all its children for rendering with the given parameters
|
||||||
func (ce *ContainerEntity) CalculateBuffer(width, startX int, bare bool) int {
|
func (ce *ContainerEntity) CalculateBuffer(width, startX int, ctx DrawContext) int {
|
||||||
ce.BaseEntity.CalculateBuffer(width, startX, bare)
|
ce.BaseEntity.CalculateBuffer(width, startX, ctx)
|
||||||
if len(ce.Children) > 0 {
|
if len(ce.Children) > 0 {
|
||||||
ce.height = 0
|
ce.height = 0
|
||||||
childStartX := ce.startX
|
childStartX := ce.startX
|
||||||
@ -132,7 +132,7 @@ func (ce *ContainerEntity) CalculateBuffer(width, startX int, bare bool) int {
|
|||||||
if entity.IsBlock() || childStartX == 0 || ce.height == 0 {
|
if entity.IsBlock() || childStartX == 0 || ce.height == 0 {
|
||||||
ce.height++
|
ce.height++
|
||||||
}
|
}
|
||||||
childStartX = entity.CalculateBuffer(width-ce.Indent, childStartX, bare)
|
childStartX = entity.CalculateBuffer(width-ce.Indent, childStartX, ctx)
|
||||||
ce.height += entity.Height() - 1
|
ce.height += entity.Height() - 1
|
||||||
_, isBreak := entity.(*BreakEntity)
|
_, isBreak := entity.(*BreakEntity)
|
||||||
if prevBreak && isBreak {
|
if prevBreak && isBreak {
|
||||||
|
@ -24,11 +24,23 @@ import (
|
|||||||
// AdjustStyleFunc is a lambda function type to edit an existing tcell Style.
|
// AdjustStyleFunc is a lambda function type to edit an existing tcell Style.
|
||||||
type AdjustStyleFunc func(tcell.Style) tcell.Style
|
type AdjustStyleFunc func(tcell.Style) tcell.Style
|
||||||
|
|
||||||
|
type AdjustStyleReason int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AdjustStyleReasonNormal AdjustStyleReason = iota
|
||||||
|
AdjustStyleReasonHideSpoiler
|
||||||
|
)
|
||||||
|
|
||||||
|
type DrawContext struct {
|
||||||
|
IsSelected bool
|
||||||
|
BareMessages bool
|
||||||
|
}
|
||||||
|
|
||||||
type Entity interface {
|
type Entity interface {
|
||||||
// AdjustStyle recursively changes the style of the entity and all its children.
|
// AdjustStyle recursively changes the style of the entity and all its children.
|
||||||
AdjustStyle(AdjustStyleFunc) Entity
|
AdjustStyle(AdjustStyleFunc, AdjustStyleReason) Entity
|
||||||
// Draw draws the entity onto the given mauview Screen.
|
// Draw draws the entity onto the given mauview Screen.
|
||||||
Draw(screen mauview.Screen)
|
Draw(screen mauview.Screen, ctx DrawContext)
|
||||||
// IsBlock returns whether or not it's a block-type entity.
|
// IsBlock returns whether or not it's a block-type entity.
|
||||||
IsBlock() bool
|
IsBlock() bool
|
||||||
// GetTag returns the HTML tag of the entity.
|
// GetTag returns the HTML tag of the entity.
|
||||||
@ -43,7 +55,7 @@ type Entity interface {
|
|||||||
// Height returns the render height of the entity.
|
// Height returns the render height of the entity.
|
||||||
Height() int
|
Height() int
|
||||||
// CalculateBuffer prepares the entity and all its children for rendering with the given parameters
|
// CalculateBuffer prepares the entity and all its children for rendering with the given parameters
|
||||||
CalculateBuffer(width, startX int, bare bool) int
|
CalculateBuffer(width, startX int, ctx DrawContext) int
|
||||||
|
|
||||||
getStartX() int
|
getStartX() int
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ func NewHorizontalLineEntity() *HorizontalLineEntity {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (he *HorizontalLineEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (he *HorizontalLineEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
he.BaseEntity = he.BaseEntity.AdjustStyle(fn).(*BaseEntity)
|
he.BaseEntity = he.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
|
||||||
return he
|
return he
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func (he *HorizontalLineEntity) Clone() Entity {
|
|||||||
return NewHorizontalLineEntity()
|
return NewHorizontalLineEntity()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (he *HorizontalLineEntity) Draw(screen mauview.Screen) {
|
func (he *HorizontalLineEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
width, _ := screen.Size()
|
width, _ := screen.Size()
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
screen.SetContent(x, 0, HorizontalLineChar, nil, he.Style)
|
screen.SetContent(x, 0, HorizontalLineChar, nil, he.Style)
|
||||||
|
@ -59,8 +59,9 @@ func NewListEntity(ordered bool, start int, children []Entity) *ListEntity {
|
|||||||
return entity
|
return entity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (le *ListEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (le *ListEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
le.BaseEntity = le.BaseEntity.AdjustStyle(fn).(*BaseEntity)
|
le.BaseEntity = le.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
|
||||||
|
le.ContainerEntity.AdjustStyle(fn, reason)
|
||||||
return le
|
return le
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ func (le *ListEntity) Clone() Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (le *ListEntity) Draw(screen mauview.Screen) {
|
func (le *ListEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
width, _ := screen.Size()
|
width, _ := screen.Size()
|
||||||
|
|
||||||
proxyScreen := &mauview.ProxyScreen{Parent: screen, OffsetX: le.Indent, Width: width - le.Indent, Style: le.Style}
|
proxyScreen := &mauview.ProxyScreen{Parent: screen, OffsetX: le.Indent, Width: width - le.Indent, Style: le.Style}
|
||||||
@ -85,7 +86,7 @@ func (le *ListEntity) Draw(screen mauview.Screen) {
|
|||||||
} else {
|
} else {
|
||||||
screen.SetContent(0, proxyScreen.OffsetY, '●', nil, le.Style)
|
screen.SetContent(0, proxyScreen.OffsetY, '●', nil, le.Style)
|
||||||
}
|
}
|
||||||
entity.Draw(proxyScreen)
|
entity.Draw(proxyScreen, ctx)
|
||||||
proxyScreen.SetStyle(le.Style)
|
proxyScreen.SetStyle(le.Style)
|
||||||
proxyScreen.OffsetY += entity.Height()
|
proxyScreen.OffsetY += entity.Height()
|
||||||
}
|
}
|
||||||
|
@ -75,22 +75,23 @@ func AdjustStyleBackgroundColor(color tcell.Color) func(tcell.Style) tcell.Style
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (parser *htmlParser) getAttribute(node *html.Node, attribute string) string {
|
func (parser *htmlParser) maybeGetAttribute(node *html.Node, attribute string) (string, bool) {
|
||||||
for _, attr := range node.Attr {
|
for _, attr := range node.Attr {
|
||||||
if attr.Key == attribute {
|
if attr.Key == attribute {
|
||||||
return attr.Val
|
return attr.Val, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *htmlParser) getAttribute(node *html.Node, attribute string) string {
|
||||||
|
val, _ := parser.maybeGetAttribute(node, attribute)
|
||||||
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (parser *htmlParser) hasAttribute(node *html.Node, attribute string) bool {
|
func (parser *htmlParser) hasAttribute(node *html.Node, attribute string) bool {
|
||||||
for _, attr := range node.Attr {
|
_, ok := parser.maybeGetAttribute(node, attribute)
|
||||||
if attr.Key == attribute {
|
return ok
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (parser *htmlParser) listToEntity(node *html.Node) Entity {
|
func (parser *htmlParser) listToEntity(node *html.Node) Entity {
|
||||||
@ -124,21 +125,25 @@ func (parser *htmlParser) basicFormatToEntity(node *html.Node) Entity {
|
|||||||
}
|
}
|
||||||
switch node.Data {
|
switch node.Data {
|
||||||
case "b", "strong":
|
case "b", "strong":
|
||||||
entity.AdjustStyle(AdjustStyleBold)
|
entity.AdjustStyle(AdjustStyleBold, AdjustStyleReasonNormal)
|
||||||
case "i", "em":
|
case "i", "em":
|
||||||
entity.AdjustStyle(AdjustStyleItalic)
|
entity.AdjustStyle(AdjustStyleItalic, AdjustStyleReasonNormal)
|
||||||
case "s", "del", "strike":
|
case "s", "del", "strike":
|
||||||
entity.AdjustStyle(AdjustStyleStrikethrough)
|
entity.AdjustStyle(AdjustStyleStrikethrough, AdjustStyleReasonNormal)
|
||||||
case "u", "ins":
|
case "u", "ins":
|
||||||
entity.AdjustStyle(AdjustStyleUnderline)
|
entity.AdjustStyle(AdjustStyleUnderline, AdjustStyleReasonNormal)
|
||||||
case "font", "span":
|
case "font", "span":
|
||||||
fgColor, ok := parser.parseColor(node, "data-mx-color", "color")
|
fgColor, ok := parser.parseColor(node, "data-mx-color", "color")
|
||||||
if ok {
|
if ok {
|
||||||
entity.AdjustStyle(AdjustStyleTextColor(fgColor))
|
entity.AdjustStyle(AdjustStyleTextColor(fgColor), AdjustStyleReasonNormal)
|
||||||
}
|
}
|
||||||
bgColor, ok := parser.parseColor(node, "data-mx-bg-color", "background-color")
|
bgColor, ok := parser.parseColor(node, "data-mx-bg-color", "background-color")
|
||||||
if ok {
|
if ok {
|
||||||
entity.AdjustStyle(AdjustStyleBackgroundColor(bgColor))
|
entity.AdjustStyle(AdjustStyleBackgroundColor(bgColor), AdjustStyleReasonNormal)
|
||||||
|
}
|
||||||
|
spoilerReason, isSpoiler := parser.maybeGetAttribute(node, "data-mx-spoiler")
|
||||||
|
if isSpoiler {
|
||||||
|
return NewSpoilerEntity(entity, spoilerReason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entity
|
return entity
|
||||||
@ -175,7 +180,7 @@ func (parser *htmlParser) headerToEntity(node *html.Node) Entity {
|
|||||||
[]Entity{NewTextEntity(strings.Repeat("#", int(node.Data[1]-'0')) + " ")},
|
[]Entity{NewTextEntity(strings.Repeat("#", int(node.Data[1]-'0')) + " ")},
|
||||||
parser.nodeToEntities(node.FirstChild)...,
|
parser.nodeToEntities(node.FirstChild)...,
|
||||||
),
|
),
|
||||||
}).AdjustStyle(AdjustStyleBold)
|
}).AdjustStyle(AdjustStyleBold, AdjustStyleReasonNormal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (parser *htmlParser) blockquoteToEntity(node *html.Node) Entity {
|
func (parser *htmlParser) blockquoteToEntity(node *html.Node) Entity {
|
||||||
@ -468,7 +473,7 @@ func Parse(prefs *config.UserPreferences, room *rooms.Room, content *event.Messa
|
|||||||
},
|
},
|
||||||
Children: []Entity{
|
Children: []Entity{
|
||||||
NewTextEntity("* "),
|
NewTextEntity("* "),
|
||||||
NewTextEntity(senderDisplayname).AdjustStyle(AdjustStyleTextColor(widget.GetHashColor(sender))),
|
NewTextEntity(senderDisplayname).AdjustStyle(AdjustStyleTextColor(widget.GetHashColor(sender)), AdjustStyleReasonNormal),
|
||||||
NewTextEntity(" "),
|
NewTextEntity(" "),
|
||||||
root,
|
root,
|
||||||
},
|
},
|
||||||
|
120
ui/messages/html/spoiler.go
Normal file
120
ui/messages/html/spoiler.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// gomuks - A terminal Matrix client written in Go.
|
||||||
|
// Copyright (C) 2022 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.mau.fi/mauview"
|
||||||
|
"go.mau.fi/tcell"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpoilerEntity struct {
|
||||||
|
reason string
|
||||||
|
hidden *ContainerEntity
|
||||||
|
visible *ContainerEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpoilerColor = tcell.ColorYellow
|
||||||
|
|
||||||
|
func NewSpoilerEntity(visible *ContainerEntity, reason string) *SpoilerEntity {
|
||||||
|
hidden := visible.Clone().(*ContainerEntity)
|
||||||
|
hidden.AdjustStyle(func(style tcell.Style) tcell.Style {
|
||||||
|
return style.Foreground(SpoilerColor).Background(SpoilerColor)
|
||||||
|
}, AdjustStyleReasonHideSpoiler)
|
||||||
|
if len(reason) > 0 {
|
||||||
|
reasonEnt := NewTextEntity(fmt.Sprintf("(%s)", reason))
|
||||||
|
hidden.Children = append([]Entity{reasonEnt}, hidden.Children...)
|
||||||
|
visible.Children = append([]Entity{reasonEnt}, visible.Children...)
|
||||||
|
}
|
||||||
|
return &SpoilerEntity{
|
||||||
|
reason: reason,
|
||||||
|
hidden: hidden,
|
||||||
|
visible: visible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) Clone() Entity {
|
||||||
|
return &SpoilerEntity{
|
||||||
|
reason: se.reason,
|
||||||
|
hidden: se.hidden.Clone().(*ContainerEntity),
|
||||||
|
visible: se.visible.Clone().(*ContainerEntity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) IsBlock() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) GetTag() string {
|
||||||
|
return "span"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
|
if ctx.IsSelected {
|
||||||
|
se.visible.Draw(screen, ctx)
|
||||||
|
} else {
|
||||||
|
se.hidden.Draw(screen, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
|
if reason != AdjustStyleReasonHideSpoiler {
|
||||||
|
se.hidden.AdjustStyle(func(style tcell.Style) tcell.Style {
|
||||||
|
return fn(style).Foreground(SpoilerColor).Background(SpoilerColor)
|
||||||
|
}, reason)
|
||||||
|
se.visible.AdjustStyle(fn, reason)
|
||||||
|
}
|
||||||
|
return se
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) PlainText() string {
|
||||||
|
if len(se.reason) > 0 {
|
||||||
|
return fmt.Sprintf("spoiler: %s", se.reason)
|
||||||
|
} else {
|
||||||
|
return "spoiler"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) String() string {
|
||||||
|
var buf strings.Builder
|
||||||
|
_, _ = fmt.Fprintf(&buf, `&html.SpoilerEntity{reason=%s`, se.reason)
|
||||||
|
buf.WriteString("\n visible=")
|
||||||
|
buf.WriteString(strings.Join(strings.Split(strings.TrimRight(se.visible.String(), "\n"), "\n"), "\n "))
|
||||||
|
buf.WriteString("\n hidden=")
|
||||||
|
buf.WriteString(strings.Join(strings.Split(strings.TrimRight(se.hidden.String(), "\n"), "\n"), "\n "))
|
||||||
|
buf.WriteString("\n]},")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) Height() int {
|
||||||
|
return se.visible.Height()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) CalculateBuffer(width, startX int, ctx DrawContext) int {
|
||||||
|
se.hidden.CalculateBuffer(width, startX, ctx)
|
||||||
|
return se.visible.CalculateBuffer(width, startX, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) getStartX() int {
|
||||||
|
return se.visible.getStartX()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *SpoilerEntity) IsEmpty() bool {
|
||||||
|
return se.visible.IsEmpty()
|
||||||
|
}
|
@ -49,8 +49,8 @@ func (te *TextEntity) IsEmpty() bool {
|
|||||||
return len(te.Text) == 0
|
return len(te.Text) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
|
func (te *TextEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
|
||||||
te.BaseEntity = te.BaseEntity.AdjustStyle(fn).(*BaseEntity)
|
te.BaseEntity = te.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
|
||||||
return te
|
return te
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ func (te *TextEntity) String() string {
|
|||||||
return fmt.Sprintf("&html.TextEntity{Text=%s, Base=%s},\n", te.Text, te.BaseEntity)
|
return fmt.Sprintf("&html.TextEntity{Text=%s, Base=%s},\n", te.Text, te.BaseEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntity) Draw(screen mauview.Screen) {
|
func (te *TextEntity) Draw(screen mauview.Screen, ctx DrawContext) {
|
||||||
width, _ := screen.Size()
|
width, _ := screen.Size()
|
||||||
x := te.startX
|
x := te.startX
|
||||||
for y, line := range te.buffer {
|
for y, line := range te.buffer {
|
||||||
@ -78,8 +78,8 @@ func (te *TextEntity) Draw(screen mauview.Screen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (te *TextEntity) CalculateBuffer(width, startX int, bare bool) int {
|
func (te *TextEntity) CalculateBuffer(width, startX int, ctx DrawContext) int {
|
||||||
te.BaseEntity.CalculateBuffer(width, startX, bare)
|
te.BaseEntity.CalculateBuffer(width, startX, ctx)
|
||||||
if len(te.Text) == 0 {
|
if len(te.Text) == 0 {
|
||||||
return te.startX
|
return te.startX
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ func (te *TextEntity) CalculateBuffer(width, startX int, bare bool) int {
|
|||||||
for {
|
for {
|
||||||
// TODO add option no wrap and character wrap options
|
// TODO add option no wrap and character wrap options
|
||||||
extract := runewidth.Truncate(text, width-textStartX, "")
|
extract := runewidth.Truncate(text, width-textStartX, "")
|
||||||
extract, wordWrapped := trim(extract, text, bare)
|
extract, wordWrapped := trim(extract, text, ctx.BareMessages)
|
||||||
if !wordWrapped && textStartX > 0 {
|
if !wordWrapped && textStartX > 0 {
|
||||||
if bufPtr < len(te.buffer) {
|
if bufPtr < len(te.buffer) {
|
||||||
te.buffer[bufPtr] = ""
|
te.buffer[bufPtr] = ""
|
||||||
|
@ -28,9 +28,7 @@ import (
|
|||||||
|
|
||||||
type HTMLMessage struct {
|
type HTMLMessage struct {
|
||||||
Root html.Entity
|
Root html.Entity
|
||||||
FocusedBg tcell.Color
|
|
||||||
TextColor tcell.Color
|
TextColor tcell.Color
|
||||||
focused bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTMLMessage(evt *muksevt.Event, displayname string, root html.Entity) *UIMessage {
|
func NewHTMLMessage(evt *muksevt.Event, displayname string, root html.Entity) *UIMessage {
|
||||||
@ -41,15 +39,11 @@ func NewHTMLMessage(evt *muksevt.Event, displayname string, root html.Entity) *U
|
|||||||
|
|
||||||
func (hw *HTMLMessage) Clone() MessageRenderer {
|
func (hw *HTMLMessage) Clone() MessageRenderer {
|
||||||
return &HTMLMessage{
|
return &HTMLMessage{
|
||||||
Root: hw.Root.Clone(),
|
Root: hw.Root.Clone(),
|
||||||
FocusedBg: hw.FocusedBg,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hw *HTMLMessage) Draw(screen mauview.Screen) {
|
func (hw *HTMLMessage) Draw(screen mauview.Screen, msg *UIMessage) {
|
||||||
if hw.focused {
|
|
||||||
screen.SetStyle(tcell.StyleDefault.Background(hw.FocusedBg).Foreground(hw.TextColor))
|
|
||||||
}
|
|
||||||
if hw.TextColor != tcell.ColorDefault {
|
if hw.TextColor != tcell.ColorDefault {
|
||||||
hw.Root.AdjustStyle(func(style tcell.Style) tcell.Style {
|
hw.Root.AdjustStyle(func(style tcell.Style) tcell.Style {
|
||||||
fg, _, _ := style.Decompose()
|
fg, _, _ := style.Decompose()
|
||||||
@ -57,18 +51,10 @@ func (hw *HTMLMessage) Draw(screen mauview.Screen) {
|
|||||||
return style.Foreground(hw.TextColor)
|
return style.Foreground(hw.TextColor)
|
||||||
}
|
}
|
||||||
return style
|
return style
|
||||||
})
|
}, html.AdjustStyleReasonNormal)
|
||||||
}
|
}
|
||||||
screen.Clear()
|
screen.Clear()
|
||||||
hw.Root.Draw(screen)
|
hw.Root.Draw(screen, html.DrawContext{IsSelected: msg.IsSelected})
|
||||||
}
|
|
||||||
|
|
||||||
func (hw *HTMLMessage) Focus() {
|
|
||||||
hw.focused = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hw *HTMLMessage) Blur() {
|
|
||||||
hw.focused = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hw *HTMLMessage) OnKeyEvent(event mauview.KeyEvent) bool {
|
func (hw *HTMLMessage) OnKeyEvent(event mauview.KeyEvent) bool {
|
||||||
@ -90,7 +76,10 @@ func (hw *HTMLMessage) CalculateBuffer(preferences config.UserPreferences, width
|
|||||||
// TODO account for bare messages in initial startX
|
// TODO account for bare messages in initial startX
|
||||||
startX := 0
|
startX := 0
|
||||||
hw.TextColor = msg.TextColor()
|
hw.TextColor = msg.TextColor()
|
||||||
hw.Root.CalculateBuffer(width, startX, preferences.BareMessageView)
|
hw.Root.CalculateBuffer(width, startX, html.DrawContext{
|
||||||
|
IsSelected: msg.IsSelected,
|
||||||
|
BareMessages: preferences.BareMessageView,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hw *HTMLMessage) Height() int {
|
func (hw *HTMLMessage) Height() int {
|
||||||
|
@ -59,7 +59,7 @@ const RedactionMaxWidth = 40
|
|||||||
|
|
||||||
var RedactionStyle = tcell.StyleDefault.Foreground(tcell.NewRGBColor(50, 0, 0))
|
var RedactionStyle = tcell.StyleDefault.Foreground(tcell.NewRGBColor(50, 0, 0))
|
||||||
|
|
||||||
func (msg *RedactedMessage) Draw(screen mauview.Screen) {
|
func (msg *RedactedMessage) Draw(screen mauview.Screen, _ *UIMessage) {
|
||||||
w, _ := screen.Size()
|
w, _ := screen.Size()
|
||||||
for x := 0; x < w && x < RedactionMaxWidth; x++ {
|
for x := 0; x < w && x < RedactionMaxWidth; x++ {
|
||||||
screen.SetContent(x, 0, RedactionChar, nil, RedactionStyle)
|
screen.SetContent(x, 0, RedactionChar, nil, RedactionStyle)
|
||||||
|
@ -21,9 +21,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.mau.fi/mauview"
|
"go.mau.fi/mauview"
|
||||||
"maunium.net/go/gomuks/matrix/muksevt"
|
|
||||||
|
|
||||||
"maunium.net/go/gomuks/config"
|
"maunium.net/go/gomuks/config"
|
||||||
|
"maunium.net/go/gomuks/matrix/muksevt"
|
||||||
"maunium.net/go/gomuks/ui/messages/tstring"
|
"maunium.net/go/gomuks/ui/messages/tstring"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ func (msg *TextMessage) Height() int {
|
|||||||
return len(msg.buffer)
|
return len(msg.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *TextMessage) Draw(screen mauview.Screen) {
|
func (msg *TextMessage) Draw(screen mauview.Screen, _ *UIMessage) {
|
||||||
for y, line := range msg.buffer {
|
for y, line := range msg.buffer {
|
||||||
line.Draw(screen, 0, y)
|
line.Draw(screen, 0, y)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user