breaking out styles into smaller files; break out chat helper functions into utils

This commit is contained in:
Ginger Wong 2020-08-13 02:43:41 -07:00
parent ab5f8df96e
commit 7a1512ef6b
5 changed files with 314 additions and 372 deletions

View file

@ -74,6 +74,7 @@ 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>
<div id="message-form-actions" class="flex">
<span id="message-form-warning" class="text-red-600 text-xs"></span>

View file

@ -8,7 +8,7 @@ export const CHAT_PLACEHOLDER_OFFLINE = 'Chat is offline.';
export function formatMessageText(message) {
showdown.setFlavor('github');
var markdownToHTML = new showdown.Converter({
let formattedText = new showdown.Converter({
emoji: true,
openLinksInNewWindow: true,
tables: false,
@ -16,20 +16,158 @@ export function formatMessageText(message) {
literalMidWordUnderscores: true,
strikethrough: true,
ghMentions: false,
}).makeHtml(this.body);
const linked = autoLink(markdownToHTML, {
embed: true,
removeHTTP: true,
linkAttr: {
target: '_blank'
}
});
const highlighted = highlightUsername(linked);
return addNewlines(highlighted);
}).makeHtml(message);
formattedText = linkify(formattedText, message);
formattedText = highlightUsername(formattedText);
return addNewlines(formattedText);
}
function highlightUsername(message) {
const username = document.getElementById('self-message-author').value;
const pattern = new RegExp('@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
const pattern = new RegExp('@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
return message.replace(pattern, '<span class="highlighted">$&</span>');
}
function 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 = `<a href="${linkURL}" target="_blank">${displayURL}</a>`;
text = text.replace(url, link);
if (getYoutubeIdFromURL(url)) {
if (isTextJustURLs(text, [url, displayURL])) {
text = '';
} else {
text += '<br/>';
}
const youtubeID = getYoutubeIdFromURL(url);
text += getYoutubeEmbedFromID(youtubeID);
} else if (url.indexOf('instagram.com/p/') > -1) {
if (isTextJustURLs(text, [url, displayURL])) {
text = '';
} else {
text += `<br/>`;
}
text += getInstagramEmbedFromURL(url);
} else if (isImage(url)) {
if (isTextJustURLs(text, [url, displayURL])) {
text = '';
} else {
text += `<br/>`;
}
text += getImageForURL(url);
}
}.bind(this));
}
return text;
}
function isTextJustURLs(text, urls) {
for (var i = 0; i < urls.length; i++) {
const url = urls[i];
if (stripTags(text) === url) {
return true;
}
}
return false;
}
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
export 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
export 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);
}

View file

