Add standalone chat with ability to send messages (#1270)
* properly pass the messagesOnly to chat * use actual username if embed is not messageonly * mv embed chat to chat-overlay * add new embed chat page * fix router * secure random number for non-secure application! * add chat enable/disable functionality * add username form add customStyles * mv overlay css * add style for embed chat style cleanup * rm username form from chat overlay * refactoring * css cleanup css adjust * minor cleanup * mark the embed chats as readonly and readwrite * replace 301 redirects with 307 * add redirect for the cached address * set insatnce name in chat
This commit is contained in:
parent
41a7e8b896
commit
7e6f53c846
@ -4,12 +4,17 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetChatEmbed gets the embed for chat.
|
// GetChatEmbedreadwrite gets the embed for readwrite chat.
|
||||||
func GetChatEmbed(w http.ResponseWriter, r *http.Request) {
|
func GetChatEmbedreadwrite(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/index-standalone-chat.html", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/index-standalone-chat-readwrite.html", http.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatEmbedreadonly gets the embed for readonly chat.
|
||||||
|
func GetChatEmbedreadonly(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/index-standalone-chat-readonly.html", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVideoEmbed gets the embed for video.
|
// GetVideoEmbed gets the embed for video.
|
||||||
func GetVideoEmbed(w http.ResponseWriter, r *http.Request) {
|
func GetVideoEmbed(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/index-video-only.html", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/index-video-only.html", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,14 @@ func Start() error {
|
|||||||
// web config api
|
// web config api
|
||||||
http.HandleFunc("/api/config", controllers.GetWebConfig)
|
http.HandleFunc("/api/config", controllers.GetWebConfig)
|
||||||
|
|
||||||
// chat embed
|
// pre v0.0.8 chat embed
|
||||||
http.HandleFunc("/embed/chat", controllers.GetChatEmbed)
|
http.HandleFunc("/embed/chat", controllers.GetChatEmbedreadonly)
|
||||||
|
|
||||||
|
// readonly chat embed
|
||||||
|
http.HandleFunc("/embed/chat/readonly", controllers.GetChatEmbedreadonly)
|
||||||
|
|
||||||
|
// readwrite chat embed
|
||||||
|
http.HandleFunc("/embed/chat/readwrite", controllers.GetChatEmbedreadwrite)
|
||||||
|
|
||||||
// video embed
|
// video embed
|
||||||
http.HandleFunc("/embed/video", controllers.GetVideoEmbed)
|
http.HandleFunc("/embed/video", controllers.GetVideoEmbed)
|
||||||
|
26
webroot/index-standalone-chat-readonly.html
Normal file
26
webroot/index-standalone-chat-readonly.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<base target="_blank" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||||
|
<link href="/js/web_modules/tailwindcss/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link href="./styles/chat.css" rel="stylesheet" />
|
||||||
|
<link href="./styles/standalone-chat-readonly.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="messages-only"></div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { h, render } from '/js/web_modules/preact.js';
|
||||||
|
import htm from '/js/web_modules/htm.js';
|
||||||
|
const html = htm.bind(h);
|
||||||
|
|
||||||
|
import StandaloneChat from './js/app-standalone-chat.js';
|
||||||
|
render(
|
||||||
|
html`<${StandaloneChat} readonly />`, document.getElementById("messages-only")
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
26
webroot/index-standalone-chat-readwrite.html
Normal file
26
webroot/index-standalone-chat-readwrite.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<base target="_blank" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||||
|
<link href="/js/web_modules/tailwindcss/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link href="./styles/chat.css" rel="stylesheet" />
|
||||||
|
<link href="./styles/standalone-chat-readwrite.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="messages-only"></div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { h, render } from '/js/web_modules/preact.js';
|
||||||
|
import htm from '/js/web_modules/htm.js';
|
||||||
|
const html = htm.bind(h);
|
||||||
|
|
||||||
|
import StandaloneChat from './js/app-standalone-chat.js';
|
||||||
|
render(
|
||||||
|
html`<${StandaloneChat} />`, document.getElementById("messages-only")
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base target="_blank" />
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
|
||||||
<link href="/js/web_modules/tailwindcss/dist/tailwind.min.css" rel="stylesheet" />
|
|
||||||
<link href="./styles/chat.css" rel="stylesheet" />
|
|
||||||
<link href="./styles/standalone-chat.css" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="messages-only"></div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import { h, render } from '/js/web_modules/preact.js';
|
|
||||||
import htm from '/js/web_modules/htm.js';
|
|
||||||
const html = htm.bind(h);
|
|
||||||
|
|
||||||
import StandaloneChat from './js/app-standalone-chat.js';
|
|
||||||
render(
|
|
||||||
html`<${StandaloneChat} messagesOnly />`, document.getElementById("messages-only")
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
1
webroot/index-standalone-chat.html
Symbolic link
1
webroot/index-standalone-chat.html
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
index-standalone-chat-readonly.html
|
@ -1,47 +1,250 @@
|
|||||||
import { h, Component } from '/js/web_modules/preact.js';
|
import { h, Component } from '/js/web_modules/preact.js';
|
||||||
import htm from '/js/web_modules/htm.js';
|
import htm from '/js/web_modules/htm.js';
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
|
import UsernameForm from './components/chat/username.js';
|
||||||
import Chat from './components/chat/chat.js';
|
import Chat from './components/chat/chat.js';
|
||||||
import Websocket from './utils/websocket.js';
|
import Websocket, {
|
||||||
import { getLocalStorage, setLocalStorage } from './utils/helpers.js';
|
CALLBACKS,
|
||||||
import { KEY_EMBED_CHAT_ACCESS_TOKEN } from './utils/constants.js';
|
SOCKET_MESSAGE_TYPES,
|
||||||
|
} from './utils/websocket.js';
|
||||||
import { registerChat } from './chat/register.js';
|
import { registerChat } from './chat/register.js';
|
||||||
|
import {
|
||||||
|
getLocalStorage,
|
||||||
|
setLocalStorage,
|
||||||
|
} from './utils/helpers.js';
|
||||||
|
import {
|
||||||
|
CHAT_MAX_MESSAGE_LENGTH,
|
||||||
|
EST_SOCKET_PAYLOAD_BUFFER,
|
||||||
|
KEY_EMBED_CHAT_ACCESS_TOKEN,
|
||||||
|
KEY_ACCESS_TOKEN,
|
||||||
|
KEY_USERNAME,
|
||||||
|
TIMER_DISABLE_CHAT_AFTER_OFFLINE,
|
||||||
|
URL_STATUS,
|
||||||
|
URL_CONFIG,
|
||||||
|
TIMER_STATUS_UPDATE,
|
||||||
|
} from './utils/constants.js';
|
||||||
|
|
||||||
|
|
||||||
export default class StandaloneChat extends Component {
|
export default class StandaloneChat extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
websocket: null,
|
||||||
|
canChat: false,
|
||||||
chatEnabled: true, // always true for standalone chat
|
chatEnabled: true, // always true for standalone chat
|
||||||
|
chatInputEnabled: false, // chat input box state
|
||||||
|
accessToken: null,
|
||||||
username: null,
|
username: null,
|
||||||
|
isRegistering: false,
|
||||||
|
streamOnline: false, // stream is active/online
|
||||||
|
lastDisconnectTime: null,
|
||||||
|
configData: {
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
this.disableChatInputTimer = null;
|
||||||
this.isRegistering = false;
|
|
||||||
this.hasConfiguredChat = false;
|
this.hasConfiguredChat = false;
|
||||||
this.websocket = null;
|
|
||||||
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
||||||
|
this.handleOfflineMode = this.handleOfflineMode.bind(this);
|
||||||
|
this.handleOnlineMode = this.handleOnlineMode.bind(this);
|
||||||
|
this.handleFormFocus = this.handleFormFocus.bind(this);
|
||||||
|
this.handleFormBlur = this.handleFormBlur.bind(this);
|
||||||
|
this.getStreamStatus = this.getStreamStatus.bind(this);
|
||||||
|
this.getConfig = this.getConfig.bind(this);
|
||||||
|
this.disableChatInput = this.disableChatInput.bind(this);
|
||||||
|
this.setupChatAuth = this.setupChatAuth.bind(this);
|
||||||
|
this.disableChat = this.disableChat.bind(this);
|
||||||
|
|
||||||
|
// user events
|
||||||
|
this.handleWebsocketMessage = this.handleWebsocketMessage.bind(this);
|
||||||
|
|
||||||
|
this.getConfig();
|
||||||
|
|
||||||
|
this.getStreamStatus();
|
||||||
|
this.statusTimer = setInterval(this.getStreamStatus, TIMER_STATUS_UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch /config data
|
||||||
|
getConfig() {
|
||||||
|
fetch(URL_CONFIG)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Network response was not ok ${response.ok}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
this.setConfigData(json);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.handleNetworkingError(`Fetch config: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch stream status
|
||||||
|
getStreamStatus() {
|
||||||
|
fetch(URL_STATUS)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Network response was not ok ${response.ok}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
this.updateStreamStatus(json);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.handleOfflineMode();
|
||||||
|
this.handleNetworkingError(`Stream status: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfigData(data = {}) {
|
||||||
|
const { chatDisabled } = data;
|
||||||
|
|
||||||
// If this is the first time setting the config
|
// If this is the first time setting the config
|
||||||
// then setup chat if it's enabled.
|
// then setup chat if it's enabled.
|
||||||
const chatBlocked = getLocalStorage('owncast_chat_blocked');
|
const chatBlocked = getLocalStorage('owncast_chat_blocked');
|
||||||
if (!chatBlocked && !this.hasConfiguredChat) {
|
if (!chatBlocked && !this.hasConfiguredChat && !chatDisabled) {
|
||||||
this.setupChatAuth();
|
this.setupChatAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasConfiguredChat = true;
|
this.hasConfiguredChat = true;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
canChat: !chatBlocked,
|
||||||
|
configData: {
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle UI things from stream status result
|
||||||
|
updateStreamStatus(status = {}) {
|
||||||
|
const { streamOnline: curStreamOnline } = this.state;
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
online,
|
||||||
|
lastDisconnectTime,
|
||||||
|
} = status;
|
||||||
|
|
||||||
|
if (status.online && !curStreamOnline) {
|
||||||
|
// stream has just come online.
|
||||||
|
this.handleOnlineMode();
|
||||||
|
} else if (!status.online && curStreamOnline) {
|
||||||
|
// stream has just flipped offline.
|
||||||
|
this.handleOfflineMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
lastDisconnectTime,
|
||||||
|
streamOnline: online,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop status timer and disable chat after some time.
|
||||||
|
handleOfflineMode() {
|
||||||
|
const remainingChatTime =
|
||||||
|
TIMER_DISABLE_CHAT_AFTER_OFFLINE -
|
||||||
|
(Date.now() - new Date(this.state.lastDisconnectTime));
|
||||||
|
const countdown = remainingChatTime < 0 ? 0 : remainingChatTime;
|
||||||
|
this.disableChatInputTimer = setTimeout(this.disableChatInput, countdown);
|
||||||
|
this.setState({
|
||||||
|
streamOnline: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlineMode() {
|
||||||
|
clearTimeout(this.disableChatInputTimer);
|
||||||
|
this.disableChatInputTimer = null;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
streamOnline: true,
|
||||||
|
chatInputEnabled: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUsernameChange(newName) {
|
handleUsernameChange(newName) {
|
||||||
this.setState({
|
this.setState({
|
||||||
username: newName,
|
username: newName,
|
||||||
});
|
});
|
||||||
|
this.sendUsernameChange(newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
disableChatInput() {
|
||||||
|
this.setState({
|
||||||
|
chatInputEnabled: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNetworkingError(error) {
|
||||||
|
console.error(`>>> App Error: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWebsocketMessage(e) {
|
||||||
|
if (e.type === SOCKET_MESSAGE_TYPES.ERROR_USER_DISABLED) {
|
||||||
|
// User has been actively disabled on the backend. Turn off chat for them.
|
||||||
|
this.handleBlockedChat();
|
||||||
|
} else if (
|
||||||
|
e.type === SOCKET_MESSAGE_TYPES.ERROR_NEEDS_REGISTRATION &&
|
||||||
|
!this.isRegistering
|
||||||
|
) {
|
||||||
|
// User needs an access token, so start the user auth flow.
|
||||||
|
this.state.websocket.shutdown();
|
||||||
|
this.setState({ websocket: null });
|
||||||
|
this.setupChatAuth(true);
|
||||||
|
} else if (e.type === SOCKET_MESSAGE_TYPES.ERROR_MAX_CONNECTIONS_EXCEEDED) {
|
||||||
|
// Chat server cannot support any more chat clients. Turn off chat for them.
|
||||||
|
this.disableChat();
|
||||||
|
} else if (e.type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) {
|
||||||
|
// When connected the user will return an event letting us know what our
|
||||||
|
// user details are so we can display them properly.
|
||||||
|
const { user } = e;
|
||||||
|
const { displayName } = user;
|
||||||
|
|
||||||
|
this.setState({ username: displayName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlockedChat() {
|
||||||
|
setLocalStorage('owncast_chat_blocked', true);
|
||||||
|
this.disableChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFormFocus() {
|
||||||
|
if (this.hasTouchScreen) {
|
||||||
|
this.setState({
|
||||||
|
touchKeyboardActive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFormBlur() {
|
||||||
|
if (this.hasTouchScreen) {
|
||||||
|
this.setState({
|
||||||
|
touchKeyboardActive: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disableChat() {
|
||||||
|
this.state.websocket.shutdown();
|
||||||
|
this.setState({ websocket: null, canChat: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setupChatAuth(force) {
|
async setupChatAuth(force) {
|
||||||
var accessToken = getLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN);
|
const { readonly } = this.props;
|
||||||
const randomInt = Math.floor(Math.random() * 100) + 1;
|
var accessToken = readonly ? getLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN) : getLocalStorage(KEY_ACCESS_TOKEN);
|
||||||
var username = 'chat-embed-' + randomInt;
|
var randomIntArray = new Uint32Array(1);
|
||||||
|
window.crypto.getRandomValues(randomIntArray);
|
||||||
|
var username = readonly ? 'chat-embed-' + randomIntArray[0] : getLocalStorage(KEY_USERNAME);
|
||||||
|
|
||||||
if (!accessToken || force) {
|
if (!accessToken || force) {
|
||||||
try {
|
try {
|
||||||
@ -50,7 +253,12 @@ export default class StandaloneChat extends Component {
|
|||||||
accessToken = registration.accessToken;
|
accessToken = registration.accessToken;
|
||||||
username = registration.displayName;
|
username = registration.displayName;
|
||||||
|
|
||||||
setLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN, accessToken);
|
if (readonly) {
|
||||||
|
setLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN, accessToken);
|
||||||
|
} else {
|
||||||
|
setLocalStorage(KEY_ACCESS_TOKEN, accessToken);
|
||||||
|
setLocalStorage(KEY_USERNAME, username);
|
||||||
|
}
|
||||||
|
|
||||||
this.isRegistering = false;
|
this.isRegistering = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -67,6 +275,10 @@ export default class StandaloneChat extends Component {
|
|||||||
|
|
||||||
// Without a valid access token he websocket connection will be rejected.
|
// Without a valid access token he websocket connection will be rejected.
|
||||||
const websocket = new Websocket(accessToken);
|
const websocket = new Websocket(accessToken);
|
||||||
|
websocket.addListener(
|
||||||
|
CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED,
|
||||||
|
this.handleWebsocketMessage
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
username,
|
username,
|
||||||
@ -75,15 +287,54 @@ export default class StandaloneChat extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendUsernameChange(newName) {
|
||||||
|
const nameChange = {
|
||||||
|
type: SOCKET_MESSAGE_TYPES.NAME_CHANGE,
|
||||||
|
newName,
|
||||||
|
};
|
||||||
|
this.state.websocket.send(nameChange);
|
||||||
|
}
|
||||||
|
|
||||||
render(props, state) {
|
render(props, state) {
|
||||||
const { username, websocket, accessToken } = state;
|
const {
|
||||||
return html`
|
username,
|
||||||
|
websocket,
|
||||||
|
accessToken,
|
||||||
|
chatInputEnabled,
|
||||||
|
configData,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const {
|
||||||
|
chatDisabled,
|
||||||
|
maxSocketPayloadSize,
|
||||||
|
customStyles,
|
||||||
|
name,
|
||||||
|
} = configData;
|
||||||
|
|
||||||
|
const { readonly } = props;
|
||||||
|
return this.state.websocket ?
|
||||||
|
html`${!readonly ?
|
||||||
|
html`<style>
|
||||||
|
${customStyles}
|
||||||
|
</style>
|
||||||
|
<header class="flex flex-row-reverse fixed z-10 w-full bg-gray-900">
|
||||||
|
<${UsernameForm}
|
||||||
|
username=${username}
|
||||||
|
onUsernameChange=${this.handleUsernameChange}
|
||||||
|
onFocus=${this.handleFormFocus}
|
||||||
|
onBlur=${this.handleFormBlur}
|
||||||
|
/>
|
||||||
|
</header>` : ''}
|
||||||
<${Chat}
|
<${Chat}
|
||||||
websocket=${websocket}
|
websocket=${websocket}
|
||||||
username=${username}
|
username=${username}
|
||||||
accessToken=${accessToken}
|
accessToken=${accessToken}
|
||||||
messagesOnly
|
readonly=${readonly}
|
||||||
/>
|
instanceTitle=${name}
|
||||||
`;
|
chatInputEnabled=${chatInputEnabled && !chatDisabled}
|
||||||
|
inputMaxBytes=${maxSocketPayloadSize - EST_SOCKET_PAYLOAD_BUFFER ||
|
||||||
|
CHAT_MAX_MESSAGE_LENGTH}
|
||||||
|
/>`
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ export default class Chat extends Component {
|
|||||||
|
|
||||||
window.addEventListener('resize', this.handleWindowResize);
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
|
|
||||||
if (!this.props.messagesOnly) {
|
if (!this.props.readonly) {
|
||||||
window.addEventListener('blur', this.handleWindowBlur);
|
window.addEventListener('blur', this.handleWindowBlur);
|
||||||
window.addEventListener('focus', this.handleWindowFocus);
|
window.addEventListener('focus', this.handleWindowFocus);
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ export default class Chat extends Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.handleWindowResize);
|
window.removeEventListener('resize', this.handleWindowResize);
|
||||||
if (!this.props.messagesOnly) {
|
if (!this.props.readonly) {
|
||||||
window.removeEventListener('blur', this.handleWindowBlur);
|
window.removeEventListener('blur', this.handleWindowBlur);
|
||||||
window.removeEventListener('focus', this.handleWindowFocus);
|
window.removeEventListener('focus', this.handleWindowFocus);
|
||||||
}
|
}
|
||||||
@ -183,7 +183,7 @@ export default class Chat extends Component {
|
|||||||
visible: messageVisible,
|
visible: messageVisible,
|
||||||
} = message;
|
} = message;
|
||||||
const { messages: curMessages } = this.state;
|
const { messages: curMessages } = this.state;
|
||||||
const { username, messagesOnly } = this.props;
|
const { username, readonly } = this.props;
|
||||||
|
|
||||||
const existingIndex = curMessages.findIndex(
|
const existingIndex = curMessages.findIndex(
|
||||||
(item) => item.id === messageId
|
(item) => item.id === messageId
|
||||||
@ -236,7 +236,7 @@ export default class Chat extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if window is blurred and we get a new message, add 1 to title
|
// if window is blurred and we get a new message, add 1 to title
|
||||||
if (!messagesOnly && messageType === 'CHAT' && this.windowBlurred) {
|
if (!readonly && messageType === 'CHAT' && this.windowBlurred) {
|
||||||
this.numMessagesSinceBlur += 1;
|
this.numMessagesSinceBlur += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +333,7 @@ export default class Chat extends Component {
|
|||||||
// update document title if window blurred
|
// update document title if window blurred
|
||||||
if (
|
if (
|
||||||
this.numMessagesSinceBlur &&
|
this.numMessagesSinceBlur &&
|
||||||
!this.props.messagesOnly &&
|
!this.props.readonly &&
|
||||||
this.windowBlurred
|
this.windowBlurred
|
||||||
) {
|
) {
|
||||||
this.updateDocumentTitle();
|
this.updateDocumentTitle();
|
||||||
@ -348,7 +348,7 @@ export default class Chat extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(props, state) {
|
render(props, state) {
|
||||||
const { username, messagesOnly, chatInputEnabled, inputMaxBytes } = props;
|
const { username, readonly, chatInputEnabled, inputMaxBytes } = props;
|
||||||
const { messages, chatUserNames, webSocketConnected } = state;
|
const { messages, chatUserNames, webSocketConnected } = state;
|
||||||
|
|
||||||
const messageList = messages
|
const messageList = messages
|
||||||
@ -362,7 +362,7 @@ export default class Chat extends Component {
|
|||||||
/>`
|
/>`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (messagesOnly) {
|
if (readonly) {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
id="messages-container"
|
id="messages-container"
|
||||||
|
16
webroot/styles/standalone-chat-readwrite.css
Normal file
16
webroot/styles/standalone-chat-readwrite.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--header-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
header{
|
||||||
|
height: var(--header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages-container {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user