Add support for rendering spoilers. Fixes #331
This commit is contained in:
		@@ -34,7 +34,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MessageRenderer interface {
 | 
			
		||||
	Draw(screen mauview.Screen)
 | 
			
		||||
	Draw(screen mauview.Screen, msg *UIMessage)
 | 
			
		||||
	NotificationContent() string
 | 
			
		||||
	PlainText() string
 | 
			
		||||
	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) {
 | 
			
		||||
	proxyScreen := msg.DrawReply(screen)
 | 
			
		||||
	msg.Renderer.Draw(proxyScreen)
 | 
			
		||||
	msg.Renderer.Draw(proxyScreen, msg)
 | 
			
		||||
	msg.DrawReactions(proxyScreen)
 | 
			
		||||
	if msg.IsSelected {
 | 
			
		||||
		w, h := screen.Size()
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ func (msg *ExpandedTextMessage) Height() int {
 | 
			
		||||
	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 {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -170,7 +170,7 @@ func (msg *FileMessage) Height() int {
 | 
			
		||||
	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 {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ type BaseEntity struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
	return be
 | 
			
		||||
}
 | 
			
		||||
@@ -87,7 +87,7 @@ func (be *BaseEntity) String() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.startX = startX
 | 
			
		||||
	if be.Block {
 | 
			
		||||
@@ -96,6 +96,6 @@ func (be *BaseEntity) CalculateBuffer(width, startX int, bare bool) int {
 | 
			
		||||
	return be.startX
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (be *BaseEntity) Draw(screen mauview.Screen) {
 | 
			
		||||
func (be *BaseEntity) Draw(screen mauview.Screen, ctx DrawContext) {
 | 
			
		||||
	panic("Called Draw() of BaseEntity")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,8 @@ func NewBlockquoteEntity(children []Entity) *BlockquoteEntity {
 | 
			
		||||
	}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (be *BlockquoteEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
 | 
			
		||||
	be.BaseEntity = be.BaseEntity.AdjustStyle(fn).(*BaseEntity)
 | 
			
		||||
func (be *BlockquoteEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
 | 
			
		||||
	be.BaseEntity = be.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
 | 
			
		||||
	return be
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -49,8 +49,8 @@ func (be *BlockquoteEntity) Clone() Entity {
 | 
			
		||||
	return &BlockquoteEntity{ContainerEntity: be.ContainerEntity.Clone().(*ContainerEntity)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (be *BlockquoteEntity) Draw(screen mauview.Screen) {
 | 
			
		||||
	be.ContainerEntity.Draw(screen)
 | 
			
		||||
func (be *BlockquoteEntity) Draw(screen mauview.Screen, ctx DrawContext) {
 | 
			
		||||
	be.ContainerEntity.Draw(screen, ctx)
 | 
			
		||||
	for y := 0; y < be.height; y++ {
 | 
			
		||||
		screen.SetContent(0, y, BlockQuoteChar, nil, be.Style)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,8 +32,8 @@ func NewBreakEntity() *BreakEntity {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AdjustStyle changes the style of this text entity.
 | 
			
		||||
func (be *BreakEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
 | 
			
		||||
	be.BaseEntity = be.BaseEntity.AdjustStyle(fn).(*BaseEntity)
 | 
			
		||||
func (be *BreakEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
 | 
			
		||||
	be.BaseEntity = be.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
 | 
			
		||||
	return be
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -49,6 +49,6 @@ func (be *BreakEntity) String() string {
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
	ce.ContainerEntity.Draw(screen)
 | 
			
		||||
	ce.ContainerEntity.Draw(screen, ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ce *CodeBlockEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
 | 
			
		||||
	// Don't allow adjusting code block style.
 | 
			
		||||
func (ce *CodeBlockEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
 | 
			
		||||
	if reason != AdjustStyleReasonNormal {
 | 
			
		||||
		ce.ContainerEntity.AdjustStyle(fn, reason)
 | 
			
		||||
	}
 | 
			
		||||
	return ce
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,15 +61,15 @@ func (ce *ContainerEntity) PlainText() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
		child.AdjustStyle(fn)
 | 
			
		||||
		child.AdjustStyle(fn, reason)
 | 
			
		||||
	}
 | 
			
		||||
	ce.Style = fn(ce.Style)
 | 
			
		||||
	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 {
 | 
			
		||||
	children := make([]Entity, len(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.
 | 
			
		||||
func (ce *ContainerEntity) Draw(screen mauview.Screen) {
 | 
			
		||||
func (ce *ContainerEntity) Draw(screen mauview.Screen, ctx DrawContext) {
 | 
			
		||||
	if len(ce.Children) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -110,7 +110,7 @@ func (ce *ContainerEntity) Draw(screen mauview.Screen) {
 | 
			
		||||
			proxyScreen.OffsetY++
 | 
			
		||||
		}
 | 
			
		||||
		proxyScreen.Height = entity.Height()
 | 
			
		||||
		entity.Draw(proxyScreen)
 | 
			
		||||
		entity.Draw(proxyScreen, ctx)
 | 
			
		||||
		proxyScreen.SetStyle(ce.Style)
 | 
			
		||||
		proxyScreen.OffsetY += entity.Height() - 1
 | 
			
		||||
		_, 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
 | 
			
		||||
func (ce *ContainerEntity) CalculateBuffer(width, startX int, bare bool) int {
 | 
			
		||||
	ce.BaseEntity.CalculateBuffer(width, startX, bare)
 | 
			
		||||
func (ce *ContainerEntity) CalculateBuffer(width, startX int, ctx DrawContext) int {
 | 
			
		||||
	ce.BaseEntity.CalculateBuffer(width, startX, ctx)
 | 
			
		||||
	if len(ce.Children) > 0 {
 | 
			
		||||
		ce.height = 0
 | 
			
		||||
		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 {
 | 
			
		||||
				ce.height++
 | 
			
		||||
			}
 | 
			
		||||
			childStartX = entity.CalculateBuffer(width-ce.Indent, childStartX, bare)
 | 
			
		||||
			childStartX = entity.CalculateBuffer(width-ce.Indent, childStartX, ctx)
 | 
			
		||||
			ce.height += entity.Height() - 1
 | 
			
		||||
			_, isBreak := entity.(*BreakEntity)
 | 
			
		||||
			if prevBreak && isBreak {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,23 @@ import (
 | 
			
		||||
// AdjustStyleFunc is a lambda function type to edit an existing 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 {
 | 
			
		||||
	// 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(screen mauview.Screen)
 | 
			
		||||
	Draw(screen mauview.Screen, ctx DrawContext)
 | 
			
		||||
	// IsBlock returns whether or not it's a block-type entity.
 | 
			
		||||
	IsBlock() bool
 | 
			
		||||
	// GetTag returns the HTML tag of the entity.
 | 
			
		||||
@@ -43,7 +55,7 @@ type Entity interface {
 | 
			
		||||
	// Height returns the render height of the entity.
 | 
			
		||||
	Height() int
 | 
			
		||||
	// 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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,8 +36,8 @@ func NewHorizontalLineEntity() *HorizontalLineEntity {
 | 
			
		||||
	}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (he *HorizontalLineEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
 | 
			
		||||
	he.BaseEntity = he.BaseEntity.AdjustStyle(fn).(*BaseEntity)
 | 
			
		||||
func (he *HorizontalLineEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
 | 
			
		||||
	he.BaseEntity = he.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
 | 
			
		||||
	return he
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +45,7 @@ func (he *HorizontalLineEntity) Clone() Entity {
 | 
			
		||||
	return NewHorizontalLineEntity()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (he *HorizontalLineEntity) Draw(screen mauview.Screen) {
 | 
			
		||||
func (he *HorizontalLineEntity) Draw(screen mauview.Screen, ctx DrawContext) {
 | 
			
		||||
	width, _ := screen.Size()
 | 
			
		||||
	for x := 0; x < width; x++ {
 | 
			
		||||
		screen.SetContent(x, 0, HorizontalLineChar, nil, he.Style)
 | 
			
		||||
 
 | 
			
		||||
@@ -59,8 +59,9 @@ func NewListEntity(ordered bool, start int, children []Entity) *ListEntity {
 | 
			
		||||
	return entity
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (le *ListEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
 | 
			
		||||
	le.BaseEntity = le.BaseEntity.AdjustStyle(fn).(*BaseEntity)
 | 
			
		||||
func (le *ListEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
 | 
			
		||||
	le.BaseEntity = le.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
 | 
			
		||||
	le.ContainerEntity.AdjustStyle(fn, reason)
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
			screen.SetContent(0, proxyScreen.OffsetY, '●', nil, le.Style)
 | 
			
		||||
		}
 | 
			
		||||
		entity.Draw(proxyScreen)
 | 
			
		||||
		entity.Draw(proxyScreen, ctx)
 | 
			
		||||
		proxyScreen.SetStyle(le.Style)
 | 
			
		||||
		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 {
 | 
			
		||||
		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 {
 | 
			
		||||
	for _, attr := range node.Attr {
 | 
			
		||||
		if attr.Key == attribute {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
	_, ok := parser.maybeGetAttribute(node, attribute)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (parser *htmlParser) listToEntity(node *html.Node) Entity {
 | 
			
		||||
@@ -124,21 +125,25 @@ func (parser *htmlParser) basicFormatToEntity(node *html.Node) Entity {
 | 
			
		||||
	}
 | 
			
		||||
	switch node.Data {
 | 
			
		||||
	case "b", "strong":
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleBold)
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleBold, AdjustStyleReasonNormal)
 | 
			
		||||
	case "i", "em":
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleItalic)
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleItalic, AdjustStyleReasonNormal)
 | 
			
		||||
	case "s", "del", "strike":
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleStrikethrough)
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleStrikethrough, AdjustStyleReasonNormal)
 | 
			
		||||
	case "u", "ins":
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleUnderline)
 | 
			
		||||
		entity.AdjustStyle(AdjustStyleUnderline, AdjustStyleReasonNormal)
 | 
			
		||||
	case "font", "span":
 | 
			
		||||
		fgColor, ok := parser.parseColor(node, "data-mx-color", "color")
 | 
			
		||||
		if ok {
 | 
			
		||||
			entity.AdjustStyle(AdjustStyleTextColor(fgColor))
 | 
			
		||||
			entity.AdjustStyle(AdjustStyleTextColor(fgColor), AdjustStyleReasonNormal)
 | 
			
		||||
		}
 | 
			
		||||
		bgColor, ok := parser.parseColor(node, "data-mx-bg-color", "background-color")
 | 
			
		||||
		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
 | 
			
		||||
@@ -175,7 +180,7 @@ func (parser *htmlParser) headerToEntity(node *html.Node) Entity {
 | 
			
		||||
			[]Entity{NewTextEntity(strings.Repeat("#", int(node.Data[1]-'0')) + " ")},
 | 
			
		||||
			parser.nodeToEntities(node.FirstChild)...,
 | 
			
		||||
		),
 | 
			
		||||
	}).AdjustStyle(AdjustStyleBold)
 | 
			
		||||
	}).AdjustStyle(AdjustStyleBold, AdjustStyleReasonNormal)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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{
 | 
			
		||||
				NewTextEntity("* "),
 | 
			
		||||
				NewTextEntity(senderDisplayname).AdjustStyle(AdjustStyleTextColor(widget.GetHashColor(sender))),
 | 
			
		||||
				NewTextEntity(senderDisplayname).AdjustStyle(AdjustStyleTextColor(widget.GetHashColor(sender)), AdjustStyleReasonNormal),
 | 
			
		||||
				NewTextEntity(" "),
 | 
			
		||||
				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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (te *TextEntity) AdjustStyle(fn AdjustStyleFunc) Entity {
 | 
			
		||||
	te.BaseEntity = te.BaseEntity.AdjustStyle(fn).(*BaseEntity)
 | 
			
		||||
func (te *TextEntity) AdjustStyle(fn AdjustStyleFunc, reason AdjustStyleReason) Entity {
 | 
			
		||||
	te.BaseEntity = te.BaseEntity.AdjustStyle(fn, reason).(*BaseEntity)
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (te *TextEntity) Draw(screen mauview.Screen) {
 | 
			
		||||
func (te *TextEntity) Draw(screen mauview.Screen, ctx DrawContext) {
 | 
			
		||||
	width, _ := screen.Size()
 | 
			
		||||
	x := te.startX
 | 
			
		||||
	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 {
 | 
			
		||||
	te.BaseEntity.CalculateBuffer(width, startX, bare)
 | 
			
		||||
func (te *TextEntity) CalculateBuffer(width, startX int, ctx DrawContext) int {
 | 
			
		||||
	te.BaseEntity.CalculateBuffer(width, startX, ctx)
 | 
			
		||||
	if len(te.Text) == 0 {
 | 
			
		||||
		return te.startX
 | 
			
		||||
	}
 | 
			
		||||
@@ -94,7 +94,7 @@ func (te *TextEntity) CalculateBuffer(width, startX int, bare bool) int {
 | 
			
		||||
	for {
 | 
			
		||||
		// TODO add option no wrap and character wrap options
 | 
			
		||||
		extract := runewidth.Truncate(text, width-textStartX, "")
 | 
			
		||||
		extract, wordWrapped := trim(extract, text, bare)
 | 
			
		||||
		extract, wordWrapped := trim(extract, text, ctx.BareMessages)
 | 
			
		||||
		if !wordWrapped && textStartX > 0 {
 | 
			
		||||
			if bufPtr < len(te.buffer) {
 | 
			
		||||
				te.buffer[bufPtr] = ""
 | 
			
		||||
 
 | 
			
		||||
@@ -28,9 +28,7 @@ import (
 | 
			
		||||
 | 
			
		||||
type HTMLMessage struct {
 | 
			
		||||
	Root      html.Entity
 | 
			
		||||
	FocusedBg tcell.Color
 | 
			
		||||
	TextColor tcell.Color
 | 
			
		||||
	focused   bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	return &HTMLMessage{
 | 
			
		||||
		Root:      hw.Root.Clone(),
 | 
			
		||||
		FocusedBg: hw.FocusedBg,
 | 
			
		||||
		Root: hw.Root.Clone(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
	if hw.focused {
 | 
			
		||||
		screen.SetStyle(tcell.StyleDefault.Background(hw.FocusedBg).Foreground(hw.TextColor))
 | 
			
		||||
	}
 | 
			
		||||
func (hw *HTMLMessage) Draw(screen mauview.Screen, msg *UIMessage) {
 | 
			
		||||
	if hw.TextColor != tcell.ColorDefault {
 | 
			
		||||
		hw.Root.AdjustStyle(func(style tcell.Style) tcell.Style {
 | 
			
		||||
			fg, _, _ := style.Decompose()
 | 
			
		||||
@@ -57,18 +51,10 @@ func (hw *HTMLMessage) Draw(screen mauview.Screen) {
 | 
			
		||||
				return style.Foreground(hw.TextColor)
 | 
			
		||||
			}
 | 
			
		||||
			return style
 | 
			
		||||
		})
 | 
			
		||||
		}, html.AdjustStyleReasonNormal)
 | 
			
		||||
	}
 | 
			
		||||
	screen.Clear()
 | 
			
		||||
	hw.Root.Draw(screen)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Focus() {
 | 
			
		||||
	hw.focused = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (hw *HTMLMessage) Blur() {
 | 
			
		||||
	hw.focused = false
 | 
			
		||||
	hw.Root.Draw(screen, html.DrawContext{IsSelected: msg.IsSelected})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	startX := 0
 | 
			
		||||
	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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ const RedactionMaxWidth = 40
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
	for x := 0; x < w && x < RedactionMaxWidth; x++ {
 | 
			
		||||
		screen.SetContent(x, 0, RedactionChar, nil, RedactionStyle)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,9 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"go.mau.fi/mauview"
 | 
			
		||||
	"maunium.net/go/gomuks/matrix/muksevt"
 | 
			
		||||
 | 
			
		||||
	"maunium.net/go/gomuks/config"
 | 
			
		||||
	"maunium.net/go/gomuks/matrix/muksevt"
 | 
			
		||||
	"maunium.net/go/gomuks/ui/messages/tstring"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +95,7 @@ func (msg *TextMessage) Height() int {
 | 
			
		||||
	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 {
 | 
			
		||||
		line.Draw(screen, 0, y)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user