mirror of
https://github.com/owncast/owncast.git
synced 2024-11-25 22:31:09 +03:00
parent
40264bec8c
commit
bf33d08384
8 changed files with 183 additions and 97 deletions
|
@ -435,6 +435,23 @@ func SetSocialHandles(w http.ResponseWriter, r *http.Request) {
|
||||||
controllers.WriteSimpleResponse(w, true, "social handles updated")
|
controllers.WriteSimpleResponse(w, true, "social handles updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetChatDisabled will disable chat functionality.
|
||||||
|
func SetChatDisabled(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !requirePOST(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configValue, success := getValueFromRequest(w, r)
|
||||||
|
if !success {
|
||||||
|
controllers.WriteSimpleResponse(w, false, "unable to update chat disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data.SetChatDisabled(configValue.Value.(bool))
|
||||||
|
|
||||||
|
controllers.WriteSimpleResponse(w, true, "chat disabled status updated")
|
||||||
|
}
|
||||||
|
|
||||||
func requirePOST(w http.ResponseWriter, r *http.Request) bool {
|
func requirePOST(w http.ResponseWriter, r *http.Request) bool {
|
||||||
if r.Method != controllers.POST {
|
if r.Method != controllers.POST {
|
||||||
controllers.WriteSimpleResponse(w, false, r.Method+" not supported")
|
controllers.WriteSimpleResponse(w, false, r.Method+" not supported")
|
||||||
|
|
|
@ -43,6 +43,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
StreamKey: data.GetStreamKey(),
|
StreamKey: data.GetStreamKey(),
|
||||||
WebServerPort: config.WebServerPort,
|
WebServerPort: config.WebServerPort,
|
||||||
RTMPServerPort: data.GetRTMPPortNumber(),
|
RTMPServerPort: data.GetRTMPPortNumber(),
|
||||||
|
ChatDisabled: data.GetChatDisabled(),
|
||||||
VideoSettings: videoSettings{
|
VideoSettings: videoSettings{
|
||||||
VideoQualityVariants: videoQualityVariants,
|
VideoQualityVariants: videoQualityVariants,
|
||||||
LatencyLevel: data.GetStreamLatencyLevel().Level,
|
LatencyLevel: data.GetStreamLatencyLevel().Level,
|
||||||
|
@ -71,6 +72,7 @@ type serverConfigAdminResponse struct {
|
||||||
VideoSettings videoSettings `json:"videoSettings"`
|
VideoSettings videoSettings `json:"videoSettings"`
|
||||||
LatencyLevel int `json:"latencyLevel"`
|
LatencyLevel int `json:"latencyLevel"`
|
||||||
YP yp `json:"yp"`
|
YP yp `json:"yp"`
|
||||||
|
ChatDisabled bool `json:"chatDisabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type videoSettings struct {
|
type videoSettings struct {
|
||||||
|
|
|
@ -21,6 +21,7 @@ type webConfigResponse struct {
|
||||||
ExtraPageContent string `json:"extraPageContent"`
|
ExtraPageContent string `json:"extraPageContent"`
|
||||||
StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream
|
StreamTitle string `json:"streamTitle,omitempty"` // What's going on with the current stream
|
||||||
SocialHandles []models.SocialHandle `json:"socialHandles"`
|
SocialHandles []models.SocialHandle `json:"socialHandles"`
|
||||||
|
ChatDisabled bool `json:"chatDisabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWebConfig gets the status of the server.
|
// GetWebConfig gets the status of the server.
|
||||||
|
@ -48,6 +49,7 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
ExtraPageContent: pageContent,
|
ExtraPageContent: pageContent,
|
||||||
StreamTitle: data.GetStreamTitle(),
|
StreamTitle: data.GetStreamTitle(),
|
||||||
SocialHandles: socialHandles,
|
SocialHandles: socialHandles,
|
||||||
|
ChatDisabled: data.GetChatDisabled(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(configuration); err != nil {
|
if err := json.NewEncoder(w).Encode(configuration); err != nil {
|
||||||
|
|
|
@ -135,6 +135,10 @@ func (s *server) Listen() {
|
||||||
case c := <-s.delCh:
|
case c := <-s.delCh:
|
||||||
s.removeClient(c)
|
s.removeClient(c)
|
||||||
case msg := <-s.sendAllCh:
|
case msg := <-s.sendAllCh:
|
||||||
|
if data.GetChatDisabled() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if !msg.Empty() {
|
if !msg.Empty() {
|
||||||
// set defaults before sending msg to anywhere
|
// set defaults before sending msg to anywhere
|
||||||
msg.SetDefaults()
|
msg.SetDefaults()
|
||||||
|
|
|
@ -33,6 +33,7 @@ const s3StorageEnabledKey = "s3_storage_enabled"
|
||||||
const s3StorageConfigKey = "s3_storage_config"
|
const s3StorageConfigKey = "s3_storage_config"
|
||||||
const videoLatencyLevel = "video_latency_level"
|
const videoLatencyLevel = "video_latency_level"
|
||||||
const videoStreamOutputVariantsKey = "video_stream_output_variants"
|
const videoStreamOutputVariantsKey = "video_stream_output_variants"
|
||||||
|
const chatDisabledKey = "chat_disabled"
|
||||||
|
|
||||||
// GetExtraPageBodyContent will return the user-supplied body content.
|
// GetExtraPageBodyContent will return the user-supplied body content.
|
||||||
func GetExtraPageBodyContent() string {
|
func GetExtraPageBodyContent() string {
|
||||||
|
@ -408,6 +409,21 @@ func SetStreamOutputVariants(variants []models.StreamOutputVariant) error {
|
||||||
return _datastore.Save(configEntry)
|
return _datastore.Save(configEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetChatDisabled will disable chat if set to true.
|
||||||
|
func SetChatDisabled(disabled bool) error {
|
||||||
|
return _datastore.SetBool(chatDisabledKey, disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatDisabled will return if chat is disabled.
|
||||||
|
func GetChatDisabled() bool {
|
||||||
|
disabled, err := _datastore.GetBool(chatDisabledKey)
|
||||||
|
if err == nil {
|
||||||
|
return disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// VerifySettings will perform a sanity check for specific settings values.
|
// VerifySettings will perform a sanity check for specific settings values.
|
||||||
func VerifySettings() error {
|
func VerifySettings() error {
|
||||||
if GetStreamKey() == "" {
|
if GetStreamKey() == "" {
|
||||||
|
|
|
@ -109,6 +109,9 @@ func Start() error {
|
||||||
// Server summary
|
// Server summary
|
||||||
http.HandleFunc("/api/admin/config/serversummary", middleware.RequireAdminAuth(admin.SetServerSummary))
|
http.HandleFunc("/api/admin/config/serversummary", middleware.RequireAdminAuth(admin.SetServerSummary))
|
||||||
|
|
||||||
|
// Disable chat
|
||||||
|
http.HandleFunc("/api/admin/config/chat/disable", middleware.RequireAdminAuth(admin.SetChatDisabled))
|
||||||
|
|
||||||
// Return all webhooks
|
// Return all webhooks
|
||||||
http.HandleFunc("/api/admin/webhooks", middleware.RequireAdminAuth(admin.GetWebhooks))
|
http.HandleFunc("/api/admin/webhooks", middleware.RequireAdminAuth(admin.GetWebhooks))
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,11 @@ import UsernameForm from './components/chat/username.js';
|
||||||
import VideoPoster from './components/video-poster.js';
|
import VideoPoster from './components/video-poster.js';
|
||||||
import Chat from './components/chat/chat.js';
|
import Chat from './components/chat/chat.js';
|
||||||
import Websocket from './utils/websocket.js';
|
import Websocket from './utils/websocket.js';
|
||||||
import { parseSecondsToDurationString, hasTouchScreen, getOrientation } from './utils/helpers.js';
|
import {
|
||||||
|
parseSecondsToDurationString,
|
||||||
|
hasTouchScreen,
|
||||||
|
getOrientation,
|
||||||
|
} from './utils/helpers.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addNewlines,
|
addNewlines,
|
||||||
|
@ -50,6 +54,7 @@ export default class App extends Component {
|
||||||
websocket: new Websocket(),
|
websocket: new Websocket(),
|
||||||
displayChat: chatStorage === null ? true : chatStorage,
|
displayChat: chatStorage === null ? true : chatStorage,
|
||||||
chatInputEnabled: false, // chat input box state
|
chatInputEnabled: false, // chat input box state
|
||||||
|
chatDisabled: false,
|
||||||
username: getLocalStorage(KEY_USERNAME) || generateUsername(),
|
username: getLocalStorage(KEY_USERNAME) || generateUsername(),
|
||||||
touchKeyboardActive: false,
|
touchKeyboardActive: false,
|
||||||
|
|
||||||
|
@ -195,12 +200,7 @@ export default class App extends Component {
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {
|
const { viewerCount, online, lastConnectTime, streamTitle } = status;
|
||||||
viewerCount,
|
|
||||||
online,
|
|
||||||
lastConnectTime,
|
|
||||||
streamTitle,
|
|
||||||
} = status;
|
|
||||||
|
|
||||||
this.lastDisconnectTime = status.lastDisconnectTime;
|
this.lastDisconnectTime = status.lastDisconnectTime;
|
||||||
|
|
||||||
|
@ -265,7 +265,9 @@ export default class App extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.windowBlurred) {
|
if (this.windowBlurred) {
|
||||||
document.title = ` 🔴 ${this.state.configData && this.state.configData.name}`;
|
document.title = ` 🔴 ${
|
||||||
|
this.state.configData && this.state.configData.name
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +291,9 @@ export default class App extends Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.windowBlurred) {
|
if (this.windowBlurred) {
|
||||||
document.title = ` 🟢 ${this.state.configData && this.state.configData.name}`;
|
document.title = ` 🟢 ${
|
||||||
|
this.state.configData && this.state.configData.name
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +306,6 @@ export default class App extends Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
streamStatusMessage: `${MESSAGE_ONLINE} ${streamDurationString}`,
|
streamStatusMessage: `${MESSAGE_ONLINE} ${streamDurationString}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUsernameChange(newName) {
|
handleUsernameChange(newName) {
|
||||||
|
@ -370,7 +373,7 @@ export default class App extends Component {
|
||||||
|
|
||||||
handleSpaceBarPressed(e) {
|
handleSpaceBarPressed(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if(this.state.isPlaying) {
|
if (this.state.isPlaying) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
});
|
});
|
||||||
|
@ -384,7 +387,11 @@ export default class App extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyPressed(e) {
|
handleKeyPressed(e) {
|
||||||
if (e.code === 'Space' && e.target === document.body && this.state.streamOnline) {
|
if (
|
||||||
|
e.code === 'Space' &&
|
||||||
|
e.target === document.body &&
|
||||||
|
this.state.streamOnline
|
||||||
|
) {
|
||||||
this.handleSpaceBarPressed(e);
|
this.handleSpaceBarPressed(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,7 +415,6 @@ export default class App extends Component {
|
||||||
windowWidth,
|
windowWidth,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
version: appVersion,
|
version: appVersion,
|
||||||
logo = TEMP_IMAGE,
|
logo = TEMP_IMAGE,
|
||||||
|
@ -417,11 +423,13 @@ export default class App extends Component {
|
||||||
tags = [],
|
tags = [],
|
||||||
name,
|
name,
|
||||||
extraPageContent,
|
extraPageContent,
|
||||||
|
chatDisabled,
|
||||||
} = configData;
|
} = configData;
|
||||||
|
|
||||||
const bgUserLogo = { backgroundImage: `url(${logo})` };
|
const bgUserLogo = { backgroundImage: `url(${logo})` };
|
||||||
|
|
||||||
const tagList = (tags !== null && tags.length > 0)
|
const tagList =
|
||||||
|
tags !== null && tags.length > 0
|
||||||
? tags.map(
|
? tags.map(
|
||||||
(tag, index) => html`
|
(tag, index) => html`
|
||||||
<li
|
<li
|
||||||
|
@ -434,28 +442,33 @@ export default class App extends Component {
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const viewerCountMessage = streamOnline && viewerCount > 0 ? (
|
const viewerCountMessage =
|
||||||
html`${viewerCount} ${pluralize('viewer', viewerCount)}`
|
streamOnline && viewerCount > 0
|
||||||
) : null;
|
? html`${viewerCount} ${pluralize('viewer', viewerCount)}`
|
||||||
|
: null;
|
||||||
|
|
||||||
const mainClass = playerActive ? 'online' : '';
|
const mainClass = playerActive ? 'online' : '';
|
||||||
const isPortrait = this.hasTouchScreen && orientation === ORIENTATION_PORTRAIT;
|
const isPortrait =
|
||||||
|
this.hasTouchScreen && orientation === ORIENTATION_PORTRAIT;
|
||||||
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE && !isPortrait;
|
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE && !isPortrait;
|
||||||
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
|
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
|
||||||
|
|
||||||
|
const shouldDisplayChat = displayChat && !chatDisabled;
|
||||||
|
const usernameStyle = chatDisabled ? 'none' : 'flex';
|
||||||
|
|
||||||
const extraAppClasses = classNames({
|
const extraAppClasses = classNames({
|
||||||
chat: displayChat,
|
chat: shouldDisplayChat,
|
||||||
'no-chat': !displayChat,
|
'no-chat': !shouldDisplayChat,
|
||||||
'single-col': singleColMode,
|
'single-col': singleColMode,
|
||||||
'bg-gray-800': singleColMode && displayChat,
|
'bg-gray-800': singleColMode && shouldDisplayChat,
|
||||||
'short-wide': shortHeight && windowWidth > WIDTH_SINGLE_COL,
|
'short-wide': shortHeight && windowWidth > WIDTH_SINGLE_COL,
|
||||||
'touch-screen': this.hasTouchScreen,
|
'touch-screen': this.hasTouchScreen,
|
||||||
'touch-keyboard-active': touchKeyboardActive,
|
'touch-keyboard-active': touchKeyboardActive,
|
||||||
});
|
});
|
||||||
|
|
||||||
const poster = isPlaying ? null : html`
|
const poster = isPlaying
|
||||||
<${VideoPoster} offlineImage=${logo} active=${streamOnline} />
|
? null
|
||||||
`;
|
: html` <${VideoPoster} offlineImage=${logo} active=${streamOnline} /> `;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
|
@ -473,15 +486,20 @@ export default class App extends Component {
|
||||||
id="logo-container"
|
id="logo-container"
|
||||||
class="inline-block rounded-full bg-white w-8 min-w-8 min-h-8 h-8 mr-2 bg-no-repeat bg-center"
|
class="inline-block rounded-full bg-white w-8 min-w-8 min-h-8 h-8 mr-2 bg-no-repeat bg-center"
|
||||||
>
|
>
|
||||||
<img class="logo visually-hidden" src=${OWNCAST_LOGO_LOCAL} alt="owncast logo" />
|
<img
|
||||||
|
class="logo visually-hidden"
|
||||||
|
src=${OWNCAST_LOGO_LOCAL}
|
||||||
|
alt="owncast logo"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="instance-title overflow-hidden truncate"
|
<span class="instance-title overflow-hidden truncate"
|
||||||
>${(streamOnline && streamTitle) ? streamTitle : name}</span
|
>${streamOnline && streamTitle ? streamTitle : name}</span
|
||||||
>
|
>
|
||||||
</h1>
|
</h1>
|
||||||
<div
|
<div
|
||||||
id="user-options-container"
|
id="user-options-container"
|
||||||
class="flex flex-row justify-end items-center flex-no-wrap"
|
class="flex flex-row justify-end items-center flex-no-wrap"
|
||||||
|
style=${{ display: usernameStyle }}
|
||||||
>
|
>
|
||||||
<${UsernameForm}
|
<${UsernameForm}
|
||||||
username=${username}
|
username=${username}
|
||||||
|
@ -538,9 +556,7 @@ export default class App extends Component {
|
||||||
class="user-content-header border-b border-gray-500 border-solid"
|
class="user-content-header border-b border-gray-500 border-solid"
|
||||||
>
|
>
|
||||||
<h2 class="font-semibold text-5xl">
|
<h2 class="font-semibold text-5xl">
|
||||||
<span class="streamer-name text-indigo-600"
|
<span class="streamer-name text-indigo-600">${name}</span>
|
||||||
>${name}</span
|
|
||||||
>
|
|
||||||
</h2>
|
</h2>
|
||||||
<h3 class="font-semibold text-3xl">
|
<h3 class="font-semibold text-3xl">
|
||||||
${streamOnline && streamTitle}
|
${streamOnline && streamTitle}
|
||||||
|
@ -573,11 +589,10 @@ export default class App extends Component {
|
||||||
<${Chat}
|
<${Chat}
|
||||||
websocket=${websocket}
|
websocket=${websocket}
|
||||||
username=${username}
|
username=${username}
|
||||||
chatInputEnabled=${chatInputEnabled}
|
chatInputEnabled=${chatInputEnabled && !chatDisabled}
|
||||||
instanceTitle=${name}
|
instanceTitle=${name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,17 @@ const html = htm.bind(h);
|
||||||
import { EmojiButton } from '/js/web_modules/@joeattardi/emoji-button.js';
|
import { EmojiButton } from '/js/web_modules/@joeattardi/emoji-button.js';
|
||||||
|
|
||||||
import ContentEditable, { replaceCaret } from './content-editable.js';
|
import ContentEditable, { replaceCaret } from './content-editable.js';
|
||||||
import { generatePlaceholderText, getCaretPosition, convertToText, convertOnPaste } from '../../utils/chat.js';
|
import {
|
||||||
import { getLocalStorage, setLocalStorage, classNames } from '../../utils/helpers.js';
|
generatePlaceholderText,
|
||||||
|
getCaretPosition,
|
||||||
|
convertToText,
|
||||||
|
convertOnPaste,
|
||||||
|
} from '../../utils/chat.js';
|
||||||
|
import {
|
||||||
|
getLocalStorage,
|
||||||
|
setLocalStorage,
|
||||||
|
classNames,
|
||||||
|
} from '../../utils/helpers.js';
|
||||||
import {
|
import {
|
||||||
URL_CUSTOM_EMOJIS,
|
URL_CUSTOM_EMOJIS,
|
||||||
KEY_CHAT_FIRST_MESSAGE_SENT,
|
KEY_CHAT_FIRST_MESSAGE_SENT,
|
||||||
|
@ -46,7 +55,9 @@ export default class ChatInput extends Component {
|
||||||
this.handleSubmitChatButton = this.handleSubmitChatButton.bind(this);
|
this.handleSubmitChatButton = this.handleSubmitChatButton.bind(this);
|
||||||
this.handlePaste = this.handlePaste.bind(this);
|
this.handlePaste = this.handlePaste.bind(this);
|
||||||
|
|
||||||
this.handleContentEditableChange = this.handleContentEditableChange.bind(this);
|
this.handleContentEditableChange = this.handleContentEditableChange.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -55,13 +66,13 @@ export default class ChatInput extends Component {
|
||||||
|
|
||||||
getCustomEmojis() {
|
getCustomEmojis() {
|
||||||
fetch(URL_CUSTOM_EMOJIS)
|
fetch(URL_CUSTOM_EMOJIS)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Network response was not ok ${response.ok}`);
|
throw new Error(`Network response was not ok ${response.ok}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(json => {
|
.then((json) => {
|
||||||
this.emojiPicker = new EmojiButton({
|
this.emojiPicker = new EmojiButton({
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
theme: 'owncast', // see chat.css
|
theme: 'owncast', // see chat.css
|
||||||
|
@ -75,7 +86,7 @@ export default class ChatInput extends Component {
|
||||||
position: 'right-start',
|
position: 'right-start',
|
||||||
strategy: 'absolute',
|
strategy: 'absolute',
|
||||||
});
|
});
|
||||||
this.emojiPicker.on('emoji', emoji => {
|
this.emojiPicker.on('emoji', (emoji) => {
|
||||||
this.handleEmojiSelected(emoji);
|
this.handleEmojiSelected(emoji);
|
||||||
});
|
});
|
||||||
this.emojiPicker.on('hidden', () => {
|
this.emojiPicker.on('hidden', () => {
|
||||||
|
@ -83,7 +94,7 @@ export default class ChatInput extends Component {
|
||||||
replaceCaret(this.formMessageInput.current);
|
replaceCaret(this.formMessageInput.current);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
// this.handleNetworkingError(`Emoji Fetch: ${error}`);
|
// this.handleNetworkingError(`Emoji Fetch: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -98,9 +109,9 @@ export default class ChatInput extends Component {
|
||||||
const { inputHTML } = this.state;
|
const { inputHTML } = this.state;
|
||||||
let content = '';
|
let content = '';
|
||||||
if (emoji.url) {
|
if (emoji.url) {
|
||||||
const url = location.protocol + "//" + location.host + "/" + emoji.url;
|
const url = location.protocol + '//' + location.host + '/' + emoji.url;
|
||||||
const name = url.split('\\').pop().split('/').pop();
|
const name = url.split('\\').pop().split('/').pop();
|
||||||
content = "<img class=\"emoji\" alt=\"" + name + "\" src=\"" + url + "\"/>";
|
content = '<img class="emoji" alt="' + name + '" src="' + url + '"/>';
|
||||||
} else {
|
} else {
|
||||||
content = emoji.emoji;
|
content = emoji.emoji;
|
||||||
}
|
}
|
||||||
|
@ -109,7 +120,7 @@ export default class ChatInput extends Component {
|
||||||
inputHTML: inputHTML + content,
|
inputHTML: inputHTML + content,
|
||||||
});
|
});
|
||||||
// a hacky way add focus back into input field
|
// a hacky way add focus back into input field
|
||||||
setTimeout( () => {
|
setTimeout(() => {
|
||||||
const input = this.formMessageInput.current;
|
const input = this.formMessageInput.current;
|
||||||
input.focus();
|
input.focus();
|
||||||
replaceCaret(input);
|
replaceCaret(input);
|
||||||
|
@ -138,7 +149,10 @@ export default class ChatInput extends Component {
|
||||||
return username.toLowerCase().startsWith(partial.toLowerCase());
|
return username.toLowerCase().startsWith(partial.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.completionIndex === undefined || ++this.completionIndex >= possibilities.length) {
|
if (
|
||||||
|
this.completionIndex === undefined ||
|
||||||
|
++this.completionIndex >= possibilities.length
|
||||||
|
) {
|
||||||
this.completionIndex = 0;
|
this.completionIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,8 +160,12 @@ export default class ChatInput extends Component {
|
||||||
this.suggestion = possibilities[this.completionIndex];
|
this.suggestion = possibilities[this.completionIndex];
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
inputHTML: inputHTML.substring(0, at + 1) + this.suggestion + ' ' + inputHTML.substring(position),
|
inputHTML:
|
||||||
})
|
inputHTML.substring(0, at + 1) +
|
||||||
|
this.suggestion +
|
||||||
|
' ' +
|
||||||
|
inputHTML.substring(position),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -264,25 +282,29 @@ export default class ChatInput extends Component {
|
||||||
|
|
||||||
render(props, state) {
|
render(props, state) {
|
||||||
const { hasSentFirstChatMessage, inputCharsLeft, inputHTML } = state;
|
const { hasSentFirstChatMessage, inputCharsLeft, inputHTML } = state;
|
||||||
const { inputEnabled } = props;
|
const { inputEnabled, chatDisabled } = props;
|
||||||
const emojiButtonStyle = {
|
const emojiButtonStyle = {
|
||||||
display: this.emojiPicker && inputCharsLeft > 0 ? 'block' : 'none',
|
display: this.emojiPicker && inputCharsLeft > 0 ? 'block' : 'none',
|
||||||
};
|
};
|
||||||
const extraClasses = classNames({
|
const extraClasses = classNames({
|
||||||
'display-count': inputCharsLeft <= CHAT_CHAR_COUNT_BUFFER,
|
'display-count': inputCharsLeft <= CHAT_CHAR_COUNT_BUFFER,
|
||||||
});
|
});
|
||||||
const placeholderText = generatePlaceholderText(inputEnabled, hasSentFirstChatMessage);
|
const placeholderText = generatePlaceholderText(
|
||||||
return (
|
inputEnabled,
|
||||||
html`
|
hasSentFirstChatMessage
|
||||||
<div id="message-input-container" class="relative shadow-md bg-gray-900 border-t border-gray-700 border-solid p-4 z-20 ${extraClasses}">
|
);
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
id="message-input-container"
|
||||||
|
class="relative shadow-md bg-gray-900 border-t border-gray-700 border-solid p-4 z-20 ${extraClasses}"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
id="message-input-wrap"
|
id="message-input-wrap"
|
||||||
class="flex flex-row justify-end appearance-none w-full bg-gray-200 border border-black-500 rounded py-2 px-2 pr-12 my-2 overflow-auto">
|
class="flex flex-row justify-end appearance-none w-full bg-gray-200 border border-black-500 rounded py-2 px-2 pr-12 my-2 overflow-auto"
|
||||||
|
>
|
||||||
<${ContentEditable}
|
<${ContentEditable}
|
||||||
id="message-input"
|
id="message-input"
|
||||||
class="appearance-none block w-full bg-transparent text-sm text-gray-700 h-full focus:outline-none"
|
class="appearance-none block w-full bg-transparent text-sm text-gray-700 h-full focus:outline-none"
|
||||||
|
|
||||||
placeholderText=${placeholderText}
|
placeholderText=${placeholderText}
|
||||||
innerRef=${this.formMessageInput}
|
innerRef=${this.formMessageInput}
|
||||||
html=${inputHTML}
|
html=${inputHTML}
|
||||||
|
@ -291,11 +313,13 @@ export default class ChatInput extends Component {
|
||||||
onKeyDown=${this.handleMessageInputKeydown}
|
onKeyDown=${this.handleMessageInputKeydown}
|
||||||
onKeyUp=${this.handleMessageInputKeyup}
|
onKeyUp=${this.handleMessageInputKeyup}
|
||||||
onBlur=${this.handleMessageInputBlur}
|
onBlur=${this.handleMessageInputBlur}
|
||||||
|
|
||||||
onPaste=${this.handlePaste}
|
onPaste=${this.handlePaste}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="message-form-actions" class="absolute flex flex-col w-10 justify-end items-center">
|
<div
|
||||||
|
id="message-form-actions"
|
||||||
|
class="absolute flex flex-col w-10 justify-end items-center"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
ref=${this.emojiPickerButton}
|
ref=${this.emojiPickerButton}
|
||||||
id="emoji-button"
|
id="emoji-button"
|
||||||
|
@ -304,12 +328,15 @@ export default class ChatInput extends Component {
|
||||||
style=${emojiButtonStyle}
|
style=${emojiButtonStyle}
|
||||||
onclick=${this.handleEmojiButtonClick}
|
onclick=${this.handleEmojiButtonClick}
|
||||||
disabled=${!inputEnabled}
|
disabled=${!inputEnabled}
|
||||||
><img src="../../../img/smiley.png" /></button>
|
>
|
||||||
|
<img src="../../../img/smiley.png" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<span id="message-form-warning" class="text-red-600 text-xs">${inputCharsLeft}/${CHAT_MAX_MESSAGE_LENGTH}</span>
|
<span id="message-form-warning" class="text-red-600 text-xs"
|
||||||
|
>${inputCharsLeft}/${CHAT_MAX_MESSAGE_LENGTH}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue