mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
initial set up for styling updates; actually add files
This commit is contained in:
parent
e5d8087979
commit
ebc852b430
131
webroot/js/chat/content-editable.js
Normal file
131
webroot/js/chat/content-editable.js
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
Since we can't really import react-contenteditable here, I'm borrowing code for this component from here:
|
||||
github.com/lovasoa/react-contenteditable/
|
||||
|
||||
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) {
|
||||
// Place the caret at the end of the element
|
||||
const target = document.createTextNode('');
|
||||
el.appendChild(target);
|
||||
// do not move caret if element was not focused
|
||||
const isTargetFocused = document.activeElement === el;
|
||||
if (target !== null && target.nodeValue !== null && isTargetFocused) {
|
||||
var sel = window.getSelection();
|
||||
if (sel !== null) {
|
||||
var range = document.createRange();
|
||||
range.setStart(target, target.nodeValue.length);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
if (el) el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHtml(str) {
|
||||
return str && str.replace(/ |\u202F|\u00A0/g, ' ');
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default class ContentEditable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.el = createRef();
|
||||
|
||||
this.lastHtml = '';
|
||||
|
||||
this.emitChange = this.emitChange.bind(this);
|
||||
this.getDOMElement = this.getDOMElement.bind(this);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { props } = this;
|
||||
const el = this.getDOMElement();
|
||||
|
||||
// We need not rerender if the change of props simply reflects the user's edits.
|
||||
// Rerendering in this case would make the cursor/caret jump
|
||||
|
||||
// Rerender if there is no element yet... (somehow?)
|
||||
if (!el) return true;
|
||||
|
||||
// ...or if html really changed... (programmatically, not by user edit)
|
||||
if (
|
||||
normalizeHtml(nextProps.html) !== normalizeHtml(el.innerHTML)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle additional properties
|
||||
return props.disabled !== nextProps.disabled ||
|
||||
props.tagName !== nextProps.tagName ||
|
||||
props.className !== nextProps.className ||
|
||||
props.innerRef !== nextProps.innerRef;
|
||||
}
|
||||
|
||||
|
||||
|
||||
componentDidUpdate() {
|
||||
const el = this.getDOMElement();
|
||||
if (!el) return;
|
||||
|
||||
// Perhaps React (whose VDOM gets outdated because we often prevent
|
||||
// rerendering) did not update the DOM. So we update it manually now.
|
||||
if (this.props.html !== el.innerHTML) {
|
||||
el.innerHTML = this.props.html;
|
||||
}
|
||||
this.lastHtml = this.props.html;
|
||||
replaceCaret(el);
|
||||
}
|
||||
|
||||
getDOMElement() {
|
||||
return (this.props.innerRef && typeof this.props.innerRef !== 'function' ? this.props.innerRef : this.el).current;
|
||||
}
|
||||
|
||||
|
||||
emitChange(originalEvt) {
|
||||
const el = this.getDOMElement();
|
||||
if (!el) return;
|
||||
|
||||
const html = el.innerHTML;
|
||||
if (this.props.onChange && html !== this.lastHtml) {
|
||||
// Clone event with Object.assign to avoid
|
||||
// "Cannot assign to read only property 'target' of object"
|
||||
const evt = Object.assign({}, originalEvt, {
|
||||
target: {
|
||||
value: html
|
||||
}
|
||||
});
|
||||
this.props.onChange(evt);
|
||||
}
|
||||
this.lastHtml = html;
|
||||
}
|
||||
|
||||
render(props) {
|
||||
const { html, innerRef } = props;
|
||||
return createElement(
|
||||
'div',
|
||||
{
|
||||
...props,
|
||||
ref: typeof innerRef === 'function' ? (current) => {
|
||||
innerRef(current)
|
||||
this.el.current = current
|
||||
} : innerRef || this.el,
|
||||
onInput: this.emitChange,
|
||||
onBlur: this.props.onBlur || this.emitChange,
|
||||
onKeyup: this.props.onKeyUp || this.emitChange,
|
||||
onKeydown: this.props.onKeyDown || this.emitChange,
|
||||
contentEditable: !this.props.disabled,
|
||||
dangerouslySetInnerHTML: { __html: html },
|
||||
},
|
||||
this.props.children,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { html, Component } from "https://unpkg.com/htm/preact/index.mjs?module";
|
||||
import UserInfo from './user-info.js';
|
||||
import UsernameForm from './username.js';
|
||||
import Chat from './chat.js';
|
||||
import Websocket from '../websocket.js';
|
||||
|
||||
@ -37,17 +37,19 @@ export default class StandaloneChat extends Component {
|
||||
return (
|
||||
html`
|
||||
<div class="flex">
|
||||
<${UserInfo}
|
||||
<${UsernameForm}
|
||||
username=${username}
|
||||
userAvatarImage=${userAvatarImage}
|
||||
handleUsernameChange=${this.handleUsernameChange}
|
||||
handleChatToggle=${this.handleChatToggle}
|
||||
/>
|
||||
|
||||
<${Chat}
|
||||
websocket=${websocket}
|
||||
username=${username}
|
||||
userAvatarImage=${userAvatarImage}
|
||||
chatEnabled />
|
||||
chatEnabled
|
||||
/>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
@ -6,8 +6,7 @@ const html = htm.bind(h);
|
||||
import { generateAvatar, setLocalStorage } from '../utils.js';
|
||||
import { KEY_USERNAME, KEY_AVATAR } from '../utils/chat.js';
|
||||
|
||||
|
||||
export default class UserInfo extends Component {
|
||||
export default class UsernameForm extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@ -83,25 +82,28 @@ export default class UserInfo extends Component {
|
||||
<img
|
||||
src=${userAvatarImage}
|
||||
alt=""
|
||||
id="username-avatar"
|
||||
class="rounded-full bg-black bg-opacity-50 border border-solid border-gray-700"
|
||||
/>
|
||||
<span class="text-indigo-600">${username}</span>
|
||||
<span id="username-display" class="text-indigo-600">${username}</span>
|
||||
</div>
|
||||
|
||||
<div id="user-info-change" 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"
|
||||
maxlength="100"
|
||||
placeholder="Update username"
|
||||
value=${username}
|
||||
onKeydown=${this.handleKeydown}
|
||||
ref=${this.textInput}
|
||||
>
|
||||
<button onClick=${this.handleUpdateUsername} class="bg-blue-500 hover:bg-blue-700 text-white py-1 px-1 rounded user-btn">Update</button>
|
||||
<button 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-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-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>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onClick=${handleChatToggle} class="flex bg-gray-800 hover:bg-gray-700">💬</button>
|
||||
<button type="button" id="chat-toggle" onClick=${handleChatToggle} class="flex bg-gray-800 hover:bg-gray-700">💬</button>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
// DELETE THIS FILE LATER.
|
||||
|
||||
Vue.component('owncast-footer', {
|
||||
props: {
|
||||
appVersion: {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// DELETE THIS FILE LATER.
|
||||
|
||||
import SOCKET_MESSAGE_TYPES from './utils/socket-message-types.js';
|
||||
|
||||
const KEY_USERNAME = 'owncast_username';
|
||||
|
||||
@ -4,6 +4,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||
<link href="./styles/layout.css" rel="stylesheet" />
|
||||
<link href="./styles/chat.css" rel="stylesheet" />
|
||||
<link href="./styles/standalone-chat.css" rel="stylesheet" />
|
||||
|
||||
|
||||
<script src="//unpkg.com/showdown/dist/showdown.min.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@ -165,3 +165,5 @@ But really it's just the innerHTML content.
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
/*
|
||||
Overall layout styles for all of owncast app.
|
||||
|
||||
*/
|
||||
|
||||
/* variables */
|
||||
:root {
|
||||
--header-height: 3.5em;
|
||||
|
||||
4
webroot/styles/standalone-chat.css
Normal file
4
webroot/styles/standalone-chat.css
Normal file
@ -0,0 +1,4 @@
|
||||
/*
|
||||
The styles in this file mostly ovveride those coming fro chat.css
|
||||
|
||||
*/
|
||||
Loading…
x
Reference in New Issue
Block a user