diff --git a/webroot/js/app2.js b/webroot/js/app2.js
index 35e6bd6bd..d0151b8f8 100644
--- a/webroot/js/app2.js
+++ b/webroot/js/app2.js
@@ -2,12 +2,11 @@ import { h, Component } from 'https://unpkg.com/preact?module';
import htm from 'https://unpkg.com/htm?module';
const html = htm.bind(h);
-
-import SocialIcon from './social.js';
-import UsernameForm from './chat/username.js';
-import Chat from './chat/chat.js';
-import Websocket from './websocket.js';
-import { OwncastPlayer } from './player.js';
+import { OwncastPlayer } from './components/player.js';
+import SocialIcon from './components/social.js';
+import UsernameForm from './components/chat/username.js';
+import Chat from './components/chat/chat.js';
+import Websocket from './utils/websocket.js';
import {
getLocalStorage,
@@ -15,19 +14,23 @@ import {
clearLocalStorage,
generateAvatar,
generateUsername,
+ addNewlines,
+ pluralize,
+} from './utils/helpers.js';
+import {
URL_OWNCAST,
URL_CONFIG,
URL_STATUS,
- addNewlines,
- pluralize,
TIMER_STATUS_UPDATE,
TIMER_DISABLE_CHAT_AFTER_OFFLINE,
TIMER_STREAM_DURATION_COUNTER,
TEMP_IMAGE,
MESSAGE_OFFLINE,
MESSAGE_ONLINE,
-} from './utils.js';
-import { KEY_USERNAME, KEY_AVATAR, KEY_CHAT_DISPLAYED } from './utils/chat.js';
+ KEY_USERNAME,
+ KEY_AVATAR,
+ KEY_CHAT_DISPLAYED,
+} from './utils/constants.js';
export default class App extends Component {
constructor(props, context) {
diff --git a/webroot/js/components.js b/webroot/js/components.js
deleted file mode 100644
index 959e2aa1d..000000000
--- a/webroot/js/components.js
+++ /dev/null
@@ -1,64 +0,0 @@
-// DELETE THIS FILE LATER.
-
-Vue.component('owncast-footer', {
- props: {
- appVersion: {
- type: String,
- default: '0.1',
- },
- },
-
- template: `
-
- `,
-});
-
-
-Vue.component('stream-tags', {
- props: ['tags'],
- template: `
-
- `,
-});
-
-Vue.component('user-details', {
- props: ['logo', 'platforms', 'summary', 'tags'],
- template: `
-
-
-
![Logo]()
-
-
-
- `,
-});
diff --git a/webroot/js/chat/chat-input.js b/webroot/js/components/chat/chat-input.js
similarity index 97%
rename from webroot/js/chat/chat-input.js
rename to webroot/js/components/chat/chat-input.js
index 0a8d3717f..fd472626b 100644
--- a/webroot/js/chat/chat-input.js
+++ b/webroot/js/components/chat/chat-input.js
@@ -3,15 +3,10 @@ import htm from 'https://unpkg.com/htm?module';
const html = htm.bind(h);
import { EmojiButton } from 'https://cdn.skypack.dev/@joeattardi/emoji-button';
-
-import { URL_CUSTOM_EMOJIS, getLocalStorage, setLocalStorage } from '../utils.js';
-import {
- KEY_CHAT_FIRST_MESSAGE_SENT,
- generatePlaceholderText,
- getCaretPosition,
-} from '../utils/chat.js';
-
import ContentEditable from './content-editable.js';
+import { generatePlaceholderText, getCaretPosition } from '../../utils/chat.js';
+import { getLocalStorage, setLocalStorage } from '../../utils/helpers.js';
+import { URL_CUSTOM_EMOJIS, KEY_CHAT_FIRST_MESSAGE_SENT } from '../../utils/constants.js';
export default class ChatInput extends Component {
diff --git a/webroot/js/chat/chat.js b/webroot/js/components/chat/chat.js
similarity index 94%
rename from webroot/js/chat/chat.js
rename to webroot/js/components/chat/chat.js
index b248c48d1..119f3ea26 100644
--- a/webroot/js/chat/chat.js
+++ b/webroot/js/components/chat/chat.js
@@ -3,15 +3,12 @@ import htm from 'https://unpkg.com/htm?module';
// Initialize htm with Preact
const html = htm.bind(h);
-
-import SOCKET_MESSAGE_TYPES from '../utils/socket-message-types.js';
import Message from './message.js';
import ChatInput from './chat-input.js';
-import { CALLBACKS } from '../websocket.js';
-
-
-import { URL_CHAT_HISTORY, setVHvar, hasTouchScreen } from '../utils.js';
-import { extraUserNamesFromMessageHistory } from '../utils/chat.js';
+import { CALLBACKS, SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
+import { setVHvar, hasTouchScreen } from '../../utils/helpers.js';
+import { extraUserNamesFromMessageHistory } from '../../utils/chat.js';
+import { URL_CHAT_HISTORY } from '../../utils/constants.js';
export default class Chat extends Component {
constructor(props, context) {
@@ -199,3 +196,4 @@ export default class Chat extends Component {
}
}
+
diff --git a/webroot/js/chat/content-editable.js b/webroot/js/components/chat/content-editable.js
similarity index 99%
rename from webroot/js/chat/content-editable.js
rename to webroot/js/components/chat/content-editable.js
index 40514b8b0..08c41cf19 100644
--- a/webroot/js/chat/content-editable.js
+++ b/webroot/js/components/chat/content-editable.js
@@ -6,7 +6,6 @@ and here:
https://stackoverflow.com/questions/22677931/react-js-onchange-event-for-contenteditable/27255103#27255103
*/
-
import { Component, createRef, createElement } from 'https://unpkg.com/preact?module';
function replaceCaret(el) {
diff --git a/webroot/js/chat/message.js b/webroot/js/components/chat/message.js
similarity index 88%
rename from webroot/js/chat/message.js
rename to webroot/js/components/chat/message.js
index 78cecfe81..b4e10f53f 100644
--- a/webroot/js/chat/message.js
+++ b/webroot/js/components/chat/message.js
@@ -1,9 +1,9 @@
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';
-import { generateAvatar } from '../utils.js';
-import SOCKET_MESSAGE_TYPES from '../utils/socket-message-types.js';
+import { messageBubbleColorForString } from '../../utils/user-colors.js';
+import { formatMessageText } from '../../utils/chat.js';
+import { generateAvatar } from '../../utils/helpers.js';
+import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
export default class Message extends Component {
render(props) {
diff --git a/webroot/js/chat/username.js b/webroot/js/components/chat/username.js
similarity index 96%
rename from webroot/js/chat/username.js
rename to webroot/js/components/chat/username.js
index bb67ba260..013b2abe6 100644
--- a/webroot/js/chat/username.js
+++ b/webroot/js/components/chat/username.js
@@ -3,8 +3,8 @@ import htm from 'https://unpkg.com/htm?module';
// Initialize htm with Preact
const html = htm.bind(h);
-import { generateAvatar, setLocalStorage } from '../utils.js';
-import { KEY_USERNAME, KEY_AVATAR } from '../utils/chat.js';
+import { generateAvatar, setLocalStorage } from '../../utils/helpers.js';
+import { KEY_USERNAME, KEY_AVATAR } from '../../utils/constants.js';
export default class UsernameForm extends Component {
constructor(props, context) {
diff --git a/webroot/js/player.js b/webroot/js/components/player.js
similarity index 100%
rename from webroot/js/player.js
rename to webroot/js/components/player.js
diff --git a/webroot/js/social.js b/webroot/js/components/social.js
similarity index 91%
rename from webroot/js/social.js
rename to webroot/js/components/social.js
index dc46920a2..0bdf94102 100644
--- a/webroot/js/social.js
+++ b/webroot/js/components/social.js
@@ -1,6 +1,6 @@
import { html } from "https://unpkg.com/htm/preact/index.mjs?module";
-import { SOCIAL_PLATFORMS } from './utils/social.js';
-import { classNames } from './utils.js';
+import { SOCIAL_PLATFORMS } from '../utils/social.js';
+import { classNames } from '../utils/helpers.js';
export default function SocialIcon(props) {
const { platform, url } = props;
diff --git a/webroot/js/message.js b/webroot/js/message.js
deleted file mode 100644
index 65733bd27..000000000
--- a/webroot/js/message.js
+++ /dev/null
@@ -1,440 +0,0 @@
-// DELETE THIS FILE LATER.
-
-import SOCKET_MESSAGE_TYPES from './utils/socket-message-types.js';
-
-const KEY_USERNAME = 'owncast_username';
-const KEY_AVATAR = 'owncast_avatar';
-const KEY_CHAT_DISPLAYED = 'owncast_chat';
-const KEY_CHAT_FIRST_MESSAGE_SENT = 'owncast_first_message_sent';
-const CHAT_INITIAL_PLACEHOLDER_TEXT = 'Type here to chat, no account necessary.';
-const CHAT_PLACEHOLDER_TEXT = 'Message';
-const CHAT_PLACEHOLDER_OFFLINE = 'Chat is offline.';
-
-class Message {
- constructor(model) {
- this.author = model.author;
- this.body = model.body;
- this.image = model.image || generateAvatar(model.author);
- this.id = model.id;
- this.type = model.type;
- }
-
- formatText() {
- showdown.setFlavor('github');
- let formattedText = new showdown.Converter({
- emoji: true,
- openLinksInNewWindow: true,
- tables: false,
- simplifiedAutoLink: false,
- literalMidWordUnderscores: true,
- strikethrough: true,
- ghMentions: false,
- }).makeHtml(this.body);
-
- formattedText = this.linkify(formattedText, this.body);
- formattedText = this.highlightUsername(formattedText);
-
- return addNewlines(formattedText);
- }
-
- // TODO: Move this into a util function once we can organize code
- // and split things up.
- linkify(text, rawText) {
- const urls = getURLs(stripTags(rawText));
- if (urls) {
- urls.forEach(function (url) {
- let linkURL = url;
-
- // Add http prefix if none exist in the URL so it actually
- // will work in an anchor tag.
- if (linkURL.indexOf('http') === -1) {
- linkURL = 'http://' + linkURL;
- }
-
- // Remove the protocol prefix in the display URLs just to make
- // things look a little nicer.
- const displayURL = url.replace(/(^\w+:|^)\/\//, '');
- const link = `${displayURL}`;
- text = text.replace(url, link);
-
- if (getYoutubeIdFromURL(url)) {
- if (this.isTextJustURLs(text, [url, displayURL])) {
- text = '';
- } else {
- text += '
';
- }
-
- const youtubeID = getYoutubeIdFromURL(url);
- text += getYoutubeEmbedFromID(youtubeID);
- } else if (url.indexOf('instagram.com/p/') > -1) {
- if (this.isTextJustURLs(text, [url, displayURL])) {
- text = '';
- } else {
- text += `
`;
- }
- text += getInstagramEmbedFromURL(url);
- } else if (isImage(url)) {
- if (this.isTextJustURLs(text, [url, displayURL])) {
- text = '';
- } else {
- text += `
`;
- }
- text += getImageForURL(url);
- }
- }.bind(this));
- }
- return text;
- }
-
- isTextJustURLs(text, urls) {
- for (var i = 0; i < urls.length; i++) {
- const url = urls[i];
- if (stripTags(text) === url) {
- return true;
- }
- }
-
- return false;
- }
-
- userColor() {
- return messageBubbleColorForString(this.author);
- }
-
- highlightUsername(message) {
- const username = document.getElementById('self-message-author').value;
- const pattern = new RegExp('@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
- return message.replace(pattern, '$&');
- }
-}
-
-
-
-class MessagingInterface {
- constructor() {
- this.chatDisplayed = false;
- this.username = '';
- this.messageCharCount = 0;
- this.maxMessageLength = 500;
- this.maxMessageBuffer = 20;
- this.chatUsernames = [];
-
- this.onReceivedMessages = this.onReceivedMessages.bind(this);
- this.disableChat = this.disableChat.bind(this);
- this.enableChat = this.enableChat.bind(this);
- }
-
- init() {
- this.tagAppContainer = document.getElementById('app-container');
- this.tagChatToggle = document.getElementById('chat-toggle');
- this.tagUserInfoChanger = document.getElementById('user-info-change');
- this.tagUsernameDisplay = document.getElementById('username-display');
- this.tagMessageFormWarning = document.getElementById('message-form-warning');
-
- 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.btnSubmitMessage = document.getElementById('button-submit-message');
-
- this.formMessageInput = document.getElementById('message-body-form');
-
- this.imgUsernameAvatar = document.getElementById('username-avatar');
- this.textUserInfoDisplay = document.getElementById('user-info-display');
-
- this.scrollableMessagesContainer = document.getElementById('messages-container');
-
- // 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));
- this.formMessageInput.addEventListener('blur', this.handleMessageInputBlur.bind(this));
- this.btnSubmitMessage.addEventListener('click', this.handleSubmitChatButton.bind(this));
-
- this.initLocalStates();
-
- if (hasTouchScreen()) {
- setVHvar();
- window.addEventListener("orientationchange", setVHvar);
- this.tagAppContainer.classList.add('touch-screen');
- }
- }
-
- initLocalStates() {
- this.username = getLocalStorage(KEY_USERNAME) || generateUsername();
- this.imgUsernameAvatar.src =
- getLocalStorage(KEY_AVATAR) || generateAvatar(`${this.username}${Date.now()}`);
- this.updateUsernameFields(this.username);
-
- this.chatDisplayed = getLocalStorage(KEY_CHAT_DISPLAYED) || true;
- this.displayChat();
- this.disableChat(); // Disabled by default.
- }
-
- updateUsernameFields(username) {
- this.tagUsernameDisplay.innerText = username;
- this.inputChangeUserName.value = username;
- this.inputMessageAuthor.value = username;
- }
-
- displayChat() {
- if (this.chatDisplayed) {
- this.tagAppContainer.classList.add('chat');
- this.tagAppContainer.classList.remove('no-chat');
- jumpToBottom(this.scrollableMessagesContainer);
- } else {
- this.tagAppContainer.classList.add('no-chat');
- this.tagAppContainer.classList.remove('chat');
- }
- this.setChatPlaceholderText();
- }
-
-
- handleChatToggle() {
- this.chatDisplayed = !this.chatDisplayed;
- if (this.chatDisplayed) {
- setLocalStorage(KEY_CHAT_DISPLAYED, this.chatDisplayed);
- } else {
- clearLocalStorage(KEY_CHAT_DISPLAYED);
- }
- this.displayChat();
- }
-
- handleShowChangeNameForm() {
- this.textUserInfoDisplay.style.display = 'none';
- this.tagUserInfoChanger.style.display = 'flex';
- if (document.body.clientWidth < 640) {
- this.tagChatToggle.style.display = 'none';
- }
- }
-
- handleHideChangeNameForm() {
- this.textUserInfoDisplay.style.display = 'flex';
- this.tagUserInfoChanger.style.display = 'none';
- if (document.body.clientWidth < 640) {
- this.tagChatToggle.style.display = 'inline-block';
- }
- }
-
- handleUpdateUsername() {
- const oldName = this.username;
- var newValue = this.inputChangeUserName.value;
- newValue = newValue.trim();
- // do other string cleanup?
-
- if (newValue) {
- this.username = newValue;
- this.updateUsernameFields(newValue);
- this.imgUsernameAvatar.src = generateAvatar(`${newValue}${Date.now()}`);
- setLocalStorage(KEY_USERNAME, newValue);
- setLocalStorage(KEY_AVATAR, this.imgUsernameAvatar.src);
- }
- this.handleHideChangeNameForm();
-
- if (oldName !== newValue) {
- this.sendUsernameChange(oldName, newValue, this.imgUsernameAvatar.src);
- }
- }
-
- handleUsernameKeydown(event) {
- if (event.keyCode === 13) { // enter
- this.handleUpdateUsername();
- } else if (event.keyCode === 27) { // esc
- this.handleHideChangeNameForm();
- }
- }
-
- sendUsernameChange(oldName, newName, image) {
- const nameChange = {
- type: SOCKET_MESSAGE_TYPES.NAME_CHANGE,
- oldName: oldName,
- newName: newName,
- image: image,
- };
-
- this.send(nameChange);
- }
-
- tryToComplete() {
- const rawValue = this.formMessageInput.innerHTML;
- const position = getCaretPosition(this.formMessageInput);
- const at = rawValue.lastIndexOf('@', position - 1);
-
- if (at === -1) {
- return false;
- }
-
- var partial = rawValue.substring(at + 1, position).trim();
-
- if (partial === this.suggestion) {
- partial = this.partial;
- } else {
- this.partial = partial;
- }
-
- const possibilities = this.chatUsernames.filter(function (username) {
- return username.toLowerCase().startsWith(partial.toLowerCase());
- });
-
- if (this.completionIndex === undefined || ++this.completionIndex >= possibilities.length) {
- this.completionIndex = 0;
- }
-
- if (possibilities.length > 0) {
- this.suggestion = possibilities[this.completionIndex];
-
- // TODO: Fix the space not working. I'm guessing because the DOM ignores spaces and it requires a nbsp or something?
- this.formMessageInput.innerHTML = rawValue.substring(0, at + 1) + this.suggestion + ' ' + rawValue.substring(position);
- setCaretPosition(this.formMessageInput, at + this.suggestion.length + 2);
- }
-
- return true;
- }
-
- handleMessageInputKeydown(event) {
- var okCodes = [37,38,39,40,16,91,18,46,8];
- var value = this.formMessageInput.innerHTML.trim();
- var numCharsLeft = this.maxMessageLength - value.length;
- if (event.keyCode === 13) { // enter
- if (!this.prepNewLine) {
- this.submitChat(value);
- event.preventDefault();
- this.prepNewLine = false;
-
- return;
- }
- }
- if (event.keyCode === 16 || event.keyCode === 17) { // ctrl, shift
- this.prepNewLine = true;
- }
- if (event.keyCode === 9) { // tab
- if (this.tryToComplete()) {
- event.preventDefault();
-
- // value could have been changed, update variables
- value = this.formMessageInput.innerHTML.trim();
- numCharsLeft = this.maxMessageLength - value.length;
- }
- }
-
- if (numCharsLeft <= this.maxMessageBuffer) {
- this.tagMessageFormWarning.innerText = `${numCharsLeft} chars left`;
- if (numCharsLeft <= 0 && !okCodes.includes(event.keyCode)) {
- event.preventDefault();
- return;
- }
- } else {
- this.tagMessageFormWarning.innerText = '';
- }
- }
-
- handleMessageInputKeyup(event) {
- if (event.keyCode === 16 || event.keyCode === 17) { // ctrl, shift
- this.prepNewLine = false;
- }
- }
-
- handleMessageInputBlur(event) {
- this.prepNewLine = false;
- }
-
- handleSubmitChatButton(event) {
- var value = this.formMessageInput.innerHTML.trim();
- if (value) {
- this.submitChat(value);
- event.preventDefault();
- return false;
- }
- event.preventDefault();
- return false;
- }
-
- submitChat(content) {
- if (!content) {
- return;
- }
- var message = new Message({
- body: content,
- author: this.username,
- image: this.imgUsernameAvatar.src,
- type: SOCKET_MESSAGE_TYPES.CHAT,
- });
- this.send(message);
-
- // clear out things.
- this.formMessageInput.innerHTML = '';
- this.tagMessageFormWarning.innerText = '';
-
- const hasSentFirstChatMessage = getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT);
- if (!hasSentFirstChatMessage) {
- setLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT, true);
- this.setChatPlaceholderText();
- }
- }
-
- disableChat() {
- if (this.formMessageInput) {
- this.formMessageInput.contentEditable = false;
- this.formMessageInput.innerHTML = '';
- this.formMessageInput.setAttribute("placeholder", CHAT_PLACEHOLDER_OFFLINE);
- }
- }
-
- enableChat() {
- if (this.formMessageInput) {
- this.formMessageInput.contentEditable = true;
- this.setChatPlaceholderText();
- }
- }
-
- setChatPlaceholderText() {
- // NOTE: This is a fake placeholder that is being styled via CSS.
- // You can't just set the .placeholder property because it's not a form element.
- const hasSentFirstChatMessage = getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT);
- const placeholderText = hasSentFirstChatMessage ? CHAT_PLACEHOLDER_TEXT : CHAT_INITIAL_PLACEHOLDER_TEXT;
- this.formMessageInput.setAttribute("placeholder", placeholderText);
- }
-
- // handle Vue.js message display
- onReceivedMessages(newMessages, oldMessages) {
- // update the list of chat usernames
- newMessages.slice(oldMessages.length).forEach(function (message) {
- var username;
-
- switch (message.type) {
- case SOCKET_MESSAGE_TYPES.CHAT:
- username = message.author;
- break;
-
- case SOCKET_MESSAGE_TYPES.NAME_CHANGE:
- username = message.newName;
- break;
-
- default:
- return;
- }
-
- if (!this.chatUsernames.includes(username)) {
- this.chatUsernames.push(username);
- }
- }, this);
-
- if (newMessages.length !== oldMessages.length) {
- // jump to bottom
- jumpToBottom(this.scrollableMessagesContainer);
- }
- }
-
- send(messageJSON) {
- console.error('MessagingInterface send() is not linked to the websocket component.');
- }
-}
-
-export { Message, MessagingInterface }
diff --git a/webroot/js/chat/standalone.js b/webroot/js/standalone-chat-app.js
similarity index 88%
rename from webroot/js/chat/standalone.js
rename to webroot/js/standalone-chat-app.js
index 115982d63..221083466 100644
--- a/webroot/js/chat/standalone.js
+++ b/webroot/js/standalone-chat-app.js
@@ -2,13 +2,12 @@ import { h, Component, Fragment } from 'https://unpkg.com/preact?module';
import htm from 'https://unpkg.com/htm?module';
const html = htm.bind(h);
+import UsernameForm from './components/chat/username.js';
+import Chat from './components/chat.js';
+import Websocket from './utils/websocket.js';
-import UsernameForm from './username.js';
-import Chat from './chat.js';
-import Websocket from '../websocket.js';
-
-import { getLocalStorage, generateAvatar, generateUsername } from '../utils.js';
-import { KEY_USERNAME, KEY_AVATAR } from '../utils/chat.js';
+import { getLocalStorage, generateAvatar, generateUsername } from './utils/helpers.js';
+import { KEY_USERNAME, KEY_AVATAR } from '../utils/constants.js';
export default class StandaloneChat extends Component {
constructor(props, context) {
diff --git a/webroot/js/utils/chat.js b/webroot/js/utils/chat.js
index 4ce504f43..b7db2659c 100644
--- a/webroot/js/utils/chat.js
+++ b/webroot/js/utils/chat.js
@@ -1,12 +1,13 @@
-import { addNewlines } from '../utils.js';
-
-export const KEY_USERNAME = 'owncast_username';
-export const KEY_AVATAR = 'owncast_avatar';
-export const KEY_CHAT_DISPLAYED = 'owncast_chat';
-export const KEY_CHAT_FIRST_MESSAGE_SENT = 'owncast_first_message_sent';
-export const CHAT_INITIAL_PLACEHOLDER_TEXT = 'Type here to chat, no account necessary.';
-export const CHAT_PLACEHOLDER_TEXT = 'Message';
-export const CHAT_PLACEHOLDER_OFFLINE = 'Chat is offline.';
+import { addNewlines } from './helpers.js';
+import {
+ KEY_USERNAME,
+ KEY_AVATAR,
+ KEY_CHAT_DISPLAYED,
+ KEY_CHAT_FIRST_MESSAGE_SENT,
+ CHAT_INITIAL_PLACEHOLDER_TEXT,
+ CHAT_PLACEHOLDER_TEXT,
+ CHAT_PLACEHOLDER_OFFLINE,
+} from './constants.js';
export function formatMessageText(message, username) {
showdown.setFlavor('github');
diff --git a/webroot/js/utils/constants.js b/webroot/js/utils/constants.js
new file mode 100644
index 000000000..a21d32997
--- /dev/null
+++ b/webroot/js/utils/constants.js
@@ -0,0 +1,29 @@
+// misc constants used throughout the app
+
+export const URL_STATUS = `/status`;
+export const URL_CHAT_HISTORY = `/chat`;
+export const URL_CUSTOM_EMOJIS = `/emoji`;
+export const URL_CONFIG = `/config`;
+
+// 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_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
+
+export const TIMER_STATUS_UPDATE = 5000; // ms
+export const TIMER_DISABLE_CHAT_AFTER_OFFLINE = 5 * 60 * 1000; // 5 mins
+export const TIMER_STREAM_DURATION_COUNTER = 1000;
+export const TEMP_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
+
+export const MESSAGE_OFFLINE = 'Stream is offline.';
+export const MESSAGE_ONLINE = 'Stream is online';
+
+export const URL_OWNCAST = 'https://github.com/gabek/owncast'; // used in footer
+
+
+export const KEY_USERNAME = 'owncast_username';
+export const KEY_AVATAR = 'owncast_avatar';
+export const KEY_CHAT_DISPLAYED = 'owncast_chat';
+export const KEY_CHAT_FIRST_MESSAGE_SENT = 'owncast_first_message_sent';
+export const CHAT_INITIAL_PLACEHOLDER_TEXT = 'Type here to chat, no account necessary.';
+export const CHAT_PLACEHOLDER_TEXT = 'Message';
+export const CHAT_PLACEHOLDER_OFFLINE = 'Chat is offline.';
diff --git a/webroot/js/utils.js b/webroot/js/utils/helpers.js
similarity index 79%
rename from webroot/js/utils.js
rename to webroot/js/utils/helpers.js
index 2be01ff7f..6962dfd61 100644
--- a/webroot/js/utils.js
+++ b/webroot/js/utils/helpers.js
@@ -1,22 +1,3 @@
-export const URL_STATUS = `/status`;
-export const URL_CHAT_HISTORY = `/chat`;
-export const URL_CUSTOM_EMOJIS = `/emoji`;
-export const URL_CONFIG = `/config`;
-
-// 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_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
-
-export const TIMER_STATUS_UPDATE = 5000; // ms
-export const TIMER_DISABLE_CHAT_AFTER_OFFLINE = 5 * 60 * 1000; // 5 mins
-export const TIMER_STREAM_DURATION_COUNTER = 1000;
-export const TEMP_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
-
-export const MESSAGE_OFFLINE = 'Stream is offline.';
-export const MESSAGE_ONLINE = 'Stream is online';
-
-export const URL_OWNCAST = 'https://github.com/gabek/owncast'; // used in footer
-
export function getLocalStorage(key) {
try {
return localStorage.getItem(key);
diff --git a/webroot/js/utils/social.js b/webroot/js/utils/social.js
index dad57a546..9f42063e5 100644
--- a/webroot/js/utils/social.js
+++ b/webroot/js/utils/social.js
@@ -1,4 +1,3 @@
-
// x, y pixel psitions of /img/social.gif image.
export const SOCIAL_PLATFORMS = {
default: {
diff --git a/webroot/js/utils/socket-message-types.js b/webroot/js/utils/socket-message-types.js
deleted file mode 100644
index f52b57a3e..000000000
--- a/webroot/js/utils/socket-message-types.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * These are the types of messages that we can handle with the websocket.
- * Mostly used by `websocket.js` but if other components need to handle
- * different types then it can import this file.
- */
-export default {
- CHAT: 'CHAT',
- PING: 'PING',
- NAME_CHANGE: 'NAME_CHANGE',
- PONG: 'PONG'
-};
diff --git a/webroot/js/websocket.js b/webroot/js/utils/websocket.js
similarity index 91%
rename from webroot/js/websocket.js
rename to webroot/js/utils/websocket.js
index 3b558f99f..bc392faa6 100644
--- a/webroot/js/websocket.js
+++ b/webroot/js/utils/websocket.js
@@ -1,8 +1,14 @@
-import SOCKET_MESSAGE_TYPES from './utils/socket-message-types.js';
-
-const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
-
-const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
+/**
+ * These are the types of messages that we can handle with the websocket.
+ * Mostly used by `websocket.js` but if other components need to handle
+ * different types then it can import this file.
+ */
+export const SOCKET_MESSAGE_TYPES = {
+ CHAT: 'CHAT',
+ PING: 'PING',
+ NAME_CHANGE: 'NAME_CHANGE',
+ PONG: 'PONG'
+};
export const CALLBACKS = {
RAW_WEBSOCKET_MESSAGE_RECEIVED: 'rawWebsocketMessageReceived',
@@ -10,8 +16,10 @@ export const CALLBACKS = {
WEBSOCKET_DISCONNECTED: 'websocketDisconnected',
}
-class Websocket {
+const URL_WEBSOCKET = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/entry`;
+const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
+export default class Websocket {
constructor() {
this.websocket = null;
this.websocketReconnectTimer = null;
@@ -133,7 +141,5 @@ class Websocket {
handleNetworkingError(error) {
console.error(`Websocket Error: ${error}`)
- };
+ }
}
-
-export default Websocket;
diff --git a/webroot/standalone-chat.html b/webroot/standalone-chat.html
index fcb2001ea..ec3899684 100644
--- a/webroot/standalone-chat.html
+++ b/webroot/standalone-chat.html
@@ -19,7 +19,7 @@