0

Prettified Code!

This commit is contained in:
gabek 2021-07-20 02:23:06 +00:00 committed by GitHub Action
parent b6f68628c0
commit 7af5030f5b
10 changed files with 142 additions and 93 deletions

View File

@ -40,7 +40,7 @@ export default class StandaloneChat extends Component {
async setupChatAuth(force) { async setupChatAuth(force) {
var accessToken = getLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN); var accessToken = getLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN);
const randomInt = Math.floor(Math.random() * 100) + 1 const randomInt = Math.floor(Math.random() * 100) + 1;
var username = 'chat-embed-' + randomInt; var username = 'chat-embed-' + randomInt;
if (!accessToken || force) { if (!accessToken || force) {

View File

@ -7,7 +7,10 @@ import SocialIconsList from './components/platform-logos-list.js';
import UsernameForm from './components/chat/username.js'; 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, { CALLBACKS, SOCKET_MESSAGE_TYPES } from './utils/websocket.js'; import Websocket, {
CALLBACKS,
SOCKET_MESSAGE_TYPES,
} from './utils/websocket.js';
import { registerChat } from './chat/register.js'; import { registerChat } from './chat/register.js';
import ExternalActionModal, { import ExternalActionModal, {
@ -60,7 +63,7 @@ export default class App extends Component {
this.state = { this.state = {
websocket: null, websocket: null,
canChat: false, // all of chat functionality (panel + username) canChat: false, // all of chat functionality (panel + username)
displayChatPanel: chatStorage === null ? true : (chatStorage === 'true'), // just the chat panel displayChatPanel: chatStorage === null ? true : chatStorage === 'true', // just the chat panel
chatInputEnabled: false, // chat input box state chatInputEnabled: false, // chat input box state
accessToken: null, accessToken: null,
username: getLocalStorage(KEY_USERNAME), username: getLocalStorage(KEY_USERNAME),
@ -519,10 +522,13 @@ export default class App extends Component {
if (e.type === SOCKET_MESSAGE_TYPES.ERROR_USER_DISABLED) { if (e.type === SOCKET_MESSAGE_TYPES.ERROR_USER_DISABLED) {
// User has been actively disabled on the backend. Turn off chat for them. // User has been actively disabled on the backend. Turn off chat for them.
this.handleBlockedChat(); this.handleBlockedChat();
} else if (e.type === SOCKET_MESSAGE_TYPES.ERROR_NEEDS_REGISTRATION && !this.isRegistering) { } else if (
e.type === SOCKET_MESSAGE_TYPES.ERROR_NEEDS_REGISTRATION &&
!this.isRegistering
) {
// User needs an access token, so start the user auth flow. // User needs an access token, so start the user auth flow.
this.state.websocket.shutdown(); this.state.websocket.shutdown();
this.setState({websocket: null}); this.setState({ websocket: null });
this.setupChatAuth(true); this.setupChatAuth(true);
} else if (e.type === SOCKET_MESSAGE_TYPES.ERROR_MAX_CONNECTIONS_EXCEEDED) { } else if (e.type === SOCKET_MESSAGE_TYPES.ERROR_MAX_CONNECTIONS_EXCEEDED) {
// Chat server cannot support any more chat clients. Turn off chat for them. // Chat server cannot support any more chat clients. Turn off chat for them.
@ -530,10 +536,10 @@ export default class App extends Component {
} else if (e.type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) { } else if (e.type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) {
// When connected the user will return an event letting us know what our // When connected the user will return an event letting us know what our
// user details are so we can display them properly. // user details are so we can display them properly.
const {user} = e; const { user } = e;
const {displayName} = user; const { displayName } = user;
this.setState({username: displayName}); this.setState({ username: displayName });
} }
} }
@ -570,7 +576,7 @@ export default class App extends Component {
if (this.state.websocket) { if (this.state.websocket) {
this.state.websocket.shutdown(); this.state.websocket.shutdown();
this.setState({ this.setState({
websocket: null websocket: null,
}); });
} }
@ -589,14 +595,13 @@ export default class App extends Component {
} }
sendUsernameChange(newName) { sendUsernameChange(newName) {
const nameChange = { const nameChange = {
type: SOCKET_MESSAGE_TYPES.NAME_CHANGE, type: SOCKET_MESSAGE_TYPES.NAME_CHANGE,
newName, newName,
}; };
this.state.websocket.send(nameChange); this.state.websocket.send(nameChange);
} }
render(props, state) { render(props, state) {
const { const {
chatInputEnabled, chatInputEnabled,
@ -702,7 +707,8 @@ export default class App extends Component {
chatInputEnabled=${chatInputEnabled && !chatDisabled} chatInputEnabled=${chatInputEnabled && !chatDisabled}
instanceTitle=${name} instanceTitle=${name}
accessToken=${this.state.accessToken} accessToken=${this.state.accessToken}
inputMaxBytes=${(maxSocketPayloadSize - EST_SOCKET_PAYLOAD_BUFFER) || CHAT_MAX_MESSAGE_LENGTH} inputMaxBytes=${maxSocketPayloadSize - EST_SOCKET_PAYLOAD_BUFFER ||
CHAT_MAX_MESSAGE_LENGTH}
/> />
` `
: null; : null;

View File

@ -1,19 +1,19 @@
import {URL_CHAT_REGISTRATION} from "../utils/constants.js"; import { URL_CHAT_REGISTRATION } from '../utils/constants.js';
export async function registerChat(username) { export async function registerChat(username) {
const options = { const options = {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify({displayName: username}) body: JSON.stringify({ displayName: username }),
} };
try { try {
const response = await fetch(URL_CHAT_REGISTRATION, options); const response = await fetch(URL_CHAT_REGISTRATION, options);
const result = await response.json(); const result = await response.json();
return result; return result;
} catch(e) { } catch (e) {
console.error(e); console.error(e);
} }
} }

View File

@ -167,7 +167,11 @@ export default class ChatInput extends Component {
if (possibilities.length > 0) { if (possibilities.length > 0) {
this.suggestion = possibilities[this.completionIndex]; this.suggestion = possibilities[this.completionIndex];
const newHTML = inputHTML.substring(0, at + 1) + this.suggestion + ' ' + inputHTML.substring(position); const newHTML =
inputHTML.substring(0, at + 1) +
this.suggestion +
' ' +
inputHTML.substring(position);
this.setState({ this.setState({
inputHTML: newHTML, inputHTML: newHTML,

View File

@ -29,7 +29,7 @@ export default class ChatMessageView extends Component {
async componentDidMount() { async componentDidMount() {
const { message, username } = this.props; const { message, username } = this.props;
const { body } = message; const { body } = message;
if (message && username) { if (message && username) {
const formattedMessage = await formatMessageText(body, username); const formattedMessage = await formatMessageText(body, username);
this.setState({ this.setState({
@ -48,7 +48,9 @@ export default class ChatMessageView extends Component {
return null; return null;
} }
const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`; const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`;
const userMetadata = `${displayName} first joined ${formatTimestamp(createdAt)}`; const userMetadata = `${displayName} first joined ${formatTimestamp(
createdAt
)}`;
const isSystemMessage = message.type === SOCKET_MESSAGE_TYPES.SYSTEM; const isSystemMessage = message.type === SOCKET_MESSAGE_TYPES.SYSTEM;
@ -69,7 +71,11 @@ export default class ChatMessageView extends Component {
title=${formattedTimestamp} title=${formattedTimestamp}
> >
<div class="message-content break-words w-full"> <div class="message-content break-words w-full">
<div style=${authorTextColor} class="message-author font-bold" title=${userMetadata}> <div
style=${authorTextColor}
class="message-author font-bold"
title=${userMetadata}
>
${displayName} ${displayName}
</div> </div>
<div <div
@ -90,7 +96,7 @@ function getChatMessageClassString() {
return 'message flex flex-row items-start p-3 m-3 rounded-lg shadow-s text-sm'; return 'message flex flex-row items-start p-3 m-3 rounded-lg shadow-s text-sm';
} }
export async function formatMessageText(message, username) { export async function formatMessageText(message, username) {
let formattedText = getMessageWithEmbeds(message); let formattedText = getMessageWithEmbeds(message);
formattedText = convertToMarkup(formattedText); formattedText = convertToMarkup(formattedText);
return await highlightUsername(formattedText, username); return await highlightUsername(formattedText, username);

View File

@ -253,7 +253,7 @@ export default class Chat extends Component {
const { username } = this.props; const { username } = this.props;
const message = { const message = {
body: content, body: content,
type: SOCKET_MESSAGE_TYPES.CHAT, type: SOCKET_MESSAGE_TYPES.CHAT,
}; };
this.websocket.send(message); this.websocket.send(message);
} }

View File

@ -9,54 +9,71 @@ import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
export default function Message(props) { export default function Message(props) {
const { message } = props; const { message } = props;
const { type } = message; const { type } = message;
if (type === SOCKET_MESSAGE_TYPES.CHAT || type === SOCKET_MESSAGE_TYPES.SYSTEM) { if (
type === SOCKET_MESSAGE_TYPES.CHAT ||
type === SOCKET_MESSAGE_TYPES.SYSTEM
) {
return html`<${ChatMessageView} ...${props} />`; return html`<${ChatMessageView} ...${props} />`;
} else if (type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) { } else if (type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) {
const { oldName, user } = message; const { oldName, user } = message;
const { displayName } = user; const { displayName } = user;
return ( return html`
html` <div
<div class="message message-name-change flex items-center justify-start p-3"> class="message message-name-change flex items-center justify-start p-3"
<div class="message-content flex flex-row items-center justify-center text-sm w-full"> >
<div class="text-white text-center opacity-50 overflow-hidden break-words"> <div
<span class="font-bold">${oldName}</span> is now known as <span class="font-bold">${displayName}</span>. class="message-content flex flex-row items-center justify-center text-sm w-full"
</div> >
<div
class="text-white text-center opacity-50 overflow-hidden break-words"
>
<span class="font-bold">${oldName}</span> is now known as
<span class="font-bold">${displayName}</span>.
</div> </div>
</div> </div>
` </div>
); `;
} else if (type === SOCKET_MESSAGE_TYPES.USER_JOINED) { } else if (type === SOCKET_MESSAGE_TYPES.USER_JOINED) {
const { user } = message const { user } = message;
const { displayName } = user; const { displayName } = user;
return ( return html`
html` <div
<div class="message message-user-joined flex items-center justify-start p-3"> class="message message-user-joined flex items-center justify-start p-3"
<div class="message-content flex flex-row items-center justify-center text-sm w-full"> >
<div class="text-white text-center opacity-50 overflow-hidden break-words"> <div
<span class="font-bold">${displayName}</span> joined the chat. class="message-content flex flex-row items-center justify-center text-sm w-full"
</div> >
</div> <div
class="text-white text-center opacity-50 overflow-hidden break-words"
>
<span class="font-bold">${displayName}</span> joined the chat.
</div> </div>
` </div>
); </div>
`;
} else if (type === SOCKET_MESSAGE_TYPES.CHAT_ACTION) { } else if (type === SOCKET_MESSAGE_TYPES.CHAT_ACTION) {
const { body } = message; const { body } = message;
const formattedMessage = `${body}` const formattedMessage = `${body}`;
return ( return html`
html` <div
<div class="message message-user-joined flex items-center justify-start p-3"> class="message message-user-joined flex items-center justify-start p-3"
<div class="message-content flex flex-row items-center justify-center text-sm w-full"> >
<div class="text-white text-center opacity-50 overflow-hidden break-words"> <div
<span dangerouslySetInnerHTML=${{ __html: formattedMessage }}></span> class="message-content flex flex-row items-center justify-center text-sm w-full"
</div> >
</div> <div
class="text-white text-center opacity-50 overflow-hidden break-words"
>
<span
dangerouslySetInnerHTML=${{ __html: formattedMessage }}
></span>
</div> </div>
` </div>
); </div>
`;
} else if (type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) { } else if (type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) {
// noop for now // noop for now
} else { } else {
console.log("Unknown message type:", type); console.log('Unknown message type:', type);
} }
} }

View File

@ -4,11 +4,11 @@ import {
CHAT_PLACEHOLDER_OFFLINE, CHAT_PLACEHOLDER_OFFLINE,
} from './constants.js'; } from './constants.js';
// Taken from https://stackoverflow.com/questions/3972014/get-contenteditable-caret-index-position // Taken from https://stackoverflow.com/questions/3972014/get-contenteditable-caret-index-position
export function getCaretPosition(editableDiv) { export function getCaretPosition(editableDiv) {
var caretPos = 0, var caretPos = 0,
sel, range; sel,
range;
if (window.getSelection) { if (window.getSelection) {
sel = window.getSelection(); sel = window.getSelection();
if (sel.rangeCount) { if (sel.rangeCount) {
@ -20,11 +20,11 @@ export function getCaretPosition(editableDiv) {
} else if (document.selection && document.selection.createRange) { } else if (document.selection && document.selection.createRange) {
range = document.selection.createRange(); range = document.selection.createRange();
if (range.parentElement() == editableDiv) { if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span"); var tempEl = document.createElement('span');
editableDiv.insertBefore(tempEl, editableDiv.firstChild); editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate(); var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl); tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range); tempRange.setEndPoint('EndToEnd', range);
caretPos = tempRange.text.length; caretPos = tempRange.text.length;
} }
} }
@ -44,10 +44,11 @@ export function setCaretPosition(editableDiv, position) {
sel.addRange(range); sel.addRange(range);
} }
export function generatePlaceholderText(isEnabled, hasSentFirstChatMessage) { export function generatePlaceholderText(isEnabled, hasSentFirstChatMessage) {
if (isEnabled) { if (isEnabled) {
return hasSentFirstChatMessage ? CHAT_PLACEHOLDER_TEXT : CHAT_INITIAL_PLACEHOLDER_TEXT; return hasSentFirstChatMessage
? CHAT_PLACEHOLDER_TEXT
: CHAT_INITIAL_PLACEHOLDER_TEXT;
} }
return CHAT_PLACEHOLDER_OFFLINE; return CHAT_PLACEHOLDER_OFFLINE;
} }
@ -112,7 +113,7 @@ export function convertToText(str = '') {
You would call this when a user pastes from You would call this when a user pastes from
the clipboard into a `contenteditable` area. the clipboard into a `contenteditable` area.
*/ */
export function convertOnPaste(event = { preventDefault() { } }, emojiList) { export function convertOnPaste(event = { preventDefault() {} }, emojiList) {
// Prevent paste. // Prevent paste.
event.preventDefault(); event.preventDefault();
@ -149,17 +150,29 @@ export function convertOnPaste(event = { preventDefault() { } }, emojiList) {
export function createEmojiMarkup(data, isCustom) { export function createEmojiMarkup(data, isCustom) {
const emojiUrl = isCustom ? data.emoji : data.url; const emojiUrl = isCustom ? data.emoji : data.url;
const emojiName = (isCustom ? data.name : data.url.split('\\').pop().split('/').pop().split('.').shift()).toLowerCase(); const emojiName = (
return '<img class="emoji" alt=":' + emojiName + ':" title=":' + emojiName + ':" src="' + emojiUrl + '"/>'; isCustom
? data.name
: data.url.split('\\').pop().split('/').pop().split('.').shift()
).toLowerCase();
return (
'<img class="emoji" alt=":' +
emojiName +
':" title=":' +
emojiName +
':" src="' +
emojiUrl +
'"/>'
);
} }
// trim html white space characters from ends of messages for more accurate counting // trim html white space characters from ends of messages for more accurate counting
export function trimNbsp(html) { export function trimNbsp(html) {
return html.replace(/^(?:&nbsp;|\s)+|(?:&nbsp;|\s)+$/ig,''); return html.replace(/^(?:&nbsp;|\s)+|(?:&nbsp;|\s)+$/gi, '');
} }
export function emojify(HTML, emojiList) { export function emojify(HTML, emojiList) {
const textValue = convertToText(HTML) const textValue = convertToText(HTML);
for (var lastPos = textValue.length; lastPos >= 0; lastPos--) { for (var lastPos = textValue.length; lastPos >= 0; lastPos--) {
const endPos = textValue.lastIndexOf(':', lastPos); const endPos = textValue.lastIndexOf(':', lastPos);
@ -176,10 +189,9 @@ export function emojify(HTML, emojiList) {
}); });
if (emojiIndex != -1) { if (emojiIndex != -1) {
const emojiImgElement = createEmojiMarkup(emojiList[emojiIndex], true) const emojiImgElement = createEmojiMarkup(emojiList[emojiIndex], true);
HTML = HTML.replace(":" + typedEmoji + ":", emojiImgElement) HTML = HTML.replace(':' + typedEmoji + ':', emojiImgElement);
} }
} }
return HTML; return HTML;
} }

View File

@ -8,7 +8,9 @@ export const URL_VIEWER_PING = `/api/ping`;
// TODO: This directory is customizable in the config. So we should expose this via the config API. // TODO: This directory is customizable in the config. So we should expose this via the config API.
export const URL_STREAM = `/hls/stream.m3u8`; export const URL_STREAM = `/hls/stream.m3u8`;
export const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`; export const URL_WEBSOCKET = `${
location.protocol === 'https:' ? 'wss' : 'ws'
}://${location.host}/ws`;
export const URL_CHAT_REGISTRATION = `/api/chat/register`; export const URL_CHAT_REGISTRATION = `/api/chat/register`;
export const TIMER_STATUS_UPDATE = 5000; // ms export const TIMER_STATUS_UPDATE = 5000; // ms

View File

@ -21,7 +21,7 @@ export const SOCKET_MESSAGE_TYPES = {
export const CALLBACKS = { export const CALLBACKS = {
RAW_WEBSOCKET_MESSAGE_RECEIVED: 'rawWebsocketMessageReceived', RAW_WEBSOCKET_MESSAGE_RECEIVED: 'rawWebsocketMessageReceived',
WEBSOCKET_CONNECTED: 'websocketConnected', WEBSOCKET_CONNECTED: 'websocketConnected',
} };
const TIMER_WEBSOCKET_RECONNECT = 5000; // ms const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
@ -48,7 +48,7 @@ export default class Websocket {
createAndConnect() { createAndConnect() {
const url = new URL(URL_WEBSOCKET); const url = new URL(URL_WEBSOCKET);
url.searchParams.append('accessToken', this.accessToken); url.searchParams.append('accessToken', this.accessToken);
const ws = new WebSocket(url.toString()); const ws = new WebSocket(url.toString());
ws.onopen = this.onOpen.bind(this); ws.onopen = this.onOpen.bind(this);
ws.onclose = this.onClose.bind(this); ws.onclose = this.onClose.bind(this);
@ -161,10 +161,10 @@ export default class Websocket {
} }
if (!model.type) { if (!model.type) {
console.error("No type provided", model); console.error('No type provided', model);
return; return;
} }
// Send PONGs // Send PONGs
if (model.type === SOCKET_MESSAGE_TYPES.PING) { if (model.type === SOCKET_MESSAGE_TYPES.PING) {
this.sendPong(); this.sendPong();
@ -182,6 +182,8 @@ export default class Websocket {
} }
handleNetworkingError(error) { handleNetworkingError(error) {
console.error(`Websocket Error. Chat is likely not working. Visit troubleshooting steps to resolve. https://owncast.online/docs/troubleshooting/#chat-is-disabled: ${error}`); console.error(
`Websocket Error. Chat is likely not working. Visit troubleshooting steps to resolve. https://owncast.online/docs/troubleshooting/#chat-is-disabled: ${error}`
);
} }
} }