Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
diamondburned
GitHub Repository: diamondburned/gtkcord4
Path: blob/main/internal/messages/composer/autocomplete_emojis.go
366 views
1
package composer
2
3
import (
4
"context"
5
"fmt"
6
"html"
7
"sort"
8
"time"
9
10
"github.com/diamondburned/arikawa/v3/discord"
11
"github.com/diamondburned/chatkit/components/autocomplete"
12
"github.com/diamondburned/gotk4/pkg/gtk/v4"
13
"github.com/diamondburned/gotk4/pkg/pango"
14
"github.com/diamondburned/gotkit/components/onlineimage"
15
"github.com/diamondburned/gotkit/gtkutil/cssutil"
16
"github.com/diamondburned/gotkit/gtkutil/imgutil"
17
"github.com/diamondburned/ningen/v3/states/emoji"
18
"github.com/sahilm/fuzzy"
19
"libdb.so/dissent/internal/gtkcord"
20
21
unicodeemoji "github.com/enescakir/emoji"
22
)
23
24
const (
25
maxAutocompletion = 15
26
emojiCacheExpiry = time.Minute
27
)
28
29
type emojis []EmojiData
30
31
func (e emojis) Len() int { return len(e) }
32
func (e emojis) String(i int) string { return e[i].EmojiName + " " + e[i].GuildName }
33
34
type emojiCompleter struct {
35
emojis emojis
36
matched []autocomplete.Data
37
updated time.Time
38
guildID discord.GuildID
39
}
40
41
var unicodeEmojis = unicodeemoji.Map()
42
43
// NewEmojiCompleter creaets a new autocomplete searcher that searches for
44
// emojis.
45
func NewEmojiCompleter(gID discord.GuildID) autocomplete.Searcher {
46
return &emojiCompleter{
47
guildID: gID,
48
matched: make([]autocomplete.Data, 0, maxAutocompletion),
49
}
50
}
51
52
func (c *emojiCompleter) Rune() rune { return ':' }
53
54
func (c *emojiCompleter) Search(ctx context.Context, str string) []autocomplete.Data {
55
if len(str) < 2 {
56
return nil
57
}
58
59
now := time.Now()
60
61
state := gtkcord.FromContext(ctx)
62
hasNitro := state.EmojiState.HasNitro()
63
64
if c.emojis != nil && c.updated.Add(emojiCacheExpiry).After(now) {
65
return c.search(str, hasNitro)
66
}
67
68
c.updated = now
69
70
if c.emojis != nil {
71
c.emojis = c.emojis[:0]
72
} else {
73
c.emojis = make(emojis, 0, len(unicodeEmojis)+64)
74
}
75
76
for name, unicode := range unicodeEmojis {
77
c.emojis = append(c.emojis, EmojiData{
78
Emoji: &discord.Emoji{Name: unicode},
79
EmojiName: name,
80
})
81
}
82
83
var emojis []emoji.Guild
84
if showAllEmojis.Value() {
85
emojis, _ = state.EmojiState.AllEmojis()
86
} else {
87
emojis, _ = state.EmojiState.ForGuild(c.guildID)
88
}
89
90
for i, guild := range emojis {
91
for j, emoji := range guild.Emojis {
92
c.emojis = append(c.emojis, EmojiData{
93
GuildID: guild.ID,
94
GuildName: guild.Name,
95
Emoji: &emojis[i].Emojis[j],
96
EmojiName: emoji.Name,
97
})
98
}
99
}
100
101
return c.search(str, hasNitro)
102
}
103
104
func (c *emojiCompleter) search(str string, hasNitro bool) []autocomplete.Data {
105
res := fuzzy.FindFrom(str, c.emojis)
106
if len(res) > maxAutocompletion {
107
res = res[:maxAutocompletion]
108
}
109
110
data := c.matched[:0]
111
for _, r := range res {
112
data = append(data, c.emojis[r.Index])
113
}
114
115
// Put the guild emojis first if we don't have Nitro.
116
if !hasNitro {
117
sort.SliceStable(data, func(i, j int) bool {
118
a := data[i].(EmojiData)
119
b := data[j].(EmojiData)
120
correctA := a.GuildID == c.guildID
121
correctB := b.GuildID == c.guildID
122
return correctA && !correctB
123
})
124
}
125
126
return data
127
}
128
129
// EmojiData is the Data structure for each emoji.
130
type EmojiData struct {
131
GuildID discord.GuildID
132
GuildName string
133
Emoji *discord.Emoji
134
EmojiName string
135
}
136
137
const emojiSize = 32 // px
138
139
var _ = cssutil.WriteCSS(`
140
.autocompleter-unicode {
141
font-size: 26px;
142
}
143
`)
144
145
// Row satisfies autocomplete.Data.
146
func (d EmojiData) Row(ctx context.Context) *gtk.ListBoxRow {
147
b := gtk.NewBox(gtk.OrientationHorizontal, 4)
148
markup := html.EscapeString(d.EmojiName)
149
150
if !d.Emoji.ID.IsValid() {
151
l := gtk.NewLabel(d.Emoji.Name)
152
l.AddCSSClass("autocompleter-unicode")
153
154
b.Append(l)
155
} else {
156
i := onlineimage.NewImage(ctx, imgutil.HTTPProvider)
157
i.AddCSSClass("autocompleter-customemoji")
158
i.SetSizeRequest(emojiSize, emojiSize)
159
i.SetFromURL(gtkcord.EmojiURL(d.Emoji.ID.String(), false))
160
161
b.Append(i)
162
163
markup += "\n" + fmt.Sprintf(
164
`<span size="smaller" fgalpha="75%%" rise="-1200">%s</span>`,
165
html.EscapeString(d.GuildName),
166
)
167
}
168
169
l := gtk.NewLabel("")
170
l.SetMaxWidthChars(35)
171
l.SetEllipsize(pango.EllipsizeEnd)
172
l.SetMarkup(markup)
173
b.Append(l)
174
175
r := gtk.NewListBoxRow()
176
r.AddCSSClass("autocomplete-emoji")
177
r.SetChild(b)
178
179
return r
180
}
181
182