@ -23,9 +23,9 @@ a:hover {
}
.visually-hidden {
.visually-hidden {
position: absolute !important;
height: 1px;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
@ -111,7 +111,7 @@ footer span {
flex-direction: row;
justify-content: space-between;
}
#stream-info span {
font-size: .7em;
@ -128,23 +128,7 @@ footer span {
/* ************************************************8 */
.user-content {
padding: 3em;
display: flex;
flex-direction: row;
}
.user-content .user-image {
padding: 1em;
margin-right: 2em;
min-width: var(--user-image-width);
width: var(--user-image-width);
height: var(--user-image-width);
max-height: var(--user-image-width);
background-repeat: no-repeat;
background-position: center center;
background-size: calc(var(--user-image-width) - 1em);
}
/* .user-image img {
display: inline-block;
@ -156,58 +140,7 @@ footer span {
}
h2 {
font-size: 3em;
}
.user-content-header {
margin-bottom: 2em;
}
.tag-list {
flex-direction: row;
margin: 1em 0;
}
.tag-list li {
font-size: .75em;
text-transform: uppercase;
margin-right: .75em;
padding: .5em;
}
.social-list {
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
}
.social-list .follow-label {
font-weight: bold;
font-size: .75em;
margin-right: .5em;
text-transform: uppercase;
}
.user-social-item {
display: flex;
justify-content: flex-start;
align-items: center;
margin-right: -.25em;
}
.user-social-item .platform-icon {
--icon-width: 40px;
height: var(--icon-width);
width: var(--icon-width);
background-image: url(../img/social-icons.gif);
background-repeat: no-repeat;
background-position: calc(var(--imgCol) * var(--icon-width)) calc(var(--imgRow) * var(--icon-width));
transform: scale(.65);
}
.user-social-item.use-default .platform-label {
font-size: .7em;
text-transform: uppercase;
display: inline-block;
max-width: 10em;
font-size: 3em;
}
@ -281,8 +214,8 @@ h2 {
margin-top: var(--header-height);
background-position: center center;
background-repeat: no-repeat;
background-size: 30%;
background-size: 30%;
}
.owncast-video-container {
@ -360,81 +293,6 @@ h2 {
}
#messages-container {
overflow: auto;
padding: 1em 0;
}
#message-input-container {
width: 100%;
padding: 1em;
}
#message-form {
flex-direction: column;
align-items: flex-end;
margin-bottom: 0;
}
#message-body-form {
font-size: 1em;
height: 60px;
}
#message-body-form:disabled{
opacity: .5;
}
#message-body-form img {
display: inline;
padding-left: 5px;
padding-right: 5px;
}
#message-body-form .emoji {
width: 40px;
}
#message-form-actions {
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
}
.message-text img {
display: inline;
padding-left: 5px;
padding-right: 5px;
}
.message-text .emoji {
width: 60px;
}
/* ************************************************8 */
.message {
padding: .85em;
align-items: flex-start;
}
.message-avatar {
margin-right: .75em;
}
.message-avatar img {
max-width: unset;
height: 3.0em;
width: 3.0em;
padding: 5px;
}
.message-content {
font-size: .85em;
max-width: 85%;
word-wrap: break-word;
}
.message-content a {
color: #7F9CF5; /* indigo-400 */
}
.message-content a:hover {
text-decoration: underline;
}
/* ************************************************8 */
@ -452,7 +310,7 @@ h2 {
--right-col-width: 20em;
--user-image-width: 6em;
}
#chat-container {
width: var(--right-col-width);
}
@ -504,21 +362,6 @@ h2 {
}
}
/* try not making the video fixed position for now */
@media (min-height: 861px) {
/* main {
position: fixed;
z-index: 9;
width: 100%;
}
#user-content {
margin-top: calc(var(--video-container-height) + var(--header-height) + 2em)
} */
}
@ -530,194 +373,3 @@ h2 {
flex-direction: column;
}
}
.extra-user-content {
padding: 1em 3em 3em 3em;
}
.extra-user-content ol {
list-style: decimal;
}
.extra-user-content ul {
list-style: unset;
}
.extra-user-content h1, .extra-user-content h2, .extra-user-content h3, .extra-user-content h4 {
color: #111111;
font-weight: 400; }
.extra-user-content h1, .extra-user-content h2, .extra-user-content h3, .extra-user-content h4, .extra-user-content h5, .extra-user-content p {
margin-bottom: 24px;
padding: 0; }
.extra-user-content h1 {
font-size: 48px; }
.extra-user-content h2 {
font-size: 36px;
margin: 24px 0 6px; }
.extra-user-content h3 {
font-size: 24px; }
.extra-user-content h4 {
font-size: 21px; }
.extra-user-content h5 {
font-size: 18px; }
.extra-user-content a {
color: #0099ff;
margin: 0;
padding: 0;
vertical-align: baseline; }
.extra-user-content ul, .extra-user-content ol {
padding: 0;
margin: 0; }
.extra-user-content li {
line-height: 24px; }
.extra-user-content li ul, .extra-user-content li ul {
margin-left: 24px; }
.extra-user-content p, .extra-user-content ul, .extra-user-content ol {
font-size: 16px;
line-height: 24px;
}
.extra-user-content pre {
padding: 0px 24px;
max-width: 800px;
white-space: pre-wrap; }
.extra-user-content code {
font-family: Consolas, Monaco, Andale Mono, monospace;
line-height: 1.5;
font-size: 13px; }
.extra-user-content aside {
display: block;
float: right;
width: 390px; }
.extra-user-content blockquote {
margin: 1em 2em;
max-width: 476px; }
.extra-user-content blockquote p {
color: #666;
max-width: 460px; }
.extra-user-content hr {
width: 540px;
text-align: left;
margin: 0 auto 0 0;
color: #999; }
.extra-user-content table {
border-collapse: collapse;
margin: 1em 1em;
border: 1px solid #CCC; }
.extra-user-content table thead {
background-color: #EEE; }
.extra-user-content table thead td {
color: #666; }
.extra-user-content table td {
padding: 0.5em 1em;
border: 1px solid #CCC; }
.message-text iframe {
width: 100%;
height: 170px;
border-radius: 15px;
}
.message-text .instagram-embed {
height: 314px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
/* Emoji picker */
#emoji-button {
position: relative;
top: -65px;
right: 10px;
cursor: pointer;
}
.message-text .embedded-image {
width: 100%;
height: 170px;
border-radius: 15px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
/* Emoji picker */
#emoji-button {
position: relative;
top: -65px;
right: 10px;
cursor: pointer;
}
.message-text .embedded-image {
width: 100%;
height: 170px;
border-radius: 15px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
.message-text .highlighted {
color: orange;
font-weight: 400;
font-size: 14px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
/*
The chat input has a fake placeholder that is styled below.
It pulls the placeholder text from the div's placeholder attribute.
But really it's just the innerHTML content.
*/
/* If the div is empty then show the placeholder */
#message-body-form:empty:before{
content: attr(placeholder);
pointer-events: none;
display: block; /* For Firefox */
/* Style the div's placeholder text color */
color: rgba(0, 0, 0, 0.5);
}
/* When chat is enabled (contenteditable=true) */
#message-body-form[contenteditable=true]:before {
opacity: 1.0;
}
/* When chat is disabled (contenteditable=false) chat input div should appear disabled. */
#message-body-form[contenteditable=false] {
opacity: 0.6;
}

View file

@ -48,6 +48,7 @@
}
.message {
padding: .85em;
align-items: flex-start;
@ -74,14 +75,93 @@
text-decoration: underline;
}
.message-text iframe {
width: 100%;
height: 170px;
border-radius: 15px;
}
.message-text .instagram-embed {
height: 314px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
/* Emoji picker */
#emoji-button {
margin: 0 .5em;
font-size: 1.5em
}
position: relative;
top: -65px;
right: 10px;
cursor: pointer;
}
.message-text .embedded-image {
width: 100%;
height: 170px;
border-radius: 15px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
/* Emoji picker */
#emoji-button {
position: relative;
top: -65px;
right: 10px;
cursor: pointer;
}
.message-text .embedded-image {
width: 100%;
height: 170px;
border-radius: 15px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
.message-text .highlighted {
color: orange;
font-weight: 400;
font-size: 14px;
}
.message-text code {
background-color:darkslategrey;
padding: 3px;
}
/*
The chat input has a fake placeholder that is styled below.
It pulls the placeholder text from the div's placeholder attribute.
But really it's just the innerHTML content.
*/
/* If the div is empty then show the placeholder */
#message-body-form:empty:before{
content: attr(placeholder);
pointer-events: none;
display: block; /* For Firefox */
/* Style the div's placeholder text color */
color: rgba(0, 0, 0, 0.5);
}
/* When chat is enabled (contenteditable=true) */
#message-body-form[contenteditable=true]:before {
opacity: 1.0;
}
/* When chat is disabled (contenteditable=false) chat input div should appear disabled. */
#message-body-form[contenteditable=false] {
opacity: 0.6;
}

