- reduced custom styles, use mostly tailwind classes

- updated markdown css for extrausercontent
This commit is contained in:
Ginger Wong
2020-08-21 23:44:10 -07:00
parent 66dc2f84c9
commit 13cfd112b7
14 changed files with 392 additions and 395 deletions

View File

@@ -332,7 +332,7 @@ export default class App extends Component {
const tagList = !tags.length ?
null :
tags.map((tag, index) => html`
<li key="tag${index}" class="tag rounded-sm text-gray-100 bg-gray-700">${tag}</li>
<li key="tag${index}" class="tag rounded-sm text-gray-100 bg-gray-700 text-xs uppercase mr-3 p-2 whitespace-no-wrap">${tag}</li>
`);
const socialIconsList =
@@ -350,26 +350,26 @@ export default class App extends Component {
const streamInfoClass = streamOnline ? 'online' : '';
return (
html`
<div id="app-container" class="flex ${chatClass}">
<div id="app-container" class="flex w-full flex-col justify-start relative ${chatClass}">
<div id="top-content">
<header class="flex border-b border-gray-900 border-solid shadow-md">
<h1 class="flex text-gray-400">
<header class="flex border-b border-gray-900 border-solid shadow-md fixed z-10 w-full top-0 left-0 flex flex-row justify-between flex-no-wrap">
<h1 class="flex flex-row items-center justify-start p-2 uppercase text-gray-400 text-xl font-thin tracking-wider overflow-hidden whitespace-no-wrap">
<span
id="logo-container"
class="rounded-full bg-white px-1 py-1"
class="inline-block rounded-full bg-white w-8 min-w-8 min-h-8 h-8 p-1 mr-2 bg-no-repeat bg-center"
style=${bgLogo}
>
<img class="logo visually-hidden" src=${smallLogo} />
<img class="logo visually-hidden" src=${smallLogo} alt=""/>
</span>
<span class="instance-title">${title}</span>
<span class="instance-title overflow-hidden truncate">${title}</span>
</h1>
<div id="user-options-container" class="flex">
<div id="user-options-container" class="flex flex-row justify-end items-center flex-no-wrap">
<${UsernameForm}
username=${username}
userAvatarImage=${userAvatarImage}
handleUsernameChange=${this.handleUsernameChange}
/>
<button type="button" id="chat-toggle" onClick=${this.handleChatPanelToggle} class="flex bg-gray-800 hover:bg-gray-700">💬</button>
<button type="button" id="chat-toggle" onClick=${this.handleChatPanelToggle} class="flex cursor-pointer text-center justify-center items-center min-w-12 h-full bg-gray-800 hover:bg-gray-700">💬</button>
</div>
</header>
</div>
@@ -377,21 +377,20 @@ export default class App extends Component {
<main class=${mainClass}>
<div
id="video-container"
class="flex owncast-video-container bg-black"
class="flex owncast-video-container bg-black w-full bg-center bg-no-repeat flex flex-col items-center justify-start"
style=${bgLogoLarge}
>
<video
class="video-js vjs-big-play-centered"
class="video-js vjs-big-play-centered display-block w-full h-full"
id="video"
preload="auto"
controls
playsinline
>
</video>
></video>
</div>
<section id="stream-info" aria-label="Stream status" class="flex font-mono bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid ${streamInfoClass}">
<section id="stream-info" aria-label="Stream status" class="flex text-center flex-row justify-between w-full text-xs font-mono py-2 px-8 bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid ${streamInfoClass}">
<span>${streamStatusMessage}</span>
<span>${viewerCount} ${pluralize('viewer', viewerCount)}.</span>
<span>Max ${pluralize('viewer', sessionMaxViewerCount)}.</span>
@@ -399,47 +398,45 @@ export default class App extends Component {
</section>
</main>
<section id="user-content" aria-label="User information">
<div class="user-content">
<section id="user-content" aria-label="User information" class="p-8">
<div class="user-content flex flex-row p-8">
<div
class="user-image rounded-full bg-white"
class="user-image rounded-full bg-white p-4 mr-8 bg-no-repeat bg-center"
style=${bgLogoLarge}
>
<img
class="logo visually-hidden"
alt="Logo"
src=${largeLogo}
>
src=${largeLogo}/>
</div>
<div class="user-content-header border-b border-gray-500 border-solid">
<h2 class="font-semibold">
About
<span class="streamer-name text-indigo-600">${streamerName}</span>
<h2 class="font-semibold text-5xl">
About <span class="streamer-name text-indigo-600">${streamerName}</span>
</h2>
<ul class="social-list flex" v-if="this.platforms.length">
<span class="follow-label">Follow me: </span>
<ul id="social-list" class="social-list flex flex-row items-center justify-start flex-wrap">
<span class="follow-label text-xs font-bold mr-2 uppercase">Follow me: </span>
${socialIconsList}
</ul>
<div class="stream-summary" dangerouslySetInnerHTML=${{ __html: summary }}></div>
<ul class="tag-list flex">
<div id="stream-summary" class="stream-summary my-4" dangerouslySetInnerHTML=${{ __html: summary }}></div>
<ul id="tag-list" class="tag-list flex flex-row my-4">
${tagList}
</ul>
</div>
</div>
<div
id="extra-user-content"
class="extra-user-content"
class="extra-user-content px-8"
dangerouslySetInnerHTML=${{ __html: extraUserContent }}
></div>
</section>
<footer class="flex">
<span>
<footer class="flex flex-row justify-start p-8 opacity-50 text-xs">
<span class="mx-1 inline-block">
<a href="${URL_OWNCAST}" target="_blank">About Owncast</a>
</span>
<span>Version ${appVersion}</span>
<span class="mx-1 inline-block">Version ${appVersion}</span>
</footer>
</div>
<${Chat}
websocket=${websocket}

View File

@@ -236,11 +236,11 @@ export default class ChatInput extends Component {
const placeholderText = generatePlaceholderText(inputEnabled, hasSentFirstChatMessage);
return (
html`
<div id="message-input-container" class="shadow-md bg-gray-900 border-t border-gray-700 border-solid">
<div id="message-input-container" class="w-full shadow-md bg-gray-900 border-t border-gray-700 border-solid p-4">
<${ContentEditable}
id="message-input"
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"
class="appearance-none block w-full bg-gray-200 text-sm text-gray-700 border border-black-500 rounded py-2 px-2 my-2 focus:bg-white h-20 overflow-auto"
placeholderText=${placeholderText}
innerRef=${this.formMessageInput}
@@ -254,13 +254,14 @@ export default class ChatInput extends Component {
onPaste=${this.handlePaste}
/>
<div id="message-form-actions" class="flex">
<div id="message-form-actions" class="flex flex-row justify-between items-center w-full">
<span id="message-form-warning" class="text-red-600 text-xs">${inputWarning}</span>
<div id="message-form-actions-buttons" class="flex">
<div id="message-form-actions-buttons" class="flex flex-row justify-end items-center">
<button
ref=${this.emojiPickerButton}
id="emoji-button"
class="mr-2 text-2xl cursor-pointer"
type="button"
style=${emojiButtonStyle}
onclick=${this.handleEmojiButtonClick}

View File

@@ -95,12 +95,6 @@ export default class Chat extends Component {
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);
// }
}
// if incoming message has same id as existing message, don't add it
@@ -181,7 +175,7 @@ export default class Chat extends Component {
if (messagesOnly) {
return (
html`
<div id="messages-container">
<div id="messages-container" class="py-1 overflow-auto">
${messageList}
</div>
`);
@@ -189,9 +183,9 @@ export default class Chat extends Component {
return (
html`
<section id="chat-container-wrap" class="flex">
<div id="chat-container" class="bg-gray-800">
<div id="messages-container">
<section id="chat-container-wrap" class="flex flex-col">
<div id="chat-container" class="bg-gray-800 flex flex-col justify-end overflow-auto">
<div id="messages-container" class="py-1 overflow-auto">
${messageList}
</div>
<${ChatInput}

View File

@@ -20,15 +20,17 @@ export default class Message extends Component {
const authorTextColor = { color: authorColor };
return (
html`
<div class="message flex">
<div class="message flex flex-row align-start p-3">
<div
class="message-avatar rounded-full flex items-center justify-center"
class="message-avatar rounded-full flex items-center justify-center mr-3"
style=${avatarBgColor}
>
<img src=${avatar} />
<img src=${avatar} class="p-1" />
</div>
<div class="message-content text-sm">
<p class="message-author text-white font-bold" style=${authorTextColor}>${author}</p>
<div class="message-content text-sm break-words">
<div class="message-author text-white font-bold" style=${authorTextColor}>
${author}
</div>
<div
class="message-text text-gray-300 font-normal"
dangerouslySetInnerHTML=${
@@ -42,7 +44,7 @@ export default class Message extends Component {
const { oldName, newName, image } = message;
return (
html`
<div class="message flex">
<div class="message flex align-start p3">
<div class="message-content text-sm">
<img class="mr-2" src=${image} />
<div class="text-white text-center">
@@ -50,7 +52,8 @@ export default class Message extends Component {
</div>
</div>
</div>
`);
`
);
}
}
}

View File

@@ -77,29 +77,29 @@ export default class UsernameForm extends Component {
return (
html`
<div id="user-info">
<div id="user-info-display" style=${styles.info} title="Click to update user name" class="flex" onClick=${this.handleDisplayForm}>
<div id="user-info-display" style=${styles.info} title="Click to update user name" class="flex flex-row justify-end items-center cursor-pointer py-2 px-4 overflow-hidden w-full opacity-1 transition-opacity duration-200 hover:opacity-75" onClick=${this.handleDisplayForm}>
<img
src=${userAvatarImage}
alt=""
id="username-avatar"
class="rounded-full bg-black bg-opacity-50 border border-solid border-gray-700"
class="rounded-full bg-black bg-opacity-50 border border-solid border-gray-700 mr-2 h-8 w-8"
/>
<span id="username-display" class="text-indigo-600">${username}</span>
<span id="username-display" class="text-indigo-600 text-xs font-semibold truncate overflow-hidden whitespace-no-wrap">${username}</span>
</div>
<div id="user-info-change" style=${styles.form}>
<div id="user-info-change" class="flex p-1 items-center justify-end" style=${styles.form}>
<input type="text"
id="username-change-input"
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-black-500 rounded py-1 px-1 leading-tight focus:bg-white"
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-black-500 rounded py-1 px-1 leading-tight text-xs focus:bg-white"
maxlength="100"
placeholder="Update username"
value=${username}
onKeydown=${this.handleKeydown}
ref=${this.textInput}
/>
<button id="button-update-username" onClick=${this.handleUpdateUsername} class="bg-blue-500 hover:bg-blue-700 text-white py-1 px-1 rounded user-btn">Update</button>
<button id="button-update-username" onClick=${this.handleUpdateUsername} type="button" class="bg-blue-500 hover:bg-blue-700 text-white text-xs uppercase p-1 mx-1 rounded cursor-pointer user-btn">Update</button>
<button id="button-cancel-change" onClick=${this.handleHideForm} class="bg-gray-900 hover:bg-gray-800 py-1 px-2 rounded user-btn text-white text-opacity-50" title="cancel">X</button>
<button id="button-cancel-change" onClick=${this.handleHideForm} type="button" class="bg-gray-900 hover:bg-gray-800 py-1 px-2 mx-1 rounded cursor-pointer user-btn text-white text-xs uppercase text-opacity-50" title="cancel">X</button>
</div>
</div>
`);

View File

@@ -1,43 +0,0 @@
// DEPRECATE.
import { EmojiButton } from 'https://cdn.skypack.dev/@joeattardi/emoji-button'
fetch('/emoji')
.then(response => {
if (!response.ok) {
throw new Error(`Network response was not ok ${response.ok}`);
}
return response.json();
})
.then(json => {
setupEmojiPickerWithCustomEmoji(json);
})
.catch(error => {
this.handleNetworkingError(`Emoji Fetch: ${error}`);
});
function setupEmojiPickerWithCustomEmoji(customEmoji) {
const picker = new EmojiButton({
zIndex: 100,
theme: 'dark',
custom: customEmoji,
initialCategory: 'custom',
showPreview: false,
position: {
top: '50%',
right: '100'
}
});
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;
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;
}
});
}

View File

@@ -1,76 +1,6 @@
import { html } from "https://unpkg.com/htm/preact/index.mjs?module";
const SOCIAL_PLATFORMS = {
default: {
name: "default",
imgPos: [0,0], // [row,col]
},
facebook: {
name: "Facebook",
imgPos: [0,1],
},
twitter: {
name: "Twitter",
imgPos: [0,2],
},
instagram: {
name: "Instagram",
imgPos: [0,3],
},
snapchat: {
name: "Snapchat",
imgPos: [0,4],
},
tiktok: {
name: "TikTok",
imgPos: [0,5],
},
soundcloud: {
name: "Soundcloud",
imgPos: [0,6],
},
bandcamp: {
name: "Bandcamp",
imgPos: [0,7],
},
patreon: {
name: "Patreon",
imgPos: [0,1],
},
youtube: {
name: "YouTube",
imgPos: [0,9 ],
},
spotify: {
name: "Spotify",
imgPos: [0,10],
},
twitch: {
name: "Twitch",
imgPos: [0,11],
},
paypal: {
name: "Paypal",
imgPos: [0,12],
},
github: {
name: "Github",
imgPos: [0,13],
},
linkedin: {
name: "LinkedIn",
imgPos: [0,14],
},
discord: {
name: "Discord",
imgPos: [0,15],
},
mastodon: {
name: "Mastodon",
imgPos: [0,16],
},
};
import { SOCIAL_PLATFORMS } from './utils/social.js';
import { classNames } from './utils.js';
export default function SocialIcon(props) {
const { platform, url } = props;
@@ -82,20 +12,28 @@ export default function SocialIcon(props) {
const name = inList ? platformInfo.name : platform;
const style = `--imgRow: -${imgRow}; --imgCol: -${imgCol};`;
const itemClass = {
const itemClass = classNames({
"user-social-item": true,
"flex": true,
"justify-start": true,
"items-center": true,
"-mr-1": true,
"use-default": !inList,
};
const labelClass = {
});
const labelClass = classNames({
"platform-label": true,
"visually-hidden": inList,
"text-indigo-800": true,
};
"text-xs": true,
"uppercase": true,
"max-w-xs": true,
"inline-block": true,
});
return (
html`
<a class=${itemClass} target="_blank" href=${url}>
<span class="platform-icon rounded-lg" style=${style}></span>
<span class="platform-icon rounded-lg bg-no-repeat" style=${style}></span>
<span class=${labelClass}>Find me on ${name}</span>
</a>
`);

View File

@@ -98,7 +98,7 @@ export function hasTouchScreen() {
export function generateAvatar(hash) {
const avatarSource = 'https://robohash.org/';
const optionSize = '?size=80x80';
const optionSet = '&set=set3';
const optionSet = '&set=set2';
const optionBg = ''; // or &bgset=bg1 or bg2
return avatarSource + hash + optionSize + optionSet + optionBg;
@@ -133,3 +133,17 @@ export function setVHvar() {
export function doesObjectSupportFunction(object, functionName) {
return typeof object[functionName] === "function";
}
// return a string of css classes
export function classNames(json) {
const classes = [];
Object.entries(json).map(function(item) {
const [ key, value ] = item;
if (value) {
classes.push(key);
}
return null;
});
return classes.join(' ');
}

View File

@@ -28,7 +28,7 @@ export function formatMessageText(message, username) {
function highlightUsername(message, username) {
const pattern = new RegExp('@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
return message.replace(pattern, '<span class="highlighted">$&</span>');
return message.replace(pattern, '<span class="highlighted font-bold bg-orange-500">$&</span>');
}
function linkify(text, rawText) {
@@ -126,8 +126,7 @@ function getInstagramEmbedFromURL(url) {
function isImage(url) {
const re = /\.(jpe?g|png|gif)$/i;
const isImage = re.test(url);
return isImage;
return re.test(url);
}
function getImageForURL(url) {

View File

@@ -0,0 +1,73 @@
// x, y pixel psitions of /img/social.gif image.
export const SOCIAL_PLATFORMS = {
default: {
name: "default",
imgPos: [0,0], // [row,col]
},
facebook: {
name: "Facebook",
imgPos: [0,1],
},
twitter: {
name: "Twitter",
imgPos: [0,2],
},
instagram: {
name: "Instagram",
imgPos: [0,3],
},
snapchat: {
name: "Snapchat",
imgPos: [0,4],
},
tiktok: {
name: "TikTok",
imgPos: [0,5],
},
soundcloud: {
name: "Soundcloud",
imgPos: [0,6],
},
bandcamp: {
name: "Bandcamp",
imgPos: [0,7],
},
patreon: {
name: "Patreon",
imgPos: [0,1],
},
youtube: {
name: "YouTube",
imgPos: [0,9 ],
},
spotify: {
name: "Spotify",
imgPos: [0,10],
},
twitch: {
name: "Twitch",
imgPos: [0,11],
},
paypal: {
name: "Paypal",
imgPos: [0,12],
},
github: {
name: "Github",
imgPos: [0,13],
},
linkedin: {
name: "LinkedIn",
imgPos: [0,14],
},
discord: {
name: "Discord",
imgPos: [0,15],
},
mastodon: {
name: "Mastodon",
imgPos: [0,16],
},
};