Update chat styling

This commit is contained in:
Gabe Kangas 2020-10-16 17:36:11 -07:00
parent 1b821e4c4e
commit f4815e331c
5 changed files with 49 additions and 149 deletions

View file

@ -11,8 +11,9 @@ for (var i = 0; i < 20; i++) {
console.log(`</body>`);
function generateElement(string) {
const color = messageBubbleColorForString(string);
return `<div style="color: ${color}">${string}</div>`
const bgColor = messageBubbleColorForString(string);
const fgColor = textColorForString(string);
return `<div style="background-color: ${bgColor}; color: ${fgColor}; padding: 30px;">${string}</div>`;
}
function randomString(length) {
@ -27,9 +28,25 @@ function messageBubbleColorForString(str) {
}
// Tweak these to adjust the result of the color
const saturation = 75;
const lightness = 65;
const alpha = 1.0;
const saturation = 25;
const lightness = 45;
const alpha = 0.3;
const hue = parseInt(Math.abs(hash), 16) % 360;
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
}
function textColorForString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// eslint-disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
// Tweak these to adjust the result of the color
const saturation = 80;
const lightness = 80;
const alpha = 0.8;
const hue = parseInt(Math.abs(hash), 16) % 360;
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;

View file

@ -2,38 +2,17 @@ import { h, Component } from '/js/web_modules/preact.js';
import htm from '/js/web_modules/htm.js';
const html = htm.bind(h);
import ChatMessageView from './chat-message-view.js';
import { messageBubbleColorForString } from '../../utils/user-colors.js';
import { convertToText } from '../../utils/chat.js';
import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
export default class Message extends Component {
render(props) {
const { message, username } = props;
const { message } = props;
const { type } = message;
if (type === SOCKET_MESSAGE_TYPES.CHAT) {
const { author, body, timestamp } = message;
const formattedMessage = formatMessageText(body, username);
const formattedTimestamp = formatTimestamp(timestamp);
const authorColor = messageBubbleColorForString(author);
const authorTextColor = { color: authorColor };
return (
html`
<div class="message flex flex-row items-start p-3">
<div class="message-content text-sm break-words w-full">
<div class="message-author text-white font-bold" style=${authorTextColor}>
${author}
</div>
<div
class="message-text text-gray-300 font-normal overflow-y-hidden"
title=${formattedTimestamp}
dangerouslySetInnerHTML=${
{ __html: formattedMessage }
}
></div>
</div>
</div>
`);
if (type === SOCKET_MESSAGE_TYPES.CHAT || type === SOCKET_MESSAGE_TYPES.SYSTEM) {
return html`<${ChatMessageView} ...${props} />`;
} else if (type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) {
const { oldName, newName } = message;
return (
@ -47,121 +26,9 @@ export default class Message extends Component {
</div>
`
);
}
}
}
export function formatMessageText(message, username) {
let formattedText = highlightUsername(message, username);
formattedText = getMessageWithEmbeds(formattedText);
return convertToMarkup(formattedText);
}
function highlightUsername(message, username) {
const pattern = new RegExp(
'@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),
'gi'
);
return message.replace(
pattern,
'<span class="highlighted px-1 rounded font-bold bg-orange-500">$&</span>'
);
}
function getMessageWithEmbeds(message) {
var embedText = '';
// Make a temporary element so we can actually parse the html and pull anchor tags from it.
// This is a better approach than regex.
var container = document.createElement('p');
container.innerHTML = message;
var anchors = container.getElementsByTagName('a');
for (var i = 0; i < anchors.length; i++) {
const url = anchors[i].href;
if (getYoutubeIdFromURL(url)) {
const youtubeID = getYoutubeIdFromURL(url);
embedText += getYoutubeEmbedFromID(youtubeID);
} else if (url.indexOf('instagram.com/p/') > -1) {
embedText += getInstagramEmbedFromURL(url);
} else if (isImage(url)) {
embedText += getImageForURL(url);
}
}
// If this message only consists of a single embeddable link
// then only return the embed and strip the link url from the text.
if (embedText !== '' && anchors.length == 1 && isMessageJustAnchor(message, anchors[0])) {
return embedText;
}
return message + embedText;
}
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;
console.log("Unknown message type:", type);
}
} catch (e) {
console.log(e);
return null;
}
}
function getYoutubeEmbedFromID(id) {
return `<div class="chat-embed youtube-embed"><lite-youtube videoid="${id}" /></div>`;
}
function getInstagramEmbedFromURL(url) {
const urlObject = new URL(url.replace(/\/$/, ''));
urlObject.pathname += '/embed';
return `<iframe class="chat-embed instagram-embed" src="${urlObject.href}" frameborder="0" allowfullscreen></iframe>`;
}
function isImage(url) {
const re = /\.(jpe?g|png|gif)$/i;
return re.test(url);
}
function getImageForURL(url) {
return `<a target="_blank" href="${url}"><img class="chat-embed embedded-image" src="${url}" /></a>`;
}
function isMessageJustAnchor(message, anchor) {
return stripTags(message) === stripTags(anchor.innerHTML);
}
function formatTimestamp(sentAt) {
sentAt = new Date(sentAt);
if (isNaN(sentAt)) {
return '';
}
let diffInDays = (new Date() - sentAt) / (24 * 3600 * 1000);
if (diffInDays >= 1) {
return (
`Sent at ${sentAt.toLocaleDateString('en-US', {
dateStyle: 'medium',
})} at ` + sentAt.toLocaleTimeString()
);
}
return `Sent at ${sentAt.toLocaleTimeString()}`;
}
/*
You would call this when receiving a plain text
value back from an API, and before inserting the
text into the `contenteditable` area on a page.
*/
function convertToMarkup(str = '') {
return convertToText(str).replace(/\n/g, '<br>');
}
function stripTags(str) {
return str.replace(/<\/?[^>]+(>|$)/g, '');
}

View file

@ -64,7 +64,6 @@ export function extraUserNamesFromMessageHistory(messages) {
return list;
}
// utils from https://gist.github.com/nathansmith/86b5d4b23ed968a92fd4
/*
You would call this after getting an element's

View file

@ -6,9 +6,25 @@ export function messageBubbleColorForString(str) {
}
// Tweak these to adjust the result of the color
const saturation = 75;
const lightness = 65;
const alpha = 1.0;
const saturation = 25;
const lightness = 45;
const alpha = 0.3;
const hue = parseInt(Math.abs(hash), 16) % 360;
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
}
export function textColorForString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// eslint-disable-next-line
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
// Tweak these to adjust the result of the color
const saturation = 80;
const lightness = 80;
const alpha = 0.8;
const hue = parseInt(Math.abs(hash), 16) % 360;
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;

View file

@ -7,7 +7,8 @@ export const SOCKET_MESSAGE_TYPES = {
CHAT: 'CHAT',
PING: 'PING',
NAME_CHANGE: 'NAME_CHANGE',
PONG: 'PONG'
PONG: 'PONG',
SYSTEM: 'SYSTEM'
};
export const CALLBACKS = {