Echo Public Channel Messages
Add a feature where recent public channel messages can be echoed back to newly joining users when they enter the chat room. * Configure in settings.toml with EchoMessagesOnJoin. 0 = disable storage. * Messages are stored in RAM and lost on a server reboot. * A buffer of recent public messages per channel can be kept, e.g. for the 10 most recent messages. * The settings can be reloaded with /reconfigure and the message buffers will rebalance on the next message sent. * When a new user logs in, a new "echo" message is sent that contains all of the echoed messages on a "messages" list, in one WebSocket packet. * Echoed messages are put above the ChatServer welcome messages. * If a message is taken back, it's removed from the echo message buffer. Other Changes * Don't broadcast Presence messages within 30 seconds of the server boot, to lessen a flood of messages when a lot of users were connected at reboot. * Change the default "Join messages" setting on front-end to hide them in public channels. * For the admin buttons in ProfileModal, use the AlertModal instead of native browser prompts.
This commit is contained in:
parent
859e9dee5b
commit
885adda156
27
Protocol.md
27
Protocol.md
|
@ -149,6 +149,33 @@ If the message is a DM, the channel will be the username prepended by an @ symbo
|
|||
Every message or file share originated from a user has a "msgID" attached
|
||||
which is useful for [takebacks](#takeback).
|
||||
|
||||
## Echo
|
||||
|
||||
Sent by: Server.
|
||||
|
||||
This supports the feature for the server to echo recent public messages that took place before a user joined chat.
|
||||
|
||||
It is basically a wrapper around a list of Messages that happened in these channels.
|
||||
|
||||
```javascript
|
||||
// Server message
|
||||
{
|
||||
"action": "echo",
|
||||
"messages": [
|
||||
{
|
||||
"action": "message",
|
||||
"channel": "lobby",
|
||||
"username": "senderName",
|
||||
"message": "Hello!",
|
||||
"msgID": 123,
|
||||
"timestamp": "2024-01-01 00:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
A notable feature compared to how those Messages originally were sent is that the echoed ones carry a "timestamp" since it is sending outdated messages to the client.
|
||||
|
||||
## File
|
||||
|
||||
Sent by: Client.
|
||||
|
|
6
go.mod
6
go.mod
|
@ -1,6 +1,8 @@
|
|||
module git.kirsle.net/apps/barertc
|
||||
|
||||
go 1.19
|
||||
go 1.21.0
|
||||
|
||||
toolchain go1.22.0
|
||||
|
||||
require (
|
||||
git.kirsle.net/go/log v0.0.0-20200902035305-70ac2848949b
|
||||
|
@ -30,6 +32,8 @@ require (
|
|||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
|
|
5
go.sum
5
go.sum
|
@ -122,6 +122,10 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
|
|||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
|
@ -166,6 +170,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10=
|
||||
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"os"
|
||||
|
||||
"git.kirsle.net/apps/barertc/pkg/log"
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// Version of the config format - when new fields are added, it will attempt
|
||||
// to write the settings.toml to disk so new defaults populate.
|
||||
var currentVersion = 15
|
||||
var currentVersion = 16
|
||||
|
||||
// Config for your BareRTC app.
|
||||
type Config struct {
|
||||
Version int // will re-save your settings.toml on migrations
|
||||
Version int `toml:"" comment:"Version of your config file (do not touch). When new features are added to BareRTC,\nthe Version is incremented and your settings.toml is written with sensible defaults added"` // will re-save your settings.toml on migrations
|
||||
|
||||
JWT struct {
|
||||
Enabled bool
|
||||
Strict bool
|
||||
SecretKey string
|
||||
LandingPageURL string
|
||||
}
|
||||
} `toml:"" comment:"Use JWT tokens to log users into chat from your main website."`
|
||||
|
||||
Title string
|
||||
Branding string
|
||||
WebsiteURL string
|
||||
Title string `toml:"" comment:"Your chat room title (plain text)"`
|
||||
Branding string `toml:"" comment:"Your logo in the top-left corner of page. This can just be your Title again,\nOr you can use HTML here for custom style or image."`
|
||||
WebsiteURL string `toml:"" comment:"Your main website's base URL, for e.g. avatars and profile URLs to be relative to"`
|
||||
|
||||
CORSHosts []string
|
||||
AdminAPIKey string
|
||||
|
@ -42,9 +41,9 @@ type Config struct {
|
|||
MaxImageWidth int
|
||||
PreviewImageWidth int
|
||||
|
||||
TURN TurnConfig
|
||||
TURN TurnConfig `toml:"" comment:"Configure your TURN or STUN servers here.\n\nSTUN servers help WebRTC clients connect peer-to-peer for video, which is\npreferable as it saves on your bandwidth. You should list at least one, and\nthere are many public servers available such as Google's.\n\nTURN servers help WebRTC clients connect when a direct connection isn't\npossible. An open source server called 'coturn' can do both STUN and TURN."`
|
||||
|
||||
PublicChannels []Channel
|
||||
PublicChannels []Channel `toml:"" comment:"Your pre-defined common public chat rooms.\n"`
|
||||
|
||||
WebhookURLs []WebhookURL
|
||||
|
||||
|
@ -106,6 +105,8 @@ type Channel struct {
|
|||
|
||||
// ChatServer messages to send to the user immediately upon connecting.
|
||||
WelcomeMessages []string
|
||||
|
||||
EchoMessagesOnJoin int
|
||||
}
|
||||
|
||||
// WebhookURL allows tighter integration with your website.
|
||||
|
@ -167,6 +168,7 @@ func DefaultConfig() Config {
|
|||
"Welcome to the chat server!",
|
||||
"Please follow the basic rules:\n\n1. Have fun\n2. Be kind",
|
||||
},
|
||||
EchoMessagesOnJoin: 10,
|
||||
},
|
||||
{
|
||||
ID: "offtopic",
|
||||
|
@ -259,8 +261,7 @@ func LoadSettings() error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = toml.Decode(string(data), &Current)
|
||||
if err != nil {
|
||||
if err = toml.Unmarshal(data, &Current); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -279,12 +280,12 @@ func LoadSettings() error {
|
|||
// WriteSettings will commit the settings.toml to disk.
|
||||
func WriteSettings() error {
|
||||
log.Error("Note: initial settings.toml was written to disk.")
|
||||
var buf = new(bytes.Buffer)
|
||||
err := toml.NewEncoder(buf).Encode(Current)
|
||||
buf, err := toml.Marshal(Current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile("./settings.toml", buf.Bytes(), 0644)
|
||||
|
||||
return os.WriteFile("./settings.toml", buf, 0644)
|
||||
}
|
||||
|
||||
// GetModerationRule returns a matching ModerationRule for the given user, or nil if no rule is found.
|
||||
|
|
97
pkg/echo_messages.go
Normal file
97
pkg/echo_messages.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package barertc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/barertc/pkg/config"
|
||||
"git.kirsle.net/apps/barertc/pkg/log"
|
||||
"git.kirsle.net/apps/barertc/pkg/messages"
|
||||
)
|
||||
|
||||
// Functionality for storing recent public channel messages and echo them to new joiners.
|
||||
|
||||
/*
|
||||
Echo Messages On Join
|
||||
|
||||
This feature stores recent public messages to channels (in memory) to echo them
|
||||
back to new users when they join the room.
|
||||
*/
|
||||
var (
|
||||
echoMessages = map[string][]messages.Message{} // map channel ID -> messages
|
||||
echoLock sync.RWMutex
|
||||
)
|
||||
|
||||
// SendEchoedMessages will repeat recent public messages in public channels to the newly
|
||||
// connecting subscriber as echoed messages.
|
||||
func (sub *Subscriber) SendEchoedMessages() {
|
||||
var echoes []messages.Message
|
||||
|
||||
// Read lock to collect the messages.
|
||||
echoLock.RLock()
|
||||
|
||||
for _, msgs := range echoMessages {
|
||||
echoes = append(echoes, msgs...)
|
||||
}
|
||||
|
||||
// Release the lock.
|
||||
echoLock.RUnlock()
|
||||
|
||||
// Send all of these in one Echo message.
|
||||
sub.SendJSON(messages.Message{
|
||||
Action: messages.ActionEcho,
|
||||
Messages: echoes,
|
||||
})
|
||||
}
|
||||
|
||||
// EchoPushPublicMessage pushes a message into the recent message history of the channel ID.
|
||||
//
|
||||
// The buffer of recent messages (the size configured in settings.toml) is echoed to
|
||||
// a new user when they join the chat so they can catch up.
|
||||
func (s *Server) EchoPushPublicMessage(sub *Subscriber, channel string, msg messages.Message) {
|
||||
|
||||
// Get the channel from settings to see its capacity.
|
||||
ch, ok := config.Current.GetChannel(channel)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
echoLock.Lock()
|
||||
defer echoLock.Unlock()
|
||||
|
||||
// Initialize the storage for this channel?
|
||||
if _, ok := echoMessages[channel]; !ok {
|
||||
echoMessages[channel] = []messages.Message{}
|
||||
}
|
||||
|
||||
// Timestamp it and append this message.
|
||||
msg.Timestamp = time.Now().Format(time.RFC3339)
|
||||
echoMessages[channel] = append(echoMessages[channel], msg)
|
||||
|
||||
// Trim the history to the configured window size.
|
||||
if ln := len(echoMessages[channel]); ln > ch.EchoMessagesOnJoin {
|
||||
echoMessages[channel] = echoMessages[channel][ln-ch.EchoMessagesOnJoin:]
|
||||
}
|
||||
}
|
||||
|
||||
// EchoTakebackMessage will remove any taken-back message that was cached
|
||||
// in the echo buffer for new joiners.
|
||||
func (s *Server) EchoTakebackMessage(msgID int64) {
|
||||
|
||||
// Takebacks are relatively uncommon enough, write lock while we read and/or
|
||||
// maybe remove messages from the echo cache.
|
||||
echoLock.Lock()
|
||||
defer echoLock.Unlock()
|
||||
|
||||
// Find matching messages in each channel.
|
||||
for _, ch := range config.Current.PublicChannels {
|
||||
for i, msg := range echoMessages[ch.ID] {
|
||||
if msg.MessageID == msgID {
|
||||
log.Error("EchoTakebackMessage: message ID %d removed from channel %s", msgID, ch.ID)
|
||||
|
||||
// Remove this message.
|
||||
echoMessages[ch.ID] = append(echoMessages[ch.ID][:i], echoMessages[ch.ID][i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -94,6 +94,15 @@ func (s *Server) OnLogin(sub *Subscriber, msg messages.Message) {
|
|||
sub.loginAt = time.Now()
|
||||
log.Debug("OnLogin: %s joins the room", sub.Username)
|
||||
|
||||
// Send the user back their settings.
|
||||
sub.SendMe()
|
||||
|
||||
// Send the WhoList to everybody.
|
||||
s.SendWhoList()
|
||||
|
||||
// Echo recent public channel messages to the user.
|
||||
sub.SendEchoedMessages()
|
||||
|
||||
// Tell everyone they joined.
|
||||
s.Broadcast(messages.Message{
|
||||
Action: messages.ActionPresence,
|
||||
|
@ -101,12 +110,6 @@ func (s *Server) OnLogin(sub *Subscriber, msg messages.Message) {
|
|||
Message: messages.PresenceJoined,
|
||||
})
|
||||
|
||||
// Send the user back their settings.
|
||||
sub.SendMe()
|
||||
|
||||
// Send the WhoList to everybody.
|
||||
s.SendWhoList()
|
||||
|
||||
// Send the initial ChatServer messages to the public channels.
|
||||
for _, channel := range config.Current.PublicChannels {
|
||||
for _, msg := range channel.WelcomeMessages {
|
||||
|
@ -250,6 +253,9 @@ func (s *Server) OnMessage(sub *Subscriber, msg messages.Message) {
|
|||
LogChannel(s, msg.Channel, sub.Username, msg)
|
||||
}
|
||||
|
||||
// Append it to the public channel's echo buffer.
|
||||
s.EchoPushPublicMessage(sub, message.Channel, message)
|
||||
|
||||
// Broadcast a chat message to the room.
|
||||
s.Broadcast(message)
|
||||
}
|
||||
|
@ -279,6 +285,9 @@ func (s *Server) OnTakeback(sub *Subscriber, msg messages.Message) {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove it from cached echo buffers for public channels.
|
||||
s.EchoTakebackMessage(msg.MessageID)
|
||||
|
||||
// Broadcast to everybody to remove this message.
|
||||
s.Broadcast(messages.Message{
|
||||
Action: messages.ActionTakeback,
|
||||
|
|
|
@ -61,6 +61,9 @@ type Message struct {
|
|||
Reason string `json:"reason,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
|
||||
// Sent on `echo` actions to condense multiple messages into one packet.
|
||||
Messages []Message `json:"messages,omitempty"`
|
||||
|
||||
// WebRTC negotiation messages: proxy their signaling messages
|
||||
// between the two users to negotiate peer connection.
|
||||
Candidate string `json:"candidate,omitempty"` // candidate
|
||||
|
@ -80,6 +83,7 @@ const (
|
|||
|
||||
// Actions sent by server or client
|
||||
ActionMessage = "message" // post a message to the room
|
||||
ActionEcho = "echo" // echo recent public message on join
|
||||
ActionMe = "me" // user self-info sent by FE or BE
|
||||
ActionOpen = "open" // user wants to view a webcam (open WebRTC)
|
||||
ActionRing = "ring" // receiver of a WebRTC open request
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/barertc/pkg/config"
|
||||
"git.kirsle.net/apps/barertc/pkg/log"
|
||||
|
@ -12,6 +13,9 @@ import (
|
|||
|
||||
// Server is the primary back-end server struct for BareRTC, see main.go
|
||||
type Server struct {
|
||||
// Timestamp when the server started.
|
||||
upSince time.Time
|
||||
|
||||
// HTTP router.
|
||||
mux *http.ServeMux
|
||||
|
||||
|
@ -71,6 +75,7 @@ func (s *Server) Setup() error {
|
|||
// ListenAndServe starts the web server.
|
||||
func (s *Server) ListenAndServe(address string) error {
|
||||
// Run the polling user idle kicker.
|
||||
s.upSince = time.Now()
|
||||
go s.KickIdlePollUsers()
|
||||
return http.ListenAndServe(address, s.mux)
|
||||
}
|
||||
|
|
|
@ -354,6 +354,15 @@ func (s *Server) Broadcast(msg messages.Message) {
|
|||
log.Debug("Broadcast: %+v", msg)
|
||||
}
|
||||
|
||||
// Don't send Presence actions within 30 seconds of server startup, to reduce spam
|
||||
// during a chat server reboot.
|
||||
if time.Since(s.upSince) < 30*time.Second {
|
||||
if msg.Action == messages.ActionPresence {
|
||||
log.Debug("Skip sending Presence messages within 30 seconds of server reboot")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get the sender of this message.
|
||||
sender, err := s.GetSubscriber(msg.Username)
|
||||
if err != nil {
|
||||
|
|
|
@ -167,7 +167,7 @@ export default {
|
|||
// Misc. user preferences (TODO: move all of them here)
|
||||
prefs: {
|
||||
usePolling: false, // use the polling API instead of WebSockets.
|
||||
joinMessages: true, // show "has entered the room" in public channels
|
||||
joinMessages: false, // hide "has entered the room" in public channels
|
||||
exitMessages: false, // hide exit messages by default in public channels
|
||||
watchNotif: true, // notify in chat about cameras being watched
|
||||
closeDMs: false, // ignore unsolicited DMs
|
||||
|
@ -1581,6 +1581,7 @@ export default {
|
|||
username: msg.username,
|
||||
message: msg.message,
|
||||
messageID: msg.msgID,
|
||||
timestamp: msg.timestamp,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import AlertModal from './AlertModal.vue';
|
||||
import VideoFlag from '../lib/VideoFlag';
|
||||
|
||||
export default {
|
||||
|
@ -15,6 +16,9 @@ export default {
|
|||
profileWebhookEnabled: Boolean,
|
||||
vipConfig: Object, // VIP config settings for BareRTC
|
||||
},
|
||||
components: {
|
||||
AlertModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
busy: false,
|
||||
|
@ -27,6 +31,16 @@ export default {
|
|||
banReason: "",
|
||||
banDuration: 24,
|
||||
|
||||
// Alert modal
|
||||
alertModal: {
|
||||
visible: false,
|
||||
isConfirm: false,
|
||||
title: "Alert",
|
||||
icon: "fa-exclamation-triangle",
|
||||
message: "",
|
||||
callback() {},
|
||||
},
|
||||
|
||||
// Error messaging from backend
|
||||
error: null,
|
||||
};
|
||||
|
@ -136,21 +150,38 @@ export default {
|
|||
|
||||
// Operator commands (may be rejected by server if not really Op)
|
||||
markNsfw() {
|
||||
if (!window.confirm("Mark this user's webcam as 'Explicit'?")) return;
|
||||
this.$emit('send-command', `/nsfw ${this.user.username}`);
|
||||
this.modalConfirm({
|
||||
message: "Mark this user's webcam as 'Explicit'?\n\n" +
|
||||
`If @${this.user.username} is behaving sexually while on a Blue camera, click OK to confirm ` +
|
||||
"that their camera should be marked as Red (explicit).",
|
||||
title: "Mark a webcam as Explicit",
|
||||
icon: "fa fa-fire",
|
||||
}).then(() => {
|
||||
this.$emit('send-command', `/nsfw ${this.user.username}`);
|
||||
|
||||
// Close the modal immediately: our view of the user's cam data is a copy
|
||||
// and we can't follow the current value.
|
||||
this.cancel();
|
||||
// Close the modal immediately: our view of the user's cam data is a copy
|
||||
// and we can't follow the current value.
|
||||
this.cancel();
|
||||
});
|
||||
},
|
||||
cutCamera() {
|
||||
if (!window.confirm("Make this user stop broadcasting their camera?")) return;
|
||||
this.$emit('send-command', `/cut ${this.user.username}`);
|
||||
this.cancel();
|
||||
this.modalConfirm({
|
||||
message: "Make this user stop broadcasting their camera?",
|
||||
title: "Cut Camera",
|
||||
icon: "fa fa-video-slash",
|
||||
}).then(() => {
|
||||
this.$emit('send-command', `/cut ${this.user.username}`);
|
||||
this.cancel();
|
||||
});
|
||||
},
|
||||
kickUser() {
|
||||
if (!window.confirm("Really kick this user from the chat room?")) return;
|
||||
this.$emit('send-command', `/kick ${this.user.username}`);
|
||||
this.modalConfirm({
|
||||
message: "Really kick this user from the chat room?",
|
||||
title: "Kick User",
|
||||
}).then(() => {
|
||||
this.$emit('send-command', `/kick ${this.user.username}`);
|
||||
this.cancel();
|
||||
});
|
||||
},
|
||||
banUser() {
|
||||
this.banModalVisible = true;
|
||||
|
@ -193,6 +224,31 @@ export default {
|
|||
}
|
||||
return this.websiteUrl.replace(/\/+$/, "") + url;
|
||||
},
|
||||
|
||||
// Alert Modal funcs, copied from/the same as App.vue (TODO: make it D.R.Y.)
|
||||
async modalAlert({ message, title="Alert", icon="", isConfirm=false }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.alertModal.isConfirm = isConfirm;
|
||||
this.alertModal.title = title;
|
||||
this.alertModal.icon = icon;
|
||||
this.alertModal.message = message;
|
||||
this.alertModal.callback = () => {
|
||||
resolve();
|
||||
};
|
||||
this.alertModal.visible = true;
|
||||
});
|
||||
},
|
||||
async modalConfirm({ message, title="Confirmation", icon=""}) {
|
||||
return this.modalAlert({
|
||||
isConfirm: true,
|
||||
message,
|
||||
title,
|
||||
icon,
|
||||
})
|
||||
},
|
||||
modalClose() {
|
||||
this.alertModal.visible = false;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -368,6 +424,15 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alert modal (for alert/confirm prompts) -->
|
||||
<AlertModal :visible="alertModal.visible"
|
||||
:is-confirm="alertModal.isConfirm"
|
||||
:title="alertModal.title"
|
||||
:icon="alertModal.icon"
|
||||
:message="alertModal.message"
|
||||
@callback="alertModal.callback"
|
||||
@close="modalClose()"></AlertModal>
|
||||
|
||||
<!-- Ban User Modal (for chat admins) -->
|
||||
<div class="modal" :class="{ 'is-active': banModalVisible }">
|
||||
<div class="modal-background" @click="banModalVisible = false"></div>
|
||||
|
|
|
@ -175,6 +175,12 @@ class ChatClient {
|
|||
case "message":
|
||||
this.onMessage(msg);
|
||||
break;
|
||||
case "echo":
|
||||
// An echo is basically a wrapper for batch messages.
|
||||
for (let sub of msg.messages) {
|
||||
this.handle(sub);
|
||||
}
|
||||
break;
|
||||
case "takeback":
|
||||
this.onTakeback(msg);
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue
Block a user