mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
set up websocket and emoji in chat component
This commit is contained in:
parent
7a1512ef6b
commit
3814c24cab
@ -1,10 +1,16 @@
|
||||
import { h, Component, render } from 'https://unpkg.com/preact?module';
|
||||
import { h, Component } from 'https://unpkg.com/preact?module';
|
||||
import htm from 'https://unpkg.com/htm?module';
|
||||
// Initialize htm with Preact
|
||||
const html = htm.bind(h);
|
||||
|
||||
import { EmojiButton } from 'https://cdn.skypack.dev/@joeattardi/emoji-button';
|
||||
|
||||
|
||||
import SOCKET_MESSAGE_TYPES from '../utils/socket-message-types.js';
|
||||
import Message from './message.js';
|
||||
import Websocket, { CALLBACKS } from '../websocket.js';
|
||||
|
||||
import { URL_CHAT_HISTORY, URL_CUSTOM_EMOJIS } from '../utils.js';
|
||||
|
||||
export default class Chat extends Component {
|
||||
constructor(props, context) {
|
||||
@ -21,9 +27,27 @@ export default class Chat extends Component {
|
||||
chatUserNames: [],
|
||||
}
|
||||
|
||||
this.emojiPicker = null;
|
||||
this.websocket = null;
|
||||
|
||||
|
||||
this.handleEmojiButtonClick = this.handleEmojiButtonClick.bind(this);
|
||||
this.handleEmojiSelected = this.handleEmojiSelected.bind(this);
|
||||
this.getCustomEmojis = this.getCustomEmojis.bind(this);
|
||||
this.getChatHistory = this.getChatHistory.bind(this);
|
||||
this.receivedWebsocketMessage = this.receivedWebsocketMessage.bind(this);
|
||||
this.websocketDisconnected = this.websocketDisconnected.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
/*
|
||||
- set up websocket
|
||||
- get emojis
|
||||
- get chat history
|
||||
*/
|
||||
this.setupWebSocket();
|
||||
this.getChatHistory();
|
||||
this.getCustomEmojis();
|
||||
|
||||
}
|
||||
|
||||
@ -37,6 +61,64 @@ export default class Chat extends Component {
|
||||
|
||||
}
|
||||
|
||||
setupWebSocket() {
|
||||
this.websocket = new Websocket();
|
||||
this.websocket.addListener(CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, this.receivedWebsocketMessage);
|
||||
this.websocket.addListener(CALLBACKS.WEBSOCKET_DISCONNECTED, this.websocketDisconnected);
|
||||
}
|
||||
|
||||
// fetch chat history
|
||||
getChatHistory() {
|
||||
fetch(URL_CHAT_HISTORY)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok ${response.ok}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
this.setState({
|
||||
messages: data,
|
||||
});
|
||||
// const formattedMessages = data.map(function (message) {
|
||||
// return new Message(message);
|
||||
// })
|
||||
// this.vueApp.messages = formattedMessages.concat(this.vueApp.messages);
|
||||
})
|
||||
.catch(error => {
|
||||
this.handleNetworkingError(`Fetch getChatHistory: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
getCustomEmojis() {
|
||||
fetch(URL_CUSTOM_EMOJIS)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok ${response.ok}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(json => {
|
||||
this.emojiPicker = new EmojiButton({
|
||||
zIndex: 100,
|
||||
theme: 'dark',
|
||||
custom: json,
|
||||
initialCategory: 'custom',
|
||||
showPreview: false,
|
||||
position: {
|
||||
top: '50%',
|
||||
right: '100'
|
||||
},
|
||||
});
|
||||
this.emojiPicker.on('emoji', emoji => {
|
||||
this.handleEmojiSelected(emoji);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.handleNetworkingError(`Emoji Fetch: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
sendUsernameChange(oldName, newName, image) {
|
||||
const nameChange = {
|
||||
type: SOCKET_MESSAGE_TYPES.NAME_CHANGE,
|
||||
@ -47,8 +129,49 @@ export default class Chat extends Component {
|
||||
this.send(nameChange);
|
||||
}
|
||||
|
||||
handleEmojiButtonClick() {
|
||||
if (this.emojiPicker) {
|
||||
this.emojiPicker.togglePicker(this.picker);
|
||||
}
|
||||
}
|
||||
|
||||
handleEmojiSelected(emoji) {
|
||||
if (emoji.url) {
|
||||
const url = location.protocol + "//" + location.host + "/" + emoji.url;
|
||||
const name = url.split('\\').pop().split('/').pop();
|
||||
document.querySelector('#message-body-form').innerHTML += "<img class=\"emoji\" alt=\"" + name + "\" src=\"" + url + "\"/>";
|
||||
} else {
|
||||
document.querySelector('#message-body-form').innerHTML += emoji.emoji;
|
||||
}
|
||||
}
|
||||
|
||||
receivedWebsocketMessage(message) {
|
||||
this.addMessage(message);
|
||||
// if (model.type === SOCKET_MESSAGE_TYPES.CHAT) {
|
||||
// const message = new Message(model);
|
||||
// this.addMessage(message);
|
||||
// } else if (model.type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) {
|
||||
// this.addMessage(model);
|
||||
// }
|
||||
}
|
||||
|
||||
addMessage(message) {
|
||||
const { messages: curMessages } = this.state;
|
||||
const existing = curMessages.filter(function (item) {
|
||||
return item.id === message.id;
|
||||
})
|
||||
if (existing.length === 0 || !existing) {
|
||||
this.setState({
|
||||
messages: [...curMessages, message],
|
||||
});
|
||||
}
|
||||
}
|
||||
websocketDisconnected() {
|
||||
this.websocket = null;
|
||||
}
|
||||
|
||||
render(props, state) {
|
||||
const { username, userAvatarImage } = props;
|
||||
const { username } = props;
|
||||
const { messages } = state;
|
||||
|
||||
return (
|
||||
@ -74,7 +197,10 @@ export default class Chat extends Component {
|
||||
placeholder="Message"
|
||||
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-black-500 rounded py-2 px-2 my-2 focus:bg-white"
|
||||
></textarea>
|
||||
<div id="emoji-button">😏</div>
|
||||
<button
|
||||
id="emoji-button"
|
||||
onClick=${this.handleEmojiButtonClick}
|
||||
>😏</button>
|
||||
|
||||
<div id="message-form-actions" class="flex">
|
||||
<span id="message-form-warning" class="text-red-600 text-xs"></span>
|
||||
|
@ -1,7 +1,4 @@
|
||||
import { h, Component, createRef } from 'https://unpkg.com/preact?module';
|
||||
import htm from 'https://unpkg.com/htm?module';
|
||||
// Initialize htm with Preact
|
||||
const html = htm.bind(h);
|
||||
import { html, Component } from "https://unpkg.com/htm/preact/index.mjs?module";
|
||||
|
||||
import { messageBubbleColorForString } from '../utils/user-colors.js';
|
||||
import { formatMessageText } from '../utils/chat.js';
|
||||
@ -14,7 +11,7 @@ export default class Message extends Component {
|
||||
const { type } = message;
|
||||
|
||||
if (type === SOCKET_MESSAGE_TYPES.CHAT) {
|
||||
const { image, author, body, type } = message;
|
||||
const { image, author, body } = message;
|
||||
const formattedMessage = formatMessageText(body);
|
||||
const avatar = image || generateAvatar(author);
|
||||
const avatarBgColor = { backgroundColor: messageBubbleColorForString(author) };
|
||||
@ -43,8 +40,7 @@ export default class Message extends Component {
|
||||
<span class="font-bold">${oldName}</span> is now known as <span class="font-bold">${newName}</span>.
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,4 @@
|
||||
import { html, Component } from "https://unpkg.com/htm/preact/index.mjs?module";
|
||||
|
||||
// import { h, Component, render } from 'https://unpkg.com/preact?module';
|
||||
// import htm from 'https://unpkg.com/htm?module';
|
||||
// Initialize htm with Preact
|
||||
// const html = htm.bind(h);
|
||||
|
||||
import UserInfo from './user-info.js';
|
||||
import Chat from './chat.js';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
// DEPRECATE.
|
||||
import { EmojiButton } from 'https://cdn.skypack.dev/@joeattardi/emoji-button'
|
||||
|
||||
fetch('/emoji')
|
||||
@ -29,6 +30,7 @@ function setupEmojiPickerWithCustomEmoji(customEmoji) {
|
||||
const trigger = document.querySelector('#emoji-button');
|
||||
|
||||
trigger.addEventListener('click', () => picker.togglePicker(picker));
|
||||
|
||||
picker.on('emoji', emoji => {
|
||||
if (emoji.url) {
|
||||
const url = location.protocol + "//" + location.host + "/" + emoji.url;
|
||||
|
@ -436,87 +436,3 @@ class MessagingInterface {
|
||||
}
|
||||
|
||||
export { Message, MessagingInterface }
|
||||
|
||||
function stripTags(str) {
|
||||
return str.replace(/<\/?[^>]+(>|$)/g, "");
|
||||
}
|
||||
|
||||
function getURLs(str) {
|
||||
var exp = /((\w+:\/\/\S+)|(\w+[\.:]\w+\S+))[^\s,\.]/ig;
|
||||
return str.match(exp);
|
||||
}
|
||||
|
||||
function getYoutubeIdFromURL(url) {
|
||||
try {
|
||||
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
||||
var match = url.match(regExp);
|
||||
|
||||
if (match && match[2].length == 11) {
|
||||
return match[2];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getYoutubeEmbedFromID(id) {
|
||||
return `<iframe class="chat-embed" src="//www.youtube.com/embed/${id}" frameborder="0" allowfullscreen></iframe>`;
|
||||
}
|
||||
|
||||
function getInstagramEmbedFromURL(url) {
|
||||
const urlObject = new URL(url.replace(/\/$/, ""));
|
||||
urlObject.pathname += "/embed";
|
||||
return `<iframe class="chat-embed instagram-embed" height="150px" src="${urlObject.href}" frameborder="0" allowfullscreen></iframe>`;
|
||||
}
|
||||
|
||||
function isImage(url) {
|
||||
const re = /\.(jpe?g|png|gif)$/;
|
||||
const isImage = re.test(url);
|
||||
return isImage;
|
||||
}
|
||||
|
||||
function getImageForURL(url) {
|
||||
return `<a target="_blank" href="${url}"><img class="embedded-image" src="${url}" width="100%" height="150px"/></a>`;
|
||||
}
|
||||
|
||||
|
||||
// Taken from https://stackoverflow.com/questions/3972014/get-contenteditable-caret-index-position
|
||||
function getCaretPosition(editableDiv) {
|
||||
var caretPos = 0,
|
||||
sel, range;
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
if (sel.rangeCount) {
|
||||
range = sel.getRangeAt(0);
|
||||
if (range.commonAncestorContainer.parentNode == editableDiv) {
|
||||
caretPos = range.endOffset;
|
||||
}
|
||||
}
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
range = document.selection.createRange();
|
||||
if (range.parentElement() == editableDiv) {
|
||||
var tempEl = document.createElement("span");
|
||||
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
|
||||
var tempRange = range.duplicate();
|
||||
tempRange.moveToElementText(tempEl);
|
||||
tempRange.setEndPoint("EndToEnd", range);
|
||||
caretPos = tempRange.text.length;
|
||||
}
|
||||
}
|
||||
return caretPos;
|
||||
}
|
||||
|
||||
// Pieced together from parts of https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div
|
||||
function setCaretPosition(editableDiv, position) {
|
||||
var range = document.createRange();
|
||||
var sel = window.getSelection();
|
||||
range.selectNode(editableDiv);
|
||||
range.setStart(editableDiv.childNodes[0], position);
|
||||
range.collapse(true);
|
||||
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
|
||||
const URL_STATUS = `/status`;
|
||||
const URL_CHAT_HISTORY = `/chat`;
|
||||
export const URL_STATUS = `/status`;
|
||||
export const URL_CHAT_HISTORY = `/chat`;
|
||||
export const URL_CUSTOM_EMOJIS = `/emoji`;
|
||||
// TODO: This directory is customizable in the config. So we should expose this via the config API.
|
||||
const URL_STREAM = `/hls/stream.m3u8`;
|
||||
const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
|
||||
export const URL_STREAM = `/hls/stream.m3u8`;
|
||||
export const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
|
||||
|
||||
const POSTER_DEFAULT = `/img/logo.png`;
|
||||
const POSTER_THUMB = `/thumbnail.jpg`;
|
||||
export const POSTER_DEFAULT = `/img/logo.png`;
|
||||
export const POSTER_THUMB = `/thumbnail.jpg`;
|
||||
|
||||
export const URL_OWNCAST = 'https://github.com/gabek/owncast'; // used in footer
|
||||
|
||||
@ -126,5 +126,3 @@ export function setVHvar() {
|
||||
export function doesObjectSupportFunction(object, functionName) {
|
||||
return typeof object[functionName] === "function";
|
||||
}
|
||||
|
||||
const DEFAULT_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
|
@ -4,7 +4,7 @@ const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${loca
|
||||
|
||||
const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
|
||||
|
||||
const CALLBACKS = {
|
||||
export const CALLBACKS = {
|
||||
RAW_WEBSOCKET_MESSAGE_RECEIVED: 'rawWebsocketMessageReceived',
|
||||
WEBSOCKET_CONNECTED: 'websocketConnected',
|
||||
WEBSOCKET_DISCONNECTED: 'websocketDisconnected',
|
||||
|
Loading…
x
Reference in New Issue
Block a user