Compare commits
No commits in common. "LP-5613" and "main" have entirely different histories.
@ -3,7 +3,6 @@ package badgesmodel
|
|||||||
const (
|
const (
|
||||||
NameMaxLength = 20
|
NameMaxLength = 20
|
||||||
DescriptionMaxLength = 120
|
DescriptionMaxLength = 120
|
||||||
DefaultTypeName = "Общий"
|
|
||||||
|
|
||||||
ImageTypeEmoji ImageType = "emoji"
|
ImageTypeEmoji ImageType = "emoji"
|
||||||
ImageTypeRelativeURL ImageType = "rel_url"
|
ImageTypeRelativeURL ImageType = "rel_url"
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package badgesmodel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BadgeType string
|
type BadgeType string
|
||||||
@ -57,7 +56,6 @@ type BadgeTypeDefinition struct {
|
|||||||
CreatedBy string `json:"created_by"`
|
CreatedBy string `json:"created_by"`
|
||||||
CanGrant PermissionScheme `json:"can_grant"`
|
CanGrant PermissionScheme `json:"can_grant"`
|
||||||
CanCreate PermissionScheme `json:"can_create"`
|
CanCreate PermissionScheme `json:"can_create"`
|
||||||
IsDefault bool `json:"is_default"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermissionScheme struct {
|
type PermissionScheme struct {
|
||||||
@ -89,8 +87,8 @@ type Subscription struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b Badge) IsValid() bool {
|
func (b Badge) IsValid() bool {
|
||||||
return utf8.RuneCountInString(b.Name) <= NameMaxLength &&
|
return len(b.Name) <= NameMaxLength &&
|
||||||
utf8.RuneCountInString(b.Description) <= DescriptionMaxLength &&
|
len(b.Description) <= DescriptionMaxLength &&
|
||||||
b.Image != ""
|
b.Image != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
66
go.mod
66
go.mod
@ -1,77 +1,13 @@
|
|||||||
module github.com/larkox/mattermost-plugin-badges
|
module github.com/larkox/mattermost-plugin-badges
|
||||||
|
|
||||||
go 1.24.0
|
go 1.12
|
||||||
|
|
||||||
toolchain go1.24.3
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/mattermost/mattermost-plugin-api v0.0.14
|
github.com/mattermost/mattermost-plugin-api v0.0.14
|
||||||
github.com/mattermost/mattermost-server/v5 v5.3.2-0.20210422214809-ff657bfdef24
|
github.com/mattermost/mattermost-server/v5 v5.3.2-0.20210422214809-ff657bfdef24
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/text v0.34.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
|
||||||
github.com/dyatlov/go-opengraph v0.0.0-20210112100619-dae8665a5b09 // indirect
|
|
||||||
github.com/fatih/color v1.10.0 // indirect
|
|
||||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
|
|
||||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.1 // indirect
|
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
|
||||||
github.com/google/uuid v1.2.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-hclog v0.15.0 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/hashicorp/go-plugin v1.4.0 // indirect
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.10 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.5 // indirect
|
|
||||||
github.com/lib/pq v1.10.0 // indirect
|
|
||||||
github.com/mattermost/go-i18n v1.11.0 // indirect
|
|
||||||
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
|
|
||||||
github.com/mattermost/logr v1.0.13 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
|
||||||
github.com/minio/minio-go/v7 v7.0.10 // indirect
|
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
|
||||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
|
||||||
github.com/oklog/run v1.1.0 // indirect
|
|
||||||
github.com/pborman/uuid v1.2.1 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
|
||||||
github.com/philhofer/fwd v1.1.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rs/xid v1.2.1 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
|
||||||
github.com/tinylib/msgp v1.1.5 // indirect
|
|
||||||
github.com/wiggin77/cfg v1.0.2 // indirect
|
|
||||||
github.com/wiggin77/merror v1.0.3 // indirect
|
|
||||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
|
||||||
go.uber.org/zap v1.16.0 // indirect
|
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
|
||||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb // indirect
|
|
||||||
golang.org/x/net v0.49.0 // indirect
|
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20210322173543-5f0e89347f5a // indirect
|
|
||||||
google.golang.org/grpc v1.36.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.26.0 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
40
go.sum
40
go.sum
@ -50,9 +50,8 @@ github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
|
||||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||||
@ -280,6 +279,7 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E=
|
github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E=
|
||||||
|
github.com/go-gorp/gorp v2.2.0+incompatible h1:xAUh4QgEeqPPhK3vxZN+bzrim1z5Av6q837gtjUlshc=
|
||||||
github.com/go-gorp/gorp v2.2.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E=
|
github.com/go-gorp/gorp v2.2.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
@ -362,9 +362,8 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@ -551,6 +550,7 @@ github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
|
|||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
@ -639,6 +639,7 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
|
|||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
@ -699,13 +700,12 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
|
|||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
|
github.com/nelsam/hel/v2 v2.3.2 h1:tXRsJBqRxj4ISSPCrXhbqF8sT+BXA/UaIvjhYjP5Bhk=
|
||||||
github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w=
|
github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w=
|
||||||
github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI=
|
github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
|
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
|
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
|
||||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
@ -785,6 +785,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
|
github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
|
||||||
|
github.com/poy/onpar v1.0.0 h1:MfdQ9bnas+J1si8vUHAABXKxqOqDVaH4T3LRDYYv5Lo=
|
||||||
github.com/poy/onpar v1.0.0/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
github.com/poy/onpar v1.0.0/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
@ -1012,6 +1013,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
@ -1056,8 +1058,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
|
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
|
||||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
|
golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
|
||||||
@ -1081,9 +1081,8 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -1126,9 +1125,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
|
||||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -1184,9 +1182,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -1209,8 +1206,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -1281,9 +1276,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1292,9 +1286,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -1364,14 +1357,14 @@ golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc
|
|||||||
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
|
||||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
@ -1528,9 +1521,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "ru.loop.plugin.achievements",
|
"id": "ru.loop.plugin.achievements",
|
||||||
"name": "Achievements",
|
"name": "Badges for Mattermost",
|
||||||
"description": "Плагин достижений и значков для Loop.",
|
"description": "This plugin add badges support to Mattermost.",
|
||||||
"homepage_url": "https://github.com/larkox/mattermost-plugin-badges",
|
"homepage_url": "https://github.com/larkox/mattermost-plugin-badges",
|
||||||
"support_url": "https://github.com/larkox/mattermost-plugin-badges/issues",
|
"support_url": "https://github.com/larkox/mattermost-plugin-badges/issues",
|
||||||
"release_notes_url": "https://github.com/larkox/mattermost-plugin-badges/releases/tag/v0.2.1",
|
"release_notes_url": "https://github.com/larkox/mattermost-plugin-badges/releases/tag/v0.2.1",
|
||||||
@ -24,8 +24,9 @@
|
|||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"key": "BadgesAdmin",
|
"key": "BadgesAdmin",
|
||||||
"display_name": "Achievements Admin:",
|
"display_name": "Badges admin:",
|
||||||
"type": "custom"
|
"type": "text",
|
||||||
|
"help_text": "This user will be considered as an admin for the badges plugin. They can create types, and modify and grant any badge."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
558
server/api.go
558
server/api.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
@ -32,41 +33,6 @@ type APIErrorResponse struct {
|
|||||||
StatusCode int `json:"status_code"`
|
StatusCode int `json:"status_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateBadgeRequest struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Image string `json:"image"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Multiple bool `json:"multiple"`
|
|
||||||
ChannelID string `json:"channel_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateBadgeRequest struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Image string `json:"image"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Multiple bool `json:"multiple"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateTypeRequest struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
EveryoneCanCreate bool `json:"everyone_can_create"`
|
|
||||||
EveryoneCanGrant bool `json:"everyone_can_grant"`
|
|
||||||
ChannelID string `json:"channel_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TypeWithBadgeCount struct {
|
|
||||||
*badgesmodel.BadgeTypeDefinition
|
|
||||||
BadgeCount int `json:"badge_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetTypesResponse struct {
|
|
||||||
Types []TypeWithBadgeCount `json:"types"`
|
|
||||||
CanCreateType bool `json:"can_create_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) initializeAPI() {
|
func (p *Plugin) initializeAPI() {
|
||||||
p.router = mux.NewRouter()
|
p.router = mux.NewRouter()
|
||||||
p.router.Use(p.withRecovery)
|
p.router.Use(p.withRecovery)
|
||||||
@ -79,12 +45,6 @@ func (p *Plugin) initializeAPI() {
|
|||||||
apiRouter.HandleFunc("/getUserBadges/{userID}", p.extractUserMiddleWare(p.getUserBadges, ResponseTypeJSON)).Methods(http.MethodGet)
|
apiRouter.HandleFunc("/getUserBadges/{userID}", p.extractUserMiddleWare(p.getUserBadges, ResponseTypeJSON)).Methods(http.MethodGet)
|
||||||
apiRouter.HandleFunc("/getBadgeDetails/{badgeID}", p.extractUserMiddleWare(p.getBadgeDetails, ResponseTypeJSON)).Methods(http.MethodGet)
|
apiRouter.HandleFunc("/getBadgeDetails/{badgeID}", p.extractUserMiddleWare(p.getBadgeDetails, ResponseTypeJSON)).Methods(http.MethodGet)
|
||||||
apiRouter.HandleFunc("/getAllBadges", p.extractUserMiddleWare(p.getAllBadges, ResponseTypeJSON)).Methods(http.MethodGet)
|
apiRouter.HandleFunc("/getAllBadges", p.extractUserMiddleWare(p.getAllBadges, ResponseTypeJSON)).Methods(http.MethodGet)
|
||||||
apiRouter.HandleFunc("/getTypes", p.extractUserMiddleWare(p.getTypes, ResponseTypeJSON)).Methods(http.MethodGet)
|
|
||||||
apiRouter.HandleFunc("/createBadge", p.extractUserMiddleWare(p.apiCreateBadge, ResponseTypeJSON)).Methods(http.MethodPost)
|
|
||||||
apiRouter.HandleFunc("/createType", p.extractUserMiddleWare(p.apiCreateType, ResponseTypeJSON)).Methods(http.MethodPost)
|
|
||||||
apiRouter.HandleFunc("/updateBadge", p.extractUserMiddleWare(p.apiUpdateBadge, ResponseTypeJSON)).Methods(http.MethodPut)
|
|
||||||
apiRouter.HandleFunc("/deleteBadge/{badgeID}", p.extractUserMiddleWare(p.apiDeleteBadge, ResponseTypeJSON)).Methods(http.MethodDelete)
|
|
||||||
apiRouter.HandleFunc("/deleteType/{typeID}", p.extractUserMiddleWare(p.apiDeleteType, ResponseTypeJSON)).Methods(http.MethodDelete)
|
|
||||||
|
|
||||||
pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathEnsure, checkPluginRequest(p.ensureBadges)).Methods(http.MethodPost)
|
pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathEnsure, checkPluginRequest(p.ensureBadges)).Methods(http.MethodPost)
|
||||||
pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathGrant, checkPluginRequest(p.grantBadge)).Methods(http.MethodPost)
|
pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathGrant, checkPluginRequest(p.grantBadge)).Methods(http.MethodPost)
|
||||||
@ -132,376 +92,18 @@ func dialogKeepOpen(w http.ResponseWriter) {
|
|||||||
_, _ = w.Write(resp.ToJson())
|
_, _ = w.Write(resp.ToJson())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) getTypes(w http.ResponseWriter, r *http.Request, userID string) {
|
|
||||||
u, err := p.mm.User.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
types, err := p.filterCreateBadgeTypes(u)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_types", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
badges, err := p.store.GetRawBadges()
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_badges", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeCountByType := map[badgesmodel.BadgeType]int{}
|
|
||||||
for _, badge := range badges {
|
|
||||||
badgeCountByType[badge.Type]++
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]TypeWithBadgeCount, len(types))
|
|
||||||
for i, t := range types {
|
|
||||||
result[i] = TypeWithBadgeCount{
|
|
||||||
BadgeTypeDefinition: t,
|
|
||||||
BadgeCount: badgeCountByType[t.ID],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := GetTypesResponse{
|
|
||||||
Types: result,
|
|
||||||
CanCreateType: canCreateType(u, p.badgeAdminUserIDs, false),
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := json.Marshal(resp)
|
|
||||||
_, _ = w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) apiCreateBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
|
||||||
var req CreateBadgeRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := p.mm.User.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Name = strings.TrimSpace(req.Name)
|
|
||||||
req.Description = strings.TrimSpace(req.Description)
|
|
||||||
req.Image = strings.TrimSpace(req.Image)
|
|
||||||
|
|
||||||
if req.Name == "" {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_name", Message: "Name is required", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if length := len(req.Image); length > 1 && req.Image[0] == ':' && req.Image[length-1] == ':' {
|
|
||||||
req.Image = req.Image[1 : length-1]
|
|
||||||
}
|
|
||||||
if req.Image == "" {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_image", Message: "Emoji is required", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := p.store.GetType(badgesmodel.BadgeType(req.Type))
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "type_not_found", Message: "Badge type not found", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !canCreateBadge(user, p.badgeAdminUserIDs, t) {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "no_permission", Message: "No permission to create badge of this type", StatusCode: http.StatusForbidden,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
toCreate := &badgesmodel.Badge{
|
|
||||||
Name: req.Name,
|
|
||||||
Description: req.Description,
|
|
||||||
Image: req.Image,
|
|
||||||
ImageType: badgesmodel.ImageTypeEmoji,
|
|
||||||
Multiple: req.Multiple,
|
|
||||||
Type: badgesmodel.BadgeType(req.Type),
|
|
||||||
CreatedBy: userID,
|
|
||||||
}
|
|
||||||
|
|
||||||
created, err := p.store.AddBadge(toCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_create_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.ChannelID != "" {
|
|
||||||
T := p.getT(user.Locale)
|
|
||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
|
||||||
UserId: p.BotUserID,
|
|
||||||
ChannelId: req.ChannelID,
|
|
||||||
Message: T("badges.api.badge_created", "Значок `%s` создан.", toCreate.Name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := json.Marshal(created)
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
_, _ = w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) apiCreateType(w http.ResponseWriter, r *http.Request, userID string) {
|
|
||||||
var req CreateTypeRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := p.mm.User.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !canCreateType(user, p.badgeAdminUserIDs, false) {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "no_permission", Message: "No permission to create type", StatusCode: http.StatusForbidden,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Name = strings.TrimSpace(req.Name)
|
|
||||||
if req.Name == "" {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_name", Message: "Type name is required", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
toCreate := &badgesmodel.BadgeTypeDefinition{
|
|
||||||
Name: req.Name,
|
|
||||||
CreatedBy: userID,
|
|
||||||
}
|
|
||||||
toCreate.CanCreate.Everyone = req.EveryoneCanCreate
|
|
||||||
toCreate.CanGrant.Everyone = req.EveryoneCanGrant
|
|
||||||
|
|
||||||
created, err := p.store.AddType(toCreate)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_create_type", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.ChannelID != "" {
|
|
||||||
T := p.getT(user.Locale)
|
|
||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
|
||||||
UserId: p.BotUserID,
|
|
||||||
ChannelId: req.ChannelID,
|
|
||||||
Message: T("badges.api.type_created", "Тип `%s` создан.", toCreate.Name),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := json.Marshal(created)
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
_, _ = w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) apiUpdateBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
|
||||||
var req UpdateBadgeRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := p.mm.User.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
badge, err := p.store.GetBadge(badgesmodel.BadgeID(req.ID))
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "badge_not_found", Message: "Badge not found", StatusCode: http.StatusNotFound,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !canEditBadge(user, p.badgeAdminUserIDs, badge) {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "no_permission", Message: "No permission to edit this badge", StatusCode: http.StatusForbidden,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Name = strings.TrimSpace(req.Name)
|
|
||||||
req.Description = strings.TrimSpace(req.Description)
|
|
||||||
req.Image = strings.TrimSpace(req.Image)
|
|
||||||
|
|
||||||
if req.Name == "" {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_name", Message: "Name is required", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if length := len(req.Image); length > 1 && req.Image[0] == ':' && req.Image[length-1] == ':' {
|
|
||||||
req.Image = req.Image[1 : length-1]
|
|
||||||
}
|
|
||||||
if req.Image == "" {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "invalid_image", Message: "Emoji is required", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
badge.Name = req.Name
|
|
||||||
badge.Description = req.Description
|
|
||||||
badge.Image = req.Image
|
|
||||||
badge.Type = badgesmodel.BadgeType(req.Type)
|
|
||||||
badge.Multiple = req.Multiple
|
|
||||||
|
|
||||||
if err := p.store.UpdateBadge(badge); err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_update_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := json.Marshal(badge)
|
|
||||||
_, _ = w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) apiDeleteBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
|
||||||
badgeID, ok := mux.Vars(r)["badgeID"]
|
|
||||||
if !ok {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "missing_badge_id", Message: "Missing badge ID", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := p.mm.User.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeID))
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "badge_not_found", Message: "Badge not found", StatusCode: http.StatusNotFound,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !canEditBadge(user, p.badgeAdminUserIDs, badge) {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "no_permission", Message: "No permission to delete this badge", StatusCode: http.StatusForbidden,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.store.DeleteBadge(badgesmodel.BadgeID(badgeID)); err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_delete_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.Write([]byte(`{"success": true}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) apiDeleteType(w http.ResponseWriter, r *http.Request, userID string) {
|
|
||||||
typeID, ok := mux.Vars(r)["typeID"]
|
|
||||||
if !ok {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "missing_type_id", Message: "Missing type ID", StatusCode: http.StatusBadRequest,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := p.mm.User.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := p.store.GetType(badgesmodel.BadgeType(typeID))
|
|
||||||
if err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "type_not_found", Message: "Type not found", StatusCode: http.StatusNotFound,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.IsDefault {
|
|
||||||
T := p.getT("ru")
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_delete_default_type", Message: T("badges.api.cannot_delete_default_type", "Нельзя удалить тип по умолчанию"), StatusCode: http.StatusForbidden,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !canEditType(user, p.badgeAdminUserIDs, t) {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "no_permission", Message: "No permission to delete this type", StatusCode: http.StatusForbidden,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.store.DeleteType(badgesmodel.BadgeType(typeID)); err != nil {
|
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
|
||||||
ID: "cannot_delete_type", Message: err.Error(), StatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.Write([]byte(`{"success": true}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := p.mm.User.Get(userID)
|
user, err := p.mm.User.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the user", nil)
|
||||||
dialogError(w, T("badges.api.cannot_get_user", "Не удалось найти пользователя"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(user.Locale)
|
|
||||||
|
|
||||||
toCreate := &badgesmodel.Badge{}
|
toCreate := &badgesmodel.Badge{}
|
||||||
toCreate.CreatedBy = userID
|
toCreate.CreatedBy = userID
|
||||||
@ -530,7 +132,7 @@ func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userI
|
|||||||
image = image[1 : len(image)-1]
|
image = image[1 : len(image)-1]
|
||||||
}
|
}
|
||||||
if image == "" {
|
if image == "" {
|
||||||
dialogError(w, T("badges.api.invalid_field", "Некорректное поле"), map[string]string{"image": T("badges.api.empty_emoji", "Пустой эмодзи")})
|
dialogError(w, "Invalid field", map[string]string{"image": "Empty emoji"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toCreate.Image = image
|
toCreate.Image = image
|
||||||
@ -546,12 +148,12 @@ func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userI
|
|||||||
|
|
||||||
t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
|
t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.type_not_exist", "Этот тип не существует"), nil)
|
dialogError(w, "this type does not exist", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canCreateBadge(user, p.badgeAdminUserIDs, t) {
|
if !canCreateBadge(user, p.badgeAdminUserID, t) {
|
||||||
dialogError(w, T("badges.api.no_permissions_create_badge", "У вас нет прав на создание этого значка"), nil)
|
dialogError(w, "you have no permissions to create this badge", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,7 +166,7 @@ func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userI
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.badge_created", "Значок `%s` создан.", toCreate.Name),
|
Message: fmt.Sprintf("Badge `%s` created.", toCreate.Name),
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -573,21 +175,18 @@ func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userI
|
|||||||
func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := p.mm.User.Get(userID)
|
u, err := p.mm.User.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "cannot get user", nil)
|
||||||
dialogError(w, T("badges.api.cannot_get_user", "Не удалось найти пользователя"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canCreateType(u, p.badgeAdminUserIDs, false) {
|
if !canCreateType(u, p.badgeAdminUserID, false) {
|
||||||
dialogError(w, T("badges.api.no_permissions_create_type", "У вас нет прав на создание типа"), nil)
|
dialogError(w, "you have no permissions to create a type", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,29 +212,29 @@ func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID
|
|||||||
if username == "" {
|
if username == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
foundUser, userErr := p.mm.User.GetByUsername(username)
|
u, err := p.mm.User.GetByUsername(username)
|
||||||
if userErr != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanCreate: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, userErr)})
|
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanCreate: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toCreate.CanCreate.AllowList[foundUser.Id] = true
|
toCreate.CanCreate.AllowList[u.Id] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if grantAllowList != "" {
|
if grantAllowList != "" {
|
||||||
toCreate.CanGrant.AllowList = map[string]bool{}
|
toCreate.CanGrant.AllowList = map[string]bool{}
|
||||||
usernames := strings.Split(grantAllowList, ",")
|
usernames := strings.Split(createAllowList, ",")
|
||||||
for _, username := range usernames {
|
for _, username := range usernames {
|
||||||
username = strings.TrimSpace(username)
|
username = strings.TrimSpace(username)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
foundUser, userErr := p.mm.User.GetByUsername(username)
|
u, err := p.mm.User.GetByUsername(username)
|
||||||
if userErr != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanGrant: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, userErr)})
|
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanGrant: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toCreate.CanGrant.AllowList[foundUser.Id] = true
|
toCreate.CanGrant.AllowList[u.Id] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,7 +247,7 @@ func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.type_created", "Тип `%s` создан.", toCreate.Name),
|
Message: fmt.Sprintf("Type `%s` created.", toCreate.Name),
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -658,8 +257,7 @@ func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID
|
|||||||
func (p *Plugin) dialogSelectType(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogSelectType(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,21 +269,18 @@ func (p *Plugin) dialogSelectType(w http.ResponseWriter, r *http.Request, userID
|
|||||||
|
|
||||||
t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
|
t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "Cannot get type", map[string]string{DialogFieldBadgeType: "cannot get type"})
|
||||||
dialogError(w, T("badges.api.cannot_get_type", "Не удалось получить тип"), map[string]string{DialogFieldBadgeType: T("badges.api.cannot_get_type", "Не удалось получить тип")})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := p.mm.User.Get(userID)
|
u, err := p.mm.User.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "Cannot find user", nil)
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canEditType(u, p.badgeAdminUserIDs, t) {
|
if !canEditType(u, p.badgeAdminUserID, t) {
|
||||||
dialogError(w, T("badges.api.cannot_edit_type", "Вы не можете редактировать этот тип"), nil)
|
dialogError(w, "You cannot edit this type", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -702,37 +297,30 @@ func (p *Plugin) dialogSelectType(w http.ResponseWriter, r *http.Request, userID
|
|||||||
func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := p.mm.User.Get(userID)
|
u, err := p.mm.User.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "Cannot find user", nil)
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
originalTypeID := req.State
|
originalTypeID := req.State
|
||||||
|
|
||||||
originalType, err := p.store.GetType(badgesmodel.BadgeType(originalTypeID))
|
originalType, err := p.store.GetType(badgesmodel.BadgeType(originalTypeID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.could_not_get_type", "Не удалось получить тип"), nil)
|
dialogError(w, "could not get the type", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canEditType(u, p.badgeAdminUserIDs, originalType) {
|
if !canEditType(u, p.badgeAdminUserID, originalType) {
|
||||||
dialogError(w, T("badges.api.no_permissions_edit_type", "У вас нет прав на редактирование этого типа"), nil)
|
dialogError(w, "you have no permissions to edit this type", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if getDialogSubmissionBoolField(req, DialogFieldTypeDelete) {
|
if getDialogSubmissionBoolField(req, DialogFieldTypeDelete) {
|
||||||
if originalType.IsDefault {
|
|
||||||
dialogError(w, T("badges.api.cannot_delete_default_type", "Нельзя удалить тип по умолчанию"), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.store.DeleteType(badgesmodel.BadgeType(originalTypeID))
|
err = p.store.DeleteType(badgesmodel.BadgeType(originalTypeID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, err.Error(), nil)
|
dialogError(w, err.Error(), nil)
|
||||||
@ -761,7 +349,7 @@ func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID s
|
|||||||
var allowedUser *model.User
|
var allowedUser *model.User
|
||||||
allowedUser, err = p.mm.User.GetByUsername(username)
|
allowedUser, err = p.mm.User.GetByUsername(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanCreate: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, err)})
|
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanCreate: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalType.CanCreate.AllowList[allowedUser.Id] = true
|
originalType.CanCreate.AllowList[allowedUser.Id] = true
|
||||||
@ -777,7 +365,7 @@ func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID s
|
|||||||
var allowedUser *model.User
|
var allowedUser *model.User
|
||||||
allowedUser, err = p.mm.User.GetByUsername(username)
|
allowedUser, err = p.mm.User.GetByUsername(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanGrant: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, err)})
|
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanGrant: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalType.CanGrant.AllowList[allowedUser.Id] = true
|
originalType.CanGrant.AllowList[allowedUser.Id] = true
|
||||||
@ -792,7 +380,7 @@ func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID s
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.type_updated", "Тип `%s` обновлён.", originalType.Name),
|
Message: fmt.Sprintf("Type `%s` updated.", originalType.Name),
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -802,8 +390,7 @@ func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID s
|
|||||||
func (p *Plugin) dialogSelectBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogSelectBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -815,21 +402,18 @@ func (p *Plugin) dialogSelectBadge(w http.ResponseWriter, r *http.Request, userI
|
|||||||
|
|
||||||
b, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
|
b, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "Cannot get type", map[string]string{DialogFieldBadge: "cannot get badge"})
|
||||||
dialogError(w, T("badges.api.cannot_get_badge", "Не удалось получить значок"), map[string]string{DialogFieldBadge: T("badges.api.cannot_get_badge", "Не удалось получить значок")})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := p.mm.User.Get(userID)
|
u, err := p.mm.User.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "Cannot find user", nil)
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canEditBadge(u, p.badgeAdminUserIDs, b) {
|
if !canEditBadge(u, p.badgeAdminUserID, b) {
|
||||||
dialogError(w, T("badges.api.cannot_edit_badge", "Вы не можете редактировать этот значок"), nil)
|
dialogError(w, "You cannot edit this badge", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -846,29 +430,26 @@ func (p *Plugin) dialogSelectBadge(w http.ResponseWriter, r *http.Request, userI
|
|||||||
func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := p.mm.User.Get(userID)
|
u, err := p.mm.User.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "Cannot find user", nil)
|
||||||
dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
originalBadgeID := req.State
|
originalBadgeID := req.State
|
||||||
|
|
||||||
originalBadge, err := p.store.GetBadge(badgesmodel.BadgeID(originalBadgeID))
|
originalBadge, err := p.store.GetBadge(badgesmodel.BadgeID(originalBadgeID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.could_not_get_badge", "Не удалось получить значок"), nil)
|
dialogError(w, "could not get the badge", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canEditBadge(u, p.badgeAdminUserIDs, originalBadge) {
|
if !canEditBadge(u, p.badgeAdminUserID, originalBadge) {
|
||||||
dialogError(w, T("badges.api.no_permissions_edit_badge", "У вас нет прав на редактирование этого значка"), nil)
|
dialogError(w, "you have no permissions to edit this type", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,7 +485,7 @@ func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID
|
|||||||
image = image[1 : len(image)-1]
|
image = image[1 : len(image)-1]
|
||||||
}
|
}
|
||||||
if image == "" {
|
if image == "" {
|
||||||
dialogError(w, T("badges.api.invalid_field", "Некорректное поле"), map[string]string{"image": T("badges.api.empty_emoji", "Пустой эмодзи")})
|
dialogError(w, "Invalid field", map[string]string{"image": "Empty emoji"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalBadge.Image = image
|
originalBadge.Image = image
|
||||||
@ -928,7 +509,7 @@ func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.badge_updated", "Значок `%s` обновлён.", originalBadge.Name),
|
Message: fmt.Sprintf("Badge `%s` updated.", originalBadge.Name),
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -937,8 +518,7 @@ func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID
|
|||||||
func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,8 +532,7 @@ func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID stri
|
|||||||
|
|
||||||
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
|
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "badge not found", nil)
|
||||||
dialogError(w, T("badges.api.badge_not_found", "Значок не найден"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -962,7 +541,6 @@ func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID stri
|
|||||||
dialogError(w, err.Error(), nil)
|
dialogError(w, err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(granter.Locale)
|
|
||||||
|
|
||||||
badgeType, err := p.store.GetType(badge.Type)
|
badgeType, err := p.store.GetType(badge.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -970,8 +548,8 @@ func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID stri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canGrantBadge(granter, p.badgeAdminUserIDs, badge, badgeType) {
|
if !canGrantBadge(granter, p.badgeAdminUserID, badge, badgeType) {
|
||||||
dialogError(w, T("badges.api.no_permissions_grant", "У вас нет прав на выдачу этого значка"), nil)
|
dialogError(w, "you have no permissions to grant this badge", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,7 +564,7 @@ func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID stri
|
|||||||
|
|
||||||
grantToUser, err := p.mm.User.Get(grantToID)
|
grantToUser, err := p.mm.User.Get(grantToID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dialogError(w, T("badges.api.user_not_found", "Пользователь не найден"), nil)
|
dialogError(w, "user not found", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1009,7 +587,7 @@ func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID stri
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.badge_granted", "Значок `%s` выдан @%s.", badge.Name, grantToUser.Username),
|
Message: fmt.Sprintf("Badge `%s` granted to @%s.", badge.Name, grantToUser.Username),
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -1018,8 +596,7 @@ func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID stri
|
|||||||
func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1028,10 +605,9 @@ func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request
|
|||||||
dialogError(w, err.Error(), nil)
|
dialogError(w, err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canCreateSubscription(u, p.badgeAdminUserIDs, req.ChannelId) {
|
if !canCreateSubscription(u, p.badgeAdminUserID, req.ChannelId) {
|
||||||
dialogError(w, T("badges.api.cannot_create_subscription", "Вы не можете создать подписку"), nil)
|
dialogError(w, "You cannot create a subscription", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1049,7 +625,7 @@ func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.subscription_added", "Подписка добавлена"),
|
Message: "Subscription added",
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -1058,8 +634,7 @@ func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request
|
|||||||
func (p *Plugin) dialogDeleteSubscription(w http.ResponseWriter, r *http.Request, userID string) {
|
func (p *Plugin) dialogDeleteSubscription(w http.ResponseWriter, r *http.Request, userID string) {
|
||||||
req := model.SubmitDialogRequestFromJson(r.Body)
|
req := model.SubmitDialogRequestFromJson(r.Body)
|
||||||
if req == nil {
|
if req == nil {
|
||||||
T := p.getT("ru")
|
dialogError(w, "could not get the dialog request", nil)
|
||||||
dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1068,10 +643,9 @@ func (p *Plugin) dialogDeleteSubscription(w http.ResponseWriter, r *http.Request
|
|||||||
dialogError(w, err.Error(), nil)
|
dialogError(w, err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canCreateSubscription(u, p.badgeAdminUserIDs, req.ChannelId) {
|
if !canCreateSubscription(u, p.badgeAdminUserID, req.ChannelId) {
|
||||||
dialogError(w, T("badges.api.cannot_delete_subscription", "Вы не можете удалить подписку"), nil)
|
dialogError(w, "You cannot delete a subscription", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,7 +663,7 @@ func (p *Plugin) dialogDeleteSubscription(w http.ResponseWriter, r *http.Request
|
|||||||
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
p.mm.Post.SendEphemeralPost(userID, &model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: req.ChannelId,
|
ChannelId: req.ChannelId,
|
||||||
Message: T("badges.api.subscription_removed", "Подписка удалена"),
|
Message: "Subscription removed",
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogOK(w)
|
dialogOK(w)
|
||||||
@ -1162,7 +736,7 @@ func (p *Plugin) grantBadge(w http.ResponseWriter, r *http.Request, pluginID str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canGrantBadge(granter, p.badgeAdminUserIDs, badge, badgeType) {
|
if !canGrantBadge(granter, p.badgeAdminUserID, badge, badgeType) {
|
||||||
p.writeAPIError(w, &APIErrorResponse{
|
p.writeAPIError(w, &APIErrorResponse{
|
||||||
ID: "cannot grant badge",
|
ID: "cannot grant badge",
|
||||||
Message: "you have no permissions to grant this badge",
|
Message: "you have no permissions to grant this badge",
|
||||||
@ -1393,15 +967,13 @@ func (p *Plugin) extractUserMiddleWare(handler HTTPHandlerFuncWithUser, response
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
userID := r.Header.Get("Mattermost-User-ID")
|
userID := r.Header.Get("Mattermost-User-ID")
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
T := p.getT("ru")
|
|
||||||
msg := T("badges.api.not_authorized", "Не авторизован")
|
|
||||||
switch responseType {
|
switch responseType {
|
||||||
case ResponseTypeJSON:
|
case ResponseTypeJSON:
|
||||||
p.writeAPIError(w, &APIErrorResponse{ID: "", Message: msg, StatusCode: http.StatusUnauthorized})
|
p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized})
|
||||||
case ResponseTypePlain:
|
case ResponseTypePlain:
|
||||||
http.Error(w, msg, http.StatusUnauthorized)
|
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
||||||
case ResponseTypeDialog:
|
case ResponseTypeDialog:
|
||||||
dialogError(w, msg, nil)
|
dialogError(w, "Not Authorized", nil)
|
||||||
default:
|
default:
|
||||||
p.mm.Log.Error("Unknown ResponseType detected")
|
p.mm.Log.Error("Unknown ResponseType detected")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,8 @@ func getHelp() string {
|
|||||||
func (p *Plugin) getCommand() *model.Command {
|
func (p *Plugin) getCommand() *model.Command {
|
||||||
return &model.Command{
|
return &model.Command{
|
||||||
Trigger: "badges",
|
Trigger: "badges",
|
||||||
DisplayName: "Achievements Bot",
|
DisplayName: "Badges Bot",
|
||||||
Description: "Achievements",
|
Description: "Badges",
|
||||||
AutoComplete: true,
|
AutoComplete: true,
|
||||||
AutoCompleteDesc: "Available commands:",
|
AutoCompleteDesc: "Available commands:",
|
||||||
AutoCompleteHint: "[command]",
|
AutoCompleteHint: "[command]",
|
||||||
@ -77,13 +77,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo
|
|||||||
p.postCommandResponse(args, fmt.Sprintf("__Error: %s__", err.Error()))
|
p.postCommandResponse(args, fmt.Sprintf("__Error: %s__", err.Error()))
|
||||||
} else {
|
} else {
|
||||||
p.mm.Log.Error(err.Error())
|
p.mm.Log.Error(err.Error())
|
||||||
u, _ := p.mm.User.Get(args.UserId)
|
p.postCommandResponse(args, "An unknown error occurred. Please talk to your system administrator for help.")
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
p.postCommandResponse(args, T("badges.error.unknown", "Произошла неизвестная ошибка. Обратитесь к системному администратору."))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,12 +93,11 @@ func (p *Plugin) runClean(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, &model.CommandResponse{Text: "Cannot get user."}, nil
|
return false, &model.CommandResponse{Text: "Cannot get user."}, nil
|
||||||
}
|
}
|
||||||
T := p.getT(user.Locale)
|
|
||||||
if !user.IsSystemAdmin() {
|
if !user.IsSystemAdmin() {
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.only_sysadmin_clean", "Только системный администратор может очистить базу значков.")}, nil
|
return false, &model.CommandResponse{Text: "Only a system admin can clean the badges database."}, nil
|
||||||
}
|
}
|
||||||
_ = p.mm.KV.DeleteAll()
|
_ = p.mm.KV.DeleteAll()
|
||||||
return false, &model.CommandResponse{Text: T("badges.success.clean", "Очищено")}, nil
|
return false, &model.CommandResponse{Text: "Clean"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) runCreate(args []string, extra *model.CommandArgs) (bool, *model.CommandResponse, error) {
|
func (p *Plugin) runCreate(args []string, extra *model.CommandArgs) (bool, *model.CommandResponse, error) {
|
||||||
@ -112,13 +105,7 @@ func (p *Plugin) runCreate(args []string, extra *model.CommandArgs) (bool, *mode
|
|||||||
restOfArgs := []string{}
|
restOfArgs := []string{}
|
||||||
var handler func([]string, *model.CommandArgs) (bool, *model.CommandResponse, error)
|
var handler func([]string, *model.CommandArgs) (bool, *model.CommandResponse, error)
|
||||||
if lengthOfArgs == 0 {
|
if lengthOfArgs == 0 {
|
||||||
u, _ := p.mm.User.Get(extra.UserId)
|
return false, &model.CommandResponse{Text: "Specify what you want to create."}, nil
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.specify_create", "Укажите, что вы хотите создать.")}, nil
|
|
||||||
}
|
}
|
||||||
command := args[0]
|
command := args[0]
|
||||||
if lengthOfArgs > 1 {
|
if lengthOfArgs > 1 {
|
||||||
@ -130,13 +117,7 @@ func (p *Plugin) runCreate(args []string, extra *model.CommandArgs) (bool, *mode
|
|||||||
case "type":
|
case "type":
|
||||||
handler = p.runCreateType
|
handler = p.runCreateType
|
||||||
default:
|
default:
|
||||||
u, _ := p.mm.User.Get(extra.UserId)
|
return false, &model.CommandResponse{Text: "You can create either badge or type"}, nil
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.create_badge_or_type", "Можно создать badge или type")}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler(restOfArgs, extra)
|
return handler(restOfArgs, extra)
|
||||||
@ -147,7 +128,6 @@ func (p *Plugin) runCreateBadge(args []string, extra *model.CommandArgs) (bool,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
typeSuggestions, err := p.filterCreateBadgeTypes(u)
|
typeSuggestions, err := p.filterCreateBadgeTypes(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -161,45 +141,45 @@ func (p *Plugin) runCreateBadge(args []string, extra *model.CommandArgs) (bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(typeOptions) == 0 {
|
if len(typeOptions) == 0 {
|
||||||
return commandError(T("badges.error.no_types_available", "Вы не можете создать значки ни одного типа."))
|
return commandError("You cannot create badges from any type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.mm.Frontend.OpenInteractiveDialog(model.OpenDialogRequest{
|
err = p.mm.Frontend.OpenInteractiveDialog(model.OpenDialogRequest{
|
||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathCreateBadge,
|
URL: p.getDialogURL() + DialogPathCreateBadge,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.create_badge.title", "Создать значок"),
|
Title: "Create badge",
|
||||||
SubmitLabel: T("badges.dialog.create_badge.submit", "Создать"),
|
SubmitLabel: "Create",
|
||||||
Elements: []model.DialogElement{
|
Elements: []model.DialogElement{
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.name", "Название"),
|
DisplayName: "Name",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldBadgeName,
|
Name: DialogFieldBadgeName,
|
||||||
MaxLength: badgesmodel.NameMaxLength,
|
MaxLength: badgesmodel.NameMaxLength,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.description", "Описание"),
|
DisplayName: "Description",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldBadgeDescription,
|
Name: DialogFieldBadgeDescription,
|
||||||
MaxLength: badgesmodel.DescriptionMaxLength,
|
MaxLength: badgesmodel.DescriptionMaxLength,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.image", "Изображение"),
|
DisplayName: "Image",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldBadgeImage,
|
Name: DialogFieldBadgeImage,
|
||||||
HelpText: T("badges.field.image.help", "Введите название эмодзи"),
|
HelpText: "Insert a emoticon name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.type", "Тип"),
|
DisplayName: "Type",
|
||||||
Type: "select",
|
Type: "select",
|
||||||
Name: DialogFieldBadgeType,
|
Name: DialogFieldBadgeType,
|
||||||
Options: typeOptions,
|
Options: typeOptions,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.multiple", "Многократный"),
|
DisplayName: "Multiple",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldBadgeMultiple,
|
Name: DialogFieldBadgeMultiple,
|
||||||
HelpText: T("badges.field.multiple.help", "Можно ли выдавать этот значок несколько раз"),
|
HelpText: "Whether the badge can be granted multiple times",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -218,13 +198,7 @@ func (p *Plugin) runEdit(args []string, extra *model.CommandArgs) (bool, *model.
|
|||||||
restOfArgs := []string{}
|
restOfArgs := []string{}
|
||||||
var handler func([]string, *model.CommandArgs) (bool, *model.CommandResponse, error)
|
var handler func([]string, *model.CommandArgs) (bool, *model.CommandResponse, error)
|
||||||
if lengthOfArgs == 0 {
|
if lengthOfArgs == 0 {
|
||||||
u, _ := p.mm.User.Get(extra.UserId)
|
return false, &model.CommandResponse{Text: "Specify what you want to create."}, nil
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.specify_edit", "Укажите, что вы хотите отредактировать.")}, nil
|
|
||||||
}
|
}
|
||||||
command := args[0]
|
command := args[0]
|
||||||
if lengthOfArgs > 1 {
|
if lengthOfArgs > 1 {
|
||||||
@ -236,13 +210,7 @@ func (p *Plugin) runEdit(args []string, extra *model.CommandArgs) (bool, *model.
|
|||||||
case "type":
|
case "type":
|
||||||
handler = p.runEditType
|
handler = p.runEditType
|
||||||
default:
|
default:
|
||||||
u, _ := p.mm.User.Get(extra.UserId)
|
return false, &model.CommandResponse{Text: "You can create either badge or type"}, nil
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.edit_badge_or_type", "Можно редактировать badge или type")}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler(restOfArgs, extra)
|
return handler(restOfArgs, extra)
|
||||||
@ -253,7 +221,6 @@ func (p *Plugin) runEditBadge(args []string, extra *model.CommandArgs) (bool, *m
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
var badgeIDStr string
|
var badgeIDStr string
|
||||||
fs := pflag.NewFlagSet("", pflag.ContinueOnError)
|
fs := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||||
@ -263,7 +230,7 @@ func (p *Plugin) runEditBadge(args []string, extra *model.CommandArgs) (bool, *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
if badgeIDStr == "" {
|
if badgeIDStr == "" {
|
||||||
return commandError(T("badges.error.must_set_badge_id", "Необходимо указать ID значка"))
|
return commandError("You must set the badge ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
|
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
|
||||||
@ -271,8 +238,8 @@ func (p *Plugin) runEditBadge(args []string, extra *model.CommandArgs) (bool, *m
|
|||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canEditBadge(u, p.badgeAdminUserIDs, badge) {
|
if !canEditBadge(u, p.badgeAdminUserID, badge) {
|
||||||
return commandError(T("badges.error.cannot_edit_badge", "У вас нет прав на редактирование этого значка"))
|
return commandError("you cannot edit this badge")
|
||||||
}
|
}
|
||||||
|
|
||||||
typeSuggestions, err := p.filterCreateBadgeTypes(u)
|
typeSuggestions, err := p.filterCreateBadgeTypes(u)
|
||||||
@ -287,58 +254,58 @@ func (p *Plugin) runEditBadge(args []string, extra *model.CommandArgs) (bool, *m
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(typeOptions) == 0 {
|
if len(typeOptions) == 0 {
|
||||||
return commandError(T("badges.error.no_types_available", "Вы не можете создать значки ни одного типа."))
|
return commandError("You cannot create badges from any type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.mm.Frontend.OpenInteractiveDialog(model.OpenDialogRequest{
|
err = p.mm.Frontend.OpenInteractiveDialog(model.OpenDialogRequest{
|
||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathEditBadge,
|
URL: p.getDialogURL() + DialogPathEditBadge,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.edit_badge.title", "Редактировать значок"),
|
Title: "Create badge",
|
||||||
SubmitLabel: T("badges.dialog.edit_badge.submit", "Сохранить"),
|
SubmitLabel: "Edit",
|
||||||
State: string(badge.ID),
|
State: string(badge.ID),
|
||||||
Elements: []model.DialogElement{
|
Elements: []model.DialogElement{
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.name", "Название"),
|
DisplayName: "Name",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldBadgeName,
|
Name: DialogFieldBadgeName,
|
||||||
MaxLength: badgesmodel.NameMaxLength,
|
MaxLength: badgesmodel.NameMaxLength,
|
||||||
Default: badge.Name,
|
Default: badge.Name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.description", "Описание"),
|
DisplayName: "Description",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldBadgeDescription,
|
Name: DialogFieldBadgeDescription,
|
||||||
MaxLength: badgesmodel.DescriptionMaxLength,
|
MaxLength: badgesmodel.DescriptionMaxLength,
|
||||||
Default: badge.Description,
|
Default: badge.Description,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.image", "Изображение"),
|
DisplayName: "Image",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldBadgeImage,
|
Name: DialogFieldBadgeImage,
|
||||||
HelpText: T("badges.field.image.help", "Введите название эмодзи"),
|
HelpText: "Insert a emoticon name",
|
||||||
Default: badge.Image,
|
Default: badge.Image,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.type", "Тип"),
|
DisplayName: "Type",
|
||||||
Type: "select",
|
Type: "select",
|
||||||
Name: DialogFieldBadgeType,
|
Name: DialogFieldBadgeType,
|
||||||
Options: typeOptions,
|
Options: typeOptions,
|
||||||
Default: string(badge.Type),
|
Default: string(badge.Type),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.multiple", "Многократный"),
|
DisplayName: "Multiple",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldBadgeMultiple,
|
Name: DialogFieldBadgeMultiple,
|
||||||
HelpText: T("badges.field.multiple.help", "Можно ли выдавать этот значок несколько раз"),
|
HelpText: "Whether the badge can be granted multiple times",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: getBooleanString(badge.Multiple),
|
Default: getBooleanString(badge.Multiple),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.delete_badge", "Удалить значок"),
|
DisplayName: "Delete badge",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldBadgeDelete,
|
Name: DialogFieldBadgeDelete,
|
||||||
HelpText: T("badges.field.delete_badge.help", "ВНИМАНИЕ: если отметить, значок будет удалён безвозвратно."),
|
HelpText: "WARNING: Checking this will remove this badge permanently.",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -357,10 +324,9 @@ func (p *Plugin) runEditType(args []string, extra *model.CommandArgs) (bool, *mo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canCreateType(u, p.badgeAdminUserIDs, false) {
|
if !canCreateType(u, p.badgeAdminUserID, false) {
|
||||||
return commandError(T("badges.error.no_permissions_edit_type", "У вас нет прав на редактирование типа значков."))
|
return commandError("You have no permissions to edit a badge type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var badgeTypeStr string
|
var badgeTypeStr string
|
||||||
@ -371,7 +337,7 @@ func (p *Plugin) runEditType(args []string, extra *model.CommandArgs) (bool, *mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if badgeTypeStr == "" {
|
if badgeTypeStr == "" {
|
||||||
return commandError(T("badges.error.must_provide_type_id", "Необходимо указать ID типа"))
|
return commandError("You must provide a type id")
|
||||||
}
|
}
|
||||||
|
|
||||||
typeDefinition, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
|
typeDefinition, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
|
||||||
@ -379,8 +345,8 @@ func (p *Plugin) runEditType(args []string, extra *model.CommandArgs) (bool, *mo
|
|||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canEditType(u, p.badgeAdminUserIDs, typeDefinition) {
|
if !canEditType(u, p.badgeAdminUserID, typeDefinition) {
|
||||||
return commandError(T("badges.error.cannot_edit_type", "У вас нет прав на редактирование этого типа"))
|
return commandError("you cannot edit this type")
|
||||||
}
|
}
|
||||||
|
|
||||||
canGrantAllowList := ""
|
canGrantAllowList := ""
|
||||||
@ -423,56 +389,56 @@ func (p *Plugin) runEditType(args []string, extra *model.CommandArgs) (bool, *mo
|
|||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathEditType,
|
URL: p.getDialogURL() + DialogPathEditType,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.edit_type.title", "Редактировать тип"),
|
Title: "Edit type",
|
||||||
SubmitLabel: T("badges.dialog.edit_type.submit", "Сохранить"),
|
SubmitLabel: "Edit",
|
||||||
State: badgeTypeStr,
|
State: badgeTypeStr,
|
||||||
Elements: []model.DialogElement{
|
Elements: []model.DialogElement{
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.name", "Название"),
|
DisplayName: "Name",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldTypeName,
|
Name: DialogFieldTypeName,
|
||||||
MaxLength: badgesmodel.NameMaxLength,
|
MaxLength: badgesmodel.NameMaxLength,
|
||||||
Default: typeDefinition.Name,
|
Default: typeDefinition.Name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.everyone_can_create", "Все могут создавать значки"),
|
DisplayName: "Everyone can create badge",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldTypeEveryoneCanCreate,
|
Name: DialogFieldTypeEveryoneCanCreate,
|
||||||
HelpText: T("badges.field.everyone_can_create.help", "Любой пользователь может создать значок этого типа"),
|
HelpText: "Whether any user can create a badge of this type",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: getBooleanString(typeDefinition.CanCreate.Everyone),
|
Default: getBooleanString(typeDefinition.CanCreate.Everyone),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.allowlist_create", "Список допущенных к созданию"),
|
DisplayName: "Can create allowlist",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldTypeAllowlistCanCreate,
|
Name: DialogFieldTypeAllowlistCanCreate,
|
||||||
HelpText: T("badges.field.allowlist_create.help", "Укажите имена пользователей через запятую (,), которые могут создавать значки этого типа."),
|
HelpText: "Fill the usernames separated by comma (,) of the people that can create badges of this type.",
|
||||||
Placeholder: "user-1, user-2, user-3",
|
Placeholder: "user-1, user-2, user-3",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: canCreateAllowList,
|
Default: canCreateAllowList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.everyone_can_grant", "Все могут выдавать значки"),
|
DisplayName: "Everyone can grant badge",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldTypeEveryoneCanGrant,
|
Name: DialogFieldTypeEveryoneCanGrant,
|
||||||
HelpText: T("badges.field.everyone_can_grant.help", "Любой пользователь может выдать значок этого типа"),
|
HelpText: "Whether any user can grant a badge of this type",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: getBooleanString(typeDefinition.CanGrant.Everyone),
|
Default: getBooleanString(typeDefinition.CanGrant.Everyone),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.allowlist_grant", "Список допущенных к выдаче"),
|
DisplayName: "Can grant allowlist",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldTypeAllowlistCanGrant,
|
Name: DialogFieldTypeAllowlistCanGrant,
|
||||||
HelpText: T("badges.field.allowlist_grant.help", "Укажите имена пользователей через запятую (,), которые могут выдавать значки этого типа."),
|
HelpText: "Fill the usernames separated by comma (,) of the people that can grant badges of this type.",
|
||||||
Placeholder: "user-1, user-2, user-3",
|
Placeholder: "user-1, user-2, user-3",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: canGrantAllowList,
|
Default: canGrantAllowList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.delete_type", "Удалить тип"),
|
DisplayName: "Remove type",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldTypeDelete,
|
Name: DialogFieldTypeDelete,
|
||||||
HelpText: T("badges.field.delete_type.help", "ВНИМАНИЕ: если отметить, этот тип и все связанные значки будут удалены безвозвратно."),
|
HelpText: "WARNING: checking this will remove this type and all associated badges permanently.",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -491,52 +457,51 @@ func (p *Plugin) runCreateType(args []string, extra *model.CommandArgs) (bool, *
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(u.Locale)
|
|
||||||
|
|
||||||
if !canCreateType(u, p.badgeAdminUserIDs, false) {
|
if !canCreateType(u, p.badgeAdminUserID, false) {
|
||||||
return commandError(T("badges.error.no_permissions_create_type", "У вас нет прав на создание типа значков."))
|
return commandError("You have no permissions to create a badge type.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.mm.Frontend.OpenInteractiveDialog(model.OpenDialogRequest{
|
err = p.mm.Frontend.OpenInteractiveDialog(model.OpenDialogRequest{
|
||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathCreateType,
|
URL: p.getDialogURL() + DialogPathCreateType,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.create_type.title", "Создать тип"),
|
Title: "Create type",
|
||||||
SubmitLabel: T("badges.dialog.create_type.submit", "Создать"),
|
SubmitLabel: "Create",
|
||||||
Elements: []model.DialogElement{
|
Elements: []model.DialogElement{
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.name", "Название"),
|
DisplayName: "Name",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldTypeName,
|
Name: DialogFieldTypeName,
|
||||||
MaxLength: badgesmodel.NameMaxLength,
|
MaxLength: badgesmodel.NameMaxLength,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.everyone_can_create", "Все могут создавать значки"),
|
DisplayName: "Everyone can create badge",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldTypeEveryoneCanCreate,
|
Name: DialogFieldTypeEveryoneCanCreate,
|
||||||
HelpText: T("badges.field.everyone_can_create.help", "Любой пользователь может создать значок этого типа"),
|
HelpText: "Whether any user can create a badge of this type",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.allowlist_create", "Список допущенных к созданию"),
|
DisplayName: "Can create allowlist",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldTypeAllowlistCanCreate,
|
Name: DialogFieldTypeAllowlistCanCreate,
|
||||||
HelpText: T("badges.field.allowlist_create.help", "Укажите имена пользователей через запятую (,), которые могут создавать значки этого типа."),
|
HelpText: "Fill the usernames separated by comma (,) of the people that can create badges of this type.",
|
||||||
Placeholder: "user-1, user-2, user-3",
|
Placeholder: "user-1, user-2, user-3",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.everyone_can_grant", "Все могут выдавать значки"),
|
DisplayName: "Everyone can grant badge",
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
Name: DialogFieldTypeEveryoneCanGrant,
|
Name: DialogFieldTypeEveryoneCanGrant,
|
||||||
HelpText: T("badges.field.everyone_can_grant.help", "Любой пользователь может выдать значок этого типа"),
|
HelpText: "Whether any user can grant a badge of this type",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.allowlist_grant", "Список допущенных к выдаче"),
|
DisplayName: "Can grant allowlist",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Name: DialogFieldTypeAllowlistCanGrant,
|
Name: DialogFieldTypeAllowlistCanGrant,
|
||||||
HelpText: T("badges.field.allowlist_grant.help", "Укажите имена пользователей через запятую (,), которые могут выдавать значки этого типа."),
|
HelpText: "Fill the usernames separated by comma (,) of the people that can grant badges of this type.",
|
||||||
Placeholder: "user-1, user-2, user-3",
|
Placeholder: "user-1, user-2, user-3",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
@ -570,7 +535,6 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(granter.Locale)
|
|
||||||
|
|
||||||
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeStr))
|
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -582,8 +546,8 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canGrantBadge(granter, p.badgeAdminUserIDs, badge, badgeType) {
|
if !canGrantBadge(granter, p.badgeAdminUserID, badge, badgeType) {
|
||||||
return commandError(T("badges.error.no_permissions_grant", "У вас нет прав на выдачу этого значка"))
|
return commandError("you have no permissions to grant this badge")
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := p.mm.User.GetByUsername(username)
|
user, err := p.mm.User.GetByUsername(username)
|
||||||
@ -600,16 +564,10 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
p.notifyGrant(badgesmodel.BadgeID(badgeStr), extra.UserId, user, false, "", "")
|
p.notifyGrant(badgesmodel.BadgeID(badgeStr), extra.UserId, user, false, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.postCommandResponse(extra, T("badges.success.granted", "Выдано"))
|
p.postCommandResponse(extra, "Granted")
|
||||||
return false, &model.CommandResponse{}, nil
|
return false, &model.CommandResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
actingUser, err := p.mm.User.Get(extra.UserId)
|
|
||||||
if err != nil {
|
|
||||||
return commandError(err.Error())
|
|
||||||
}
|
|
||||||
T := p.getT(actingUser.Locale)
|
|
||||||
|
|
||||||
elements := []model.DialogElement{}
|
elements := []model.DialogElement{}
|
||||||
|
|
||||||
stateText := ""
|
stateText := ""
|
||||||
@ -624,19 +582,24 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
introductionText = T("badges.dialog.grant.intro", "Выдать значок пользователю @%s", username)
|
introductionText = "Grant badge to @" + username
|
||||||
stateText = user.Id
|
stateText = user.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
if stateText == "" {
|
if stateText == "" {
|
||||||
elements = append(elements, model.DialogElement{
|
elements = append(elements, model.DialogElement{
|
||||||
DisplayName: T("badges.field.user", "Пользователь"),
|
DisplayName: "User",
|
||||||
Type: "select",
|
Type: "select",
|
||||||
Name: DialogFieldUser,
|
Name: DialogFieldUser,
|
||||||
DataSource: "users",
|
DataSource: "users",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actingUser, err := p.mm.User.Get(extra.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return commandError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
options := []*model.PostActionOptions{}
|
options := []*model.PostActionOptions{}
|
||||||
grantableBadges, err := p.filterGrantBadges(actingUser)
|
grantableBadges, err := p.filterGrantBadges(actingUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -647,7 +610,7 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
}
|
}
|
||||||
|
|
||||||
badgeElement := model.DialogElement{
|
badgeElement := model.DialogElement{
|
||||||
DisplayName: T("badges.field.badge", "Значок"),
|
DisplayName: "Badge",
|
||||||
Type: "select",
|
Type: "select",
|
||||||
Name: DialogFieldBadge,
|
Name: DialogFieldBadge,
|
||||||
Options: options,
|
Options: options,
|
||||||
@ -663,7 +626,7 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return commandError(T("badges.error.cannot_grant_badge", "Вы не можете выдать этот значок"))
|
return commandError("You cannot grant that badge")
|
||||||
}
|
}
|
||||||
|
|
||||||
badgeElement.Default = badgeStr
|
badgeElement.Default = badgeStr
|
||||||
@ -672,18 +635,18 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
elements = append(elements, badgeElement)
|
elements = append(elements, badgeElement)
|
||||||
|
|
||||||
elements = append(elements, model.DialogElement{
|
elements = append(elements, model.DialogElement{
|
||||||
DisplayName: T("badges.field.reason", "Причина"),
|
DisplayName: "Reason",
|
||||||
Name: DialogFieldGrantReason,
|
Name: DialogFieldGrantReason,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
HelpText: T("badges.field.reason.help", "Причина выдачи значка. Будет видна пользователю и в уведомлениях о выдаче (например, в подписках)."),
|
HelpText: "Reason why you are granting this badge. This will be seen by the user, and wherever this grant notification is shown (e.g. subscriptions).",
|
||||||
Type: "text",
|
Type: "text",
|
||||||
})
|
})
|
||||||
|
|
||||||
elements = append(elements, model.DialogElement{
|
elements = append(elements, model.DialogElement{
|
||||||
DisplayName: T("badges.field.notify_here", "Уведомить в этом канале"),
|
DisplayName: "Notify on this channel",
|
||||||
Name: DialogFieldNotifyHere,
|
Name: DialogFieldNotifyHere,
|
||||||
Type: "bool",
|
Type: "bool",
|
||||||
HelpText: T("badges.field.notify_here.help", "Если отметить, бот отправит сообщение в этот канал о том, что вы выдали значок этому пользователю."),
|
HelpText: "If you mark this, the bot will send a message to this channel notifying that you granted this badge to this person.",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -691,9 +654,9 @@ func (p *Plugin) runGrant(args []string, extra *model.CommandArgs) (bool, *model
|
|||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathGrant,
|
URL: p.getDialogURL() + DialogPathGrant,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.grant.title", "Выдать значок"),
|
Title: "Grant badge",
|
||||||
IntroductionText: introductionText,
|
IntroductionText: introductionText,
|
||||||
SubmitLabel: T("badges.dialog.grant.submit", "Выдать"),
|
SubmitLabel: "Grant",
|
||||||
Elements: elements,
|
Elements: elements,
|
||||||
State: stateText,
|
State: stateText,
|
||||||
},
|
},
|
||||||
@ -711,13 +674,7 @@ func (p *Plugin) runSubscription(args []string, extra *model.CommandArgs) (bool,
|
|||||||
restOfArgs := []string{}
|
restOfArgs := []string{}
|
||||||
var handler func([]string, *model.CommandArgs) (bool, *model.CommandResponse, error)
|
var handler func([]string, *model.CommandArgs) (bool, *model.CommandResponse, error)
|
||||||
if lengthOfArgs == 0 {
|
if lengthOfArgs == 0 {
|
||||||
u, _ := p.mm.User.Get(extra.UserId)
|
return false, &model.CommandResponse{Text: "Specify what you want to do."}, nil
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.specify_subscription", "Укажите, что вы хотите сделать.")}, nil
|
|
||||||
}
|
}
|
||||||
command := args[0]
|
command := args[0]
|
||||||
if lengthOfArgs > 1 {
|
if lengthOfArgs > 1 {
|
||||||
@ -729,13 +686,7 @@ func (p *Plugin) runSubscription(args []string, extra *model.CommandArgs) (bool,
|
|||||||
case "remove":
|
case "remove":
|
||||||
handler = p.runDeleteSubscription
|
handler = p.runDeleteSubscription
|
||||||
default:
|
default:
|
||||||
u, _ := p.mm.User.Get(extra.UserId)
|
return false, &model.CommandResponse{Text: "You can either create or delete subscriptions"}, nil
|
||||||
locale := "ru"
|
|
||||||
if u != nil {
|
|
||||||
locale = u.Locale
|
|
||||||
}
|
|
||||||
T := p.getT(locale)
|
|
||||||
return false, &model.CommandResponse{Text: T("badges.error.create_or_delete_subscription", "Можно создать или удалить подписку")}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler(restOfArgs, extra)
|
return handler(restOfArgs, extra)
|
||||||
@ -753,10 +704,9 @@ func (p *Plugin) runCreateSubscription(args []string, extra *model.CommandArgs)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(actingUser.Locale)
|
|
||||||
|
|
||||||
if !canCreateSubscription(actingUser, p.badgeAdminUserIDs, extra.ChannelId) {
|
if !canCreateSubscription(actingUser, p.badgeAdminUserID, extra.ChannelId) {
|
||||||
return commandError(T("badges.error.cannot_create_subscription", "Вы не можете создавать подписки"))
|
return commandError("You cannot create subscriptions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if typeStr != "" {
|
if typeStr != "" {
|
||||||
@ -766,7 +716,7 @@ func (p *Plugin) runCreateSubscription(args []string, extra *model.CommandArgs)
|
|||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
p.postCommandResponse(extra, T("badges.success.granted", "Выдано"))
|
p.postCommandResponse(extra, "Granted")
|
||||||
return false, &model.CommandResponse{}, nil
|
return false, &model.CommandResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,12 +733,12 @@ func (p *Plugin) runCreateSubscription(args []string, extra *model.CommandArgs)
|
|||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathCreateSubscription,
|
URL: p.getDialogURL() + DialogPathCreateSubscription,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.create_subscription.title", "Создать подписку"),
|
Title: "Create subscription",
|
||||||
IntroductionText: T("badges.dialog.create_subscription.intro", "Выберите тип значка, на который хотите подписать этот канал."),
|
IntroductionText: "Introduce the badge type you want to subscribe to this channel.",
|
||||||
SubmitLabel: T("badges.dialog.create_subscription.submit", "Добавить"),
|
SubmitLabel: "Add",
|
||||||
Elements: []model.DialogElement{
|
Elements: []model.DialogElement{
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.type", "Тип"),
|
DisplayName: "Type",
|
||||||
Type: "select",
|
Type: "select",
|
||||||
Name: DialogFieldBadgeType,
|
Name: DialogFieldBadgeType,
|
||||||
Options: options,
|
Options: options,
|
||||||
@ -816,10 +766,9 @@ func (p *Plugin) runDeleteSubscription(args []string, extra *model.CommandArgs)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
T := p.getT(actingUser.Locale)
|
|
||||||
|
|
||||||
if !canCreateSubscription(actingUser, p.badgeAdminUserIDs, extra.ChannelId) {
|
if !canCreateSubscription(actingUser, p.badgeAdminUserID, extra.ChannelId) {
|
||||||
return commandError(T("badges.error.cannot_create_subscription", "Вы не можете создавать подписки"))
|
return commandError("You cannot create subscriptions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if typeStr != "" {
|
if typeStr != "" {
|
||||||
@ -828,7 +777,7 @@ func (p *Plugin) runDeleteSubscription(args []string, extra *model.CommandArgs)
|
|||||||
return commandError(err.Error())
|
return commandError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
p.postCommandResponse(extra, T("badges.success.removed", "Удалено"))
|
p.postCommandResponse(extra, "Removed")
|
||||||
return false, &model.CommandResponse{}, nil
|
return false, &model.CommandResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -845,12 +794,12 @@ func (p *Plugin) runDeleteSubscription(args []string, extra *model.CommandArgs)
|
|||||||
TriggerId: extra.TriggerId,
|
TriggerId: extra.TriggerId,
|
||||||
URL: p.getDialogURL() + DialogPathDeleteSubscription,
|
URL: p.getDialogURL() + DialogPathDeleteSubscription,
|
||||||
Dialog: model.Dialog{
|
Dialog: model.Dialog{
|
||||||
Title: T("badges.dialog.delete_subscription.title", "Удалить подписку"),
|
Title: "Delete subscription",
|
||||||
IntroductionText: T("badges.dialog.delete_subscription.intro", "Выберите тип значка, подписку на который хотите удалить из этого канала."),
|
IntroductionText: "Introduce the badge type you want to remove from this channel.",
|
||||||
SubmitLabel: T("badges.dialog.delete_subscription.submit", "Удалить"),
|
SubmitLabel: "Remove",
|
||||||
Elements: []model.DialogElement{
|
Elements: []model.DialogElement{
|
||||||
{
|
{
|
||||||
DisplayName: T("badges.field.type", "Тип"),
|
DisplayName: "Type",
|
||||||
Type: "select",
|
Type: "select",
|
||||||
Name: DialogFieldBadgeType,
|
Name: DialogFieldBadgeType,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -79,20 +78,13 @@ func (p *Plugin) OnConfigurationChange() error {
|
|||||||
return errors.Wrap(err, "failed to load plugin configuration")
|
return errors.Wrap(err, "failed to load plugin configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.badgeAdminUserIDs = make(map[string]bool)
|
p.badgeAdminUserID = ""
|
||||||
if configuration.BadgesAdmin != "" {
|
if configuration.BadgesAdmin != "" {
|
||||||
for username := range strings.SplitSeq(configuration.BadgesAdmin, ",") {
|
u, err := p.API.GetUserByUsername(configuration.BadgesAdmin)
|
||||||
username = strings.TrimSpace(username)
|
|
||||||
if username == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
u, err := p.API.GetUserByUsername(username)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.API.LogWarn("Cannot find badge admin user", "username", username, "error", err.Error())
|
return errors.Wrap(err, "cannot get badge admin user")
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.badgeAdminUserIDs[u.Id] = true
|
|
||||||
}
|
}
|
||||||
|
p.badgeAdminUserID = u.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setConfiguration(configuration)
|
p.setConfiguration(configuration)
|
||||||
|
|||||||
@ -1,107 +0,0 @@
|
|||||||
[
|
|
||||||
{"id": "badges.dialog.create_badge.title", "translation": "Create badge"},
|
|
||||||
{"id": "badges.dialog.create_badge.submit", "translation": "Create"},
|
|
||||||
{"id": "badges.dialog.edit_badge.title", "translation": "Edit badge"},
|
|
||||||
{"id": "badges.dialog.edit_badge.submit", "translation": "Save"},
|
|
||||||
{"id": "badges.dialog.create_type.title", "translation": "Create type"},
|
|
||||||
{"id": "badges.dialog.create_type.submit", "translation": "Create"},
|
|
||||||
{"id": "badges.dialog.edit_type.title", "translation": "Edit type"},
|
|
||||||
{"id": "badges.dialog.edit_type.submit", "translation": "Save"},
|
|
||||||
{"id": "badges.dialog.grant.title", "translation": "Grant badge"},
|
|
||||||
{"id": "badges.dialog.grant.submit", "translation": "Grant"},
|
|
||||||
{"id": "badges.dialog.grant.intro", "translation": "Grant badge to @%s"},
|
|
||||||
{"id": "badges.dialog.create_subscription.title", "translation": "Create subscription"},
|
|
||||||
{"id": "badges.dialog.create_subscription.submit", "translation": "Add"},
|
|
||||||
{"id": "badges.dialog.create_subscription.intro", "translation": "Select the badge type you want to subscribe to this channel."},
|
|
||||||
{"id": "badges.dialog.delete_subscription.title", "translation": "Delete subscription"},
|
|
||||||
{"id": "badges.dialog.delete_subscription.submit", "translation": "Remove"},
|
|
||||||
{"id": "badges.dialog.delete_subscription.intro", "translation": "Select the badge type you want to unsubscribe from this channel."},
|
|
||||||
|
|
||||||
{"id": "badges.field.name", "translation": "Name"},
|
|
||||||
{"id": "badges.field.description", "translation": "Description"},
|
|
||||||
{"id": "badges.field.image", "translation": "Image"},
|
|
||||||
{"id": "badges.field.image.help", "translation": "Enter an emoticon name"},
|
|
||||||
{"id": "badges.field.type", "translation": "Type"},
|
|
||||||
{"id": "badges.field.multiple", "translation": "Multiple"},
|
|
||||||
{"id": "badges.field.multiple.help", "translation": "Whether the badge can be granted multiple times"},
|
|
||||||
{"id": "badges.field.delete_badge", "translation": "Delete badge"},
|
|
||||||
{"id": "badges.field.delete_badge.help", "translation": "WARNING: checking this will remove this badge permanently."},
|
|
||||||
{"id": "badges.field.everyone_can_create", "translation": "Everyone can create badge"},
|
|
||||||
{"id": "badges.field.everyone_can_create.help", "translation": "Whether any user can create a badge of this type"},
|
|
||||||
{"id": "badges.field.allowlist_create", "translation": "Can create allowlist"},
|
|
||||||
{"id": "badges.field.allowlist_create.help", "translation": "Fill the usernames separated by comma (,) of the people that can create badges of this type."},
|
|
||||||
{"id": "badges.field.everyone_can_grant", "translation": "Everyone can grant badge"},
|
|
||||||
{"id": "badges.field.everyone_can_grant.help", "translation": "Whether any user can grant a badge of this type"},
|
|
||||||
{"id": "badges.field.allowlist_grant", "translation": "Can grant allowlist"},
|
|
||||||
{"id": "badges.field.allowlist_grant.help", "translation": "Fill the usernames separated by comma (,) of the people that can grant badges of this type."},
|
|
||||||
{"id": "badges.field.delete_type", "translation": "Remove type"},
|
|
||||||
{"id": "badges.field.delete_type.help", "translation": "WARNING: checking this will remove this type and all associated badges permanently."},
|
|
||||||
{"id": "badges.field.user", "translation": "User"},
|
|
||||||
{"id": "badges.field.badge", "translation": "Badge"},
|
|
||||||
{"id": "badges.field.reason", "translation": "Reason"},
|
|
||||||
{"id": "badges.field.reason.help", "translation": "Reason why you are granting this badge. This will be seen by the user, and wherever this grant notification is shown (e.g. subscriptions)."},
|
|
||||||
{"id": "badges.field.notify_here", "translation": "Notify on this channel"},
|
|
||||||
{"id": "badges.field.notify_here.help", "translation": "If you mark this, the bot will send a message to this channel notifying that you granted this badge to this person."},
|
|
||||||
|
|
||||||
{"id": "badges.error.unknown", "translation": "An unknown error occurred. Please talk to your system administrator for help."},
|
|
||||||
{"id": "badges.error.cannot_get_user", "translation": "Cannot get user."},
|
|
||||||
{"id": "badges.error.only_sysadmin_clean", "translation": "Only a system admin can clean the badges database."},
|
|
||||||
{"id": "badges.error.specify_create", "translation": "Specify what you want to create."},
|
|
||||||
{"id": "badges.error.create_badge_or_type", "translation": "You can create either badge or type"},
|
|
||||||
{"id": "badges.error.no_types_available", "translation": "You cannot create badges from any type."},
|
|
||||||
{"id": "badges.error.must_set_badge_id", "translation": "You must set the badge ID"},
|
|
||||||
{"id": "badges.error.cannot_edit_badge", "translation": "You cannot edit this badge"},
|
|
||||||
{"id": "badges.error.specify_edit", "translation": "Specify what you want to edit."},
|
|
||||||
{"id": "badges.error.edit_badge_or_type", "translation": "You can edit either badge or type"},
|
|
||||||
{"id": "badges.error.no_permissions_edit_type", "translation": "You have no permissions to edit a badge type."},
|
|
||||||
{"id": "badges.error.must_provide_type_id", "translation": "You must provide a type id"},
|
|
||||||
{"id": "badges.error.cannot_edit_type", "translation": "You cannot edit this type"},
|
|
||||||
{"id": "badges.error.no_permissions_grant", "translation": "You have no permissions to grant this badge"},
|
|
||||||
{"id": "badges.error.cannot_grant_badge", "translation": "You cannot grant that badge"},
|
|
||||||
{"id": "badges.error.specify_subscription", "translation": "Specify what you want to do."},
|
|
||||||
{"id": "badges.error.create_or_delete_subscription", "translation": "You can either create or delete subscriptions"},
|
|
||||||
{"id": "badges.error.cannot_create_subscription", "translation": "You cannot create subscriptions"},
|
|
||||||
{"id": "badges.error.no_permissions_create_type", "translation": "You have no permissions to create a badge type."},
|
|
||||||
|
|
||||||
{"id": "badges.success.clean", "translation": "Clean"},
|
|
||||||
{"id": "badges.success.granted", "translation": "Granted"},
|
|
||||||
{"id": "badges.success.removed", "translation": "Removed"},
|
|
||||||
|
|
||||||
{"id": "badges.api.dialog_parse_error", "translation": "Could not get the dialog request"},
|
|
||||||
{"id": "badges.api.cannot_get_user", "translation": "Cannot get user"},
|
|
||||||
{"id": "badges.api.empty_emoji", "translation": "Empty emoji"},
|
|
||||||
{"id": "badges.api.invalid_field", "translation": "Invalid field"},
|
|
||||||
{"id": "badges.api.type_not_exist", "translation": "This type does not exist"},
|
|
||||||
{"id": "badges.api.no_permissions_create_badge", "translation": "You have no permissions to create this badge"},
|
|
||||||
{"id": "badges.api.badge_created", "translation": "Badge `%s` created."},
|
|
||||||
{"id": "badges.api.no_permissions_create_type", "translation": "You have no permissions to create a type"},
|
|
||||||
{"id": "badges.api.cannot_find_user", "translation": "Cannot find user"},
|
|
||||||
{"id": "badges.api.error_getting_user", "translation": "Error getting user %s: %v"},
|
|
||||||
{"id": "badges.api.type_created", "translation": "Type `%s` created."},
|
|
||||||
{"id": "badges.api.cannot_get_type", "translation": "Cannot get type"},
|
|
||||||
{"id": "badges.api.cannot_edit_type", "translation": "You cannot edit this type"},
|
|
||||||
{"id": "badges.api.could_not_get_type", "translation": "Could not get the type"},
|
|
||||||
{"id": "badges.api.no_permissions_edit_type", "translation": "You have no permissions to edit this type"},
|
|
||||||
{"id": "badges.api.type_updated", "translation": "Type `%s` updated."},
|
|
||||||
{"id": "badges.api.cannot_get_badge", "translation": "Cannot get badge"},
|
|
||||||
{"id": "badges.api.cannot_edit_badge", "translation": "You cannot edit this badge"},
|
|
||||||
{"id": "badges.api.could_not_get_badge", "translation": "Could not get the badge"},
|
|
||||||
{"id": "badges.api.no_permissions_edit_badge", "translation": "You have no permissions to edit this badge"},
|
|
||||||
{"id": "badges.api.badge_updated", "translation": "Badge `%s` updated."},
|
|
||||||
{"id": "badges.api.badge_not_found", "translation": "Badge not found"},
|
|
||||||
{"id": "badges.api.no_permissions_grant", "translation": "You have no permissions to grant this badge"},
|
|
||||||
{"id": "badges.api.user_not_found", "translation": "User not found"},
|
|
||||||
{"id": "badges.api.badge_granted", "translation": "Badge `%s` granted to @%s."},
|
|
||||||
{"id": "badges.api.cannot_create_subscription", "translation": "You cannot create a subscription"},
|
|
||||||
{"id": "badges.api.subscription_added", "translation": "Subscription added"},
|
|
||||||
{"id": "badges.api.cannot_delete_subscription", "translation": "You cannot delete a subscription"},
|
|
||||||
{"id": "badges.api.subscription_removed", "translation": "Subscription removed"},
|
|
||||||
{"id": "badges.api.cannot_delete_default_type", "translation": "Cannot delete the default type"},
|
|
||||||
{"id": "badges.api.not_authorized", "translation": "Not authorized"},
|
|
||||||
|
|
||||||
{"id": "badges.notify.dm_text", "translation": "@%s granted you the %s`%s` badge."},
|
|
||||||
{"id": "badges.notify.dm_reason", "translation": "\nWhy? "},
|
|
||||||
{"id": "badges.notify.title", "translation": "%sbadge granted!"},
|
|
||||||
{"id": "badges.notify.channel_text", "translation": "@%s granted @%s the %s`%s` badge."},
|
|
||||||
{"id": "badges.notify.no_permission_channel", "translation": "You don't have permissions to notify the grant on this channel."}
|
|
||||||
]
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
package i18n
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed *.json
|
|
||||||
var i18nFiles embed.FS
|
|
||||||
|
|
||||||
type TranslationFunc func(translationId string, defaultMessage string, params ...any) string
|
|
||||||
|
|
||||||
type Bundle i18n.Bundle
|
|
||||||
|
|
||||||
func Init() *Bundle {
|
|
||||||
bundle := i18n.NewBundle(language.Russian)
|
|
||||||
_, _ = bundle.LoadMessageFileFS(i18nFiles, "en.json")
|
|
||||||
|
|
||||||
return (*Bundle)(bundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalizerFunc(bundle *Bundle, lang string) TranslationFunc {
|
|
||||||
localizer := i18n.NewLocalizer((*i18n.Bundle)(bundle), lang)
|
|
||||||
|
|
||||||
return func(translationId string, defaultMessage string, params ...any) string {
|
|
||||||
if len(params) > 0 {
|
|
||||||
return fmt.Sprintf(localizer.MustLocalize(&i18n.LocalizeConfig{
|
|
||||||
DefaultMessage: &i18n.Message{
|
|
||||||
ID: translationId,
|
|
||||||
Other: defaultMessage,
|
|
||||||
},
|
|
||||||
}), params...)
|
|
||||||
}
|
|
||||||
return localizer.MustLocalize(&i18n.LocalizeConfig{
|
|
||||||
DefaultMessage: &i18n.Message{
|
|
||||||
ID: translationId,
|
|
||||||
Other: defaultMessage,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
[
|
|
||||||
{"id": "badges.dialog.create_badge.title", "translation": "Создать значок"},
|
|
||||||
{"id": "badges.dialog.create_badge.submit", "translation": "Создать"},
|
|
||||||
{"id": "badges.dialog.edit_badge.title", "translation": "Редактировать значок"},
|
|
||||||
{"id": "badges.dialog.edit_badge.submit", "translation": "Сохранить"},
|
|
||||||
{"id": "badges.dialog.create_type.title", "translation": "Создать тип"},
|
|
||||||
{"id": "badges.dialog.create_type.submit", "translation": "Создать"},
|
|
||||||
{"id": "badges.dialog.edit_type.title", "translation": "Редактировать тип"},
|
|
||||||
{"id": "badges.dialog.edit_type.submit", "translation": "Сохранить"},
|
|
||||||
{"id": "badges.dialog.grant.title", "translation": "Выдать значок"},
|
|
||||||
{"id": "badges.dialog.grant.submit", "translation": "Выдать"},
|
|
||||||
{"id": "badges.dialog.grant.intro", "translation": "Выдать значок пользователю @%s"},
|
|
||||||
{"id": "badges.dialog.create_subscription.title", "translation": "Создать подписку"},
|
|
||||||
{"id": "badges.dialog.create_subscription.submit", "translation": "Добавить"},
|
|
||||||
{"id": "badges.dialog.create_subscription.intro", "translation": "Выберите тип значка, на который хотите подписать этот канал."},
|
|
||||||
{"id": "badges.dialog.delete_subscription.title", "translation": "Удалить подписку"},
|
|
||||||
{"id": "badges.dialog.delete_subscription.submit", "translation": "Удалить"},
|
|
||||||
{"id": "badges.dialog.delete_subscription.intro", "translation": "Выберите тип значка, подписку на который хотите удалить из этого канала."},
|
|
||||||
|
|
||||||
{"id": "badges.field.name", "translation": "Название"},
|
|
||||||
{"id": "badges.field.description", "translation": "Описание"},
|
|
||||||
{"id": "badges.field.image", "translation": "Изображение"},
|
|
||||||
{"id": "badges.field.image.help", "translation": "Введите название эмодзи"},
|
|
||||||
{"id": "badges.field.type", "translation": "Тип"},
|
|
||||||
{"id": "badges.field.multiple", "translation": "Многократный"},
|
|
||||||
{"id": "badges.field.multiple.help", "translation": "Можно ли выдавать этот значок несколько раз"},
|
|
||||||
{"id": "badges.field.delete_badge", "translation": "Удалить значок"},
|
|
||||||
{"id": "badges.field.delete_badge.help", "translation": "ВНИМАНИЕ: если отметить, значок будет удалён безвозвратно."},
|
|
||||||
{"id": "badges.field.everyone_can_create", "translation": "Все могут создавать значки"},
|
|
||||||
{"id": "badges.field.everyone_can_create.help", "translation": "Любой пользователь может создать значок этого типа"},
|
|
||||||
{"id": "badges.field.allowlist_create", "translation": "Список допущенных к созданию"},
|
|
||||||
{"id": "badges.field.allowlist_create.help", "translation": "Укажите имена пользователей через запятую (,), которые могут создавать значки этого типа."},
|
|
||||||
{"id": "badges.field.everyone_can_grant", "translation": "Все могут выдавать значки"},
|
|
||||||
{"id": "badges.field.everyone_can_grant.help", "translation": "Любой пользователь может выдать значок этого типа"},
|
|
||||||
{"id": "badges.field.allowlist_grant", "translation": "Список допущенных к выдаче"},
|
|
||||||
{"id": "badges.field.allowlist_grant.help", "translation": "Укажите имена пользователей через запятую (,), которые могут выдавать значки этого типа."},
|
|
||||||
{"id": "badges.field.delete_type", "translation": "Удалить тип"},
|
|
||||||
{"id": "badges.field.delete_type.help", "translation": "ВНИМАНИЕ: если отметить, этот тип и все связанные значки будут удалены безвозвратно."},
|
|
||||||
{"id": "badges.field.user", "translation": "Пользователь"},
|
|
||||||
{"id": "badges.field.badge", "translation": "Значок"},
|
|
||||||
{"id": "badges.field.reason", "translation": "Причина"},
|
|
||||||
{"id": "badges.field.reason.help", "translation": "Причина выдачи значка. Будет видна пользователю и в уведомлениях о выдаче (например, в подписках)."},
|
|
||||||
{"id": "badges.field.notify_here", "translation": "Уведомить в этом канале"},
|
|
||||||
{"id": "badges.field.notify_here.help", "translation": "Если отметить, бот отправит сообщение в этот канал о том, что вы выдали значок этому пользователю."},
|
|
||||||
|
|
||||||
{"id": "badges.error.unknown", "translation": "Произошла неизвестная ошибка. Обратитесь к системному администратору."},
|
|
||||||
{"id": "badges.error.cannot_get_user", "translation": "Не удалось получить пользователя."},
|
|
||||||
{"id": "badges.error.only_sysadmin_clean", "translation": "Только системный администратор может очистить базу значков."},
|
|
||||||
{"id": "badges.error.specify_create", "translation": "Укажите, что вы хотите создать."},
|
|
||||||
{"id": "badges.error.create_badge_or_type", "translation": "Можно создать badge или type"},
|
|
||||||
{"id": "badges.error.no_types_available", "translation": "Вы не можете создать значки ни одного типа."},
|
|
||||||
{"id": "badges.error.must_set_badge_id", "translation": "Необходимо указать ID значка"},
|
|
||||||
{"id": "badges.error.cannot_edit_badge", "translation": "У вас нет прав на редактирование этого значка"},
|
|
||||||
{"id": "badges.error.specify_edit", "translation": "Укажите, что вы хотите отредактировать."},
|
|
||||||
{"id": "badges.error.edit_badge_or_type", "translation": "Можно редактировать badge или type"},
|
|
||||||
{"id": "badges.error.no_permissions_edit_type", "translation": "У вас нет прав на редактирование типа значков."},
|
|
||||||
{"id": "badges.error.must_provide_type_id", "translation": "Необходимо указать ID типа"},
|
|
||||||
{"id": "badges.error.cannot_edit_type", "translation": "У вас нет прав на редактирование этого типа"},
|
|
||||||
{"id": "badges.error.no_permissions_grant", "translation": "У вас нет прав на выдачу этого значка"},
|
|
||||||
{"id": "badges.error.cannot_grant_badge", "translation": "Вы не можете выдать этот значок"},
|
|
||||||
{"id": "badges.error.specify_subscription", "translation": "Укажите, что вы хотите сделать."},
|
|
||||||
{"id": "badges.error.create_or_delete_subscription", "translation": "Можно создать или удалить подписку"},
|
|
||||||
{"id": "badges.error.cannot_create_subscription", "translation": "Вы не можете создавать подписки"},
|
|
||||||
{"id": "badges.error.no_permissions_create_type", "translation": "У вас нет прав на создание типа значков."},
|
|
||||||
|
|
||||||
{"id": "badges.success.clean", "translation": "Очищено"},
|
|
||||||
{"id": "badges.success.granted", "translation": "Выдано"},
|
|
||||||
{"id": "badges.success.removed", "translation": "Удалено"},
|
|
||||||
|
|
||||||
{"id": "badges.api.dialog_parse_error", "translation": "Не удалось получить данные диалога"},
|
|
||||||
{"id": "badges.api.cannot_get_user", "translation": "Не удалось найти пользователя"},
|
|
||||||
{"id": "badges.api.empty_emoji", "translation": "Пустой эмодзи"},
|
|
||||||
{"id": "badges.api.invalid_field", "translation": "Некорректное поле"},
|
|
||||||
{"id": "badges.api.type_not_exist", "translation": "Этот тип не существует"},
|
|
||||||
{"id": "badges.api.no_permissions_create_badge", "translation": "У вас нет прав на создание этого значка"},
|
|
||||||
{"id": "badges.api.badge_created", "translation": "Значок `%s` создан."},
|
|
||||||
{"id": "badges.api.no_permissions_create_type", "translation": "У вас нет прав на создание типа"},
|
|
||||||
{"id": "badges.api.cannot_find_user", "translation": "Не удалось найти пользователя"},
|
|
||||||
{"id": "badges.api.error_getting_user", "translation": "Ошибка получения пользователя %s: %v"},
|
|
||||||
{"id": "badges.api.type_created", "translation": "Тип `%s` создан."},
|
|
||||||
{"id": "badges.api.cannot_get_type", "translation": "Не удалось получить тип"},
|
|
||||||
{"id": "badges.api.cannot_edit_type", "translation": "Вы не можете редактировать этот тип"},
|
|
||||||
{"id": "badges.api.could_not_get_type", "translation": "Не удалось получить тип"},
|
|
||||||
{"id": "badges.api.no_permissions_edit_type", "translation": "У вас нет прав на редактирование этого типа"},
|
|
||||||
{"id": "badges.api.type_updated", "translation": "Тип `%s` обновлён."},
|
|
||||||
{"id": "badges.api.cannot_get_badge", "translation": "Не удалось получить значок"},
|
|
||||||
{"id": "badges.api.cannot_edit_badge", "translation": "Вы не можете редактировать этот значок"},
|
|
||||||
{"id": "badges.api.could_not_get_badge", "translation": "Не удалось получить значок"},
|
|
||||||
{"id": "badges.api.no_permissions_edit_badge", "translation": "У вас нет прав на редактирование этого значка"},
|
|
||||||
{"id": "badges.api.badge_updated", "translation": "Значок `%s` обновлён."},
|
|
||||||
{"id": "badges.api.badge_not_found", "translation": "Значок не найден"},
|
|
||||||
{"id": "badges.api.no_permissions_grant", "translation": "У вас нет прав на выдачу этого значка"},
|
|
||||||
{"id": "badges.api.user_not_found", "translation": "Пользователь не найден"},
|
|
||||||
{"id": "badges.api.badge_granted", "translation": "Значок `%s` выдан @%s."},
|
|
||||||
{"id": "badges.api.cannot_create_subscription", "translation": "Вы не можете создать подписку"},
|
|
||||||
{"id": "badges.api.subscription_added", "translation": "Подписка добавлена"},
|
|
||||||
{"id": "badges.api.cannot_delete_subscription", "translation": "Вы не можете удалить подписку"},
|
|
||||||
{"id": "badges.api.subscription_removed", "translation": "Подписка удалена"},
|
|
||||||
{"id": "badges.api.cannot_delete_default_type", "translation": "Нельзя удалить тип по умолчанию"},
|
|
||||||
{"id": "badges.api.not_authorized", "translation": "Не авторизован"},
|
|
||||||
|
|
||||||
{"id": "badges.notify.dm_text", "translation": "@%s выдал вам значок %s`%s`."},
|
|
||||||
{"id": "badges.notify.dm_reason", "translation": "\nПочему? "},
|
|
||||||
{"id": "badges.notify.title", "translation": "%sзначок выдан!"},
|
|
||||||
{"id": "badges.notify.channel_text", "translation": "@%s выдал @%s значок %s`%s`."},
|
|
||||||
{"id": "badges.notify.no_permission_channel", "translation": "У вас нет прав на отправку уведомления о выдаче в этот канал."}
|
|
||||||
]
|
|
||||||
8
server/manifest.go
generated
8
server/manifest.go
generated
@ -13,8 +13,8 @@ var manifest *model.Manifest
|
|||||||
const manifestStr = `
|
const manifestStr = `
|
||||||
{
|
{
|
||||||
"id": "ru.loop.plugin.achievements",
|
"id": "ru.loop.plugin.achievements",
|
||||||
"name": "Achievements",
|
"name": "Badges for Mattermost",
|
||||||
"description": "Плагин достижений и значков для Loop.",
|
"description": "This plugin add badges support to Mattermost.",
|
||||||
"homepage_url": "https://github.com/larkox/mattermost-plugin-badges",
|
"homepage_url": "https://github.com/larkox/mattermost-plugin-badges",
|
||||||
"support_url": "https://github.com/larkox/mattermost-plugin-badges/issues",
|
"support_url": "https://github.com/larkox/mattermost-plugin-badges/issues",
|
||||||
"release_notes_url": "https://github.com/larkox/mattermost-plugin-badges/releases/tag/v0.2.1",
|
"release_notes_url": "https://github.com/larkox/mattermost-plugin-badges/releases/tag/v0.2.1",
|
||||||
@ -38,9 +38,9 @@ const manifestStr = `
|
|||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"key": "BadgesAdmin",
|
"key": "BadgesAdmin",
|
||||||
"display_name": "Администратор достижений:",
|
"display_name": "Badges admin:",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"help_text": "Этот пользователь будет считаться администратором плагина достижений. Он может создавать типы, а также изменять и выдавать любые значки.",
|
"help_text": "This user will be considered as an admin for the badges plugin. They can create types, and modify and grant any badge.",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"default": null
|
"default": null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,6 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/mattermost/mattermost-server/v5/plugin"
|
"github.com/mattermost/mattermost-server/v5/plugin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
i18n "github.com/larkox/mattermost-plugin-badges/server/i18n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
||||||
@ -28,12 +26,7 @@ type Plugin struct {
|
|||||||
BotUserID string
|
BotUserID string
|
||||||
store Store
|
store Store
|
||||||
router *mux.Router
|
router *mux.Router
|
||||||
badgeAdminUserIDs map[string]bool
|
badgeAdminUserID string
|
||||||
i18nBundle *i18n.Bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) getT(locale string) i18n.TranslationFunc {
|
|
||||||
return i18n.LocalizerFunc(p.i18nBundle, locale)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
|
// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
|
||||||
@ -48,19 +41,15 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
|
|||||||
func (p *Plugin) OnActivate() error {
|
func (p *Plugin) OnActivate() error {
|
||||||
p.mm = pluginapi.NewClient(p.API)
|
p.mm = pluginapi.NewClient(p.API)
|
||||||
botID, err := p.Helpers.EnsureBot(&model.Bot{
|
botID, err := p.Helpers.EnsureBot(&model.Bot{
|
||||||
Username: "achievements",
|
Username: "badges",
|
||||||
DisplayName: "Achievements Bot",
|
DisplayName: "Badges Bot",
|
||||||
Description: "Created by the Achievements plugin.",
|
Description: "Created by the Badges plugin.",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to ensure badges bot")
|
return errors.Wrap(err, "failed to ensure badges bot")
|
||||||
}
|
}
|
||||||
p.BotUserID = botID
|
p.BotUserID = botID
|
||||||
p.store = NewStore(p.API)
|
p.store = NewStore(p.API)
|
||||||
if err := p.store.EnsureDefaultType(p.BotUserID); err != nil {
|
|
||||||
p.mm.Log.Warn("Failed to ensure default type", "error", err.Error())
|
|
||||||
}
|
|
||||||
p.i18nBundle = i18n.Init()
|
|
||||||
p.initializeAPI()
|
p.initializeAPI()
|
||||||
|
|
||||||
return p.mm.SlashCommand.Register(p.getCommand())
|
return p.mm.SlashCommand.Register(p.getCommand())
|
||||||
|
|||||||
@ -39,9 +39,6 @@ type Store interface {
|
|||||||
GetTypeSubscriptions(tID badgesmodel.BadgeType) ([]string, error)
|
GetTypeSubscriptions(tID badgesmodel.BadgeType) ([]string, error)
|
||||||
GetChannelSubscriptions(cID string) ([]*badgesmodel.BadgeTypeDefinition, error)
|
GetChannelSubscriptions(cID string) ([]*badgesmodel.BadgeTypeDefinition, error)
|
||||||
|
|
||||||
// Default type
|
|
||||||
EnsureDefaultType(botID string) error
|
|
||||||
|
|
||||||
// PAPI
|
// PAPI
|
||||||
EnsureBadges(badges []*badgesmodel.Badge, pluginID, botID string) ([]*badgesmodel.Badge, error)
|
EnsureBadges(badges []*badgesmodel.Badge, pluginID, botID string) ([]*badgesmodel.Badge, error)
|
||||||
}
|
}
|
||||||
@ -147,28 +144,6 @@ func (s *store) addType(t *badgesmodel.BadgeTypeDefinition, isPlugin bool) (*bad
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) EnsureDefaultType(botID string) error {
|
|
||||||
types, _, err := s.getAllTypes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range types {
|
|
||||||
if t.IsDefault {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.addType(&badgesmodel.BadgeTypeDefinition{
|
|
||||||
Name: badgesmodel.DefaultTypeName,
|
|
||||||
IsDefault: true,
|
|
||||||
CreatedBy: botID,
|
|
||||||
CanCreate: badgesmodel.PermissionScheme{Everyone: true},
|
|
||||||
CanGrant: badgesmodel.PermissionScheme{Everyone: true},
|
|
||||||
}, false)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *store) GetAllBadges() ([]*badgesmodel.AllBadgesBadge, error) {
|
func (s *store) GetAllBadges() ([]*badgesmodel.AllBadgesBadge, error) {
|
||||||
badges, _, err := s.getAllBadges()
|
badges, _, err := s.getAllBadges()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -442,11 +417,6 @@ func (s *store) atomicDeleteType(tID badgesmodel.BadgeType) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) DeleteType(tID badgesmodel.BadgeType) error {
|
func (s *store) DeleteType(tID badgesmodel.BadgeType) error {
|
||||||
t, err := s.GetType(tID)
|
|
||||||
if err == nil && t.IsDefault {
|
|
||||||
return errors.New("cannot delete default type")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.doAtomic(func() (bool, error) { return s.atomicDeleteType(tID) })
|
s.doAtomic(func() (bool, error) { return s.atomicDeleteType(tID) })
|
||||||
|
|
||||||
bb, _, err := s.getAllBadges()
|
bb, _, err := s.getAllBadges()
|
||||||
|
|||||||
@ -23,7 +23,7 @@ func (p *Plugin) filterGrantBadges(user *model.User) ([]*badgesmodel.Badge, erro
|
|||||||
p.mm.Log.Debug("Badge with missing type", "badge", b)
|
p.mm.Log.Debug("Badge with missing type", "badge", b)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if canGrantBadge(user, p.badgeAdminUserIDs, b, badgeType) {
|
if canGrantBadge(user, p.badgeAdminUserID, b, badgeType) {
|
||||||
out = append(out, b)
|
out = append(out, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ func (p *Plugin) filterCreateBadgeTypes(user *model.User) (badgesmodel.BadgeType
|
|||||||
|
|
||||||
out := badgesmodel.BadgeTypeList{}
|
out := badgesmodel.BadgeTypeList{}
|
||||||
for _, t := range types {
|
for _, t := range types {
|
||||||
if canCreateBadge(user, p.badgeAdminUserIDs, t) {
|
if canCreateBadge(user, p.badgeAdminUserID, t) {
|
||||||
out = append(out, t)
|
out = append(out, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ func (p *Plugin) filterEditTypes(user *model.User) (badgesmodel.BadgeTypeList, e
|
|||||||
|
|
||||||
out := badgesmodel.BadgeTypeList{}
|
out := badgesmodel.BadgeTypeList{}
|
||||||
for _, t := range types {
|
for _, t := range types {
|
||||||
if canEditType(user, p.badgeAdminUserIDs, t) {
|
if canEditType(user, p.badgeAdminUserID, t) {
|
||||||
out = append(out, t)
|
out = append(out, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ func (p *Plugin) filterEditBadges(user *model.User) ([]*badgesmodel.Badge, error
|
|||||||
|
|
||||||
out := []*badgesmodel.Badge{}
|
out := []*badgesmodel.Badge{}
|
||||||
for _, b := range bb {
|
for _, b := range bb {
|
||||||
if canEditBadge(user, p.badgeAdminUserIDs, b) {
|
if canEditBadge(user, p.badgeAdminUserID, b) {
|
||||||
out = append(out, b)
|
out = append(out, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,8 @@ func areRolesAllowed(userRoles []string, allowedRoles map[string]bool) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func canGrantBadge(user *model.User, badgeAdminIDs map[string]bool, badge *badgesmodel.Badge, badgeType *badgesmodel.BadgeTypeDefinition) bool {
|
func canGrantBadge(user *model.User, badgeAdminID string, badge *badgesmodel.Badge, badgeType *badgesmodel.BadgeTypeDefinition) bool {
|
||||||
if badgeAdminIDs[user.Id] {
|
if badgeAdminID != "" && user.Id == badgeAdminID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +57,8 @@ func canGrantBadge(user *model.User, badgeAdminIDs map[string]bool, badge *badge
|
|||||||
return badgeType.CanGrant.Everyone
|
return badgeType.CanGrant.Everyone
|
||||||
}
|
}
|
||||||
|
|
||||||
func canCreateBadge(user *model.User, badgeAdminIDs map[string]bool, badgeType *badgesmodel.BadgeTypeDefinition) bool {
|
func canCreateBadge(user *model.User, badgeAdminID string, badgeType *badgesmodel.BadgeTypeDefinition) bool {
|
||||||
if badgeAdminIDs[user.Id] {
|
if badgeAdminID != "" && user.Id == badgeAdminID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,36 +87,36 @@ func canCreateBadge(user *model.User, badgeAdminIDs map[string]bool, badgeType *
|
|||||||
return badgeType.CanCreate.Everyone
|
return badgeType.CanCreate.Everyone
|
||||||
}
|
}
|
||||||
|
|
||||||
func canEditType(user *model.User, badgeAdminIDs map[string]bool, badgeType *badgesmodel.BadgeTypeDefinition) bool {
|
func canEditType(user *model.User, badgeAdminID string, badgeType *badgesmodel.BadgeTypeDefinition) bool {
|
||||||
if badgeAdminIDs[user.Id] {
|
if badgeAdminID != "" && user.Id == badgeAdminID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.IsSystemAdmin()
|
return user.IsSystemAdmin()
|
||||||
}
|
}
|
||||||
|
|
||||||
func canEditBadge(user *model.User, badgeAdminIDs map[string]bool, badge *badgesmodel.Badge) bool {
|
func canEditBadge(user *model.User, badgeAdminID string, badge *badgesmodel.Badge) bool {
|
||||||
if badgeAdminIDs[user.Id] {
|
if badgeAdminID != "" && user.Id == badgeAdminID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.IsSystemAdmin() || user.Id == badge.CreatedBy
|
return user.IsSystemAdmin() || user.Id == badge.CreatedBy
|
||||||
}
|
}
|
||||||
|
|
||||||
func canCreateType(user *model.User, badgeAdminIDs map[string]bool, isPlugin bool) bool {
|
func canCreateType(user *model.User, badgeAdminID string, isPlugin bool) bool {
|
||||||
if isPlugin {
|
if isPlugin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if badgeAdminIDs[user.Id] {
|
if badgeAdminID != "" && user.Id == badgeAdminID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.IsSystemAdmin()
|
return user.IsSystemAdmin()
|
||||||
}
|
}
|
||||||
|
|
||||||
func canCreateSubscription(user *model.User, badgeAdminIDs map[string]bool, channelID string) bool {
|
func canCreateSubscription(user *model.User, badgeAdminID string, channelID string) bool {
|
||||||
if badgeAdminIDs[user.Id] {
|
if badgeAdminID != "" && user.Id == badgeAdminID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,15 +152,13 @@ func (p *Plugin) notifyGrant(badgeID badgesmodel.BadgeID, granter string, grante
|
|||||||
image = fmt.Sprintf(" ", b.Image)
|
image = fmt.Sprintf(" ", b.Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DM to the granted user — use their locale
|
|
||||||
Tdm := p.getT(granted.Locale)
|
|
||||||
dmPost := &model.Post{}
|
dmPost := &model.Post{}
|
||||||
dmText := Tdm("badges.notify.dm_text", "@%s выдал вам значок %s`%s`.", granterUser.Username, image, b.Name)
|
dmText := fmt.Sprintf("@%s granted you the %s`%s` badge.", granterUser.Username, image, b.Name)
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
dmText += Tdm("badges.notify.dm_reason", "\nПочему? ") + reason
|
dmText += "\nWhy? " + reason
|
||||||
}
|
}
|
||||||
dmAttachment := model.SlackAttachment{
|
dmAttachment := model.SlackAttachment{
|
||||||
Title: Tdm("badges.notify.title", "%sзначок выдан!", image),
|
Title: fmt.Sprintf("%sbadge granted!", image),
|
||||||
Text: dmText,
|
Text: dmText,
|
||||||
}
|
}
|
||||||
model.ParseSlackAttachment(dmPost, []*model.SlackAttachment{&dmAttachment})
|
model.ParseSlackAttachment(dmPost, []*model.SlackAttachment{&dmAttachment})
|
||||||
@ -169,18 +167,16 @@ func (p *Plugin) notifyGrant(badgeID badgesmodel.BadgeID, granter string, grante
|
|||||||
p.mm.Log.Debug("dm error", "err", err)
|
p.mm.Log.Debug("dm error", "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channel/subscription notifications — use granter's locale
|
|
||||||
Tch := p.getT(granterUser.Locale)
|
|
||||||
basePost := model.Post{
|
basePost := model.Post{
|
||||||
UserId: p.BotUserID,
|
UserId: p.BotUserID,
|
||||||
ChannelId: channelID,
|
ChannelId: channelID,
|
||||||
}
|
}
|
||||||
text := Tch("badges.notify.channel_text", "@%s выдал @%s значок %s`%s`.", granterUser.Username, granted.Username, image, b.Name)
|
text := fmt.Sprintf("@%s granted @%s the %s`%s` badge.", granterUser.Username, granted.Username, image, b.Name)
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
text += Tch("badges.notify.dm_reason", "\nПочему? ") + reason
|
text += "\nWhy? " + reason
|
||||||
}
|
}
|
||||||
attachment := model.SlackAttachment{
|
attachment := model.SlackAttachment{
|
||||||
Title: Tch("badges.notify.title", "%sзначок выдан!", image),
|
Title: fmt.Sprintf("%sbadge granted!", image),
|
||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
model.ParseSlackAttachment(&basePost, []*model.SlackAttachment{&attachment})
|
model.ParseSlackAttachment(&basePost, []*model.SlackAttachment{&attachment})
|
||||||
@ -194,8 +190,7 @@ func (p *Plugin) notifyGrant(badgeID badgesmodel.BadgeID, granter string, grante
|
|||||||
}
|
}
|
||||||
if inChannel {
|
if inChannel {
|
||||||
if !p.API.HasPermissionToChannel(granter, channelID, model.PERMISSION_CREATE_POST) {
|
if !p.API.HasPermissionToChannel(granter, channelID, model.PERMISSION_CREATE_POST) {
|
||||||
Tg := p.getT(granterUser.Locale)
|
p.mm.Post.SendEphemeralPost(granter, &model.Post{Message: "You don't have permissions to notify the grant on this channel.", ChannelId: channelID})
|
||||||
p.mm.Post.SendEphemeralPost(granter, &model.Post{Message: Tg("badges.notify.no_permission_channel", "У вас нет прав на отправку уведомления о выдаче в этот канал."), ChannelId: channelID})
|
|
||||||
} else {
|
} else {
|
||||||
post := basePost.Clone()
|
post := basePost.Clone()
|
||||||
post.ChannelId = channelID
|
post.ChannelId = channelID
|
||||||
|
|||||||
@ -697,8 +697,7 @@
|
|||||||
{
|
{
|
||||||
"extensions": [".jsx", ".tsx"]
|
"extensions": [".jsx", ".tsx"]
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"react/prop-types": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
BIN
webapp/.yarn/install-state.gz
Normal file
BIN
webapp/.yarn/install-state.gz
Normal file
Binary file not shown.
@ -1,120 +1 @@
|
|||||||
{
|
{}
|
||||||
"badges.loading": "Loading...",
|
|
||||||
"badges.no_badges_yet": "No badges yet.",
|
|
||||||
"badges.empty.title": "No badges yet",
|
|
||||||
"badges.empty.description": "Create your first badge to recognize achievements and contributions of your team members.",
|
|
||||||
"badges.badge_not_found": "Badge not found.",
|
|
||||||
"badges.user_not_found": "User not found.",
|
|
||||||
"badges.unknown": "unknown",
|
|
||||||
|
|
||||||
"badges.rhs.all_badges": "All badges",
|
|
||||||
"badges.rhs.my_badges": "My badges",
|
|
||||||
"badges.rhs.user_badges": "@{username}'s badges",
|
|
||||||
"badges.rhs.badge_details": "Badge Details",
|
|
||||||
|
|
||||||
"badges.label.name": "Name:",
|
|
||||||
"badges.label.description": "Description:",
|
|
||||||
"badges.label.type": "Type: {typeName}",
|
|
||||||
"badges.label.created_by": "Created by: {username}",
|
|
||||||
"badges.label.granted_by": "Granted by: {username}",
|
|
||||||
"badges.label.granted_at": "Granted at: {date}",
|
|
||||||
"badges.label.reason": "Why? {reason}",
|
|
||||||
"badges.label.count": "Count: {count}",
|
|
||||||
|
|
||||||
"badges.granted.not_yet": "Not yet granted.",
|
|
||||||
"badges.granted.multiple": "Granted {times, plural, one {# time} other {# times}} to {users, plural, one {# user} other {# users}}.",
|
|
||||||
"badges.granted.single": "Granted to {users, plural, one {# user} other {# users}}.",
|
|
||||||
"badges.granted_to": "Granted to:",
|
|
||||||
"badges.not_granted_yet": "Not granted to anyone yet",
|
|
||||||
|
|
||||||
"badges.set_status": "Set status to this badge",
|
|
||||||
"badges.grant_badge": "Grant badge",
|
|
||||||
"badges.and_more": "and {count} more. Click to see all.",
|
|
||||||
|
|
||||||
"badges.menu.open_list": "Open the list of all badges.",
|
|
||||||
"badges.menu.create_badge": "Create badge",
|
|
||||||
"badges.menu.create_type": "Create badge type",
|
|
||||||
"badges.menu.add_subscription": "Add badge subscription",
|
|
||||||
"badges.menu.remove_subscription": "Remove badge subscription",
|
|
||||||
|
|
||||||
"badges.sidebar.title": "Badges",
|
|
||||||
"badges.popover.title": "Badges",
|
|
||||||
|
|
||||||
"badges.admin.label": "Achievements Administrators:",
|
|
||||||
"badges.admin.placeholder": "Start typing a name...",
|
|
||||||
"badges.admin.help_text": "These users will be considered achievements plugin administrators. They can create types, as well as modify and grant any badges.",
|
|
||||||
"badges.admin.no_results": "No users found",
|
|
||||||
|
|
||||||
"badges.rhs.create_badge": "+ Create badge",
|
|
||||||
"badges.rhs.edit_badge": "Edit",
|
|
||||||
|
|
||||||
"badges.modal.create_badge_title": "Create Badge",
|
|
||||||
"badges.modal.edit_badge_title": "Edit Badge",
|
|
||||||
"badges.modal.field_name": "Name",
|
|
||||||
"badges.modal.field_name_placeholder": "Badge name (max 20 chars)",
|
|
||||||
"badges.modal.field_description": "Description",
|
|
||||||
"badges.modal.field_description_placeholder": "Badge description (max 120 chars)",
|
|
||||||
"badges.modal.field_image": "Emoji",
|
|
||||||
"badges.modal.field_image_placeholder": "Emoji name (e.g. star)",
|
|
||||||
"badges.modal.field_type": "Type",
|
|
||||||
"badges.modal.field_type_placeholder": "Select badge type",
|
|
||||||
"badges.modal.field_multiple": "Can be granted multiple times",
|
|
||||||
"badges.modal.create_new_type": "+ Create new type",
|
|
||||||
"badges.modal.new_type_name": "Type name",
|
|
||||||
"badges.modal.new_type_name_placeholder": "Type name (max 20 chars)",
|
|
||||||
"badges.modal.new_type_everyone_create": "Everyone can create badges",
|
|
||||||
"badges.modal.new_type_everyone_grant": "Everyone can grant badges",
|
|
||||||
"badges.modal.btn_cancel": "Cancel",
|
|
||||||
"badges.modal.btn_create": "Create",
|
|
||||||
"badges.modal.btn_save": "Save",
|
|
||||||
"badges.modal.btn_creating": "Saving...",
|
|
||||||
"badges.modal.btn_delete": "Delete badge",
|
|
||||||
"badges.modal.btn_confirm_delete": "Yes, delete",
|
|
||||||
"badges.modal.confirm_delete": "Are you sure?",
|
|
||||||
"badges.modal.error_generic": "An error occurred",
|
|
||||||
"badges.modal.error_type_name_required": "Enter type name",
|
|
||||||
"badges.modal.error_type_required": "Select badge type",
|
|
||||||
"badges.modal.delete_type": "Delete type",
|
|
||||||
"badges.modal.confirm_delete_type": "Delete type \"{name}\"?",
|
|
||||||
"badges.modal.btn_confirm_delete_type": "Yes, delete",
|
|
||||||
|
|
||||||
"badges.error.unknown": "An error occurred",
|
|
||||||
"badges.error.cannot_get_user": "Failed to get user data",
|
|
||||||
"badges.error.cannot_get_types": "Failed to load types",
|
|
||||||
"badges.error.cannot_get_badges": "Failed to load badges",
|
|
||||||
"badges.error.invalid_request": "Invalid request format",
|
|
||||||
"badges.error.invalid_name": "Name is required",
|
|
||||||
"badges.error.invalid_image": "Emoji is required",
|
|
||||||
"badges.error.type_not_found": "Badge type not found",
|
|
||||||
"badges.error.badge_not_found": "Badge not found",
|
|
||||||
"badges.error.no_permission": "Insufficient permissions",
|
|
||||||
"badges.error.missing_badge_id": "Badge ID is missing",
|
|
||||||
"badges.error.missing_type_id": "Type ID is missing",
|
|
||||||
"badges.error.cannot_create_badge": "Failed to create badge",
|
|
||||||
"badges.error.cannot_create_type": "Failed to create type",
|
|
||||||
"badges.error.cannot_update_badge": "Failed to update badge",
|
|
||||||
"badges.error.cannot_delete_badge": "Failed to delete badge",
|
|
||||||
"badges.error.cannot_delete_type": "Failed to delete type",
|
|
||||||
|
|
||||||
"emoji_picker.activities": "Activities",
|
|
||||||
"emoji_picker.animals-nature": "Animals & Nature",
|
|
||||||
"emoji_picker.close": "Close",
|
|
||||||
"emoji_picker.custom": "Custom",
|
|
||||||
"emoji_picker.custom_emoji": "Custom Emoji",
|
|
||||||
"emoji_picker.emojiPicker.button.ariaLabel": "select an emoji",
|
|
||||||
"emoji_picker.emojiPicker.previewPlaceholder": "Select an Emoji",
|
|
||||||
"emoji_picker.flags": "Flags",
|
|
||||||
"emoji_picker.food-drink": "Food & Drink",
|
|
||||||
"emoji_picker.header": "Emoji Picker",
|
|
||||||
"emoji_picker.objects": "Objects",
|
|
||||||
"emoji_picker.people-body": "People & Body",
|
|
||||||
"emoji_picker.recent": "Recently Used",
|
|
||||||
"emoji_picker.search": "Search emojis",
|
|
||||||
"emoji_picker.searchResults": "Search Results",
|
|
||||||
"emoji_picker.search_emoji": "Search for an emoji",
|
|
||||||
"emoji_picker.skin_tone": "Skin tone",
|
|
||||||
"emoji_picker.smileys-emotion": "Smileys & Emotion",
|
|
||||||
"emoji_picker.symbols": "Symbols",
|
|
||||||
"emoji_picker.travel-places": "Travel Places",
|
|
||||||
"emoji_picker_item.emoji_aria_label": "{emojiName} emoji"
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,120 +0,0 @@
|
|||||||
{
|
|
||||||
"badges.loading": "Загрузка...",
|
|
||||||
"badges.no_badges_yet": "Значков пока нет.",
|
|
||||||
"badges.empty.title": "Значков пока нет",
|
|
||||||
"badges.empty.description": "Создайте первый значок, чтобы отмечать достижения и заслуги участников команды.",
|
|
||||||
"badges.badge_not_found": "Значок не найден.",
|
|
||||||
"badges.user_not_found": "Пользователь не найден.",
|
|
||||||
"badges.unknown": "неизвестно",
|
|
||||||
|
|
||||||
"badges.rhs.all_badges": "Все значки",
|
|
||||||
"badges.rhs.my_badges": "Мои значки",
|
|
||||||
"badges.rhs.user_badges": "Значки @{username}",
|
|
||||||
"badges.rhs.badge_details": "Детали значка",
|
|
||||||
|
|
||||||
"badges.label.name": "Название:",
|
|
||||||
"badges.label.description": "Описание:",
|
|
||||||
"badges.label.type": "Тип: {typeName}",
|
|
||||||
"badges.label.created_by": "Создал: {username}",
|
|
||||||
"badges.label.granted_by": "Выдал: {username}",
|
|
||||||
"badges.label.granted_at": "Выдан: {date}",
|
|
||||||
"badges.label.reason": "Причина: {reason}",
|
|
||||||
"badges.label.count": "Количество: {count}",
|
|
||||||
|
|
||||||
"badges.granted.not_yet": "Ещё не выдан.",
|
|
||||||
"badges.granted.multiple": "Выдан {times, plural, one {# раз} few {# раза} many {# раз} other {# раз}} {users, plural, one {# пользователю} few {# пользователям} many {# пользователям} other {# пользователям}}.",
|
|
||||||
"badges.granted.single": "Выдан {users, plural, one {# пользователю} few {# пользователям} many {# пользователям} other {# пользователям}}.",
|
|
||||||
"badges.granted_to": "Выдан:",
|
|
||||||
"badges.not_granted_yet": "Ещё никому не выдан",
|
|
||||||
|
|
||||||
"badges.set_status": "Установить как статус",
|
|
||||||
"badges.grant_badge": "Выдать значок",
|
|
||||||
"badges.and_more": "и ещё {count}. Нажмите, чтобы увидеть все.",
|
|
||||||
|
|
||||||
"badges.menu.open_list": "Открыть список всех значков.",
|
|
||||||
"badges.menu.create_badge": "Создать значок",
|
|
||||||
"badges.menu.create_type": "Создать тип значков",
|
|
||||||
"badges.menu.add_subscription": "Добавить подписку на значки",
|
|
||||||
"badges.menu.remove_subscription": "Удалить подписку на значки",
|
|
||||||
|
|
||||||
"badges.sidebar.title": "Значки",
|
|
||||||
"badges.popover.title": "Значки",
|
|
||||||
|
|
||||||
"badges.admin.label": "Администраторы достижений:",
|
|
||||||
"badges.admin.placeholder": "Начните вводить имя...",
|
|
||||||
"badges.admin.help_text": "Эти пользователи будут считаться администраторами плагина достижений. Они могут создавать типы, а также изменять и выдавать любые значки.",
|
|
||||||
"badges.admin.no_results": "Пользователь не найден",
|
|
||||||
|
|
||||||
"badges.rhs.create_badge": "+ Создать значок",
|
|
||||||
"badges.rhs.edit_badge": "Редактировать",
|
|
||||||
|
|
||||||
"badges.modal.create_badge_title": "Создать значок",
|
|
||||||
"badges.modal.edit_badge_title": "Редактировать значок",
|
|
||||||
"badges.modal.field_name": "Название",
|
|
||||||
"badges.modal.field_name_placeholder": "Название значка (макс. 20 символов)",
|
|
||||||
"badges.modal.field_description": "Описание",
|
|
||||||
"badges.modal.field_description_placeholder": "Описание значка (макс. 120 символов)",
|
|
||||||
"badges.modal.field_image": "Эмодзи",
|
|
||||||
"badges.modal.field_image_placeholder": "Название эмодзи (напр. star)",
|
|
||||||
"badges.modal.field_type": "Тип",
|
|
||||||
"badges.modal.field_type_placeholder": "Выберите тип значка",
|
|
||||||
"badges.modal.field_multiple": "Можно выдавать несколько раз",
|
|
||||||
"badges.modal.create_new_type": "+ Создать новый тип",
|
|
||||||
"badges.modal.new_type_name": "Название типа",
|
|
||||||
"badges.modal.new_type_name_placeholder": "Название типа (макс. 20 символов)",
|
|
||||||
"badges.modal.new_type_everyone_create": "Все могут создавать значки",
|
|
||||||
"badges.modal.new_type_everyone_grant": "Все могут выдавать значки",
|
|
||||||
"badges.modal.btn_cancel": "Отмена",
|
|
||||||
"badges.modal.btn_create": "Создать",
|
|
||||||
"badges.modal.btn_save": "Сохранить",
|
|
||||||
"badges.modal.btn_creating": "Сохранение...",
|
|
||||||
"badges.modal.btn_delete": "Удалить значок",
|
|
||||||
"badges.modal.btn_confirm_delete": "Да, удалить",
|
|
||||||
"badges.modal.confirm_delete": "Вы уверены?",
|
|
||||||
"badges.modal.error_generic": "Произошла ошибка",
|
|
||||||
"badges.modal.error_type_name_required": "Введите название типа",
|
|
||||||
"badges.modal.error_type_required": "Выберите тип значка",
|
|
||||||
"badges.modal.delete_type": "Удалить тип",
|
|
||||||
"badges.modal.confirm_delete_type": "Удалить тип «{name}»?",
|
|
||||||
"badges.modal.btn_confirm_delete_type": "Да, удалить",
|
|
||||||
|
|
||||||
"badges.error.unknown": "Произошла ошибка",
|
|
||||||
"badges.error.cannot_get_user": "Не удалось получить данные пользователя",
|
|
||||||
"badges.error.cannot_get_types": "Не удалось загрузить типы",
|
|
||||||
"badges.error.cannot_get_badges": "Не удалось загрузить значки",
|
|
||||||
"badges.error.invalid_request": "Неверный формат запроса",
|
|
||||||
"badges.error.invalid_name": "Необходимо указать название",
|
|
||||||
"badges.error.invalid_image": "Необходимо указать эмодзи",
|
|
||||||
"badges.error.type_not_found": "Тип значка не найден",
|
|
||||||
"badges.error.badge_not_found": "Значок не найден",
|
|
||||||
"badges.error.no_permission": "Недостаточно прав для выполнения действия",
|
|
||||||
"badges.error.missing_badge_id": "Не указан ID значка",
|
|
||||||
"badges.error.missing_type_id": "Не указан ID типа",
|
|
||||||
"badges.error.cannot_create_badge": "Не удалось создать значок",
|
|
||||||
"badges.error.cannot_create_type": "Не удалось создать тип",
|
|
||||||
"badges.error.cannot_update_badge": "Не удалось обновить значок",
|
|
||||||
"badges.error.cannot_delete_badge": "Не удалось удалить значок",
|
|
||||||
"badges.error.cannot_delete_type": "Не удалось удалить тип",
|
|
||||||
|
|
||||||
"emoji_picker.activities": "Мероприятия",
|
|
||||||
"emoji_picker.animals-nature": "Животные и природа",
|
|
||||||
"emoji_picker.close": "Закрыть",
|
|
||||||
"emoji_picker.custom": "Настраиваемое",
|
|
||||||
"emoji_picker.custom_emoji": "Пользовательские смайлики",
|
|
||||||
"emoji_picker.emojiPicker.button.ariaLabel": "выберите смайлик",
|
|
||||||
"emoji_picker.emojiPicker.previewPlaceholder": "Выберите смайлик",
|
|
||||||
"emoji_picker.flags": "Флаги",
|
|
||||||
"emoji_picker.food-drink": "Еда и напитки",
|
|
||||||
"emoji_picker.header": "Выбор смайликов",
|
|
||||||
"emoji_picker.objects": "Объекты",
|
|
||||||
"emoji_picker.people-body": "Люди и тело",
|
|
||||||
"emoji_picker.recent": "Недавно использованные",
|
|
||||||
"emoji_picker.search": "Поиск смайликов",
|
|
||||||
"emoji_picker.searchResults": "Результаты поиска",
|
|
||||||
"emoji_picker.search_emoji": "Поиск смайлика",
|
|
||||||
"emoji_picker.skin_tone": "Цвет кожи",
|
|
||||||
"emoji_picker.smileys-emotion": "Смайлы и эмоции",
|
|
||||||
"emoji_picker.symbols": "Символы",
|
|
||||||
"emoji_picker.travel-places": "Места путешествий",
|
|
||||||
"emoji_picker_item.emoji_aria_label": "смайлик {emojiName}"
|
|
||||||
}
|
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"jest": "26.6.3",
|
"jest": "26.6.3",
|
||||||
"jest-canvas-mock": "2.3.1",
|
"jest-canvas-mock": "2.3.1",
|
||||||
"jest-junit": "12.0.0",
|
"jest-junit": "12.0.0",
|
||||||
"react-intl": "6.8.9",
|
"loop-plugin-sdk": "https://artifacts.wilix.dev/repository/npm-public-loop/loop-plugin-sdk/-/loop-plugin-sdk-0.1.6.tgz",
|
||||||
"sass": "1.86.0",
|
"sass": "1.86.0",
|
||||||
"sass-loader": "11.0.1",
|
"sass-loader": "11.0.1",
|
||||||
"style-loader": "2.0.0",
|
"style-loader": "2.0.0",
|
||||||
|
|||||||
@ -8,8 +8,4 @@ export default {
|
|||||||
RECEIVED_RHS_VIEW: pluginId + '_received_rhs_view',
|
RECEIVED_RHS_VIEW: pluginId + '_received_rhs_view',
|
||||||
RECEIVED_RHS_USER: pluginId + '_received_rhs_user',
|
RECEIVED_RHS_USER: pluginId + '_received_rhs_user',
|
||||||
RECEIVED_RHS_BADGE: pluginId + '_received_rhs_badge',
|
RECEIVED_RHS_BADGE: pluginId + '_received_rhs_badge',
|
||||||
OPEN_CREATE_BADGE_MODAL: pluginId + '_open_create_badge_modal',
|
|
||||||
CLOSE_CREATE_BADGE_MODAL: pluginId + '_close_create_badge_modal',
|
|
||||||
OPEN_EDIT_BADGE_MODAL: pluginId + '_open_edit_badge_modal',
|
|
||||||
CLOSE_EDIT_BADGE_MODAL: pluginId + '_close_edit_badge_modal',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {Client4} from 'mattermost-redux/client';
|
|||||||
import {IntegrationTypes} from 'mattermost-redux/action_types';
|
import {IntegrationTypes} from 'mattermost-redux/action_types';
|
||||||
|
|
||||||
import ActionTypes from 'action_types/';
|
import ActionTypes from 'action_types/';
|
||||||
import {BadgeDetails, BadgeID} from 'types/badges';
|
import {BadgeID} from 'types/badges';
|
||||||
import {RHSState} from 'types/general';
|
import {RHSState} from 'types/general';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,28 +76,14 @@ export function openCreateType() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function openCreateBadge() {
|
export function openCreateBadge() {
|
||||||
return (dispatch: Dispatch<AnyAction>) => {
|
return (dispatch: Dispatch<AnyAction>, getState: GetStateFunc) => {
|
||||||
dispatch(openCreateBadgeModal());
|
const command = '/badges create badge';
|
||||||
|
clientExecuteCommand(dispatch, getState, command);
|
||||||
|
|
||||||
return {data: true};
|
return {data: true};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openCreateBadgeModal() {
|
|
||||||
return {type: ActionTypes.OPEN_CREATE_BADGE_MODAL};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeCreateBadgeModal() {
|
|
||||||
return {type: ActionTypes.CLOSE_CREATE_BADGE_MODAL};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openEditBadgeModal(badge: BadgeDetails) {
|
|
||||||
return {type: ActionTypes.OPEN_EDIT_BADGE_MODAL, data: badge};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function closeEditBadgeModal() {
|
|
||||||
return {type: ActionTypes.CLOSE_EDIT_BADGE_MODAL};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openAddSubscription() {
|
export function openAddSubscription() {
|
||||||
return (dispatch: Dispatch<AnyAction>, getState: GetStateFunc) => {
|
return (dispatch: Dispatch<AnyAction>, getState: GetStateFunc) => {
|
||||||
const command = '/badges subscription create';
|
const command = '/badges subscription create';
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {Client4} from 'mattermost-redux/client';
|
|||||||
import {ClientError} from 'mattermost-redux/client/client4';
|
import {ClientError} from 'mattermost-redux/client/client4';
|
||||||
|
|
||||||
import manifest from 'manifest';
|
import manifest from 'manifest';
|
||||||
import {AllBadgesBadge, Badge, BadgeDetails, BadgeID, BadgeTypeDefinition, CreateBadgeRequest, CreateTypeRequest, GetTypesResponse, UpdateBadgeRequest, UserBadge} from 'types/badges';
|
import {AllBadgesBadge, BadgeDetails, BadgeID, UserBadge} from 'types/badges';
|
||||||
|
|
||||||
export default class Client {
|
export default class Client {
|
||||||
private url: string;
|
private url: string;
|
||||||
@ -41,35 +41,6 @@ export default class Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTypes(): Promise<GetTypesResponse> {
|
|
||||||
try {
|
|
||||||
const res = await this.doGet(`${this.url}/getTypes`);
|
|
||||||
return res as GetTypesResponse;
|
|
||||||
} catch {
|
|
||||||
return {types: [], can_create_type: false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBadge(req: CreateBadgeRequest): Promise<Badge> {
|
|
||||||
return await this.doPost(`${this.url}/createBadge`, req) as Badge;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createType(req: CreateTypeRequest): Promise<BadgeTypeDefinition> {
|
|
||||||
return await this.doPost(`${this.url}/createType`, req) as BadgeTypeDefinition;
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateBadge(req: UpdateBadgeRequest): Promise<Badge> {
|
|
||||||
return await this.doPut(`${this.url}/updateBadge`, req) as Badge;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteBadge(badgeID: BadgeID): Promise<void> {
|
|
||||||
await this.doDelete(`${this.url}/deleteBadge/${badgeID}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteType(typeID: string): Promise<void> {
|
|
||||||
await this.doDelete(`${this.url}/deleteType/${typeID}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private doGet = async (url: string, headers: {[x:string]: string} = {}) => {
|
private doGet = async (url: string, headers: {[x:string]: string} = {}) => {
|
||||||
headers['X-Timezone-Offset'] = String(new Date().getTimezoneOffset());
|
headers['X-Timezone-Offset'] = String(new Date().getTimezoneOffset());
|
||||||
|
|
||||||
@ -92,75 +63,4 @@ export default class Client {
|
|||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private doPost = async (url: string, body: any, headers: {[x:string]: string} = {}) => {
|
|
||||||
headers['X-Timezone-Offset'] = String(new Date().getTimezoneOffset());
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
method: 'post',
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(url, Client4.getOptions(options));
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
|
|
||||||
throw new ClientError(Client4.url, {
|
|
||||||
message: text || '',
|
|
||||||
status_code: response.status,
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private doPut = async (url: string, body: any, headers: {[x:string]: string} = {}) => {
|
|
||||||
headers['X-Timezone-Offset'] = String(new Date().getTimezoneOffset());
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
method: 'put',
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(url, Client4.getOptions(options));
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
|
|
||||||
throw new ClientError(Client4.url, {
|
|
||||||
message: text || '',
|
|
||||||
status_code: response.status,
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private doDelete = async (url: string, headers: {[x:string]: string} = {}) => {
|
|
||||||
headers['X-Timezone-Offset'] = String(new Date().getTimezoneOffset());
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
method: 'delete',
|
|
||||||
headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(url, Client4.getOptions(options));
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await response.text();
|
|
||||||
|
|
||||||
throw new ClientError(Client4.url, {
|
|
||||||
message: text || '',
|
|
||||||
status_code: response.status,
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,161 +0,0 @@
|
|||||||
.admin-user-select {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&__container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
min-height: 34px;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
cursor: text;
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border-color: var(--button-bg, #166de0);
|
|
||||||
box-shadow: 0 0 0 1px var(--button-bg, #166de0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__spinner {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border: 2px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-top-color: var(--button-bg, #166de0);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: admin-user-select-spin 0.6s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: rgba(var(--button-bg-rgb, 22, 109, 224), 0.1);
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__chip-avatar {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__chip-name {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__chip-remove {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: none;
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
flex: 1 1 60px;
|
|
||||||
min-width: 60px;
|
|
||||||
padding: 2px 0;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 24px;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__dropdown {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-top: 4px;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__avatar {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__option-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__option-fullname {
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__no-results {
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
|
||||||
font-style: italic;
|
|
||||||
|
|
||||||
&--loading {
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes admin-user-select-spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,239 +0,0 @@
|
|||||||
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import {Client4} from 'mattermost-redux/client';
|
|
||||||
import {UserProfile} from 'mattermost-redux/types/users';
|
|
||||||
|
|
||||||
import {debounce, getUserDisplayName} from 'utils/helpers';
|
|
||||||
import CloseIcon from 'components/icons/close_icon';
|
|
||||||
import SearchIcon from 'components/icons/search_icon';
|
|
||||||
|
|
||||||
import './badges_admin_setting.scss';
|
|
||||||
|
|
||||||
type SelectedUser = {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
fullName: string;
|
|
||||||
avatarUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
value: string;
|
|
||||||
disabled: boolean;
|
|
||||||
onChange: (id: string, value: any) => void;
|
|
||||||
setSaveNeeded: () => void;
|
|
||||||
config: any;
|
|
||||||
license: any;
|
|
||||||
setByEnv: boolean;
|
|
||||||
registerSaveAction: (action: () => Promise<{error?: {message?: string}}>) => void;
|
|
||||||
unRegisterSaveAction: (action: () => Promise<{error?: {message?: string}}>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BadgesAdminSetting: React.FC<Props> = ({id, value, disabled, onChange, setSaveNeeded}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
|
||||||
const [results, setResults] = useState<UserProfile[]>([]);
|
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [selectedUsers, setSelectedUsers] = useState<SelectedUser[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const usernames = value.split(',').map((u) => u.trim()).filter(Boolean);
|
|
||||||
Promise.all(usernames.map(async (username) => {
|
|
||||||
try {
|
|
||||||
const user = await Client4.getUserByUsername(username);
|
|
||||||
return {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
fullName: getUserDisplayName(user),
|
|
||||||
avatarUrl: Client4.getProfilePictureUrl(user.id, user.last_picture_update),
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return {id: '', username, fullName: '', avatarUrl: ''};
|
|
||||||
}
|
|
||||||
})).then(setSelectedUsers);
|
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (e: MouseEvent) => {
|
|
||||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
|
||||||
setDropdownOpen(false);
|
|
||||||
setSearchTerm('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const performSearch = async (term: string, excluded: Set<string>) => {
|
|
||||||
if (!term) {
|
|
||||||
setResults([]);
|
|
||||||
setDropdownOpen(false);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const data = await Client4.autocompleteUsers(term, '', '', {limit: 20});
|
|
||||||
setResults(data.users.filter((u: UserProfile) => !excluded.has(u.username)));
|
|
||||||
} catch {
|
|
||||||
setResults([]);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const doSearch = useMemo(() => debounce(performSearch, 400), []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
const saveValue = (users: SelectedUser[]) => {
|
|
||||||
onChange(id, users.map((u) => u.username).join(','));
|
|
||||||
setSaveNeeded();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const term = e.target.value;
|
|
||||||
setSearchTerm(term);
|
|
||||||
if (term) {
|
|
||||||
setDropdownOpen(true);
|
|
||||||
}
|
|
||||||
doSearch(term, new Set(selectedUsers.map((u) => u.username)));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = (user: UserProfile) => {
|
|
||||||
const next = [...selectedUsers, {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
fullName: getUserDisplayName(user),
|
|
||||||
avatarUrl: Client4.getProfilePictureUrl(user.id, user.last_picture_update),
|
|
||||||
}];
|
|
||||||
setSelectedUsers(next);
|
|
||||||
saveValue(next);
|
|
||||||
setSearchTerm('');
|
|
||||||
setResults([]);
|
|
||||||
setDropdownOpen(false);
|
|
||||||
inputRef.current?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemove = (username: string) => {
|
|
||||||
const next = selectedUsers.filter((u) => u.username !== username);
|
|
||||||
setSelectedUsers(next);
|
|
||||||
saveValue(next);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='form-group'>
|
|
||||||
<label className='control-label col-sm-4'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.admin.label'
|
|
||||||
defaultMessage='Администраторы достижений:'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='col-sm-8'>
|
|
||||||
<div
|
|
||||||
className='admin-user-select'
|
|
||||||
ref={containerRef}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className='admin-user-select__container'
|
|
||||||
onClick={() => inputRef.current?.focus()}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<div className='admin-user-select__spinner'/>
|
|
||||||
) : (
|
|
||||||
<SearchIcon/>
|
|
||||||
)}
|
|
||||||
{selectedUsers.map((user) => (
|
|
||||||
<span
|
|
||||||
key={user.username}
|
|
||||||
className='admin-user-select__chip'
|
|
||||||
>
|
|
||||||
{user.avatarUrl && (
|
|
||||||
<img
|
|
||||||
className='admin-user-select__chip-avatar'
|
|
||||||
src={user.avatarUrl}
|
|
||||||
alt={user.username}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className='admin-user-select__chip-name'>
|
|
||||||
{user.fullName || user.username}
|
|
||||||
</span>
|
|
||||||
{!disabled && (
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='admin-user-select__chip-remove'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleRemove(user.username);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon size={12}/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
className='admin-user-select__input'
|
|
||||||
type='text'
|
|
||||||
value={searchTerm}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder={selectedUsers.length === 0 ? intl.formatMessage({
|
|
||||||
id: 'badges.admin.placeholder',
|
|
||||||
defaultMessage: 'Начните вводить имя...',
|
|
||||||
}) : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{dropdownOpen && (
|
|
||||||
<div className='admin-user-select__dropdown'>
|
|
||||||
{results.length === 0 && searchTerm && (
|
|
||||||
<div className={`admin-user-select__no-results${loading ? ' admin-user-select__no-results--loading' : ''}`}>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.admin.no_results'
|
|
||||||
defaultMessage='Пользователь не найден'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{results.map((user) => (
|
|
||||||
<div
|
|
||||||
key={user.id}
|
|
||||||
className='admin-user-select__option'
|
|
||||||
onClick={() => handleSelect(user)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className='admin-user-select__avatar'
|
|
||||||
src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
|
|
||||||
alt={user.username}
|
|
||||||
/>
|
|
||||||
<span className='admin-user-select__option-name'>
|
|
||||||
{user.username}
|
|
||||||
</span>
|
|
||||||
{(user.first_name || user.last_name) && (
|
|
||||||
<span className='admin-user-select__option-fullname'>
|
|
||||||
{'— '}{`${user.first_name} ${user.last_name}`.trim()}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='help-text'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.admin.help_text'
|
|
||||||
defaultMessage='Эти пользователи будут считаться администраторами плагина достижений. Они могут создавать типы, а также изменять и выдавать любые значки.'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BadgesAdminSetting;
|
|
||||||
@ -1,348 +0,0 @@
|
|||||||
.BadgeModal {
|
|
||||||
&__backdrop {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__dialog {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 10001;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 20px 32px rgba(0, 0, 0, 0.12);
|
|
||||||
width: 480px;
|
|
||||||
max-width: 90vw;
|
|
||||||
max-height: 90vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px 24px 0;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
opacity: 0.56;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__body {
|
|
||||||
padding: 20px 24px;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
border-top: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
opacity: 0.64;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='text'],
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: var(--button-bg, #166de0);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-input {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
|
|
||||||
&:focus-within {
|
|
||||||
border-color: var(--button-bg, #166de0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoticon {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emojisprite,
|
|
||||||
.emoticon {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='text'] {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 12px 8px 0;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0;
|
|
||||||
text-transform: none;
|
|
||||||
opacity: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-type-section {
|
|
||||||
padding: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
border: 1px dashed rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.24);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 8px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&--primary {
|
|
||||||
background: var(--button-bg, #166de0);
|
|
||||||
color: var(--button-color, #fff);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.88;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--cancel {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--danger {
|
|
||||||
background: var(--error-text, #d24b4e);
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.88;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: var(--error-text, #d24b4e);
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-section {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-delete {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--error-text, #d24b4e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-select {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&__trigger {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__value {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__arrow {
|
|
||||||
font-size: 12px;
|
|
||||||
opacity: 0.56;
|
|
||||||
margin-left: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__dropdown {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-top: 4px;
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
||||||
z-index: 10;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 8px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--selected {
|
|
||||||
background: rgba(var(--button-bg-rgb, 22, 109, 224), 0.08);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--create {
|
|
||||||
border-top: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.08);
|
|
||||||
color: var(--button-bg, #166de0);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__option-name {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__delete-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
color: var(--error-text, #d24b4e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface EmojiPickerOverlayProps {
|
|
||||||
target: () => HTMLElement | null;
|
|
||||||
container?: () => HTMLElement | null;
|
|
||||||
show: boolean;
|
|
||||||
onHide: () => void;
|
|
||||||
onEmojiClick: (emoji: any) => void;
|
|
||||||
rightOffset?: number;
|
|
||||||
defaultHorizontalPosition?: 'left' | 'right';
|
|
||||||
onExited?: () => void;
|
|
||||||
hideCustomEmojiButton?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmojiPickerOverlay: React.FC<EmojiPickerOverlayProps> = (props) => {
|
|
||||||
const Overlay = (window as any).Components?.EmojiPickerOverlay;
|
|
||||||
|
|
||||||
if (!Overlay) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Overlay {...props}/>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmojiPickerOverlay;
|
|
||||||
@ -1,469 +0,0 @@
|
|||||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
|
||||||
|
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/common';
|
|
||||||
import {GlobalState} from 'mattermost-redux/types/store';
|
|
||||||
|
|
||||||
import {isCreateBadgeModalVisible, getEditBadgeModalData} from 'selectors';
|
|
||||||
import {closeCreateBadgeModal, closeEditBadgeModal} from 'actions/actions';
|
|
||||||
import {BadgeTypeDefinition} from 'types/badges';
|
|
||||||
import Client from 'client/api';
|
|
||||||
import {getServerErrorId} from 'utils/helpers';
|
|
||||||
import CloseIcon from 'components/icons/close_icon';
|
|
||||||
import EmojiIcon from 'components/icons/emoji_icon';
|
|
||||||
import RenderEmoji from 'components/utils/emoji';
|
|
||||||
|
|
||||||
import EmojiPickerOverlay from './emoji_picker';
|
|
||||||
import TypeSelect from './type_select';
|
|
||||||
|
|
||||||
import './badge_modal.scss';
|
|
||||||
|
|
||||||
const NEW_TYPE_VALUE = '__new__';
|
|
||||||
|
|
||||||
const BadgeModal: React.FC = () => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const intl = useIntl();
|
|
||||||
const createVisible = useSelector(isCreateBadgeModalVisible);
|
|
||||||
const editData = useSelector(getEditBadgeModalData);
|
|
||||||
const channelId = useSelector((state: GlobalState) => getCurrentChannelId(state));
|
|
||||||
const isOpen = createVisible || editData !== null;
|
|
||||||
const isEditMode = editData !== null;
|
|
||||||
const [name, setName] = useState('');
|
|
||||||
const [description, setDescription] = useState('');
|
|
||||||
const [image, setImage] = useState('');
|
|
||||||
const [badgeType, setBadgeType] = useState('');
|
|
||||||
const [multiple, setMultiple] = useState(false);
|
|
||||||
const [types, setTypes] = useState<BadgeTypeDefinition[]>([]);
|
|
||||||
const [showCreateType, setShowCreateType] = useState(false);
|
|
||||||
const [newTypeName, setNewTypeName] = useState('');
|
|
||||||
const [newTypeEveryoneCanCreate, setNewTypeEveryoneCanCreate] = useState(false);
|
|
||||||
const [newTypeEveryoneCanGrant, setNewTypeEveryoneCanGrant] = useState(false);
|
|
||||||
const [canCreateType, setCanCreateType] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
||||||
const [confirmDeleteTypeId, setConfirmDeleteTypeId] = useState<string | null>(null);
|
|
||||||
const [typeDropdownOpen, setTypeDropdownOpen] = useState(false);
|
|
||||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
|
||||||
const dialogRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const emojiData = (window as any)?.useGetEmojiSelectorData?.();
|
|
||||||
const {
|
|
||||||
emojiButtonRef,
|
|
||||||
calculateRightOffSet,
|
|
||||||
} = emojiData || {};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fetchTypes = async () => {
|
|
||||||
const client = new Client();
|
|
||||||
const resp = await client.getTypes();
|
|
||||||
setTypes(resp.types);
|
|
||||||
setCanCreateType(resp.can_create_type);
|
|
||||||
if (!isEditMode && resp.types.length > 0) {
|
|
||||||
const defaultType = resp.types.find((t) => t.is_default);
|
|
||||||
setBadgeType(String(defaultType ? defaultType.id : resp.types[0].id));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchTypes();
|
|
||||||
|
|
||||||
if (isEditMode && editData) {
|
|
||||||
setName(editData.name);
|
|
||||||
setDescription(editData.description);
|
|
||||||
setImage(editData.image);
|
|
||||||
setBadgeType(String(editData.type));
|
|
||||||
setMultiple(editData.multiple);
|
|
||||||
} else {
|
|
||||||
setName('');
|
|
||||||
setDescription('');
|
|
||||||
setImage('');
|
|
||||||
setBadgeType('');
|
|
||||||
setMultiple(false);
|
|
||||||
}
|
|
||||||
setShowCreateType(false);
|
|
||||||
setNewTypeName('');
|
|
||||||
setNewTypeEveryoneCanCreate(false);
|
|
||||||
setNewTypeEveryoneCanGrant(false);
|
|
||||||
setError(null);
|
|
||||||
setConfirmDelete(false);
|
|
||||||
setConfirmDeleteTypeId(null);
|
|
||||||
setTypeDropdownOpen(false);
|
|
||||||
setShowEmojiPicker(false);
|
|
||||||
setLoading(false);
|
|
||||||
}, [isOpen, isEditMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
if (createVisible) {
|
|
||||||
dispatch(closeCreateBadgeModal());
|
|
||||||
}
|
|
||||||
if (editData) {
|
|
||||||
dispatch(closeEditBadgeModal());
|
|
||||||
}
|
|
||||||
}, [dispatch, createVisible, editData]);
|
|
||||||
|
|
||||||
const handleTypeSelect = useCallback((val: string) => {
|
|
||||||
if (val === NEW_TYPE_VALUE) {
|
|
||||||
setShowCreateType(true);
|
|
||||||
setBadgeType('');
|
|
||||||
} else {
|
|
||||||
setShowCreateType(false);
|
|
||||||
setBadgeType(val);
|
|
||||||
}
|
|
||||||
setTypeDropdownOpen(false);
|
|
||||||
setConfirmDeleteTypeId(null);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEmojiSelect = (emoji: any) => {
|
|
||||||
if (emoji.short_name) {
|
|
||||||
setImage(emoji.short_name);
|
|
||||||
} else if (emoji.name) {
|
|
||||||
setImage(emoji.name);
|
|
||||||
}
|
|
||||||
setShowEmojiPicker(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteType = useCallback(async (typeId: string) => {
|
|
||||||
if (confirmDeleteTypeId !== typeId) {
|
|
||||||
setConfirmDeleteTypeId(typeId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const client = new Client();
|
|
||||||
await client.deleteType(typeId);
|
|
||||||
const removeById = (t: BadgeTypeDefinition) => String(t.id) !== typeId;
|
|
||||||
setTypes((prev) => prev.filter(removeById));
|
|
||||||
if (badgeType === typeId) {
|
|
||||||
setBadgeType('');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
setError(intl.formatMessage({id: 'badges.error.' + (getServerErrorId(err) || 'unknown'), defaultMessage: 'Произошла ошибка'}));
|
|
||||||
}
|
|
||||||
setConfirmDeleteTypeId(null);
|
|
||||||
}, [confirmDeleteTypeId, badgeType, intl]);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(async () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const client = new Client();
|
|
||||||
let typeID = badgeType;
|
|
||||||
if (showCreateType) {
|
|
||||||
if (!newTypeName.trim()) {
|
|
||||||
setError(intl.formatMessage({id: 'badges.modal.error_type_name_required', defaultMessage: 'Введите название типа'}));
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const createdType = await client.createType({
|
|
||||||
name: newTypeName.trim(),
|
|
||||||
everyone_can_create: newTypeEveryoneCanCreate,
|
|
||||||
everyone_can_grant: newTypeEveryoneCanGrant,
|
|
||||||
channel_id: channelId,
|
|
||||||
});
|
|
||||||
typeID = String(createdType.id);
|
|
||||||
}
|
|
||||||
if (!typeID) {
|
|
||||||
setError(intl.formatMessage({id: 'badges.modal.error_type_required', defaultMessage: 'Выберите тип значка'}));
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isEditMode && editData) {
|
|
||||||
await client.updateBadge({
|
|
||||||
id: String(editData.id),
|
|
||||||
name: name.trim(),
|
|
||||||
description: description.trim(),
|
|
||||||
image: image.trim(),
|
|
||||||
type: typeID,
|
|
||||||
multiple,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await client.createBadge({
|
|
||||||
name: name.trim(),
|
|
||||||
description: description.trim(),
|
|
||||||
image: image.trim(),
|
|
||||||
type: typeID,
|
|
||||||
multiple,
|
|
||||||
channel_id: channelId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleClose();
|
|
||||||
} catch (err) {
|
|
||||||
setError(intl.formatMessage({id: 'badges.error.' + (getServerErrorId(err) || 'unknown'), defaultMessage: 'Произошла ошибка'}));
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [badgeType, showCreateType, newTypeName, newTypeEveryoneCanCreate, newTypeEveryoneCanGrant, isEditMode, editData, name, description, image, multiple, handleClose, intl, channelId]);
|
|
||||||
|
|
||||||
const handleDelete = useCallback(async () => {
|
|
||||||
if (!editData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!confirmDelete) {
|
|
||||||
setConfirmDelete(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const client = new Client();
|
|
||||||
await client.deleteBadge(editData.id);
|
|
||||||
handleClose();
|
|
||||||
} catch (err) {
|
|
||||||
setError(intl.formatMessage({id: 'badges.error.' + (getServerErrorId(err) || 'unknown'), defaultMessage: 'Произошла ошибка'}));
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [editData, confirmDelete, handleClose, intl]);
|
|
||||||
|
|
||||||
if (!isOpen) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = isEditMode ? intl.formatMessage({id: 'badges.modal.edit_badge_title', defaultMessage: 'Редактировать значок'}) : intl.formatMessage({id: 'badges.modal.create_badge_title', defaultMessage: 'Создать значок'});
|
|
||||||
const submitLabel = isEditMode ? intl.formatMessage({id: 'badges.modal.btn_save', defaultMessage: 'Сохранить'}) : intl.formatMessage({id: 'badges.modal.btn_create', defaultMessage: 'Создать'});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='BadgeModal'>
|
|
||||||
<div
|
|
||||||
className='BadgeModal__backdrop'
|
|
||||||
onClick={handleClose}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className='BadgeModal__dialog'
|
|
||||||
ref={dialogRef}
|
|
||||||
>
|
|
||||||
<div className='BadgeModal__header'>
|
|
||||||
<h4>{title}</h4>
|
|
||||||
<button
|
|
||||||
className='close-btn'
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
<CloseIcon/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className='BadgeModal__body'>
|
|
||||||
<div className='form-group'>
|
|
||||||
<label>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.field_name'
|
|
||||||
defaultMessage='Название'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
maxLength={20}
|
|
||||||
placeholder={intl.formatMessage({id: 'badges.modal.field_name_placeholder', defaultMessage: 'Название значка (макс. 20 символов)'})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<label>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.field_description'
|
|
||||||
defaultMessage='Описание'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
maxLength={120}
|
|
||||||
placeholder={intl.formatMessage({id: 'badges.modal.field_description_placeholder', defaultMessage: 'Описание значка (макс. 120 символов)'})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<label>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.field_image'
|
|
||||||
defaultMessage='Эмодзи'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className='emoji-input'>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='emoji-input__icon'
|
|
||||||
onClick={() => setShowEmojiPicker((prev) => !prev)}
|
|
||||||
ref={emojiButtonRef}
|
|
||||||
>
|
|
||||||
<EmojiIcon/>
|
|
||||||
</button>
|
|
||||||
{image && (
|
|
||||||
<RenderEmoji
|
|
||||||
emojiName={image}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
value={image}
|
|
||||||
onChange={(e) => setImage(e.target.value)}
|
|
||||||
placeholder={intl.formatMessage({id: 'badges.modal.field_image_placeholder', defaultMessage: 'Название эмодзи (напр. star)'})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{showEmojiPicker && (
|
|
||||||
<EmojiPickerOverlay
|
|
||||||
target={() => emojiButtonRef?.current}
|
|
||||||
container={() => dialogRef.current}
|
|
||||||
show={showEmojiPicker}
|
|
||||||
onHide={() => setShowEmojiPicker(false)}
|
|
||||||
onEmojiClick={handleEmojiSelect}
|
|
||||||
rightOffset={calculateRightOffSet?.(emojiButtonRef?.current)}
|
|
||||||
defaultHorizontalPosition='right'
|
|
||||||
hideCustomEmojiButton={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<label>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.field_type'
|
|
||||||
defaultMessage='Тип'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<TypeSelect
|
|
||||||
types={types}
|
|
||||||
badgeType={badgeType}
|
|
||||||
showCreateType={showCreateType}
|
|
||||||
canCreateType={canCreateType}
|
|
||||||
typeDropdownOpen={typeDropdownOpen}
|
|
||||||
confirmDeleteTypeId={confirmDeleteTypeId}
|
|
||||||
onToggleDropdown={() => setTypeDropdownOpen(!typeDropdownOpen)}
|
|
||||||
onSelect={handleTypeSelect}
|
|
||||||
onDeleteType={handleDeleteType}
|
|
||||||
onCancelDeleteType={() => setConfirmDeleteTypeId(null)}
|
|
||||||
/>
|
|
||||||
{showCreateType && (
|
|
||||||
<div className='inline-type-section'>
|
|
||||||
<div className='form-group'>
|
|
||||||
<label>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.new_type_name'
|
|
||||||
defaultMessage='Название типа'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
value={newTypeName}
|
|
||||||
onChange={(e) => setNewTypeName(e.target.value)}
|
|
||||||
maxLength={20}
|
|
||||||
placeholder={intl.formatMessage({id: 'badges.modal.new_type_name_placeholder', defaultMessage: 'Название типа (макс. 20 символов)'})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='checkbox-group'>
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
id='newTypeEveryoneCanCreate'
|
|
||||||
checked={newTypeEveryoneCanCreate}
|
|
||||||
onChange={(e) => setNewTypeEveryoneCanCreate(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label htmlFor='newTypeEveryoneCanCreate'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.new_type_everyone_create'
|
|
||||||
defaultMessage='Все могут создавать значки'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className='checkbox-group'>
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
id='newTypeEveryoneCanGrant'
|
|
||||||
checked={newTypeEveryoneCanGrant}
|
|
||||||
onChange={(e) => setNewTypeEveryoneCanGrant(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label htmlFor='newTypeEveryoneCanGrant'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.new_type_everyone_grant'
|
|
||||||
defaultMessage='Все могут выдавать значки'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='checkbox-group'>
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
id='badgeMultiple'
|
|
||||||
checked={multiple}
|
|
||||||
onChange={(e) => setMultiple(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label htmlFor='badgeMultiple'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.field_multiple'
|
|
||||||
defaultMessage='Можно выдавать несколько раз'
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{error && <div className='error-message'>{error}</div>}
|
|
||||||
{isEditMode && (
|
|
||||||
<div className='delete-section'>
|
|
||||||
{confirmDelete ? (
|
|
||||||
<div className='confirm-delete'>
|
|
||||||
<span>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.confirm_delete'
|
|
||||||
defaultMessage='Вы уверены?'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
className='btn btn--danger'
|
|
||||||
onClick={handleDelete}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.btn_confirm_delete'
|
|
||||||
defaultMessage='Да, удалить'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='btn btn--cancel'
|
|
||||||
onClick={() => setConfirmDelete(false)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.btn_cancel'
|
|
||||||
defaultMessage='Отмена'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
className='btn btn--danger'
|
|
||||||
onClick={handleDelete}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.btn_delete'
|
|
||||||
defaultMessage='Удалить значок'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='BadgeModal__footer'>
|
|
||||||
<button
|
|
||||||
className='btn btn--cancel'
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.btn_cancel'
|
|
||||||
defaultMessage='Отмена'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='btn btn--primary'
|
|
||||||
onClick={handleSubmit}
|
|
||||||
disabled={loading || !name.trim() || !image.trim()}
|
|
||||||
>
|
|
||||||
{loading ? intl.formatMessage({id: 'badges.modal.btn_creating', defaultMessage: 'Сохранение...'}) : submitLabel}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BadgeModal;
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import {BadgeTypeDefinition} from 'types/badges';
|
|
||||||
import TrashIcon from 'components/icons/trash_icon';
|
|
||||||
import ConfirmDialog from 'components/confirm_dialog/confirm_dialog';
|
|
||||||
|
|
||||||
const NEW_TYPE_VALUE = '__new__';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
types: BadgeTypeDefinition[];
|
|
||||||
badgeType: string;
|
|
||||||
showCreateType: boolean;
|
|
||||||
canCreateType: boolean;
|
|
||||||
typeDropdownOpen: boolean;
|
|
||||||
confirmDeleteTypeId: string | null;
|
|
||||||
onToggleDropdown: () => void;
|
|
||||||
onSelect: (val: string) => void;
|
|
||||||
onDeleteType: (typeId: string) => void;
|
|
||||||
onCancelDeleteType: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TypeSelect: React.FC<Props> = ({
|
|
||||||
types,
|
|
||||||
badgeType,
|
|
||||||
showCreateType,
|
|
||||||
canCreateType,
|
|
||||||
typeDropdownOpen,
|
|
||||||
confirmDeleteTypeId,
|
|
||||||
onToggleDropdown,
|
|
||||||
onSelect,
|
|
||||||
onDeleteType,
|
|
||||||
onCancelDeleteType,
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const selectedTypeName = types.find((t) => String(t.id) === badgeType)?.name ||
|
|
||||||
intl.formatMessage({id: 'badges.modal.field_type_placeholder', defaultMessage: 'Выберите тип значка'});
|
|
||||||
const triggerLabel = showCreateType ? intl.formatMessage({id: 'badges.modal.create_new_type', defaultMessage: '+ Создать новый тип'}) : selectedTypeName;
|
|
||||||
const confirmType = confirmDeleteTypeId ? types.find((t) => String(t.id) === confirmDeleteTypeId) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='type-select'>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='type-select__trigger'
|
|
||||||
onClick={onToggleDropdown}
|
|
||||||
>
|
|
||||||
<span className='type-select__value'>{triggerLabel}</span>
|
|
||||||
<span className='type-select__arrow'>{'\u25BE'}</span>
|
|
||||||
</button>
|
|
||||||
{typeDropdownOpen && (
|
|
||||||
<div className='type-select__dropdown'>
|
|
||||||
{types.map((t) => {
|
|
||||||
const tid = String(t.id);
|
|
||||||
const isEmpty = t.badge_count === 0;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={tid}
|
|
||||||
className={'type-select__option' + (tid === badgeType ? ' type-select__option--selected' : '')}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className='type-select__option-name'
|
|
||||||
onClick={() => onSelect(tid)}
|
|
||||||
>
|
|
||||||
{t.name}
|
|
||||||
</span>
|
|
||||||
{isEmpty && !t.is_default && (
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='type-select__delete-btn'
|
|
||||||
onClick={() => onDeleteType(tid)}
|
|
||||||
title={intl.formatMessage({id: 'badges.modal.delete_type', defaultMessage: 'Удалить тип'})}
|
|
||||||
>
|
|
||||||
<TrashIcon/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{canCreateType && (
|
|
||||||
<div
|
|
||||||
className='type-select__option type-select__option--create'
|
|
||||||
onClick={() => onSelect(NEW_TYPE_VALUE)}
|
|
||||||
>
|
|
||||||
<span className='type-select__option-name'>
|
|
||||||
{intl.formatMessage({id: 'badges.modal.create_new_type', defaultMessage: '+ Создать новый тип'})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{confirmType && (
|
|
||||||
<ConfirmDialog
|
|
||||||
onConfirm={() => onDeleteType(String(confirmDeleteTypeId))}
|
|
||||||
onCancel={onCancelDeleteType}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.confirm_delete_type'
|
|
||||||
defaultMessage='Удалить тип «{name}»?'
|
|
||||||
values={{name: confirmType.name}}
|
|
||||||
/>
|
|
||||||
</ConfirmDialog>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TypeSelect;
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
.ConfirmDialog {
|
|
||||||
background: var(--center-channel-bg, #fff);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
|
|
||||||
min-width: 240px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&__overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 11;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__text {
|
|
||||||
margin: 0 0 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import './confirm_dialog.scss';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
onConfirm: () => void;
|
|
||||||
onCancel: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConfirmDialog: React.FC<Props> = ({children, onConfirm, onCancel}) => (
|
|
||||||
<div className='ConfirmDialog__overlay'>
|
|
||||||
<div className='ConfirmDialog'>
|
|
||||||
<p className='ConfirmDialog__text'>
|
|
||||||
{children}
|
|
||||||
</p>
|
|
||||||
<div className='ConfirmDialog__actions'>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='btn btn--cancel'
|
|
||||||
onClick={onCancel}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.btn_cancel'
|
|
||||||
defaultMessage='Отмена'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='btn btn--danger'
|
|
||||||
onClick={onConfirm}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.modal.btn_confirm_delete'
|
|
||||||
defaultMessage='Да, удалить'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ConfirmDialog;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CloseIcon: React.FC<Props> = ({size = 16}) => (
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
viewBox='0 0 24 24'
|
|
||||||
fill='none'
|
|
||||||
stroke='currentColor'
|
|
||||||
strokeWidth='2'
|
|
||||||
strokeLinecap='round'
|
|
||||||
strokeLinejoin='round'
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke='none'
|
|
||||||
d='M0 0h24v24H0z'
|
|
||||||
fill='none'
|
|
||||||
/>
|
|
||||||
<path d='M18 6l-12 12'/>
|
|
||||||
<path d='M6 6l12 12'/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default CloseIcon;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmojiIcon: React.FC<Props> = ({size = 20}) => (
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
viewBox='0 0 24 24'
|
|
||||||
fill='none'
|
|
||||||
stroke='currentColor'
|
|
||||||
strokeWidth='2'
|
|
||||||
strokeLinecap='round'
|
|
||||||
strokeLinejoin='round'
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke='none'
|
|
||||||
d='M0 0h24v24H0z'
|
|
||||||
fill='none'
|
|
||||||
/>
|
|
||||||
<path d='M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0'/>
|
|
||||||
<path d='M9 10l.01 0'/>
|
|
||||||
<path d='M15 10l.01 0'/>
|
|
||||||
<path d='M9.5 15a3.5 3.5 0 0 0 5 0'/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default EmojiIcon;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SearchIcon: React.FC<Props> = ({size = 18}) => (
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
viewBox='0 0 24 24'
|
|
||||||
fill='none'
|
|
||||||
stroke='currentColor'
|
|
||||||
strokeWidth='2'
|
|
||||||
strokeLinecap='round'
|
|
||||||
strokeLinejoin='round'
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke='none'
|
|
||||||
d='M0 0h24v24H0z'
|
|
||||||
fill='none'
|
|
||||||
/>
|
|
||||||
<path d='M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0'/>
|
|
||||||
<path d='M21 21l-6 -6'/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default SearchIcon;
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TrashIcon: React.FC<Props> = ({size = 16}) => (
|
|
||||||
<svg
|
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
viewBox='0 0 24 24'
|
|
||||||
fill='none'
|
|
||||||
stroke='currentColor'
|
|
||||||
strokeWidth='2'
|
|
||||||
strokeLinecap='round'
|
|
||||||
strokeLinejoin='round'
|
|
||||||
>
|
|
||||||
<path d='M4 7l16 0'/>
|
|
||||||
<path d='M10 11l0 6'/>
|
|
||||||
<path d='M14 11l0 6'/>
|
|
||||||
<path d='M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12'/>
|
|
||||||
<path d='M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3'/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default TrashIcon;
|
|
||||||
@ -3,63 +3,4 @@
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
&--loading {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--empty {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__emptyContent {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__emptyTitle {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--center-channel-color, #3d3c40);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__emptyDescription {
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.72);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__createButton {
|
|
||||||
background: var(--button-bg, #166de0);
|
|
||||||
color: var(--button-color, #fff);
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.88;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
||||||
|
|
||||||
import {BadgeID, AllBadgesBadge} from '../../types/badges';
|
import {BadgeID, AllBadgesBadge} from '../../types/badges';
|
||||||
@ -20,7 +18,6 @@ type Props = {
|
|||||||
setRHSView: (view: RHSState) => void;
|
setRHSView: (view: RHSState) => void;
|
||||||
setRHSBadge: (badge: BadgeID | null) => void;
|
setRHSBadge: (badge: BadgeID | null) => void;
|
||||||
getCustomEmojisByName: (names: string[]) => void;
|
getCustomEmojisByName: (names: string[]) => void;
|
||||||
openCreateBadgeModal: () => void;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,37 +62,11 @@ class AllBadges extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
return (<div className='AllBadges AllBadges--loading'>
|
return (<div className='AllBadges'>{'Loading...'}</div>);
|
||||||
<div className='spinner'/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.badges || this.state.badges.length === 0) {
|
if (!this.state.badges || this.state.badges.length === 0) {
|
||||||
return (<div className='AllBadges AllBadges--empty'>
|
return (<div className='AllBadges'>{'No badges yet.'}</div>);
|
||||||
<div className='AllBadges__emptyContent'>
|
|
||||||
<div className='AllBadges__emptyTitle'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.empty.title'
|
|
||||||
defaultMessage='Значков пока нет'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='AllBadges__emptyDescription'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.empty.description'
|
|
||||||
defaultMessage='Создайте первый значок, чтобы отмечать достижения и заслуги участников команды.'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className='AllBadges__createButton'
|
|
||||||
onClick={this.props.actions.openCreateBadgeModal}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.rhs.create_badge'
|
|
||||||
defaultMessage='+ Создать значок'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = this.state.badges.map((badge) => {
|
const content = this.state.badges.map((badge) => {
|
||||||
@ -109,23 +80,7 @@ class AllBadges extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className='AllBadges'>
|
<div className='AllBadges'>
|
||||||
<div className='AllBadges__header'>
|
<div><b>{'All badges'}</b></div>
|
||||||
<b>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.rhs.all_badges'
|
|
||||||
defaultMessage='Все значки'
|
|
||||||
/>
|
|
||||||
</b>
|
|
||||||
<button
|
|
||||||
className='AllBadges__createButton'
|
|
||||||
onClick={this.props.actions.openCreateBadgeModal}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.rhs.create_badge'
|
|
||||||
defaultMessage='+ Создать значок'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<RHSScrollbars>{content}</RHSScrollbars>
|
<RHSScrollbars>{content}</RHSScrollbars>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,59 +1,25 @@
|
|||||||
.AllBadgesRow {
|
.AllBadgesRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
padding: 12px 16px;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
padding: 5px;
|
||||||
cursor: pointer;
|
margin-bottom: 3px;
|
||||||
gap: 12px;
|
|
||||||
transition: background 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb), 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-icon {
|
.badge-icon {
|
||||||
flex-shrink: 0;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-name {
|
.badge-name {
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--center-channel-text);
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
.granted-by {
|
||||||
.badge-description {
|
font-size: 10px;
|
||||||
font-size: 13px;
|
}
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
.badge-type {
|
||||||
margin-top: 2px;
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.badge-descrition {
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0px
|
||||||
display: inline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-label {
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
margin-top: 4px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import {AllBadgesBadge} from '../../types/badges';
|
import {AllBadgesBadge} from '../../types/badges';
|
||||||
import BadgeImage from '../utils/badge_image';
|
import BadgeImage from '../utils/badge_image';
|
||||||
import {markdown} from 'utils/markdown';
|
import {markdown} from 'utils/markdown';
|
||||||
@ -13,76 +11,36 @@ type Props = {
|
|||||||
onClick: (badge: AllBadgesBadge) => void;
|
onClick: (badge: AllBadgesBadge) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGrantedText(badge: AllBadgesBadge): React.ReactNode {
|
function getGrantedText(badge: AllBadgesBadge): string {
|
||||||
if (badge.granted === 0) {
|
if (badge.granted === 0) {
|
||||||
return (
|
return 'Not yet granted.';
|
||||||
<FormattedMessage
|
|
||||||
id='badges.granted.not_yet'
|
|
||||||
defaultMessage='Ещё не выдан.'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (badge.multiple) {
|
if (badge.multiple) {
|
||||||
return (
|
return `Granted ${badge.granted_times} to ${badge.granted} users.`;
|
||||||
<FormattedMessage
|
|
||||||
id='badges.granted.multiple'
|
|
||||||
defaultMessage='Выдан {times, plural, one {# раз} few {# раза} many {# раз} other {# раз}} {users, plural, one {# пользователю} few {# пользователям} many {# пользователям} other {# пользователям}}.'
|
|
||||||
values={{times: badge.granted_times, users: badge.granted}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return `Granted to ${badge.granted} users.`;
|
||||||
<FormattedMessage
|
|
||||||
id='badges.granted.single'
|
|
||||||
defaultMessage='Выдан {users, plural, one {# пользователю} few {# пользователям} many {# пользователям} other {# пользователям}}.'
|
|
||||||
values={{users: badge.granted}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AllBadgesRow: React.FC<Props> = ({badge, onClick}: Props) => {
|
const AllBadgesRow: React.FC<Props> = ({badge, onClick}: Props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className='AllBadgesRow'>
|
||||||
className='AllBadgesRow'
|
<a
|
||||||
|
className='badge-icon'
|
||||||
onClick={() => onClick(badge)}
|
onClick={() => onClick(badge)}
|
||||||
>
|
>
|
||||||
<span className='badge-icon'>
|
<span>
|
||||||
<BadgeImage
|
<BadgeImage
|
||||||
badge={badge}
|
badge={badge}
|
||||||
size={36}
|
size={32}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div className='badge-text'>
|
</a>
|
||||||
<div className='badge-name'>
|
<div>
|
||||||
<span className='badge-label'>
|
<div className='badge-name'>{badge.name}</div>
|
||||||
<FormattedMessage
|
<div className='badge-description'>{markdown(badge.description)}</div>
|
||||||
id='badges.label.name'
|
<div className='badge-type'>{'Type: ' + badge.type_name}</div>
|
||||||
defaultMessage='Название:'
|
<div className='granted-by'>{getGrantedText(badge)}</div>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{' '}
|
|
||||||
{badge.name}
|
|
||||||
</div>
|
|
||||||
<div className='badge-description'>
|
|
||||||
<span className='badge-label'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.description'
|
|
||||||
defaultMessage='Описание:'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{' '}
|
|
||||||
{markdown(badge.description)}
|
|
||||||
</div>
|
|
||||||
<div className='badge-meta'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.type'
|
|
||||||
defaultMessage='Тип: {typeName}'
|
|
||||||
values={{typeName: badge.type_name}}
|
|
||||||
/>
|
|
||||||
{' · '}
|
|
||||||
{getGrantedText(badge)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,96 +3,26 @@
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
&--loading {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-info {
|
.badge-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
padding: 5px;
|
||||||
border-radius: 6px;
|
|
||||||
padding: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
gap: 12px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.badge-icon {
|
.badge-icon {
|
||||||
flex-shrink: 0;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
padding-right: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-label {
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-name {
|
.badge-name {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--center-channel-text);
|
|
||||||
}
|
}
|
||||||
|
.created-by {
|
||||||
.badge-description {
|
font-size: 10px;
|
||||||
font-size: 14px;
|
}
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
.badge-descrition {
|
||||||
margin-top: 4px;
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0px
|
||||||
display: inline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.badge-type {
|
||||||
.badge-meta {
|
font-size: 10px;
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__editButton {
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
background: none;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--button-bg, #166de0);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--button-bg-rgb, 22, 109, 224), 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--center-channel-text);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-owners {
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
padding: 16px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
||||||
|
|
||||||
import {BadgeDetails, BadgeID} from '../../types/badges';
|
import {BadgeDetails, BadgeID} from '../../types/badges';
|
||||||
@ -25,7 +23,6 @@ type Props = {
|
|||||||
setRHSView: (view: RHSState) => void;
|
setRHSView: (view: RHSState) => void;
|
||||||
setRHSUser: (user: string | null) => void;
|
setRHSUser: (user: string | null) => void;
|
||||||
getCustomEmojiByName: (names: string) => void;
|
getCustomEmojiByName: (names: string) => void;
|
||||||
openEditBadgeModal: (badge: BadgeDetails) => void;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,27 +87,15 @@ class BadgeDetailsComponent extends React.PureComponent<Props, State> {
|
|||||||
render() {
|
render() {
|
||||||
const {badge, loading} = this.state;
|
const {badge, loading} = this.state;
|
||||||
if (this.props.badgeID == null) {
|
if (this.props.badgeID == null) {
|
||||||
return (<div>
|
return (<div>{'Badge not found.'}</div>);
|
||||||
<FormattedMessage
|
|
||||||
id='badges.badge_not_found'
|
|
||||||
defaultMessage='Значок не найден.'
|
|
||||||
/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (<div className='BadgeDetails BadgeDetails--loading'>
|
return (<div>{'Loading...'}</div>);
|
||||||
<div className='spinner'/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!badge) {
|
if (!badge) {
|
||||||
return (<div>
|
return (<div>{'Badge not found.'}</div>);
|
||||||
<FormattedMessage
|
|
||||||
id='badges.badge_not_found'
|
|
||||||
defaultMessage='Значок не найден.'
|
|
||||||
/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = badge.owners.map((ownership) => {
|
const content = badge.owners.map((ownership) => {
|
||||||
@ -124,78 +109,23 @@ class BadgeDetailsComponent extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className='BadgeDetails'>
|
<div className='BadgeDetails'>
|
||||||
|
<div><b>{'Badge Details'}</b></div>
|
||||||
<div className='badge-info'>
|
<div className='badge-info'>
|
||||||
<span className='badge-icon'>
|
<span className='badge-icon'>
|
||||||
<BadgeImage
|
<BadgeImage
|
||||||
badge={badge}
|
badge={badge}
|
||||||
size={48}
|
size={32}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div className='badge-text'>
|
<div className='badge-text'>
|
||||||
<div className='badge-name'>
|
<div className='badge-name'>{badge.name}</div>
|
||||||
<span className='badge-label'>
|
<div className='badge-description'>{markdown(badge.description)}</div>
|
||||||
<FormattedMessage
|
<div className='badge-type'>{'Type: ' + badge.type_name}</div>
|
||||||
id='badges.label.name'
|
<div className='created-by'>{`Created by: ${badge.created_by_username}`}</div>
|
||||||
defaultMessage='Название:'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{' '}
|
|
||||||
{badge.name}
|
|
||||||
</div>
|
|
||||||
<div className='badge-description'>
|
|
||||||
<span className='badge-label'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.description'
|
|
||||||
defaultMessage='Описание:'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{' '}
|
|
||||||
{markdown(badge.description)}
|
|
||||||
</div>
|
|
||||||
<div className='badge-meta'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.type'
|
|
||||||
defaultMessage='Тип: {typeName}'
|
|
||||||
values={{typeName: badge.type_name}}
|
|
||||||
/>
|
|
||||||
{' · '}
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.created_by'
|
|
||||||
defaultMessage='Создал: {username}'
|
|
||||||
values={{username: badge.created_by_username}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{badge.created_by === this.props.currentUserID && (
|
<div><b>{'Granted to:'}</b></div>
|
||||||
<button
|
|
||||||
className='BadgeDetails__editButton'
|
|
||||||
onClick={() => this.props.actions.openEditBadgeModal(badge)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.rhs.edit_badge'
|
|
||||||
defaultMessage='Редактировать'
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{badge.owners.length > 0 ? (
|
|
||||||
<>
|
|
||||||
<div className='section-title'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.granted_to'
|
|
||||||
defaultMessage='Выдан:'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<RHSScrollbars>{content}</RHSScrollbars>
|
<RHSScrollbars>{content}</RHSScrollbars>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className='empty-owners'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.not_granted_yet'
|
|
||||||
defaultMessage='Ещё никому не выдан'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import {getCustomEmojiByName, getCustomEmojisByName} from 'mattermost-redux/acti
|
|||||||
import {getRHSBadge, getRHSUser, getRHSView} from 'selectors';
|
import {getRHSBadge, getRHSUser, getRHSView} from 'selectors';
|
||||||
import {RHS_STATE_ALL, RHS_STATE_DETAIL, RHS_STATE_OTHER, RHS_STATE_MY} from '../../constants';
|
import {RHS_STATE_ALL, RHS_STATE_DETAIL, RHS_STATE_OTHER, RHS_STATE_MY} from '../../constants';
|
||||||
import {RHSState} from 'types/general';
|
import {RHSState} from 'types/general';
|
||||||
import {openCreateBadgeModal, openEditBadgeModal, setRHSBadge, setRHSUser, setRHSView} from 'actions/actions';
|
import {setRHSBadge, setRHSUser, setRHSView} from 'actions/actions';
|
||||||
import {BadgeDetails, BadgeID} from 'types/badges';
|
import {BadgeID} from 'types/badges';
|
||||||
|
|
||||||
import UserBadges from './user_badges';
|
import UserBadges from './user_badges';
|
||||||
import BadgeDetailsComponent from './badge_details';
|
import BadgeDetailsComponent from './badge_details';
|
||||||
@ -39,7 +39,6 @@ const RHS: React.FC = () => {
|
|||||||
setRHSView: (view: RHSState) => dispatch(setRHSView(view)),
|
setRHSView: (view: RHSState) => dispatch(setRHSView(view)),
|
||||||
setRHSBadge: (badge: BadgeID | null) => dispatch(setRHSBadge(badge)),
|
setRHSBadge: (badge: BadgeID | null) => dispatch(setRHSBadge(badge)),
|
||||||
getCustomEmojisByName: (names: string[]) => dispatch(getCustomEmojisByName(names)),
|
getCustomEmojisByName: (names: string[]) => dispatch(getCustomEmojisByName(names)),
|
||||||
openCreateBadgeModal: () => dispatch(openCreateBadgeModal()),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -52,7 +51,6 @@ const RHS: React.FC = () => {
|
|||||||
setRHSView: (view: RHSState) => dispatch(setRHSView(view)),
|
setRHSView: (view: RHSState) => dispatch(setRHSView(view)),
|
||||||
setRHSUser: (user: string | null) => dispatch(setRHSUser(user)),
|
setRHSUser: (user: string | null) => dispatch(setRHSUser(user)),
|
||||||
getCustomEmojiByName: (names: string) => dispatch(getCustomEmojiByName(names)),
|
getCustomEmojiByName: (names: string) => dispatch(getCustomEmojiByName(names)),
|
||||||
openEditBadgeModal: (badge: BadgeDetails) => dispatch(openEditBadgeModal(badge)),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,76 +1,28 @@
|
|||||||
.UserBadgesRow {
|
.UserBadgesRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
padding: 12px 16px;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
padding: 5px;
|
||||||
cursor: pointer;
|
margin-bottom: 3px;
|
||||||
gap: 12px;
|
|
||||||
transition: background 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb), 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-badge-icon {
|
.user-badge-icon {
|
||||||
flex-shrink: 0;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-badge-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-badge-name {
|
.user-badge-name {
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--center-channel-text);
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
.user-badge-granted-by {
|
||||||
.user-badge-description {
|
font-size: 10px;
|
||||||
font-size: 13px;
|
}
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
.user-badge-granted-at {
|
||||||
margin-top: 2px;
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.user-badge-descrition {
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0px
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-badge-label {
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-badge-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-badge-reason {
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-badge-set-status {
|
|
||||||
margin-top: 4px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--button-bg, #166de0);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.user-badge-type {
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import Client4 from 'mattermost-redux/client/client4';
|
import Client4 from 'mattermost-redux/client/client4';
|
||||||
|
|
||||||
import {UserBadge} from '../../types/badges';
|
import {UserBadge} from '../../types/badges';
|
||||||
@ -17,93 +15,43 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UserBadgeRow: React.FC<Props> = ({badge, onClick, isCurrentUser}: Props) => {
|
const UserBadgeRow: React.FC<Props> = ({badge, onClick, isCurrentUser}: Props) => {
|
||||||
const intl = useIntl();
|
|
||||||
const time = new Date(badge.time);
|
const time = new Date(badge.time);
|
||||||
let reason = null;
|
let reason = null;
|
||||||
if (badge.reason) {
|
if (badge.reason) {
|
||||||
reason = (
|
reason = (<div className='badge-user-reason'>{'Why? ' + badge.reason}</div>);
|
||||||
<div className='user-badge-reason'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.reason'
|
|
||||||
defaultMessage='Причина: {reason}'
|
|
||||||
values={{reason: badge.reason}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let setStatus = null;
|
let setStatus = null;
|
||||||
if (isCurrentUser && badge.image_type === 'emoji') {
|
if (isCurrentUser && badge.image_type === 'emoji') {
|
||||||
setStatus = (
|
setStatus = (
|
||||||
<div className='user-badge-set-status'>
|
<div className='user-badge-set-status'>
|
||||||
<a
|
<a
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
e.stopPropagation();
|
|
||||||
const c = new Client4();
|
const c = new Client4();
|
||||||
c.updateCustomStatus({emoji: badge.image, text: badge.name});
|
c.updateCustomStatus({emoji: badge.image, text: badge.name});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
{'Set status to this badge'}
|
||||||
id='badges.set_status'
|
|
||||||
defaultMessage='Установить как статус'
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div className='UserBadgesRow'>
|
||||||
className='UserBadgesRow'
|
<a onClick={() => onClick(badge)}>
|
||||||
onClick={() => onClick(badge)}
|
|
||||||
>
|
|
||||||
<span className='user-badge-icon'>
|
<span className='user-badge-icon'>
|
||||||
<BadgeImage
|
<BadgeImage
|
||||||
badge={badge}
|
badge={badge}
|
||||||
size={36}
|
size={32}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
</a>
|
||||||
<div className='user-badge-text'>
|
<div className='user-badge-text'>
|
||||||
<div className='user-badge-name'>
|
<div className='user-badge-name'>{badge.name}</div>
|
||||||
<span className='user-badge-label'>
|
<div className='user-badge-description'>{markdown(badge.description)}</div>
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.name'
|
|
||||||
defaultMessage='Название:'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{' '}
|
|
||||||
{badge.name}
|
|
||||||
</div>
|
|
||||||
<div className='user-badge-description'>
|
|
||||||
<span className='user-badge-label'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.description'
|
|
||||||
defaultMessage='Описание:'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
{' '}
|
|
||||||
{markdown(badge.description)}
|
|
||||||
</div>
|
|
||||||
<div className='user-badge-meta'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.type'
|
|
||||||
defaultMessage='Тип: {typeName}'
|
|
||||||
values={{typeName: badge.type_name}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='user-badge-meta'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.granted_by'
|
|
||||||
defaultMessage='Выдал: {username}'
|
|
||||||
values={{username: badge.granted_by_name}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='user-badge-meta'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.granted_at'
|
|
||||||
defaultMessage='Выдан: {date}'
|
|
||||||
values={{date: intl.formatDate(time, {day: '2-digit', month: '2-digit', year: 'numeric'})}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{reason}
|
{reason}
|
||||||
|
<div className='user-badge-type'>{'Type: ' + badge.type_name}</div>
|
||||||
|
<div className='user-badge-granted-by'>{`Granted by: ${badge.granted_by_name}`}</div>
|
||||||
|
<div className='user-badge-granted-at'>{`Granted at: ${time.toDateString()}`}</div>
|
||||||
{setStatus}
|
{setStatus}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,20 +3,4 @@
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
&--loading {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
|
|
||||||
import {UserProfile} from 'mattermost-redux/types/users';
|
import {UserProfile} from 'mattermost-redux/types/users';
|
||||||
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
||||||
|
|
||||||
@ -86,27 +84,15 @@ class UserBadges extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.props.user) {
|
if (!this.props.user) {
|
||||||
return (<div>
|
return (<div>{'User not found.'}</div>);
|
||||||
<FormattedMessage
|
|
||||||
id='badges.user_not_found'
|
|
||||||
defaultMessage='Пользователь не найден.'
|
|
||||||
/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
return (<div className='UserBadges UserBadges--loading'>
|
return (<div>{'Loading...'}</div>);
|
||||||
<div className='spinner'/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.badges || this.state.badges.length === 0) {
|
if (!this.state.badges || this.state.badges.length === 0) {
|
||||||
return (<div>
|
return (<div>{'No badges yet.'}</div>);
|
||||||
<FormattedMessage
|
|
||||||
id='badges.no_badges_yet'
|
|
||||||
defaultMessage='Значков пока нет.'
|
|
||||||
/>
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = this.state.badges.map((badge) => {
|
const content = this.state.badges.map((badge) => {
|
||||||
@ -120,21 +106,13 @@ class UserBadges extends React.PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = this.props.isCurrentUser ? (
|
let title = 'My badges';
|
||||||
<FormattedMessage
|
if (!this.props.isCurrentUser) {
|
||||||
id='badges.rhs.my_badges'
|
title = `@${this.props.user.username}'s badges`;
|
||||||
defaultMessage='Мои значки'
|
}
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.rhs.user_badges'
|
|
||||||
defaultMessage='Значки @{username}'
|
|
||||||
values={{username: this.props.user.username}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div className='UserBadges'>
|
<div className='UserBadges'>
|
||||||
<div className='UserBadges__title'>{title}</div>
|
<div><b>{title}</b></div>
|
||||||
<RHSScrollbars>{content}</RHSScrollbars>
|
<RHSScrollbars>{content}</RHSScrollbars>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,32 +1,13 @@
|
|||||||
.UserRow {
|
.UserRow {
|
||||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
padding: 10px 16px;
|
align-items: center;
|
||||||
margin-bottom: 8px;
|
padding: 5px;
|
||||||
cursor: pointer;
|
margin-bottom: 3px;
|
||||||
transition: background 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(var(--center-channel-color-rgb), 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-user-username {
|
.badge-user-username {
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--button-bg, #166de0);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
}
|
.badge-user-granted-at {
|
||||||
}
|
font-size: 10px;
|
||||||
|
|
||||||
.badge-user-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import {useSelector} from 'react-redux';
|
import {useSelector} from 'react-redux';
|
||||||
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
import {GlobalState} from 'mattermost-redux/types/store';
|
import {GlobalState} from 'mattermost-redux/types/store';
|
||||||
@ -10,14 +8,12 @@ import {UserProfile} from 'mattermost-redux/types/users';
|
|||||||
import {Ownership} from '../../types/badges';
|
import {Ownership} from '../../types/badges';
|
||||||
|
|
||||||
import './user_row.scss';
|
import './user_row.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ownership: Ownership;
|
ownership: Ownership;
|
||||||
onClick: (user: string) => void;
|
onClick: (user: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserBadgeRow: React.FC<Props> = ({ownership, onClick}: Props) => {
|
const UserBadgeRow: React.FC<Props> = ({ownership, onClick}: Props) => {
|
||||||
const intl = useIntl();
|
|
||||||
const user = useSelector<GlobalState, UserProfile>((state) => getUser(state, ownership.user));
|
const user = useSelector<GlobalState, UserProfile>((state) => getUser(state, ownership.user));
|
||||||
const grantedBy = useSelector<GlobalState, UserProfile>((state) => getUser(state, ownership.granted_by));
|
const grantedBy = useSelector<GlobalState, UserProfile>((state) => getUser(state, ownership.granted_by));
|
||||||
|
|
||||||
@ -25,33 +21,17 @@ const UserBadgeRow: React.FC<Props> = ({ownership, onClick}: Props) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let grantedByName = intl.formatMessage({id: 'badges.unknown', defaultMessage: 'неизвестно'});
|
let grantedByName = 'unknown';
|
||||||
if (grantedBy) {
|
if (grantedBy) {
|
||||||
grantedByName = '@' + grantedBy.username;
|
grantedByName = '@' + grantedBy.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = new Date(ownership.time);
|
const time = new Date(ownership.time);
|
||||||
return (
|
return (
|
||||||
<div
|
<div className='UserRow'>
|
||||||
className='UserRow'
|
<div className='badge-user-username'><a onClick={() => onClick(ownership.user)}>{`@${user.username}`}</a></div>
|
||||||
onClick={() => onClick(ownership.user)}
|
<div className='badge-user-granted-by'>{`Granted by: ${grantedByName}`}</div>
|
||||||
>
|
<div className='badge-user-granted-at'>{`Granted at: ${time.toDateString()}`}</div>
|
||||||
<div className='badge-user-username'>
|
|
||||||
<a>{`@${user.username}`}</a>
|
|
||||||
</div>
|
|
||||||
<div className='badge-user-meta'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.granted_by'
|
|
||||||
defaultMessage='Выдал: {username}'
|
|
||||||
values={{username: grantedByName}}
|
|
||||||
/>
|
|
||||||
{' · '}
|
|
||||||
<FormattedMessage
|
|
||||||
id='badges.label.granted_at'
|
|
||||||
defaultMessage='Выдан: {date}'
|
|
||||||
values={{date: intl.formatDate(time, {day: '2-digit', month: '2-digit', year: 'numeric'})}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-content: flex-end;
|
align-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#showMoreButton {
|
#showMoreButton {
|
||||||
@ -24,27 +23,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-stacked {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-stack-count {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
right: -4px;
|
|
||||||
background: var(--button-bg, #166de0);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 1px 3px;
|
|
||||||
border-radius: 6px;
|
|
||||||
min-width: 14px;
|
|
||||||
text-align: center;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#grantBadgeButton {
|
#grantBadgeButton {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {UserProfile} from 'mattermost-redux/types/users';
|
import {UserProfile} from 'mattermost-redux/types/users';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
|
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
|
||||||
|
|
||||||
import {GlobalState} from 'mattermost-redux/types/store';
|
import {GlobalState} from 'mattermost-redux/types/store';
|
||||||
|
|
||||||
@ -10,13 +10,13 @@ import {systemEmojis} from 'mattermost-redux/actions/emojis';
|
|||||||
import {BadgeID, UserBadge} from 'types/badges';
|
import {BadgeID, UserBadge} from 'types/badges';
|
||||||
import Client from 'client/api';
|
import Client from 'client/api';
|
||||||
import BadgeImage from '../utils/badge_image';
|
import BadgeImage from '../utils/badge_image';
|
||||||
import TooltipWrapper from '../utils/tooltip_wrapper';
|
|
||||||
import {RHSState} from 'types/general';
|
import {RHSState} from 'types/general';
|
||||||
import {IMAGE_TYPE_EMOJI, RHS_STATE_DETAIL, RHS_STATE_MY, RHS_STATE_OTHER} from '../../constants';
|
import {IMAGE_TYPE_EMOJI, RHS_STATE_DETAIL, RHS_STATE_MY, RHS_STATE_OTHER} from '../../constants';
|
||||||
|
import {markdown} from 'utils/markdown';
|
||||||
|
|
||||||
import './badge_list.scss';
|
import './badge_list.scss';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
intl: IntlShape;
|
|
||||||
debug: GlobalState;
|
debug: GlobalState;
|
||||||
user: UserProfile;
|
user: UserProfile;
|
||||||
currentUserID: string;
|
currentUserID: string;
|
||||||
@ -32,11 +32,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type BadgeGroup = {
|
|
||||||
badge: UserBadge;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
badges?: UserBadge[];
|
badges?: UserBadge[];
|
||||||
loaded?: boolean;
|
loaded?: boolean;
|
||||||
@ -60,12 +55,12 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||||
if (this.state.badges !== prevState.badges && this.state.badges) {
|
if (this.state.badges !== prevState.badges) {
|
||||||
const groups = this.groupBadges(this.state.badges);
|
const nBadges = this.state.badges?.length || 0;
|
||||||
const toShow = Math.min(groups.length, MAX_BADGES);
|
const toShow = nBadges < MAX_BADGES ? nBadges : MAX_BADGES;
|
||||||
const names: string[] = [];
|
const names: string[] = [];
|
||||||
for (let i = 0; i < toShow; i++) {
|
for (let i = 0; i < toShow; i++) {
|
||||||
const {badge} = groups[i];
|
const badge = this.state.badges![i];
|
||||||
if (badge.image_type === IMAGE_TYPE_EMOJI) {
|
if (badge.image_type === IMAGE_TYPE_EMOJI) {
|
||||||
names.push(badge.image);
|
names.push(badge.image);
|
||||||
}
|
}
|
||||||
@ -75,19 +70,6 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
groupBadges = (badges: UserBadge[]): BadgeGroup[] => {
|
|
||||||
const map = new Map<BadgeID, BadgeGroup>();
|
|
||||||
for (const badge of badges) {
|
|
||||||
const existing = map.get(badge.id);
|
|
||||||
if (existing) {
|
|
||||||
existing.count++;
|
|
||||||
} else {
|
|
||||||
map.set(badge.id, {badge, count: 1});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(map.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
onMoreClick = () => {
|
onMoreClick = () => {
|
||||||
if (!this.props.openRHS) {
|
if (!this.props.openRHS) {
|
||||||
return;
|
return;
|
||||||
@ -96,7 +78,6 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
if (this.props.currentUserID === this.props.user.id) {
|
if (this.props.currentUserID === this.props.user.id) {
|
||||||
this.props.actions.setRHSView(RHS_STATE_MY);
|
this.props.actions.setRHSView(RHS_STATE_MY);
|
||||||
this.props.openRHS();
|
this.props.openRHS();
|
||||||
this.props.hide();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,94 +104,54 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {intl} = this.props;
|
const nBadges = this.state.badges?.length || 0;
|
||||||
const groups = this.state.badges ? this.groupBadges(this.state.badges) : [];
|
const toShow = nBadges < MAX_BADGES ? nBadges : MAX_BADGES;
|
||||||
const nGroups = groups.length;
|
|
||||||
const toShow = Math.min(nGroups, MAX_BADGES);
|
|
||||||
|
|
||||||
const nameLabel = intl.formatMessage(
|
|
||||||
{id: 'badges.label.name', defaultMessage: 'Название:'},
|
|
||||||
);
|
|
||||||
const descLabel = intl.formatMessage(
|
|
||||||
{id: 'badges.label.description', defaultMessage: 'Описание:'},
|
|
||||||
);
|
|
||||||
|
|
||||||
const content: React.ReactNode[] = [];
|
const content: React.ReactNode[] = [];
|
||||||
for (let i = 0; i < toShow; i++) {
|
for (let i = 0; i < toShow; i++) {
|
||||||
const {badge, count} = groups[i];
|
const badge = this.state.badges![i];
|
||||||
|
|
||||||
let tooltipLines: string;
|
|
||||||
if (count > 1) {
|
|
||||||
const countLabel = intl.formatMessage(
|
|
||||||
{id: 'badges.label.count', defaultMessage: 'Количество: {count}'},
|
|
||||||
{count},
|
|
||||||
);
|
|
||||||
tooltipLines = [
|
|
||||||
nameLabel + ' ' + badge.name,
|
|
||||||
descLabel + ' ' + badge.description,
|
|
||||||
countLabel,
|
|
||||||
].join('\n');
|
|
||||||
} else {
|
|
||||||
const time = new Date(badge.time);
|
const time = new Date(badge.time);
|
||||||
let reason: string | null = null;
|
let reason = null;
|
||||||
if (badge.reason) {
|
if (badge.reason) {
|
||||||
reason = intl.formatMessage(
|
reason = (<div>{'Why? ' + badge.reason}</div>);
|
||||||
{id: 'badges.label.reason', defaultMessage: 'Причина: {reason}'},
|
|
||||||
{reason: badge.reason},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const grantedBy = intl.formatMessage(
|
|
||||||
{id: 'badges.label.granted_by', defaultMessage: 'Выдал: {username}'},
|
|
||||||
{username: badge.granted_by_name},
|
|
||||||
);
|
|
||||||
const grantedAt = intl.formatMessage(
|
|
||||||
{id: 'badges.label.granted_at', defaultMessage: 'Выдан: {date}'},
|
|
||||||
{date: intl.formatDate(time, {day: '2-digit', month: '2-digit', year: 'numeric'})},
|
|
||||||
);
|
|
||||||
tooltipLines = [
|
|
||||||
nameLabel + ' ' + badge.name,
|
|
||||||
descLabel + ' ' + badge.description,
|
|
||||||
reason,
|
|
||||||
grantedBy,
|
|
||||||
grantedAt,
|
|
||||||
].filter(Boolean).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
const badgeComponent = (
|
const badgeComponent = (
|
||||||
<TooltipWrapper tooltipContent={tooltipLines}>
|
<OverlayTrigger
|
||||||
|
overlay={<Tooltip id='badgeTooltip'>
|
||||||
|
<div>{badge.name}</div>
|
||||||
|
<div>{markdown(badge.description)}</div>
|
||||||
|
{reason}
|
||||||
|
<div>{`Granted by: ${badge.granted_by_name}`}</div>
|
||||||
|
<div>{`Granted at: ${time.toDateString()}`}</div>
|
||||||
|
</Tooltip>}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
<a onClick={() => this.onBadgeClick(badge)}>
|
<a onClick={() => this.onBadgeClick(badge)}>
|
||||||
<span className='badge-stacked'>
|
|
||||||
<BadgeImage
|
<BadgeImage
|
||||||
badge={badge}
|
badge={badge}
|
||||||
size={BADGE_SIZE}
|
size={BADGE_SIZE}
|
||||||
/>
|
/>
|
||||||
{count > 1 && (
|
|
||||||
<span className='badge-stack-count'>
|
|
||||||
{'×'}{count}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
</TooltipWrapper>
|
</span>
|
||||||
|
</OverlayTrigger>
|
||||||
);
|
);
|
||||||
content.push(badgeComponent);
|
content.push(badgeComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
let andMore: React.ReactNode = null;
|
let andMore: React.ReactNode = null;
|
||||||
if (nGroups > MAX_BADGES) {
|
if (nBadges > MAX_BADGES) {
|
||||||
const andMoreText = intl.formatMessage(
|
|
||||||
{id: 'badges.and_more', defaultMessage: 'и ещё {count}. Нажмите, чтобы увидеть все.'},
|
|
||||||
{count: nGroups - MAX_BADGES},
|
|
||||||
);
|
|
||||||
andMore = (
|
andMore = (
|
||||||
<TooltipWrapper tooltipContent={andMoreText}>
|
<OverlayTrigger
|
||||||
|
overlay={<Tooltip id='badgeMoreTooltip'>
|
||||||
|
{`and ${nBadges - MAX_BADGES} more. Click to see all.`}
|
||||||
|
</Tooltip>}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
id='showMoreButton'
|
id='showMoreButton'
|
||||||
onClick={this.onMoreClick}
|
onClick={this.onMoreClick}
|
||||||
>
|
>
|
||||||
<span className={'fa fa-angle-right'}/>
|
<span className={'fa fa-angle-right'}/>
|
||||||
</button>
|
</button>
|
||||||
</TooltipWrapper>
|
</OverlayTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const maxWidth = (MAX_BADGES * BADGE_SIZE) + 30;
|
const maxWidth = (MAX_BADGES * BADGE_SIZE) + 30;
|
||||||
@ -220,18 +161,13 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
// Reserve enough height one row of badges and the "and more" button
|
// Reserve enough height one row of badges and the "and more" button
|
||||||
<div style={{height: BADGE_SIZE, minWidth: 66, maxWidth}}>
|
<div style={{height: BADGE_SIZE, minWidth: 66, maxWidth}}>
|
||||||
<div className='spinner'/>
|
{'Loading...'}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div id='badgePlugin'>
|
<div id='badgePlugin'>
|
||||||
<div><b>
|
<div><b>{'Badges'}</b></div>
|
||||||
<FormattedMessage
|
|
||||||
id='badges.popover.title'
|
|
||||||
defaultMessage='Значки'
|
|
||||||
/>
|
|
||||||
</b></div>
|
|
||||||
<div id='contentContainer' >
|
<div id='contentContainer' >
|
||||||
{content}
|
{content}
|
||||||
{andMore}
|
{andMore}
|
||||||
@ -242,10 +178,7 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
onClick={this.onGrantClick}
|
onClick={this.onGrantClick}
|
||||||
>
|
>
|
||||||
<span className={'fa fa-plus-circle'}/>
|
<span className={'fa fa-plus-circle'}/>
|
||||||
<FormattedMessage
|
{'Grant badge'}
|
||||||
id='badges.grant_badge'
|
|
||||||
defaultMessage='Выдать значок'
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
<hr className='divider divider--expanded'/>
|
<hr className='divider divider--expanded'/>
|
||||||
</div>
|
</div>
|
||||||
@ -253,4 +186,4 @@ class BadgeList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectIntl(BadgeList);
|
export default BadgeList;
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
.badge-tooltip-wrapper {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-tooltip {
|
|
||||||
position: absolute;
|
|
||||||
background-color: #000;
|
|
||||||
width: max-content;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
z-index: 10000;
|
|
||||||
max-width: 220px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
white-space: pre-line;
|
|
||||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-tooltip-arrow {
|
|
||||||
position: absolute;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
background: #000;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import React, {ReactNode, useState, useRef, useEffect} from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
import './tooltip_wrapper.scss';
|
|
||||||
|
|
||||||
type TooltipWrapperProps = {
|
|
||||||
children: ReactNode;
|
|
||||||
tooltipContent: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TooltipWrapper: React.FC<TooltipWrapperProps> = ({children, tooltipContent}) => {
|
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
||||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [position, setPosition] = useState({bottom: 0, left: 0});
|
|
||||||
|
|
||||||
const handleMouseEnter = () => setIsVisible(true);
|
|
||||||
const handleMouseLeave = () => setIsVisible(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (wrapperRef.current && isVisible) {
|
|
||||||
const wrapperRect = wrapperRef.current.getBoundingClientRect();
|
|
||||||
setPosition({
|
|
||||||
bottom: window.innerHeight - (wrapperRect.top + window.scrollY - 5),
|
|
||||||
left: wrapperRect.left + window.scrollX + wrapperRect.width / 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isVisible]);
|
|
||||||
|
|
||||||
const tooltip = (
|
|
||||||
<div
|
|
||||||
ref={tooltipRef}
|
|
||||||
className='badge-tooltip'
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: `${position.bottom}px`,
|
|
||||||
left: `${position.left}px`,
|
|
||||||
transform: 'translateX(-50%)',
|
|
||||||
visibility: isVisible ? 'visible' : 'hidden',
|
|
||||||
opacity: isVisible ? 1 : 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tooltipContent}
|
|
||||||
<div
|
|
||||||
className='badge-tooltip-arrow'
|
|
||||||
style={{
|
|
||||||
bottom: '-4px',
|
|
||||||
left: '50%',
|
|
||||||
marginLeft: '-4px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={wrapperRef}
|
|
||||||
className='badge-tooltip-wrapper'
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{isVisible && ReactDOM.createPortal(tooltip as any, document.body)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TooltipWrapper;
|
|
||||||
@ -14,6 +14,4 @@ export const initialState: PluginState = {
|
|||||||
rhsView: RHS_STATE_MY,
|
rhsView: RHS_STATE_MY,
|
||||||
rhsBadge: null,
|
rhsBadge: null,
|
||||||
rhsUser: null,
|
rhsUser: null,
|
||||||
createBadgeModalVisible: false,
|
|
||||||
editBadgeModalData: null,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,18 +6,11 @@ import {GenericAction} from 'mattermost-redux/types/actions';
|
|||||||
|
|
||||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
|
||||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {IntlProvider} from 'react-intl';
|
|
||||||
|
|
||||||
import {useSelector} from 'react-redux';
|
|
||||||
|
|
||||||
import {openAddSubscription, openCreateBadge, openCreateType, openRemoveSubscription, setRHSView, setShowRHSAction} from 'actions/actions';
|
import {openAddSubscription, openCreateBadge, openCreateType, openRemoveSubscription, setRHSView, setShowRHSAction} from 'actions/actions';
|
||||||
|
|
||||||
import RHSComponent from 'components/rhs';
|
import UserBadges from 'components/rhs';
|
||||||
import BadgeModal from 'components/badge_modal';
|
|
||||||
|
|
||||||
import ChannelHeaderButton from 'components/channel_header_button';
|
import ChannelHeaderButton from 'components/channel_header_button';
|
||||||
|
|
||||||
@ -27,57 +20,28 @@ import manifest from './manifest';
|
|||||||
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import {PluginRegistry} from './types/mattermost-webapp';
|
import {PluginRegistry} from './types/mattermost-webapp';
|
||||||
import BadgeListConnected from './components/user_popover/';
|
import BadgeList from './components/user_popover/';
|
||||||
import {RHS_STATE_ALL} from './constants';
|
import {RHS_STATE_ALL} from './constants';
|
||||||
import {getTranslations} from './utils/i18n';
|
|
||||||
import BadgesAdminSetting from './components/admin/badges_admin_setting';
|
|
||||||
|
|
||||||
function withIntl(Component: React.ElementType): React.ElementType {
|
|
||||||
const Wrapped: React.FC<any> = (props) => {
|
|
||||||
const currentUser = useSelector(getCurrentUser);
|
|
||||||
const locale = currentUser?.locale || 'ru';
|
|
||||||
return (
|
|
||||||
<IntlProvider
|
|
||||||
locale={locale}
|
|
||||||
messages={getTranslations(locale)}
|
|
||||||
>
|
|
||||||
<Component {...props}/>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return Wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WrappedRHS = withIntl(RHSComponent);
|
|
||||||
const WrappedBadgeList = withIntl(BadgeListConnected as unknown as React.ElementType);
|
|
||||||
|
|
||||||
export default class Plugin {
|
export default class Plugin {
|
||||||
public async initialize(registry: PluginRegistry, store: Store<GlobalState, GenericAction>) {
|
public async initialize(registry: PluginRegistry, store: Store<GlobalState, GenericAction>) {
|
||||||
registry.registerReducer(Reducer);
|
registry.registerReducer(Reducer);
|
||||||
|
|
||||||
registry.registerTranslations(getTranslations);
|
registry.registerPopoverUserAttributesComponent(BadgeList);
|
||||||
|
|
||||||
registry.registerAdminConsoleCustomSetting('BadgesAdmin', withIntl(BadgesAdminSetting));
|
const {showRHSPlugin, toggleRHSPlugin} = registry.registerRightHandSidebarComponent(UserBadges, 'Badges');
|
||||||
|
|
||||||
registry.registerPopoverUserAttributesComponent(WrappedBadgeList);
|
|
||||||
|
|
||||||
registry.registerRootComponent(withIntl(BadgeModal));
|
|
||||||
|
|
||||||
const locale = getCurrentUser(store.getState())?.locale || 'ru';
|
|
||||||
const messages = getTranslations(locale);
|
|
||||||
|
|
||||||
const {showRHSPlugin, toggleRHSPlugin} = registry.registerRightHandSidebarComponent(WrappedRHS, messages['badges.sidebar.title']);
|
|
||||||
store.dispatch(setShowRHSAction(() => store.dispatch(showRHSPlugin)));
|
store.dispatch(setShowRHSAction(() => store.dispatch(showRHSPlugin)));
|
||||||
|
|
||||||
const toggleRHS = () => {
|
const toggleRHS = () => {
|
||||||
store.dispatch(setRHSView(RHS_STATE_ALL));
|
store.dispatch(setRHSView(RHS_STATE_ALL));
|
||||||
store.dispatch(toggleRHSPlugin);
|
store.dispatch(toggleRHSPlugin);
|
||||||
};
|
}
|
||||||
|
|
||||||
registry.registerChannelHeaderButtonAction(
|
registry.registerChannelHeaderButtonAction(
|
||||||
<ChannelHeaderButton/>,
|
<ChannelHeaderButton/>,
|
||||||
toggleRHS,
|
toggleRHS,
|
||||||
messages['badges.sidebar.title'],
|
'Badges',
|
||||||
messages['badges.menu.open_list'],
|
'Open the list of all badges.',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (registry.registerAppBarComponent) {
|
if (registry.registerAppBarComponent) {
|
||||||
@ -86,19 +50,19 @@ export default class Plugin {
|
|||||||
registry.registerAppBarComponent(
|
registry.registerAppBarComponent(
|
||||||
iconURL,
|
iconURL,
|
||||||
toggleRHS,
|
toggleRHS,
|
||||||
messages['badges.menu.open_list'],
|
'Open the list of all badges.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.registerMainMenuAction(
|
registry.registerMainMenuAction(
|
||||||
messages['badges.menu.create_badge'],
|
'Create badge',
|
||||||
() => {
|
() => {
|
||||||
store.dispatch(openCreateBadge() as any);
|
store.dispatch(openCreateBadge() as any);
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
registry.registerMainMenuAction(
|
registry.registerMainMenuAction(
|
||||||
messages['badges.menu.create_type'],
|
'Create badge type',
|
||||||
() => {
|
() => {
|
||||||
store.dispatch(openCreateType() as any);
|
store.dispatch(openCreateType() as any);
|
||||||
},
|
},
|
||||||
@ -106,13 +70,13 @@ export default class Plugin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
registry.registerChannelHeaderMenuAction(
|
registry.registerChannelHeaderMenuAction(
|
||||||
messages['badges.menu.add_subscription'],
|
'Add badge subscription',
|
||||||
() => {
|
() => {
|
||||||
store.dispatch(openAddSubscription() as any);
|
store.dispatch(openAddSubscription() as any);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
registry.registerChannelHeaderMenuAction(
|
registry.registerChannelHeaderMenuAction(
|
||||||
messages['badges.menu.remove_subscription'],
|
'Remove badge subscription',
|
||||||
() => {
|
() => {
|
||||||
store.dispatch(openRemoveSubscription() as any);
|
store.dispatch(openRemoveSubscription() as any);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -41,33 +41,9 @@ function rhsBadge(state = null, action: GenericAction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBadgeModalVisible(state = false, action: GenericAction) {
|
|
||||||
switch (action.type) {
|
|
||||||
case ActionTypes.OPEN_CREATE_BADGE_MODAL:
|
|
||||||
return true;
|
|
||||||
case ActionTypes.CLOSE_CREATE_BADGE_MODAL:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function editBadgeModalData(state = null, action: GenericAction) {
|
|
||||||
switch (action.type) {
|
|
||||||
case ActionTypes.OPEN_EDIT_BADGE_MODAL:
|
|
||||||
return action.data;
|
|
||||||
case ActionTypes.CLOSE_EDIT_BADGE_MODAL:
|
|
||||||
return null;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
showRHS,
|
showRHS,
|
||||||
rhsView,
|
rhsView,
|
||||||
rhsUser,
|
rhsUser,
|
||||||
rhsBadge,
|
rhsBadge,
|
||||||
createBadgeModalVisible,
|
|
||||||
editBadgeModalData,
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -43,17 +43,3 @@ export const getRHSBadge = createSelector(
|
|||||||
return state.rhsBadge;
|
return state.rhsBadge;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const isCreateBadgeModalVisible = createSelector(
|
|
||||||
getPluginState,
|
|
||||||
(state) => {
|
|
||||||
return state.createBadgeModalVisible;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getEditBadgeModalData = createSelector(
|
|
||||||
getPluginState,
|
|
||||||
(state) => {
|
|
||||||
return state.editBadgeModalData;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
@ -42,46 +42,4 @@ export type BadgeTypeDefinition = {
|
|||||||
id: BadgeType;
|
id: BadgeType;
|
||||||
name: string;
|
name: string;
|
||||||
frame: string;
|
frame: string;
|
||||||
created_by: string;
|
|
||||||
can_grant: PermissionScheme;
|
|
||||||
can_create: PermissionScheme;
|
|
||||||
badge_count: number;
|
|
||||||
is_default: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PermissionScheme = {
|
|
||||||
everyone: boolean;
|
|
||||||
roles: Record<string, boolean>;
|
|
||||||
allow_list: Record<string, boolean>;
|
|
||||||
block_list: Record<string, boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GetTypesResponse = {
|
|
||||||
types: BadgeTypeDefinition[];
|
|
||||||
can_create_type: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateBadgeRequest = {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
image: string;
|
|
||||||
type: string;
|
|
||||||
multiple: boolean;
|
|
||||||
channel_id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UpdateBadgeRequest = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
image: string;
|
|
||||||
type: string;
|
|
||||||
multiple: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CreateTypeRequest = {
|
|
||||||
name: string;
|
|
||||||
everyone_can_create: boolean;
|
|
||||||
everyone_can_grant: boolean;
|
|
||||||
channel_id?: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {BadgeDetails, BadgeID} from './badges';
|
import {BadgeID} from './badges';
|
||||||
|
|
||||||
export type RHSState = string;
|
export type RHSState = string;
|
||||||
|
|
||||||
@ -7,6 +7,4 @@ export type PluginState = {
|
|||||||
rhsView: RHSState;
|
rhsView: RHSState;
|
||||||
rhsUser: string | null;
|
rhsUser: string | null;
|
||||||
rhsBadge: BadgeID | null;
|
rhsBadge: BadgeID | null;
|
||||||
createBadgeModalVisible: boolean;
|
|
||||||
editBadgeModalData: BadgeDetails | null;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,7 @@ export interface PluginRegistry {
|
|||||||
registerChannelHeaderButtonAction(icon: React.ReactNode, action: () => void, dropdownText: string, tooltip: string);
|
registerChannelHeaderButtonAction(icon: React.ReactNode, action: () => void, dropdownText: string, tooltip: string);
|
||||||
registerMainMenuAction(text: React.ReactNode, action: () => void, mobileIcon: React.ReactNode);
|
registerMainMenuAction(text: React.ReactNode, action: () => void, mobileIcon: React.ReactNode);
|
||||||
registerChannelHeaderMenuAction(text: string, action: (channelID: string) => void);
|
registerChannelHeaderMenuAction(text: string, action: (channelID: string) => void);
|
||||||
registerAppBarComponent(iconURL: string, action: (channel: Channel, member: ChannelMembership) => void, tooltipText: React.ReactNode);
|
registerAppBarComponent(iconURL: string, action: (channel: Channel, member: ChannelMembership) => void, tooltipText: React.ReactNode)
|
||||||
registerTranslations(getTranslationsForLocale: (locale: string) => Record<string, string>): void;
|
|
||||||
registerAdminConsoleCustomSetting(key: string, component: React.ElementType, options?: {showTitle: boolean}): void;
|
|
||||||
registerRootComponent(component: React.ElementType): void;
|
|
||||||
|
|
||||||
// Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference
|
// Add more if needed from https://developers.mattermost.com/extend/plugins/webapp/reference
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import {UserProfile} from 'mattermost-redux/types/users';
|
|
||||||
|
|
||||||
export function getUserDisplayName(user: UserProfile): string {
|
|
||||||
if (user.nickname) {
|
|
||||||
return user.nickname;
|
|
||||||
}
|
|
||||||
if (user.first_name || user.last_name) {
|
|
||||||
return `${user.first_name} ${user.last_name}`.trim();
|
|
||||||
}
|
|
||||||
return user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function debounce<T extends(...args: any[]) => void>(fn: T, delay: number): T {
|
|
||||||
let timer: ReturnType<typeof setTimeout>;
|
|
||||||
return ((...args: any[]) => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => fn(...args), delay);
|
|
||||||
}) as unknown as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getServerErrorId(err: unknown): string {
|
|
||||||
const msg = (err as {message?: string})?.message || '';
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(msg);
|
|
||||||
return parsed.id || '';
|
|
||||||
} catch {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import en from '../../i18n/en.json';
|
|
||||||
import ru from '../../i18n/ru.json';
|
|
||||||
|
|
||||||
const translations: Record<string, Record<string, string>> = {en, ru};
|
|
||||||
|
|
||||||
export function getTranslations(locale: string): Record<string, string> {
|
|
||||||
return translations[locale] || translations.ru;
|
|
||||||
}
|
|
||||||
@ -96,7 +96,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
react: 'React',
|
react: 'React',
|
||||||
'react-dom': 'ReactDOM',
|
|
||||||
redux: 'Redux',
|
redux: 'Redux',
|
||||||
'react-redux': 'ReactRedux',
|
'react-redux': 'ReactRedux',
|
||||||
'prop-types': 'PropTypes',
|
'prop-types': 'PropTypes',
|
||||||
|
|||||||
253
webapp/yarn.lock
253
webapp/yarn.lock
@ -1606,17 +1606,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@npm:2.2.4":
|
|
||||||
version: 2.2.4
|
|
||||||
resolution: "@formatjs/ecma402-abstract@npm:2.2.4"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/fast-memoize": "npm:2.2.3"
|
|
||||||
"@formatjs/intl-localematcher": "npm:0.5.8"
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/3f262533fa704ea7a1a7a8107deee2609774a242c621f8cb5dd4bf4c97abf2fc12f5aeda3f4ce85be18147c484a0ca87303dca6abef53290717e685c55eabd2d
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/ecma402-abstract@npm:3.1.1":
|
"@formatjs/ecma402-abstract@npm:3.1.1":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "@formatjs/ecma402-abstract@npm:3.1.1"
|
resolution: "@formatjs/ecma402-abstract@npm:3.1.1"
|
||||||
@ -1629,15 +1618,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/fast-memoize@npm:2.2.3":
|
|
||||||
version: 2.2.3
|
|
||||||
resolution: "@formatjs/fast-memoize@npm:2.2.3"
|
|
||||||
dependencies:
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/f1004c3b280de7e362bd37c5f48ff34c2ba1d6271d4a7b695fed561d1201a3379397824d8bffbf15fecee344d1e70398393bbb04297f242692310a305f12e75b
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/fast-memoize@npm:3.1.0":
|
"@formatjs/fast-memoize@npm:3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "@formatjs/fast-memoize@npm:3.1.0"
|
resolution: "@formatjs/fast-memoize@npm:3.1.0"
|
||||||
@ -1647,17 +1627,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/icu-messageformat-parser@npm:2.9.4":
|
|
||||||
version: 2.9.4
|
|
||||||
resolution: "@formatjs/icu-messageformat-parser@npm:2.9.4"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
"@formatjs/icu-skeleton-parser": "npm:1.8.8"
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/f1ed14ece7ef0abc9fb62e323b78c994fc772d346801ad5aaa9555e1a7d5c0fda791345f4f2e53a3223f0b82c1a4eaf9a83544c1c20cb39349d1a39bedcf1648
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/icu-messageformat-parser@npm:3.5.1":
|
"@formatjs/icu-messageformat-parser@npm:3.5.1":
|
||||||
version: 3.5.1
|
version: 3.5.1
|
||||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.1"
|
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.1"
|
||||||
@ -1669,16 +1638,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/icu-skeleton-parser@npm:1.8.8":
|
|
||||||
version: 1.8.8
|
|
||||||
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.8"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/5ad78a5682e83b973e6fed4fca68660b944c41d1e941f0c84d69ff3d10ae835330062dc0a2cf0d237d2675ad3463405061a3963c14c2b9d8d1c1911f892b1a8d
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/icu-skeleton-parser@npm:2.1.1":
|
"@formatjs/icu-skeleton-parser@npm:2.1.1":
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.1.1"
|
resolution: "@formatjs/icu-skeleton-parser@npm:2.1.1"
|
||||||
@ -1689,37 +1648,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl-displaynames@npm:6.8.5":
|
|
||||||
version: 6.8.5
|
|
||||||
resolution: "@formatjs/intl-displaynames@npm:6.8.5"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
"@formatjs/intl-localematcher": "npm:0.5.8"
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/1092d6bac9ba7ee22470b85c9af16802244aa8a54f07e6cd560d15b96e8a08fc359f20dee88a064fe4c9ca8860f439abb109cbb7977b9ccceb846e28aacdf29c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/intl-listformat@npm:7.7.5":
|
|
||||||
version: 7.7.5
|
|
||||||
resolution: "@formatjs/intl-listformat@npm:7.7.5"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
"@formatjs/intl-localematcher": "npm:0.5.8"
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/f514397f6b05ac29171fffbbd15636fbec086080058c79c159f24edd2038747c22579d46ebf339cbb672f8505ea408e5d960d6751064c16e02d18445cf4e7e61
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/intl-localematcher@npm:0.5.8":
|
|
||||||
version: 0.5.8
|
|
||||||
resolution: "@formatjs/intl-localematcher@npm:0.5.8"
|
|
||||||
dependencies:
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/7a660263986326b662d4cb537e8386331c34fda61fb830b105e6c62d49be58ace40728dae614883b27a41cec7b1df8b44f72f79e16e6028bfca65d398dc04f3b
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/intl-localematcher@npm:0.8.1":
|
"@formatjs/intl-localematcher@npm:0.8.1":
|
||||||
version: 0.8.1
|
version: 0.8.1
|
||||||
resolution: "@formatjs/intl-localematcher@npm:0.8.1"
|
resolution: "@formatjs/intl-localematcher@npm:0.8.1"
|
||||||
@ -1730,26 +1658,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@formatjs/intl@npm:2.10.15":
|
|
||||||
version: 2.10.15
|
|
||||||
resolution: "@formatjs/intl@npm:2.10.15"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
"@formatjs/fast-memoize": "npm:2.2.3"
|
|
||||||
"@formatjs/icu-messageformat-parser": "npm:2.9.4"
|
|
||||||
"@formatjs/intl-displaynames": "npm:6.8.5"
|
|
||||||
"@formatjs/intl-listformat": "npm:7.7.5"
|
|
||||||
intl-messageformat: "npm:10.7.7"
|
|
||||||
tslib: "npm:2"
|
|
||||||
peerDependencies:
|
|
||||||
typescript: ^4.7 || 5
|
|
||||||
peerDependenciesMeta:
|
|
||||||
typescript:
|
|
||||||
optional: true
|
|
||||||
checksum: 10c0/5d51fd0785d5547f375991d7df2d6303479b0083eeb35c42c30c9633aab77101895498f1eace419fd34fdb5c84aea19037c5280c3a9d85f9c3ffe6eef76b6f39
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@formatjs/intl@npm:4.1.2":
|
"@formatjs/intl@npm:4.1.2":
|
||||||
version: 4.1.2
|
version: 4.1.2
|
||||||
resolution: "@formatjs/intl@npm:4.1.2"
|
resolution: "@formatjs/intl@npm:4.1.2"
|
||||||
@ -1768,6 +1676,33 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@giphy/js-fetch-api@npm:^5.1.0":
|
||||||
|
version: 5.7.0
|
||||||
|
resolution: "@giphy/js-fetch-api@npm:5.7.0"
|
||||||
|
dependencies:
|
||||||
|
"@giphy/js-types": "npm:*"
|
||||||
|
"@giphy/js-util": "npm:*"
|
||||||
|
checksum: 10c0/af1990c49ed4d633be04e497f6575e4f798c61348cfca9907f74a8450746bb6ab7336b53eea99e647b902076016d994264979bd09a9aacaa85d40cd610a525ac
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@giphy/js-types@npm:*":
|
||||||
|
version: 5.1.0
|
||||||
|
resolution: "@giphy/js-types@npm:5.1.0"
|
||||||
|
checksum: 10c0/8a76b9fd72d10d47486f26902a2fdc083712b4e0582bb2b27698b2c9c58fd3ecfa07d14ac30bcb80ec0f2ec35b3de7f7791d9d9751b0e6cb7d6fa5c39d52479f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@giphy/js-util@npm:*":
|
||||||
|
version: 5.2.0
|
||||||
|
resolution: "@giphy/js-util@npm:5.2.0"
|
||||||
|
dependencies:
|
||||||
|
"@giphy/js-types": "npm:*"
|
||||||
|
uuid: "npm:^9.0.0"
|
||||||
|
checksum: 10c0/0782a4fa1d7b037b4010f76966f5b8347c5732f57516d191471d746cd733f2d212f7461c4ff613961e51cbd65584a1f5e7202dbe5ae2231978b5c52f51a2e7f7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@isaacs/balanced-match@npm:^4.0.1":
|
"@isaacs/balanced-match@npm:^4.0.1":
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
resolution: "@isaacs/balanced-match@npm:4.0.1"
|
resolution: "@isaacs/balanced-match@npm:4.0.1"
|
||||||
@ -2418,7 +2353,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/hoist-non-react-statics@npm:3, @types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1":
|
"@types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1":
|
||||||
version: 3.3.7
|
version: 3.3.7
|
||||||
resolution: "@types/hoist-non-react-statics@npm:3.3.7"
|
resolution: "@types/hoist-non-react-statics@npm:3.3.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2621,16 +2556,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:16 || 17 || 18":
|
|
||||||
version: 18.3.28
|
|
||||||
resolution: "@types/react@npm:18.3.28"
|
|
||||||
dependencies:
|
|
||||||
"@types/prop-types": "npm:*"
|
|
||||||
csstype: "npm:^3.2.2"
|
|
||||||
checksum: 10c0/683e19cd12b5c691215529af2e32b5ffbaccae3bf0ba93bfafa0e460e8dfee18423afed568be2b8eadf4b837c3749dd296a4f64e2d79f68fa66962c05f5af661
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/react@npm:17.0.3":
|
"@types/react@npm:17.0.3":
|
||||||
version: 17.0.3
|
version: 17.0.3
|
||||||
resolution: "@types/react@npm:17.0.3"
|
resolution: "@types/react@npm:17.0.3"
|
||||||
@ -5894,6 +5819,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"form-data@npm:^4.0.0":
|
||||||
|
version: 4.0.5
|
||||||
|
resolution: "form-data@npm:4.0.5"
|
||||||
|
dependencies:
|
||||||
|
asynckit: "npm:^0.4.0"
|
||||||
|
combined-stream: "npm:^1.0.8"
|
||||||
|
es-set-tostringtag: "npm:^2.1.0"
|
||||||
|
hasown: "npm:^2.0.2"
|
||||||
|
mime-types: "npm:^2.1.12"
|
||||||
|
checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fragment-cache@npm:^0.2.1":
|
"fragment-cache@npm:^0.2.1":
|
||||||
version: 0.2.1
|
version: 0.2.1
|
||||||
resolution: "fragment-cache@npm:0.2.1"
|
resolution: "fragment-cache@npm:0.2.1"
|
||||||
@ -6363,7 +6301,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"hoist-non-react-statics@npm:3, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2":
|
"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2":
|
||||||
version: 3.3.2
|
version: 3.3.2
|
||||||
resolution: "hoist-non-react-statics@npm:3.3.2"
|
resolution: "hoist-non-react-statics@npm:3.3.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6628,18 +6566,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"intl-messageformat@npm:10.7.7":
|
|
||||||
version: 10.7.7
|
|
||||||
resolution: "intl-messageformat@npm:10.7.7"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
"@formatjs/fast-memoize": "npm:2.2.3"
|
|
||||||
"@formatjs/icu-messageformat-parser": "npm:2.9.4"
|
|
||||||
tslib: "npm:2"
|
|
||||||
checksum: 10c0/691895fb6a73a2feb2569658706e0d452861441de184dd1c9201e458a39fb80fc80080dd40d3d370400a52663f87de7a6d5a263c94245492f7265dd760441a95
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"intl-messageformat@npm:11.1.2":
|
"intl-messageformat@npm:11.1.2":
|
||||||
version: 11.1.2
|
version: 11.1.2
|
||||||
resolution: "intl-messageformat@npm:11.1.2"
|
resolution: "intl-messageformat@npm:11.1.2"
|
||||||
@ -8022,6 +7948,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"loop-plugin-sdk@https://artifacts.wilix.dev/repository/npm-public-loop/loop-plugin-sdk/-/loop-plugin-sdk-0.1.6.tgz":
|
||||||
|
version: 0.1.6
|
||||||
|
resolution: "loop-plugin-sdk@https://artifacts.wilix.dev/repository/npm-public-loop/loop-plugin-sdk/-/loop-plugin-sdk-0.1.6.tgz"
|
||||||
|
dependencies:
|
||||||
|
"@giphy/js-fetch-api": "npm:^5.1.0"
|
||||||
|
form-data: "npm:^4.0.0"
|
||||||
|
rudder-sdk-js: "npm:^2.41.0"
|
||||||
|
serialize-error: "npm:^11.0.2"
|
||||||
|
shallow-equals: "npm:^1.0.0"
|
||||||
|
timezones.json: "npm:^1.7.1"
|
||||||
|
checksum: 10c0/661ed3b99bb666a5fe024dadbbb2f1cf6d7d43bed3e94e87171e76985b31f82b8b4042358f30b60bd097e452185c957ec55fc03b15f45ac1ca70a6239fac1b71
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
|
"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
resolution: "loose-envify@npm:1.4.0"
|
resolution: "loose-envify@npm:1.4.0"
|
||||||
@ -9520,30 +9460,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-intl@npm:6.8.9":
|
|
||||||
version: 6.8.9
|
|
||||||
resolution: "react-intl@npm:6.8.9"
|
|
||||||
dependencies:
|
|
||||||
"@formatjs/ecma402-abstract": "npm:2.2.4"
|
|
||||||
"@formatjs/icu-messageformat-parser": "npm:2.9.4"
|
|
||||||
"@formatjs/intl": "npm:2.10.15"
|
|
||||||
"@formatjs/intl-displaynames": "npm:6.8.5"
|
|
||||||
"@formatjs/intl-listformat": "npm:7.7.5"
|
|
||||||
"@types/hoist-non-react-statics": "npm:3"
|
|
||||||
"@types/react": "npm:16 || 17 || 18"
|
|
||||||
hoist-non-react-statics: "npm:3"
|
|
||||||
intl-messageformat: "npm:10.7.7"
|
|
||||||
tslib: "npm:2"
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.6.0 || 17 || 18
|
|
||||||
typescript: ^4.7 || 5
|
|
||||||
peerDependenciesMeta:
|
|
||||||
typescript:
|
|
||||||
optional: true
|
|
||||||
checksum: 10c0/d42a6252beac5448b4a248d84923b0f75dfbbee6208cd5c49ac2f525714ab94efe2a4933d464c64cb161ddccaa37b83dffb2dd0529428219b8a60ce548da3e57
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.6":
|
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.6":
|
||||||
version: 16.13.1
|
version: 16.13.1
|
||||||
resolution: "react-is@npm:16.13.1"
|
resolution: "react-is@npm:16.13.1"
|
||||||
@ -10169,11 +10085,11 @@ __metadata:
|
|||||||
jest: "npm:26.6.3"
|
jest: "npm:26.6.3"
|
||||||
jest-canvas-mock: "npm:2.3.1"
|
jest-canvas-mock: "npm:2.3.1"
|
||||||
jest-junit: "npm:12.0.0"
|
jest-junit: "npm:12.0.0"
|
||||||
|
loop-plugin-sdk: "https://artifacts.wilix.dev/repository/npm-public-loop/loop-plugin-sdk/-/loop-plugin-sdk-0.1.6.tgz"
|
||||||
mattermost-redux: "npm:5.33.1"
|
mattermost-redux: "npm:5.33.1"
|
||||||
memoize-one: "npm:^5.2.1"
|
memoize-one: "npm:^5.2.1"
|
||||||
react: "npm:17.0.2"
|
react: "npm:17.0.2"
|
||||||
react-custom-scrollbars: "npm:^4.2.1"
|
react-custom-scrollbars: "npm:^4.2.1"
|
||||||
react-intl: "npm:6.8.9"
|
|
||||||
react-redux: "npm:7.2.3"
|
react-redux: "npm:7.2.3"
|
||||||
redux: "npm:4.0.5"
|
redux: "npm:4.0.5"
|
||||||
sass: "npm:1.86.0"
|
sass: "npm:1.86.0"
|
||||||
@ -10209,6 +10125,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"rudder-sdk-js@npm:^2.41.0":
|
||||||
|
version: 2.52.8
|
||||||
|
resolution: "rudder-sdk-js@npm:2.52.8"
|
||||||
|
checksum: 10c0/62732c3402bf1858c1100b287bd72d7e54c293b308f2e96c633aa29dfd8758bbee50b1aa93011ea98d4960b8fb98d2ab88a69536b5fe35bd794e85d5cd4ae861
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"run-parallel@npm:^1.1.9":
|
"run-parallel@npm:^1.1.9":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "run-parallel@npm:1.2.0"
|
resolution: "run-parallel@npm:1.2.0"
|
||||||
@ -10452,6 +10375,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"serialize-error@npm:^11.0.2":
|
||||||
|
version: 11.0.3
|
||||||
|
resolution: "serialize-error@npm:11.0.3"
|
||||||
|
dependencies:
|
||||||
|
type-fest: "npm:^2.12.2"
|
||||||
|
checksum: 10c0/7263603883b8936650819f0fd5150d41427b317432678b21722c54b85367ae15b8552865eb7f3f39ba71a32a003730a2e2e971e6909431eb54db70a3ef8eca17
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"serialize-javascript@npm:^6.0.2":
|
"serialize-javascript@npm:^6.0.2":
|
||||||
version: 6.0.2
|
version: 6.0.2
|
||||||
resolution: "serialize-javascript@npm:6.0.2"
|
resolution: "serialize-javascript@npm:6.0.2"
|
||||||
@ -10546,7 +10478,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"shallow-equals@npm:1.0.0":
|
"shallow-equals@npm:1.0.0, shallow-equals@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "shallow-equals@npm:1.0.0"
|
resolution: "shallow-equals@npm:1.0.0"
|
||||||
checksum: 10c0/ba7c87947126fcfdd31d6c473c5785c235c385dcc045dd6b1543366b1e86aa8e8f69289346ffee0b13a845365fc6f3d21badd8c00e2b6c2b1d0e84d69bcf4487
|
checksum: 10c0/ba7c87947126fcfdd31d6c473c5785c235c385dcc045dd6b1543366b1e86aa8e8f69289346ffee0b13a845365fc6f3d21badd8c00e2b6c2b1d0e84d69bcf4487
|
||||||
@ -11292,6 +11224,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"timezones.json@npm:^1.7.1":
|
||||||
|
version: 1.7.2
|
||||||
|
resolution: "timezones.json@npm:1.7.2"
|
||||||
|
checksum: 10c0/209da3d2334118790f57ad060de68ba799adde9df051a6428c348079ed0df20a753e190f85cceadab336f6b24a68302bcca84c895c70270126b15ea0bcde77cd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tinyglobby@npm:^0.2.12":
|
"tinyglobby@npm:^0.2.12":
|
||||||
version: 0.2.15
|
version: 0.2.15
|
||||||
resolution: "tinyglobby@npm:0.2.15"
|
resolution: "tinyglobby@npm:0.2.15"
|
||||||
@ -11425,13 +11364,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tslib@npm:2, tslib@npm:^2.8.1":
|
|
||||||
version: 2.8.1
|
|
||||||
resolution: "tslib@npm:2.8.1"
|
|
||||||
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"tslib@npm:^1.8.1":
|
"tslib@npm:^1.8.1":
|
||||||
version: 1.14.1
|
version: 1.14.1
|
||||||
resolution: "tslib@npm:1.14.1"
|
resolution: "tslib@npm:1.14.1"
|
||||||
@ -11439,6 +11371,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tslib@npm:^2.8.1":
|
||||||
|
version: 2.8.1
|
||||||
|
resolution: "tslib@npm:2.8.1"
|
||||||
|
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tsutils@npm:^3.17.1":
|
"tsutils@npm:^3.17.1":
|
||||||
version: 3.21.0
|
version: 3.21.0
|
||||||
resolution: "tsutils@npm:3.21.0"
|
resolution: "tsutils@npm:3.21.0"
|
||||||
@ -11508,6 +11447,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"type-fest@npm:^2.12.2":
|
||||||
|
version: 2.19.0
|
||||||
|
resolution: "type-fest@npm:2.19.0"
|
||||||
|
checksum: 10c0/a5a7ecf2e654251613218c215c7493574594951c08e52ab9881c9df6a6da0aeca7528c213c622bc374b4e0cb5c443aa3ab758da4e3c959783ce884c3194e12cb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"typed-array-buffer@npm:^1.0.3":
|
"typed-array-buffer@npm:^1.0.3":
|
||||||
version: 1.0.3
|
version: 1.0.3
|
||||||
resolution: "typed-array-buffer@npm:1.0.3"
|
resolution: "typed-array-buffer@npm:1.0.3"
|
||||||
@ -11810,6 +11756,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"uuid@npm:^9.0.0":
|
||||||
|
version: 9.0.1
|
||||||
|
resolution: "uuid@npm:9.0.1"
|
||||||
|
bin:
|
||||||
|
uuid: dist/bin/uuid
|
||||||
|
checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"v8-compile-cache@npm:^2.0.3, v8-compile-cache@npm:^2.2.0":
|
"v8-compile-cache@npm:^2.0.3, v8-compile-cache@npm:^2.2.0":
|
||||||
version: 2.4.0
|
version: 2.4.0
|
||||||
resolution: "v8-compile-cache@npm:2.4.0"
|
resolution: "v8-compile-cache@npm:2.4.0"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user