mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-10-31 19:44:23 +00:00
bumped discord; partial message reference support
This commit is contained in:
parent
744f59cf38
commit
c8f5446710
4
go.mod
4
go.mod
|
@ -4,8 +4,8 @@ go 1.14
|
||||||
|
|
||||||
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20201230071527-a77c32eb3876
|
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20201230071527-a77c32eb3876
|
||||||
|
|
||||||
// replace github.com/diamondburned/gotk3-tcmalloc => ../../gotk3-tcmalloc
|
|
||||||
// replace github.com/diamondburned/cchat-discord => ../cchat-discord
|
// replace github.com/diamondburned/cchat-discord => ../cchat-discord
|
||||||
|
// replace github.com/diamondburned/gotk3-tcmalloc => ../../gotk3-tcmalloc
|
||||||
// replace github.com/diamondburned/ningen/v2 => ../../ningen
|
// replace github.com/diamondburned/ningen/v2 => ../../ningen
|
||||||
// replace github.com/diamondburned/arikawa/v2 => ../../arikawa
|
// replace github.com/diamondburned/arikawa/v2 => ../../arikawa
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ require (
|
||||||
github.com/Xuanwo/go-locale v1.0.0
|
github.com/Xuanwo/go-locale v1.0.0
|
||||||
github.com/alecthomas/chroma v0.7.3
|
github.com/alecthomas/chroma v0.7.3
|
||||||
github.com/diamondburned/cchat v0.3.15
|
github.com/diamondburned/cchat v0.3.15
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092
|
github.com/diamondburned/cchat-discord v0.0.0-20201231025836-96e97aa11705
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db
|
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db
|
||||||
github.com/diamondburned/gspell v0.0.0-20201229064336-e43698fd5828
|
github.com/diamondburned/gspell v0.0.0-20201229064336-e43698fd5828
|
||||||
github.com/diamondburned/handy v0.0.0-20201229063418-ec23c1370374
|
github.com/diamondburned/handy v0.0.0-20201229063418-ec23c1370374
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -66,6 +66,8 @@ github.com/diamondburned/cchat-discord v0.0.0-20201227023505-c4e360010fb8 h1:eyK
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201227023505-c4e360010fb8/go.mod h1:i3y8dyAFrtigpGOwunBdoJK/phwt9Gp/wfpVJb4imV0=
|
github.com/diamondburned/cchat-discord v0.0.0-20201227023505-c4e360010fb8/go.mod h1:i3y8dyAFrtigpGOwunBdoJK/phwt9Gp/wfpVJb4imV0=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092 h1:oxY7APUclLgaWjaTK++7kHBdl0GdVyqOvHQv68TcpHw=
|
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092 h1:oxY7APUclLgaWjaTK++7kHBdl0GdVyqOvHQv68TcpHw=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092/go.mod h1:rFBGZYLq0g6Pb/WGN/K0++kXrhCYlQQ1nc2FX4r8CO0=
|
github.com/diamondburned/cchat-discord v0.0.0-20201227035212-6beff5225092/go.mod h1:rFBGZYLq0g6Pb/WGN/K0++kXrhCYlQQ1nc2FX4r8CO0=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20201231025836-96e97aa11705 h1:g0hwnUpeJ3yo7WaVZjWBQw875tnKVjCz4YofnamE9Fg=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20201231025836-96e97aa11705/go.mod h1:rFBGZYLq0g6Pb/WGN/K0++kXrhCYlQQ1nc2FX4r8CO0=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2PdbsdsRJ7d669kp35GbCUO44KZ0Xfqdu4o/oqVg=
|
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2PdbsdsRJ7d669kp35GbCUO44KZ0Xfqdu4o/oqVg=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc=
|
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw=
|
github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw=
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package cozy
|
package cozy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
|
||||||
|
@ -85,7 +82,7 @@ func (c *Container) NewMessage(msg cchat.MessageCreate) container.GridMessage {
|
||||||
func (c *Container) NewPresendMessage(msg input.PresendMessage) container.PresendGridMessage {
|
func (c *Container) NewPresendMessage(msg input.PresendMessage) container.PresendGridMessage {
|
||||||
// We can do the check here since we're never using NewPresendMessage for
|
// We can do the check here since we're never using NewPresendMessage for
|
||||||
// backlog messages.
|
// backlog messages.
|
||||||
if c.lastMessageIsAuthor(msg.AuthorID(), 0) {
|
if c.lastMessageIsAuthor(msg.AuthorID(), msg.Author().String(), 0) {
|
||||||
return NewCollapsedSendingMessage(msg)
|
return NewCollapsedSendingMessage(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,53 +120,52 @@ func (c *Container) reuseAvatar(authorID, avatarURL string, full *FullMessage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) lastMessageIsAuthor(id string, offset int) bool {
|
func (c *Container) lastMessageIsAuthor(id cchat.ID, name string, offset int) bool {
|
||||||
// Get the offfsetth message from last.
|
// Get the offfsetth message from last.
|
||||||
var last = c.GridStore.NthMessage((c.GridStore.MessagesLen() - 1) + offset)
|
var last = c.GridStore.NthMessage((c.GridStore.MessagesLen() - 1) + offset)
|
||||||
return last != nil && last.AuthorID() == id
|
return gridMessageIsAuthor(last, id, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var createMessageLabel = pprof.Labels("cozy", "createMessage")
|
func gridMessageIsAuthor(gridMsg container.GridMessage, id cchat.ID, name string) bool {
|
||||||
|
return gridMsg != nil &&
|
||||||
|
gridMsg.AuthorID() == id &&
|
||||||
|
gridMsg.AuthorName() == name
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
|
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
|
||||||
gts.ExecAsync(func() {
|
gts.ExecAsync(func() {
|
||||||
pprof.Do(context.Background(), createMessageLabel, func(context.Context) {
|
// Create the message in the parent's handler. This handler will also
|
||||||
|
// wipe old messages.
|
||||||
|
c.GridContainer.CreateMessageUnsafe(msg)
|
||||||
|
|
||||||
// Create the message in the parent's handler. This handler will also
|
// Did the handler wipe old messages? It will only do so if the user is
|
||||||
// wipe old messages.
|
// scrolled to the bottom.
|
||||||
c.GridContainer.CreateMessageUnsafe(msg)
|
if c.GridContainer.CleanMessages() {
|
||||||
|
// We need to uncollapse the first (top) message. No length check is
|
||||||
|
// needed here, as we just inserted a message.
|
||||||
|
c.uncompact(c.FirstMessage())
|
||||||
|
}
|
||||||
|
|
||||||
// Did the handler wipe old messages? It will only do so if the user is
|
switch msg.ID() {
|
||||||
// scrolled to the bottom.
|
// Should we collapse this message? Yes, if the current message is
|
||||||
if c.GridContainer.CleanMessages() {
|
// inserted at the end and its author is the same as the last author.
|
||||||
// We need to uncollapse the first (top) message. No length check is
|
case c.GridContainer.LastMessage().ID():
|
||||||
// needed here, as we just inserted a message.
|
author := msg.Author()
|
||||||
c.uncompact(c.FirstMessage())
|
if c.lastMessageIsAuthor(author.ID(), author.Name().String(), -1) {
|
||||||
|
c.compact(c.GridContainer.LastMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.ID() {
|
// If we've prepended the message, then see if we need to collapse the
|
||||||
// Should we collapse this message? Yes, if the current message is
|
// second message.
|
||||||
// inserted at the end and its author is the same as the last author.
|
case c.GridContainer.FirstMessage().ID():
|
||||||
case c.GridContainer.LastMessage().ID():
|
if sec := c.NthMessage(1); sec != nil {
|
||||||
if c.lastMessageIsAuthor(msg.Author().ID(), -1) {
|
// The author is the same; collapse.
|
||||||
c.compact(c.GridContainer.LastMessage())
|
author := msg.Author()
|
||||||
}
|
if gridMessageIsAuthor(sec, author.ID(), author.Name().String()) {
|
||||||
|
|
||||||
// If we've prepended the message, then see if we need to collapse the
|
|
||||||
// second message.
|
|
||||||
case c.GridContainer.FirstMessage().ID():
|
|
||||||
if sec := c.NthMessage(1); sec != nil {
|
|
||||||
// If the author isn't the same, then ignore.
|
|
||||||
if sec.AuthorID() != msg.Author().ID() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The author is the same; collapse.
|
|
||||||
c.compact(sec)
|
c.compact(sec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ func WrapCollapsedMessage(gc *message.GenericContainer) *CollapsedMessage {
|
||||||
// Set Content's padding accordingly to FullMessage's main box.
|
// Set Content's padding accordingly to FullMessage's main box.
|
||||||
gc.Content.ToWidget().SetMarginEnd(container.ColumnSpacing * 2)
|
gc.Content.ToWidget().SetMarginEnd(container.ColumnSpacing * 2)
|
||||||
|
|
||||||
|
gc.Username.SetMaxWidthChars(30)
|
||||||
|
|
||||||
return &CollapsedMessage{
|
return &CollapsedMessage{
|
||||||
GenericContainer: gc,
|
GenericContainer: gc,
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,8 @@ func WrapFullMessage(gc *message.GenericContainer) *FullMessage {
|
||||||
gc.Timestamp.SetVAlign(gtk.ALIGN_END) // bottom-align
|
gc.Timestamp.SetVAlign(gtk.ALIGN_END) // bottom-align
|
||||||
gc.Timestamp.SetMarginStart(0) // clear margins
|
gc.Timestamp.SetMarginStart(0) // clear margins
|
||||||
|
|
||||||
|
gc.Username.SetMaxWidthChars(75)
|
||||||
|
|
||||||
// Attach the class and CSS for the left avatar.
|
// Attach the class and CSS for the left avatar.
|
||||||
avatarCSS(avatar)
|
avatarCSS(avatar)
|
||||||
|
|
||||||
|
|
|
@ -364,6 +364,10 @@ func NewMember(member cchat.ListMember) *Member {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noMentionLinks = markup.RenderConfig{
|
||||||
|
NoMentionLinks: true,
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Member) Update(member cchat.ListMember) {
|
func (m *Member) Update(member cchat.ListMember) {
|
||||||
m.ListBoxRow.SetName(member.Name().Content)
|
m.ListBoxRow.SetName(member.Name().Content)
|
||||||
|
|
||||||
|
@ -371,7 +375,7 @@ func (m *Member) Update(member cchat.ListMember) {
|
||||||
m.Avatar.AsyncSetIconer(iconer, "Failed to get member list icon")
|
m.Avatar.AsyncSetIconer(iconer, "Failed to get member list icon")
|
||||||
}
|
}
|
||||||
|
|
||||||
m.output = markup.RenderCmplxWithConfig(member.Name(), markup.NoMentionLinks)
|
m.output = markup.RenderCmplxWithConfig(member.Name(), noMentionLinks)
|
||||||
txt := strings.Builder{}
|
txt := strings.Builder{}
|
||||||
txt.WriteString(fmt.Sprintf(
|
txt.WriteString(fmt.Sprintf(
|
||||||
`<span color="#%06X">●</span> %s`,
|
`<span color="#%06X">●</span> %s`,
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Container interface {
|
||||||
ID() string
|
ID() string
|
||||||
Time() time.Time
|
Time() time.Time
|
||||||
AuthorID() string
|
AuthorID() string
|
||||||
|
AuthorName() string
|
||||||
AvatarURL() string // avatar
|
AvatarURL() string // avatar
|
||||||
Nonce() string
|
Nonce() string
|
||||||
|
|
||||||
|
@ -47,11 +48,12 @@ func RefreshContainer(c Container, gc *GenericContainer) {
|
||||||
// GenericContainer provides a single generic message container for subpackages
|
// GenericContainer provides a single generic message container for subpackages
|
||||||
// to use.
|
// to use.
|
||||||
type GenericContainer struct {
|
type GenericContainer struct {
|
||||||
id string
|
id string
|
||||||
time time.Time
|
time time.Time
|
||||||
authorID string
|
authorID string
|
||||||
avatarURL string // avatar
|
authorName string
|
||||||
nonce string
|
avatarURL string // avatar
|
||||||
|
nonce string
|
||||||
|
|
||||||
Timestamp *gtk.Label
|
Timestamp *gtk.Label
|
||||||
Username *labeluri.Label
|
Username *labeluri.Label
|
||||||
|
@ -94,7 +96,6 @@ func NewEmptyContainer() *GenericContainer {
|
||||||
ts.Show()
|
ts.Show()
|
||||||
|
|
||||||
user := labeluri.NewLabel(text.Rich{})
|
user := labeluri.NewLabel(text.Rich{})
|
||||||
user.SetMaxWidthChars(35)
|
|
||||||
user.SetLineWrap(true)
|
user.SetLineWrap(true)
|
||||||
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||||
user.SetXAlign(1) // right align
|
user.SetXAlign(1) // right align
|
||||||
|
@ -168,6 +169,10 @@ func (m *GenericContainer) AuthorID() string {
|
||||||
return m.authorID
|
return m.authorID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *GenericContainer) AuthorName() string {
|
||||||
|
return m.authorName
|
||||||
|
}
|
||||||
|
|
||||||
func (m *GenericContainer) AvatarURL() string {
|
func (m *GenericContainer) AvatarURL() string {
|
||||||
return m.avatarURL
|
return m.avatarURL
|
||||||
}
|
}
|
||||||
|
@ -189,8 +194,11 @@ func (m *GenericContainer) UpdateAuthor(author cchat.Author) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GenericContainer) UpdateAuthorName(name text.Rich) {
|
func (m *GenericContainer) UpdateAuthorName(name text.Rich) {
|
||||||
var out = markup.RenderCmplxWithConfig(name, markup.NoMentionLinks)
|
cfg := markup.RenderConfig{}
|
||||||
m.Username.SetOutput(out)
|
cfg.SetForegroundAnchor(m.ContentBody)
|
||||||
|
|
||||||
|
m.authorName = name.String()
|
||||||
|
m.Username.SetOutput(markup.RenderCmplxWithConfig(name, cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GenericContainer) UpdateContent(content text.Rich, edited bool) {
|
func (m *GenericContainer) UpdateContent(content text.Rich, edited bool) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package attrmap
|
package attrmap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -10,15 +11,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppendMap struct {
|
type AppendMap struct {
|
||||||
appended map[int]string // for opening tags
|
appended map[int][]byte // for opening tags
|
||||||
prepended map[int]string // for closing tags
|
prepended map[int][]byte // for closing tags
|
||||||
indices []int
|
indices []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppendedMap() AppendMap {
|
func NewAppendedMap() AppendMap {
|
||||||
return AppendMap{
|
return AppendMap{
|
||||||
appended: map[int]string{},
|
appended: map[int][]byte{},
|
||||||
prepended: map[int]string{},
|
prepended: map[int][]byte{},
|
||||||
indices: []int{},
|
indices: []int{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ func (a *AppendMap) Anchor(start, end int, href string) {
|
||||||
|
|
||||||
// AnchorNU makes a new <a> tag without underlines and colors.
|
// AnchorNU makes a new <a> tag without underlines and colors.
|
||||||
func (a *AppendMap) AnchorNU(start, end int, href string) {
|
func (a *AppendMap) AnchorNU(start, end int, href string) {
|
||||||
a.Openf(start, `<a href="`+html.EscapeString(href)+`%s">`)
|
a.Openf(start, `<a href="`+html.EscapeString(href)+`">`)
|
||||||
a.Close(end, "</a>")
|
a.Close(end, "</a>")
|
||||||
// a.Anchor(start, end, href)
|
// a.Anchor(start, end, href)
|
||||||
a.Span(start, end, `underline="none"`)
|
a.Span(start, end, `underline="none"`)
|
||||||
|
@ -63,7 +64,7 @@ func (a *AppendMap) Pad(start, end int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func posHaveSpace(tags map[int]string, pos int) bool {
|
func posHaveSpace(tags map[int][]byte, pos int) bool {
|
||||||
tg, ok := tags[pos]
|
tg, ok := tags[pos]
|
||||||
if !ok || len(tg) == 0 {
|
if !ok || len(tg) == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -78,7 +79,7 @@ func posHaveSpace(tags map[int]string, pos int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check spaces mid-tag. This works because strings are always escaped.
|
// Check spaces mid-tag. This works because strings are always escaped.
|
||||||
return strings.Contains(tg, "> <")
|
return bytes.Contains(tg, []byte("> <"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppendMap) Pair(start, end int, open, close string) {
|
func (a *AppendMap) Pair(start, end int, open, close string) {
|
||||||
|
@ -92,32 +93,31 @@ func (a *AppendMap) Openf(ind int, f string, argv ...interface{}) {
|
||||||
|
|
||||||
func (a *AppendMap) Open(ind int, attr string) {
|
func (a *AppendMap) Open(ind int, attr string) {
|
||||||
if str, ok := a.appended[ind]; ok {
|
if str, ok := a.appended[ind]; ok {
|
||||||
a.appended[ind] = str + attr // append
|
a.appended[ind] = append(str, []byte(attr)...) // append
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.appended[ind] = attr
|
a.appended[ind] = []byte(attr)
|
||||||
a.appendIndex(ind)
|
a.appendIndex(ind)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppendMap) Close(ind int, attr string) {
|
func (a *AppendMap) Close(ind int, attr string) {
|
||||||
if str, ok := a.prepended[ind]; ok {
|
if str, ok := a.prepended[ind]; ok {
|
||||||
a.prepended[ind] = attr + str // prepend
|
a.prepended[ind] = append([]byte(attr), str...) // prepend
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.prepended[ind] = attr
|
a.prepended[ind] = []byte(attr)
|
||||||
a.appendIndex(ind)
|
a.appendIndex(ind)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AppendMap) Get(ind int) (tags string) {
|
func (a AppendMap) Get(ind int) (tags string) {
|
||||||
if t, ok := a.appended[ind]; ok {
|
appended := a.appended[ind]
|
||||||
tags += t
|
prepended := a.prepended[ind]
|
||||||
}
|
|
||||||
if t, ok := a.prepended[ind]; ok {
|
// Borrowing appended's backing array to add prepended is probably fine, as
|
||||||
tags += t
|
// the length of the actual appended slice is going to stay the same.
|
||||||
}
|
return string(append(appended, prepended...))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AppendMap) Finalize(strlen int) []int {
|
func (a *AppendMap) Finalize(strlen int) []int {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package hl provides a syntax highlighted renderer for the markup API.
|
||||||
package hl
|
package hl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -9,10 +9,12 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/attrmap"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/attrmap"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/hl"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/hl"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/diamondburned/imgutil"
|
"github.com/diamondburned/imgutil"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hyphenate controls whether or not texts should have hyphens on wrap.
|
// Hyphenate controls whether or not texts should have hyphens on wrap.
|
||||||
|
@ -24,9 +26,10 @@ func hyphenate(text string) string {
|
||||||
|
|
||||||
// RenderOutput is the output of a render.
|
// RenderOutput is the output of a render.
|
||||||
type RenderOutput struct {
|
type RenderOutput struct {
|
||||||
Markup string
|
Markup string
|
||||||
Input string // useless to keep parts, as Go will keep all alive anyway
|
Input string // useless to keep parts, as Go will keep all alive anyway
|
||||||
Mentions []MentionSegment
|
Mentions []MentionSegment
|
||||||
|
References []ReferenceSegment
|
||||||
}
|
}
|
||||||
|
|
||||||
// MentionSegment is a type that satisfies both Segment and Mentioner.
|
// MentionSegment is a type that satisfies both Segment and Mentioner.
|
||||||
|
@ -35,24 +38,41 @@ type MentionSegment struct {
|
||||||
text.Mentioner
|
text.Mentioner
|
||||||
}
|
}
|
||||||
|
|
||||||
// f_Mention is used to print and parse mention URIs.
|
// ReferenceSegment is a type that satisfies both Segment and MessageReferencer.
|
||||||
const f_Mention = "cchat://mention/%d" // %d == Mentions[i]
|
type ReferenceSegment struct {
|
||||||
|
text.Segment
|
||||||
|
text.MessageReferencer
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// f_Mention is used to print and parse mention URIs.
|
||||||
|
f_Mention = "cchat://mention/%d" // %d == Mentions[i]
|
||||||
|
f_Reference = "cchat://reference/%d" // %d == References[i]
|
||||||
|
)
|
||||||
|
|
||||||
// IsMention returns the mention if the URI is correct, or nil if none.
|
// IsMention returns the mention if the URI is correct, or nil if none.
|
||||||
func (r RenderOutput) IsMention(uri string) text.Segment {
|
func (r RenderOutput) IsMention(uri string) text.Segment {
|
||||||
var i int
|
var i int
|
||||||
|
|
||||||
if _, err := fmt.Sscanf(uri, f_Mention, &i); err != nil {
|
_, err := fmt.Sscanf(uri, f_Mention, &i)
|
||||||
return nil
|
if err != nil || i >= len(r.Mentions) {
|
||||||
}
|
|
||||||
|
|
||||||
if i >= len(r.Mentions) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Mentions[i]
|
return r.Mentions[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r RenderOutput) IsReference(uri string) text.Segment {
|
||||||
|
var i int
|
||||||
|
|
||||||
|
_, err := fmt.Sscanf(uri, f_Reference, &i)
|
||||||
|
if err != nil || i >= len(r.References) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.References[i]
|
||||||
|
}
|
||||||
|
|
||||||
func Render(content text.Rich) string {
|
func Render(content text.Rich) string {
|
||||||
return RenderCmplx(content).Markup
|
return RenderCmplx(content).Markup
|
||||||
}
|
}
|
||||||
|
@ -63,15 +83,32 @@ func RenderCmplx(content text.Rich) RenderOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RenderConfig struct {
|
type RenderConfig struct {
|
||||||
// NoMentionLinks prevents the renderer from wrapping mentions with a
|
// NoMentionLinks, if true, will not render any mentions.
|
||||||
// hyperlink. This prevents invalid colors.
|
|
||||||
NoMentionLinks bool
|
NoMentionLinks bool
|
||||||
|
|
||||||
|
// AnchorColor forces all anchors to be of a certain color. This is used if
|
||||||
|
// the boolean is true. Else, all mention links will not work and regular
|
||||||
|
// links will be of the default color.
|
||||||
|
AnchorColor struct {
|
||||||
|
uint32
|
||||||
|
bool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoMentionLinks is the config to render author names. It disables author
|
// SetForegroundAnchor sets the AnchorColor of the render config to be that of
|
||||||
// mention links, as there's no way to make normal names not appear blue.
|
// the regular text foreground color.
|
||||||
var NoMentionLinks = RenderConfig{
|
func (c *RenderConfig) SetForegroundAnchor(styler primitives.StyleContexter) {
|
||||||
NoMentionLinks: true,
|
styleCtx, _ := styler.GetStyleContext()
|
||||||
|
|
||||||
|
if rgba := styleCtx.GetColor(gtk.STATE_FLAG_NORMAL); rgba != nil {
|
||||||
|
var color uint32
|
||||||
|
for _, v := range rgba.Floats() { // [0.0, 1.0]
|
||||||
|
color = (color << 8) + uint32(v*0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AnchorColor.bool = true
|
||||||
|
c.AnchorColor.uint32 = color
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
||||||
|
@ -104,15 +141,21 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
||||||
// map to append strings to indices
|
// map to append strings to indices
|
||||||
var appended = attrmap.NewAppendedMap()
|
var appended = attrmap.NewAppendedMap()
|
||||||
|
|
||||||
// map to store mentions
|
// map to store mentions and references
|
||||||
var mentions []MentionSegment
|
var mentions []MentionSegment
|
||||||
|
var references []ReferenceSegment
|
||||||
|
|
||||||
// Parse all segments.
|
// Parse all segments.
|
||||||
for _, segment := range content.Segments {
|
for _, segment := range content.Segments {
|
||||||
start, end := segment.Bounds()
|
start, end := segment.Bounds()
|
||||||
|
|
||||||
|
// hasAnchor is used to determine if the current segment has inserted
|
||||||
|
// any anchor tags; it is used for AnchorColor.
|
||||||
|
var hasAnchor bool
|
||||||
|
|
||||||
if linker := segment.AsLinker(); linker != nil {
|
if linker := segment.AsLinker(); linker != nil {
|
||||||
appended.Anchor(start, end, linker.Link())
|
appended.Anchor(start, end, linker.Link())
|
||||||
|
hasAnchor = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only inline images if start == end per specification.
|
// Only inline images if start == end per specification.
|
||||||
|
@ -127,19 +170,14 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if colorer := segment.AsColorer(); colorer != nil {
|
|
||||||
appended.Span(start, end, colorAttrs(colorer.Color(), false)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mentioner needs to be before colorer, as we'd want the below color
|
// Mentioner needs to be before colorer, as we'd want the below color
|
||||||
// segment to also highlight the full mention as well as make the
|
// segment to also highlight the full mention as well as make the
|
||||||
// padding part of the hyperlink.
|
// padding part of the hyperlink.
|
||||||
if mentioner := segment.AsMentioner(); mentioner != nil {
|
if mentioner := segment.AsMentioner(); mentioner != nil && !cfg.NoMentionLinks {
|
||||||
// Render the mention into "cchat://mention:0" or such. Other
|
// Render the mention into "cchat://mention:0" or such. Other
|
||||||
// components will take care of showing the information.
|
// components will take care of showing the information.
|
||||||
if !cfg.NoMentionLinks {
|
appended.AnchorNU(start, end, fmt.Sprintf(f_Mention, len(mentions)))
|
||||||
appended.AnchorNU(start, end, fmt.Sprintf(f_Mention, len(mentions)))
|
hasAnchor = true
|
||||||
}
|
|
||||||
|
|
||||||
// Add the mention segment into the list regardless of hyperlinks.
|
// Add the mention segment into the list regardless of hyperlinks.
|
||||||
mentions = append(mentions, MentionSegment{
|
mentions = append(mentions, MentionSegment{
|
||||||
|
@ -147,15 +185,44 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
||||||
Mentioner: mentioner,
|
Mentioner: mentioner,
|
||||||
})
|
})
|
||||||
|
|
||||||
if colorer := segment.AsColorer(); colorer != nil {
|
// TODO: figure out a way to readd Pad. Right now, backend
|
||||||
// Only pad the name and add a dimmed background if the bounds
|
// implementations can arbitrarily add multiple mentions onto the
|
||||||
// do not cover the whole segment.
|
// author for overloading, which we don't want to break.
|
||||||
var cover = (start == 0) && (end == len(content.Content))
|
|
||||||
appended.Span(start, end, colorAttrs(colorer.Color(), !cover)...)
|
// // Determine if the mention segment covers the entire label.
|
||||||
if !cover {
|
// // Only pad the name and add a dimmed background if the bounds do
|
||||||
appended.Pad(start, end)
|
// // not cover the whole segment.
|
||||||
}
|
// var cover = (start == 0) && (end == len(content.Content))
|
||||||
}
|
// if !cover {
|
||||||
|
// appended.Pad(start, end)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // If we don't have a mention color for this segment, then try to
|
||||||
|
// // use our own AnchorColor.
|
||||||
|
// if !hasColor && cfg.AnchorColor.bool {
|
||||||
|
// appended.Span(start, end, colorAttrs(cfg.AnchorColor.uint32, false)...)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if colorer := segment.AsColorer(); colorer != nil {
|
||||||
|
appended.Span(start, end, colorAttrs(colorer.Color(), false)...)
|
||||||
|
} else if hasAnchor && cfg.AnchorColor.bool {
|
||||||
|
appended.Span(start, end, colorAttrs(cfg.AnchorColor.uint32, false)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use AnchorColor for the link, as we're technically just
|
||||||
|
// borrowing the anchor tag for its use. We should also prefer the
|
||||||
|
// username popover (Mention) over this.
|
||||||
|
if reference := segment.AsMessageReferencer(); !hasAnchor && reference != nil {
|
||||||
|
// Render the mention into "cchat://reference:0" or such. Other
|
||||||
|
// components will take care of showing the information.
|
||||||
|
appended.AnchorNU(start, end, fmt.Sprintf(f_Reference, len(references)))
|
||||||
|
|
||||||
|
// Add the mention segment into the list regardless of hyperlinks.
|
||||||
|
references = append(references, ReferenceSegment{
|
||||||
|
Segment: segment,
|
||||||
|
MessageReferencer: reference,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if attributor := segment.AsAttributor(); attributor != nil {
|
if attributor := segment.AsAttributor(); attributor != nil {
|
||||||
|
@ -165,7 +232,12 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
||||||
if codeblocker := segment.AsCodeblocker(); codeblocker != nil {
|
if codeblocker := segment.AsCodeblocker(); codeblocker != nil {
|
||||||
start, end := segment.Bounds()
|
start, end := segment.Bounds()
|
||||||
// Syntax highlight the codeblock.
|
// Syntax highlight the codeblock.
|
||||||
hl.Segments(&appended, content.Content, start, end, codeblocker.CodeblockLanguage())
|
hl.Segments(
|
||||||
|
&appended,
|
||||||
|
content.Content,
|
||||||
|
start, end,
|
||||||
|
codeblocker.CodeblockLanguage(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this not shit. Maybe make it somehow not rely on green
|
// TODO: make this not shit. Maybe make it somehow not rely on green
|
||||||
|
@ -187,9 +259,10 @@ func RenderCmplxWithConfig(content text.Rich, cfg RenderConfig) RenderOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
return RenderOutput{
|
return RenderOutput{
|
||||||
Markup: hyphenate(buf.String()),
|
Markup: hyphenate(buf.String()),
|
||||||
Input: content.Content,
|
Input: content.Content,
|
||||||
Mentions: mentions,
|
Mentions: mentions,
|
||||||
|
References: references,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue