diff --git a/webroot/js/chat/chat.js b/webroot/js/chat/chat.js
index 23cfa3b4d..f5f246aa2 100644
--- a/webroot/js/chat/chat.js
+++ b/webroot/js/chat/chat.js
@@ -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,
@@ -45,10 +127,51 @@ export default class Chat extends Component {
image: image,
};
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 += "
";
+ } 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"
>
-
😏
+
diff --git a/webroot/js/chat/message.js b/webroot/js/chat/message.js
index 90105091a..f6084c9dd 100644
--- a/webroot/js/chat/message.js
+++ b/webroot/js/chat/message.js
@@ -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 {
${oldName} is now known as ${newName}.
- `
- )
+ `);
}
}
}
diff --git a/webroot/js/chat/standalone.js b/webroot/js/chat/standalone.js
index 3489f44ff..e36025044 100644
--- a/webroot/js/chat/standalone.js
+++ b/webroot/js/chat/standalone.js
@@ -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';
diff --git a/webroot/js/emoji.js b/webroot/js/emoji.js
index 33ef36b48..23a212828 100644
--- a/webroot/js/emoji.js
+++ b/webroot/js/emoji.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;
diff --git a/webroot/js/message.js b/webroot/js/message.js
index f3ca460b0..7d14cba2c 100644
--- a/webroot/js/message.js
+++ b/webroot/js/message.js
@@ -20,9 +20,9 @@ class Message {
formatText() {
showdown.setFlavor('github');
let formattedText = new showdown.Converter({
- emoji: true,
- openLinksInNewWindow: true,
- tables: false,
+ emoji: true,
+ openLinksInNewWindow: true,
+ tables: false,
simplifiedAutoLink: false,
literalMidWordUnderscores: true,
strikethrough: true,
@@ -132,8 +132,8 @@ class MessagingInterface {
this.inputMessageAuthor = document.getElementById('self-message-author');
this.inputChangeUserName = document.getElementById('username-change-input');
- this.btnUpdateUserName = document.getElementById('button-update-username');
- this.btnCancelUpdateUsername = document.getElementById('button-cancel-change');
+ this.btnUpdateUserName = document.getElementById('button-update-username');
+ this.btnCancelUpdateUsername = document.getElementById('button-cancel-change');
this.btnSubmitMessage = document.getElementById('button-submit-message');
this.formMessageInput = document.getElementById('message-body-form');
@@ -146,10 +146,10 @@ class MessagingInterface {
// add events
this.tagChatToggle.addEventListener('click', this.handleChatToggle.bind(this));
this.textUserInfoDisplay.addEventListener('click', this.handleShowChangeNameForm.bind(this));
-
+
this.btnUpdateUserName.addEventListener('click', this.handleUpdateUsername.bind(this));
this.btnCancelUpdateUsername.addEventListener('click', this.handleHideChangeNameForm.bind(this));
-
+
this.inputChangeUserName.addEventListener('keydown', this.handleUsernameKeydown.bind(this));
this.formMessageInput.addEventListener('keydown', this.handleMessageInputKeydown.bind(this));
this.formMessageInput.addEventListener('keyup', this.handleMessageInputKeyup.bind(this));
@@ -194,7 +194,7 @@ class MessagingInterface {
this.setChatPlaceholderText();
}
-
+
handleChatToggle() {
this.chatDisplayed = !this.chatDisplayed;
if (this.chatDisplayed) {
@@ -305,7 +305,7 @@ class MessagingInterface {
this.submitChat(value);
event.preventDefault();
this.prepNewLine = false;
-
+
return;
}
}
@@ -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 ``;
-}
-
-function getInstagramEmbedFromURL(url) {
- const urlObject = new URL(url.replace(/\/$/, ""));
- urlObject.pathname += "/embed";
- return ``;
-}
-
-function isImage(url) {
- const re = /\.(jpe?g|png|gif)$/;
- const isImage = re.test(url);
- return isImage;
-}
-
-function getImageForURL(url) {
- return `
`;
-}
-
-
-// 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);
-}
\ No newline at end of file
diff --git a/webroot/js/utils.js b/webroot/js/utils.js
index ef388da94..26da002e1 100644
--- a/webroot/js/utils.js
+++ b/webroot/js/utils.js
@@ -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';
diff --git a/webroot/js/websocket.js b/webroot/js/websocket.js
index 6c628d6e5..b1ba7dae1 100644
--- a/webroot/js/websocket.js
+++ b/webroot/js/websocket.js
@@ -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',
@@ -42,7 +42,7 @@ class Websocket {
}
}
-
+
// Interface with other components
// Outbound: Other components can pass an object to `send`.
@@ -51,7 +51,7 @@ class Websocket {
if (!message.type || !SOCKET_MESSAGE_TYPES[message.type]) {
console.warn(`Outbound message: Unknown socket message type: "${message.type}" sent.`);
}
-
+
const messageJSON = JSON.stringify(message);
this.websocket.send(messageJSON);
}
@@ -114,7 +114,7 @@ class Websocket {
} catch (e) {
console.log(e)
}
-
+
// Send PONGs
if (model.type === SOCKET_MESSAGE_TYPES.PING) {
this.sendPong();
@@ -136,4 +136,4 @@ class Websocket {
};
}
-export default Websocket;
\ No newline at end of file
+export default Websocket;