View file

@ -1,8 +1,79 @@
.extra-user-content {
padding: 1em 3em 3em 3em;
.user-content {
padding: 3em;
display: flex;
flex-direction: row;
}
.user-content .user-image {
padding: 1em;
margin-right: 2em;
min-width: var(--user-image-width);
width: var(--user-image-width);
height: var(--user-image-width);
max-height: var(--user-image-width);
background-repeat: no-repeat;
background-position: center center;
background-size: calc(var(--user-image-width) - 1em);
}
.user-content-header {
margin-bottom: 2em;
}
.tag-list {
flex-direction: row;
margin: 1em 0;
}
.tag-list li {
font-size: .75em;
text-transform: uppercase;
margin-right: .75em;
padding: .5em;
}
.social-list {
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
}
.social-list .follow-label {
font-weight: bold;
font-size: .75em;
margin-right: .5em;
text-transform: uppercase;
}
.user-social-item {
display: flex;
justify-content: flex-start;
align-items: center;
margin-right: -.25em;
}
.user-social-item .platform-icon {
--icon-width: 40px;
height: var(--icon-width);
width: var(--icon-width);
background-image: url(../img/social-icons.gif);
background-repeat: no-repeat;
background-position: calc(var(--imgCol) * var(--icon-width)) calc(var(--imgRow) * var(--icon-width));
transform: scale(.65);
}
.user-social-item.use-default .platform-label {
font-size: .7em;
text-transform: uppercase;
display: inline-block;
max-width: 10em;
}
.extra-user-content {
padding: 1em 3em 3em 3em;
}
.extra-user-content ol {
list-style: decimal;