mirror of https://gitea.com/gitea/tea
Show issue reactions (#421)
``` $ tea issue 230 #230 issue/pull details: show reactions (open) @6543 created 2020-10-22 16:39 since reactions are utf8 now and most terminals too, we can display them nicely :) https://gitea.com/api/v1/repos/gitea/tea/issues/230/reactions -------- 1x 🎉 | 1x 👀 | 1x | 1x 👍 | 1x 👎 | 1x 😆 | 1x 😕 | 1x ❤️ ``` caveats: - reactions are not returned as UTF8 (as was claimed in #230), so they need to be parsed. the library I use doesn't (and can't → ) support all reactions available in gitea - currently only for issues, as reactions for comments mean an additional API request for each comment.. fixes #230 Co-authored-by: Norwin <git@nroo.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/421 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: Norwin <noerw@noreply.gitea.io> Co-committed-by: Norwin <noerw@noreply.gitea.io>pull/425/head
parent
7a05be436c
commit
58aaa17e7e
@ -0,0 +1,15 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package emoji makes working with emojis easier.
|
||||
*/
|
||||
package emoji
|
@ -0,0 +1,124 @@
|
||||
package emoji
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
TonePlaceholder = "@"
|
||||
flagBaseIndex = '\U0001F1E6' - 'a'
|
||||
)
|
||||
|
||||
// Skin tone colors
|
||||
const (
|
||||
Default Tone = ""
|
||||
Light Tone = "\U0001F3FB"
|
||||
MediumLight Tone = "\U0001F3FC"
|
||||
Medium Tone = "\U0001F3FD"
|
||||
MediumDark Tone = "\U0001F3FE"
|
||||
Dark Tone = "\U0001F3FF"
|
||||
)
|
||||
|
||||
// Emoji defines an emoji object with no skin variations.
|
||||
type Emoji string
|
||||
|
||||
// String returns string representation of the simple emoji.
|
||||
func (e Emoji) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// EmojiWithTone defines an emoji object that has skin tone options.
|
||||
type EmojiWithTone struct {
|
||||
oneTonedCode string
|
||||
twoTonedCode string
|
||||
defaultTone Tone
|
||||
}
|
||||
|
||||
// newEmojiWithTone constructs a new emoji object that has skin tone options.
|
||||
func newEmojiWithTone(codes ...string) EmojiWithTone {
|
||||
if len(codes) == 0 {
|
||||
return EmojiWithTone{}
|
||||
}
|
||||
|
||||
one := codes[0]
|
||||
two := codes[0]
|
||||
|
||||
if len(codes) > 1 {
|
||||
two = codes[1]
|
||||
}
|
||||
|
||||
return EmojiWithTone{
|
||||
oneTonedCode: one,
|
||||
twoTonedCode: two,
|
||||
}
|
||||
}
|
||||
|
||||
// withDefaultTone sets default tone for an emoji and returns it.
|
||||
func (e EmojiWithTone) withDefaultTone(tone string) EmojiWithTone {
|
||||
e.defaultTone = Tone(tone)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// String returns string representation of the emoji with default skin tone.
|
||||
func (e EmojiWithTone) String() string {
|
||||
return strings.ReplaceAll(e.oneTonedCode, TonePlaceholder, e.defaultTone.String())
|
||||
}
|
||||
|
||||
// Tone returns string representation of the emoji with given skin tone.
|
||||
func (e EmojiWithTone) Tone(tones ...Tone) string {
|
||||
// if no tone given, return with default skin tone
|
||||
if len(tones) == 0 {
|
||||
return e.String()
|
||||
}
|
||||
|
||||
str := e.twoTonedCode
|
||||
replaceCount := 1
|
||||
|
||||
// if one tone given or emoji doesn't have twoTonedCode, use oneTonedCode
|
||||
// Also, replace all with one tone
|
||||
if len(tones) == 1 {
|
||||
str = e.oneTonedCode
|
||||
replaceCount = -1
|
||||
}
|
||||
|
||||
// replace tone one by one
|
||||
for _, t := range tones {
|
||||
// use emoji's default tone
|
||||
if t == Default {
|
||||
t = e.defaultTone
|
||||
}
|
||||
|
||||
str = strings.Replace(str, TonePlaceholder, t.String(), replaceCount)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// Tone defines skin tone options for emojis.
|
||||
type Tone string
|
||||
|
||||
// String returns string representation of the skin tone.
|
||||
func (t Tone) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
// CountryFlag returns a country flag emoji from given country code.
|
||||
// Full list of country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||
func CountryFlag(code string) (Emoji, error) {
|
||||
if len(code) != 2 {
|
||||
return "", fmt.Errorf("not valid country code: %q", code)
|
||||
}
|
||||
|
||||
code = strings.ToLower(code)
|
||||
flag := countryCodeLetter(code[0]) + countryCodeLetter(code[1])
|
||||
|
||||
return Emoji(flag), nil
|
||||
}
|
||||
|
||||
// countryCodeLetter shifts given letter byte as flagBaseIndex.
|
||||
func countryCodeLetter(l byte) string {
|
||||
return string(rune(l) + flagBaseIndex)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package emoji
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Sprint wraps fmt.Sprint with emoji support
|
||||
func Sprint(a ...interface{}) string {
|
||||
return Parse(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintf wraps fmt.Sprintf with emoji support
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return Parse(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Sprintln wraps fmt.Sprintln with emoji support
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return Parse(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Print wraps fmt.Print with emoji support
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(Sprint(a...))
|
||||
}
|
||||
|
||||
// Println wraps fmt.Println with emoji support
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(Sprint(a...))
|
||||
}
|
||||
|
||||
// Printf wraps fmt.Printf with emoji support
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Fprint wraps fmt.Fprint with emoji support
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, Sprint(a...))
|
||||
}
|
||||
|
||||
// Fprintf wraps fmt.Fprintf with emoji support
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Fprintln wraps fmt.Fprintln with emoji support
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, Sprint(a...))
|
||||
}
|
||||
|
||||
// Errorf wraps fmt.Errorf with emoji support
|
||||
func Errorf(format string, a ...interface{}) error {
|
||||
return fmt.Errorf(Sprintf(format, a...))
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module github.com/enescakir/emoji
|
||||
|
||||
go 1.13
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,127 @@
|
||||
package emoji
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
flagRegex = regexp.MustCompile(`^:flag-([a-zA-Z]{2}):$`)
|
||||
)
|
||||
|
||||
// Parse replaces emoji aliases (:pizza:) with unicode representation.
|
||||
func Parse(input string) string {
|
||||
var matched strings.Builder
|
||||
var output strings.Builder
|
||||
|
||||
for _, r := range input {
|
||||
// when it's not `:`, it might be inner or outer of the emoji alias
|
||||
if r != ':' {
|
||||
// if matched is empty, it's the outer of the emoji alias
|
||||
if matched.Len() == 0 {
|
||||
output.WriteRune(r)
|
||||
continue
|
||||
}
|
||||
|
||||
matched.WriteRune(r)
|
||||
|
||||
// if it's space, the alias's not valid.
|
||||
// reset matched for breaking the emoji alias
|
||||
if unicode.IsSpace(r) {
|
||||
output.WriteString(matched.String())
|
||||
matched.Reset()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// r is `:` now
|
||||
// if matched is empty, it's the beginning of the emoji alias
|
||||
if matched.Len() == 0 {
|
||||
matched.WriteRune(r)
|
||||
continue
|
||||
}
|
||||
|
||||
// it's the end of the emoji alias
|
||||
match := matched.String()
|
||||
alias := match + ":"
|
||||
|
||||
// check for emoji alias
|
||||
if code, ok := Find(alias); ok {
|
||||
output.WriteString(code)
|
||||
matched.Reset()
|
||||
continue
|
||||
}
|
||||
|
||||
// not found any emoji
|
||||
output.WriteString(match)
|
||||
// it might be the beginning of the another emoji alias
|
||||
matched.Reset()
|
||||
matched.WriteRune(r)
|
||||
|
||||
}
|
||||
|
||||
// if matched not empty, add it to output
|
||||
if matched.Len() != 0 {
|
||||
output.WriteString(matched.String())
|
||||
matched.Reset()
|
||||
}
|
||||
|
||||
return output.String()
|
||||
}
|
||||
|
||||
// Map returns the emojis map.
|
||||
// Key is the alias of the emoji.
|
||||
// Value is the code of the emoji.
|
||||
func Map() map[string]string {
|
||||
return emojiMap
|
||||
}
|
||||
|
||||
// AppendAlias adds new emoji pair to the emojis map.
|
||||
func AppendAlias(alias, code string) error {
|
||||
if c, ok := emojiMap[alias]; ok {
|
||||
return fmt.Errorf("emoji already exist: %q => %+q", alias, c)
|
||||
}
|
||||
|
||||
for _, r := range alias {
|
||||
if unicode.IsSpace(r) {
|
||||
return fmt.Errorf("emoji alias is not valid: %q", alias)
|
||||
}
|
||||
}
|
||||
|
||||
emojiMap[alias] = code
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exist checks existence of the emoji by alias.
|
||||
func Exist(alias string) bool {
|
||||
_, ok := Find(alias)
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Find returns the emoji code by alias.
|
||||
func Find(alias string) (string, bool) {
|
||||
if code, ok := emojiMap[alias]; ok {
|
||||
return code, true
|
||||
}
|
||||
|
||||
if flag := checkFlag(alias); len(flag) > 0 {
|
||||
return flag, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// checkFlag finds flag emoji for `flag-[CODE]` pattern
|
||||
func checkFlag(alias string) string {
|
||||
if matches := flagRegex.FindStringSubmatch(alias); len(matches) == 2 {
|
||||
flag, _ := CountryFlag(matches[1])
|
||||
|
||||
return flag.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
Loading…
Reference in New Issue