From 0286d03edbd5cf855a31c78cf9fd20b4e5e7f401 Mon Sep 17 00:00:00 2001 From: Fang Date: Sat, 27 Jul 2019 15:00:28 +0200 Subject: [PATCH 01/26] Refactor code duplication in Chat root --- pkg/interface/chat/src/js/components/root.js | 94 +++++--------------- 1 file changed, 23 insertions(+), 71 deletions(-) diff --git a/pkg/interface/chat/src/js/components/root.js b/pkg/interface/chat/src/js/components/root.js index 739909db1..360357345 100644 --- a/pkg/interface/chat/src/js/components/root.js +++ b/pkg/interface/chat/src/js/components/root.js @@ -61,7 +61,7 @@ export class Root extends Component { let internalStation = host + '/hall-internal-' + circle; if (internalStation in state.configs) { - unreads[cir] = + unreads[cir] = state.configs[internalStation].red <= messages[cir][messages[cir].length - 1].num; } else { @@ -87,6 +87,18 @@ export class Root extends Component { inviteConfig = configs[`~${window.ship}/i`]; } + const renderChannelsSidebar = (props) => ( + + ); + return (
@@ -94,17 +106,7 @@ export class Root extends Component { render={ (props) => { return ( - }> + sidebar={renderChannelsSidebar(props)}>
@@ -119,18 +121,8 @@ export class Root extends Component { return ( - }> - + { return ( - }> + sidebar={renderDefaultSidebar(props)}> { - let station = + let station = props.match.params.ship + "/" + props.match.params.station; let messages = state.messages[station] || []; return ( - }> + sidebar={renderChannelsSidebar(props) }> { return ( - }> + sidebar={renderChannelsSidebar(props) }> @@ -221,18 +183,8 @@ export class Root extends Component { return ( - }> - + Date: Sat, 27 Jul 2019 16:11:15 +0200 Subject: [PATCH 02/26] Refactor message.js to fully support %lin and %url Renders messages based on their speech type, as opposed to interpreting %lin speeches and ignoring all others. Italicizes %lin "@" messages. Makes images clickable to open in new window. --- pkg/interface/chat/src/css/custom.css | 4 + .../chat/src/js/components/lib/message.js | 97 ++++++++++++------- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/pkg/interface/chat/src/css/custom.css b/pkg/interface/chat/src/css/custom.css index 46ec6d39f..074215e2e 100644 --- a/pkg/interface/chat/src/css/custom.css +++ b/pkg/interface/chat/src/css/custom.css @@ -87,6 +87,10 @@ h2 { font-weight: bold; } +.fs-italic { + font-style: italic; +} + .bg-v-light-gray { background-color: #f9f9f9; } diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index 50d34c189..49212c465 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -5,56 +5,85 @@ import moment from 'moment'; import _ from 'lodash'; export class Message extends Component { - - renderMessage(content) { + + renderSpeech(speech) { + if (_.has(speech, 'lin')) { + return this.renderLin(speech.lin.msg, speech.lin.pat); + } else if (_.has(speech, 'url')) { + return this.renderUrl(speech.url); + } else { + return this.renderUnknown(); + } + } + + renderUnknown() { + return this.renderLin('') + } + + renderLin(content, action = false) { return ( -

+

{content}

); } - renderContent() { - const { props } = this; - - let content = _.get( - props.msg, - 'sep.lin.msg', - '' - ); - + renderUrl(url) { try { - let url = new URL(content); - let imgMatch = + let urlObject = new URL(url); + let imgMatch = /(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM)$/ .exec( - url.pathname + urlObject.pathname ); if (imgMatch) { - return ( - - ) + return this.renderImageUrl(url); } else { - let url = this.urlTransmogrifier(content); - - return ( - {url} - ) + let localUrl = this.localizeUrl(url); + return this.renderAnchor(localUrl, url); } } catch(e) { - return this.renderMessage(content); + console.error('url render error', e); + return this.renderAnchor(url); } } - urlTransmogrifier(url) { + renderImageUrl(url) { + return this.renderAnchor(url, ( + + )); + } + + renderAnchor(href, content) { + content = content || href; + return ( + {content} + ); + } + + renderContent() { + const { props } = this; + + try { + if (!_.has(props.msg, 'sep')) { + return this.renderUnknown(); + } + return this.renderSpeech(props.msg.sep); + } catch (e) { + console.error('speech rendering error', e); + return this.renderUnknown(); + } + } + + localizeUrl(url) { if (typeof url !== 'string') { throw 'Only transmogrify strings!'; } const ship = window.ship; if (url.indexOf('arvo://') === 0) { @@ -68,7 +97,7 @@ export class Message extends Component { let pending = !!props.msg.pending ? ' o-80' : ''; let timestamp = moment.unix(props.msg.wen / 1000).format('hh:mm'); let datestamp = moment.unix(props.msg.wen / 1000).format('LL'); - + return (
Date: Sat, 27 Jul 2019 18:28:04 +0200 Subject: [PATCH 03/26] Refactor chat-input to fully support %lin and %url Supports sending both regular and action %lin messages, using the optional @ prefix. Now transforms on-urbit URLs into arvo:// URLs prior to sending, as opposed to on-render. Unfortunately, the type hall uses for URLs doesn't support non-HTTP protocols yet, so we send arvo:// URLs as %lin messages for now. Input field gets styled based on detected message type. Italics for action lins, underline for URLs. --- pkg/interface/chat/src/css/custom.css | 4 + .../chat/src/js/components/lib/chat-input.js | 105 ++++++++++++++++-- .../chat/src/js/components/lib/message.js | 21 +++- 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/pkg/interface/chat/src/css/custom.css b/pkg/interface/chat/src/css/custom.css index 074215e2e..5b949ec8f 100644 --- a/pkg/interface/chat/src/css/custom.css +++ b/pkg/interface/chat/src/css/custom.css @@ -91,6 +91,10 @@ h2 { font-style: italic; } +.td-underline { + text-decoration: underline; +} + .bg-v-light-gray { background-color: #f9f9f9; } diff --git a/pkg/interface/chat/src/js/components/lib/chat-input.js b/pkg/interface/chat/src/js/components/lib/chat-input.js index 363290598..1416562b9 100644 --- a/pkg/interface/chat/src/js/components/lib/chat-input.js +++ b/pkg/interface/chat/src/js/components/lib/chat-input.js @@ -54,7 +54,8 @@ export class ChatInput extends Component { setTimeout(closure, 2000);*/ this.state = { - message: "" + message: '', + messageType: 'lin' }; this.textareaRef = React.createRef(); @@ -100,22 +101,105 @@ export class ChatInput extends Component { } messageChange(event) { - this.setState({message: event.target.value}); + this.setState({ + message: event.target.value, + messageType: this.getSpeechType(event.target.value) + }); + } + + getSpeechType(input) { + if (input[0] === '@') { + return 'lin@' + } else if (this.isUrl(input)) { + return 'url'; + } else { + return 'lin'; + } + } + + getSpeechStyle(type) { + switch (type) { + case 'lin@': + return 'fs-italic'; + case 'url': + return 'td-underline'; + default: + return ''; + } + } + + isUrl(string) { + try { + const urlObject = new URL(string); + //NOTE we check for a host to ensure a url is actually being posted + // to combat false positives for things like "marzod: ur cool". + // this does mean you can't send "mailto:e@ma.il" as %url message, + // but the desirability of that seems questionable anyway. + return (urlObject.host !== ''); + } catch (e) { + return false; + } + } + + // turns select urls into arvo:// urls + // + // we detect app names from the url. if the app is known to handle requests + // for remote data (instead of serving only from the host) we transfor the + // url into a generic arvo:// one. + // the app name format is pretty distinct and rare to find in the non-urbit + // wild, but this could still result in false positives for older-school + // websites serving pages under /~user paths. + // we could match only on ship.arvo.network, but that would exclude those + // running on localhost or under a custom domain. + // + // + globalizeUrl(url) { + const urlObject = new URL(url); + const app = urlObject.pathname.split('/')[1]; + if (app === '~chat' || + app === '~publish') { + //TODO send proper url speeches once hall starts using a url type that + // supports non-http protocols. + return { lin: { + msg: 'arvo://' + url.slice(urlObject.origin.length+1), + pat: false + } }; + } else { + return {url}; + } + } + + speechFromInput(content, type = 'lin') { + switch (type) { + case 'lin': + return { lin: { + msg: content, + pat: false + } }; + // + case 'lin@': + return { lin: { + msg: content.slice(1), + pat: true + } }; + // + case 'url': + return this.globalizeUrl(content); + // + default: + throw new Error('Unimplemented speech type', type); + } } messageSubmit() { const { props, state } = this; + let message = { uid: uuid(), aut: window.ship, wen: Date.now(), aud: [props.station], - sep: { - lin: { - msg: state.message, - pat: false - } - } + sep: this.speechFromInput(state.message, state.messageType) }; props.api.hall( @@ -125,7 +209,8 @@ export class ChatInput extends Component { ); this.setState({ - message: "" + message: '', + messageType: 'lin' }); } @@ -160,7 +245,7 @@ export class ChatInput extends Component {
- {content} @@ -83,13 +87,20 @@ export class Message extends Component { } } + //NOTE see also lib/chat-input's globalizeUrl localizeUrl(url) { - if (typeof url !== 'string') { throw 'Only transmogrify strings!'; } - const ship = window.ship; - if (url.indexOf('arvo://') === 0) { - return `http://${ship}.arvo.network` + url.split('arvo://')[1]; + if (typeof url !== 'string') { throw 'Only localize strings!'; } + const arvo = 'arvo://'; + if (url.indexOf(arvo) === 0) { + // this application is being served by an urbit also, so /path will + // point to the arvo url as hosted by this same urbit. + let path = url.slice(arvo.length); + // ensure single leading / + if (path[0] !== '/') path = '/' + path; + return path; + } else { + return url; } - return url; } render() { From c91ad893c05daa634d4a5123b8f21ac1cbff650d Mon Sep 17 00:00:00 2001 From: Fang Date: Sun, 28 Jul 2019 13:06:08 +0200 Subject: [PATCH 04/26] Support rendering %app --- .../chat/src/js/components/lib/message.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index 1aa982e84..b4ba115aa 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -11,6 +11,8 @@ export class Message extends Component { return this.renderLin(speech.lin.msg, speech.lin.pat); } else if (_.has(speech, 'url')) { return this.renderUrl(speech.url); + } else if (_.has(speech, 'app')) { + return this.renderSpeech(speech.app.sep); } else { return this.renderUnknown(); } @@ -87,6 +89,16 @@ export class Message extends Component { } } + renderAuthor() { + const msg = this.props.msg; + const ship = '~' + msg.aut; + if (_.has(msg, 'sep.app.app')) { + return `:${msg.sep.app.app} (${ship})`; + } else { + return ship; + } + } + //NOTE see also lib/chat-input's globalizeUrl localizeUrl(url) { if (typeof url !== 'string') { throw 'Only localize strings!'; } @@ -120,7 +132,7 @@ export class Message extends Component {

- ~{props.msg.aut} + {this.renderAuthor()}

{timestamp}

From 1974c8b4e4d9eb4b820acb8c326ccebdcdb6745a Mon Sep 17 00:00:00 2001 From: Fang Date: Sun, 28 Jul 2019 13:09:55 +0200 Subject: [PATCH 05/26] Support rendering %ire --- pkg/interface/chat/src/js/components/lib/message.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index b4ba115aa..41d9f5b5d 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -11,6 +11,8 @@ export class Message extends Component { return this.renderLin(speech.lin.msg, speech.lin.pat); } else if (_.has(speech, 'url')) { return this.renderUrl(speech.url); + } else if (_.has(speech, 'ire')) { + return this.renderSpeech(speech.ire.sep); } else if (_.has(speech, 'app')) { return this.renderSpeech(speech.app.sep); } else { From ea3f46183a6c0f36c54a62955edd5adfc644e9c5 Mon Sep 17 00:00:00 2001 From: Fang Date: Sun, 28 Jul 2019 13:21:54 +0200 Subject: [PATCH 06/26] Support rendering %exp --- pkg/interface/chat/src/js/components/lib/message.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index 41d9f5b5d..6441c6feb 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -11,6 +11,8 @@ export class Message extends Component { return this.renderLin(speech.lin.msg, speech.lin.pat); } else if (_.has(speech, 'url')) { return this.renderUrl(speech.url); + } else if (_.has(speech, 'exp')) { + return this.renderExp(speech.exp.exp, speech.exp.res); } else if (_.has(speech, 'ire')) { return this.renderSpeech(speech.ire.sep); } else if (_.has(speech, 'app')) { @@ -77,6 +79,13 @@ export class Message extends Component { ); } + renderExp(expression, result) { + return (<> +

# {expression}

+

{result[0]}

+ ); + } + renderContent() { const { props } = this; From c4ad4aa2473d2730dc2c37fde045868d930cb2a9 Mon Sep 17 00:00:00 2001 From: Fang Date: Sun, 28 Jul 2019 22:33:54 +0200 Subject: [PATCH 07/26] Support rendering %fat --- pkg/interface/chat/src/css/custom.css | 5 ++++ .../chat/src/js/components/lib/message.js | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/pkg/interface/chat/src/css/custom.css b/pkg/interface/chat/src/css/custom.css index 5b949ec8f..293bcf79b 100644 --- a/pkg/interface/chat/src/css/custom.css +++ b/pkg/interface/chat/src/css/custom.css @@ -123,6 +123,11 @@ h2 { -webkit-box-orient: vertical; } +.clamp-attachment { + overflow: scroll; + max-height: 10em; +} + .lh-16 { line-height: 16px; } diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index 6441c6feb..b4c011e77 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -17,6 +17,8 @@ export class Message extends Component { return this.renderSpeech(speech.ire.sep); } else if (_.has(speech, 'app')) { return this.renderSpeech(speech.app.sep); + } else if (_.has(speech, 'fat')) { + return this.renderFat(speech.fat.sep, speech.fat.tac); } else { return this.renderUnknown(); } @@ -86,6 +88,29 @@ export class Message extends Component { ); } + renderFat(speech, attachment) { + return (<> + {this.renderSpeech(speech)} + {this.renderAttachment(attachment)} + ); + } + + renderAttachment(content, title = '') { + if (_.has(content, 'name')) { + return this.renderAttachment(content.name.tac, content.name.nom); + } + + return (
+ {'Attached: ' + title} + { _.has(content, 'text') + ?
{content.text}
+ : _.has(content, 'tank') + ? content.tank.map(l =>

{l}

) + : null + } + ); + } + renderContent() { const { props } = this; From 716af367bf00910225209c2698316814443cba56 Mon Sep 17 00:00:00 2001 From: Fang Date: Sun, 28 Jul 2019 23:21:03 +0200 Subject: [PATCH 08/26] Forbid sending empty messages And don't render any elements for them. --- pkg/interface/chat/src/js/components/lib/chat-input.js | 4 ++++ pkg/interface/chat/src/js/components/lib/message.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/pkg/interface/chat/src/js/components/lib/chat-input.js b/pkg/interface/chat/src/js/components/lib/chat-input.js index 1416562b9..777f9ae9b 100644 --- a/pkg/interface/chat/src/js/components/lib/chat-input.js +++ b/pkg/interface/chat/src/js/components/lib/chat-input.js @@ -194,6 +194,10 @@ export class ChatInput extends Component { messageSubmit() { const { props, state } = this; + if (state.message === '') { + return; + } + let message = { uid: uuid(), aut: window.ship, diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index b4c011e77..4910ac0a1 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -29,6 +29,9 @@ export class Message extends Component { } renderLin(content, action = false) { + if (content === '') { + return null; + } //TODO remove once arvo:// urls are supported in url speeches if (content.indexOf('arvo://') === 0) { return this.renderUrl(content); From c880736eea1fb7447ac87430625da813d6ba6ee1 Mon Sep 17 00:00:00 2001 From: Fang Date: Mon, 29 Jul 2019 01:58:08 +0200 Subject: [PATCH 09/26] Remove noisy printf --- pkg/interface/chat/src/js/components/lib/icons/sigil.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/interface/chat/src/js/components/lib/icons/sigil.js b/pkg/interface/chat/src/js/components/lib/icons/sigil.js index 2f3227ed0..b639de087 100644 --- a/pkg/interface/chat/src/js/components/lib/icons/sigil.js +++ b/pkg/interface/chat/src/js/components/lib/icons/sigil.js @@ -6,8 +6,6 @@ export class Sigil extends Component { render() { const { props } = this; - console.log("sigil ship", props.ship); - if (props.ship.length > 14) { return (
From c04ee7a0dee5eb279df869f6341badad729b6ce6 Mon Sep 17 00:00:00 2001 From: Fang Date: Mon, 29 Jul 2019 02:01:16 +0200 Subject: [PATCH 10/26] Ensure (%fat) messages get rendered within screen bounds --- pkg/interface/chat/src/css/custom.css | 5 +++++ pkg/interface/chat/src/js/components/lib/message.js | 12 +++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/interface/chat/src/css/custom.css b/pkg/interface/chat/src/css/custom.css index 293bcf79b..2f29bd562 100644 --- a/pkg/interface/chat/src/css/custom.css +++ b/pkg/interface/chat/src/css/custom.css @@ -123,9 +123,14 @@ h2 { -webkit-box-orient: vertical; } +.clamp-message { + max-width: calc(100% - 32px - .5em); +} + .clamp-attachment { overflow: scroll; max-height: 10em; + max-width: 100%; } .lh-16 { diff --git a/pkg/interface/chat/src/js/components/lib/message.js b/pkg/interface/chat/src/js/components/lib/message.js index 4910ac0a1..6e23f5a3e 100644 --- a/pkg/interface/chat/src/js/components/lib/message.js +++ b/pkg/interface/chat/src/js/components/lib/message.js @@ -103,12 +103,14 @@ export class Message extends Component { return this.renderAttachment(content.name.tac, content.name.nom); } - return (
- {'Attached: ' + title} + return (
+ {'Attached: ' + title} { _.has(content, 'text') - ?
{content.text}
+ ?
{content.text}
: _.has(content, 'tank') - ? content.tank.map(l =>

{l}

) + ?
+ {content.tank.map(l =>

{l}

)} +
: null } ); @@ -168,7 +170,7 @@ export class Message extends Component {
-
+

{this.renderAuthor()} From 37d061cbfed7c668b407524f373cafeda04d0cab Mon Sep 17 00:00:00 2001 From: Fang Date: Mon, 29 Jul 2019 02:08:19 +0200 Subject: [PATCH 11/26] Support sending %fat messages for clipboard pastes and long-form content. If a multi-line string is pasted into an empty input field, treat is as a "clipboard" message if it's sent directly afterwards. (A text %fat.) If a multi-line message is composed in any other way, treat it as a long-form message, moving all but the first line into the attachment. (A more display-friendly tank %fat.) --- .../chat/src/js/components/lib/chat-input.js | 66 +++++++++++++++---- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/pkg/interface/chat/src/js/components/lib/chat-input.js b/pkg/interface/chat/src/js/components/lib/chat-input.js index 777f9ae9b..2fc68e790 100644 --- a/pkg/interface/chat/src/js/components/lib/chat-input.js +++ b/pkg/interface/chat/src/js/components/lib/chat-input.js @@ -55,7 +55,8 @@ export class ChatInput extends Component { this.state = { message: '', - messageType: 'lin' + messageType: 'lin', + clipboard: null }; this.textareaRef = React.createRef(); @@ -101,15 +102,24 @@ export class ChatInput extends Component { } messageChange(event) { - this.setState({ - message: event.target.value, - messageType: this.getSpeechType(event.target.value) - }); + const input = event.target.value; + const previous = this.state.message; + //NOTE dumb hack to work around paste event flow oddities + const pasted = (previous.length === 0 && input.length > 1); + if (input !== this.state.clipboard) { + this.setState({ + message: input, + messageType: this.getSpeechType(input), + clipboard: (pasted ? input : null) + }); + } } getSpeechType(input) { - if (input[0] === '@') { - return 'lin@' + if (input.indexOf('\n') >= 0) { + return 'fat'; + } else if (input[0] === '@') { + return 'lin@'; } else if (this.isUrl(input)) { return 'url'; } else { @@ -117,12 +127,14 @@ export class ChatInput extends Component { } } - getSpeechStyle(type) { + getSpeechStyle(type, clipboard) { switch (type) { case 'lin@': return 'fs-italic'; case 'url': return 'td-underline'; + case 'fat': + if (clipboard) return 'code'; default: return ''; } @@ -169,7 +181,7 @@ export class ChatInput extends Component { } } - speechFromInput(content, type = 'lin') { + speechFromInput(content, type, clipboard) { switch (type) { case 'lin': return { lin: { @@ -186,6 +198,31 @@ export class ChatInput extends Component { case 'url': return this.globalizeUrl(content); // + case 'fat': + // clipboard contents + if (clipboard !== null) { + return { fat: { + sep: { lin: { msg: '', pat: false } }, + tac: { name: { + nom: 'clipboard', + tac: { text: content } + } } + } }; + // long-form message + } else { + const lines = content.split('\n'); + return { fat: { + sep: { lin: { + msg: lines[0], + pat: false + } }, + tac: { name: { + nom: 'long-form', + tac: { tank: lines.slice(1).map(l => { return {leaf: l }; }) } + } }, + } }; + } + // default: throw new Error('Unimplemented speech type', type); } @@ -203,7 +240,11 @@ export class ChatInput extends Component { aut: window.ship, wen: Date.now(), aud: [props.station], - sep: this.speechFromInput(state.message, state.messageType) + sep: this.speechFromInput( + state.message, + state.messageType, + state.clipboard + ) }; props.api.hall( @@ -249,7 +290,10 @@ export class ChatInput extends Component {

- Date: Mon, 29 Jul 2019 02:16:11 +0200 Subject: [PATCH 12/26] Make input area resizable and touch up its styling issues. --- pkg/interface/chat/src/js/components/chat.js | 4 ++-- pkg/interface/chat/src/js/components/lib/chat-input.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/interface/chat/src/js/components/chat.js b/pkg/interface/chat/src/js/components/chat.js index 4fb15236e..3a7445566 100644 --- a/pkg/interface/chat/src/js/components/chat.js +++ b/pkg/interface/chat/src/js/components/chat.js @@ -210,8 +210,8 @@ export class ChatScreen extends Component { numPeers={peers.length} />
{ this.scrollElement = el; }}>
{chatMessages} diff --git a/pkg/interface/chat/src/js/components/lib/chat-input.js b/pkg/interface/chat/src/js/components/lib/chat-input.js index 2fc68e790..159374f1d 100644 --- a/pkg/interface/chat/src/js/components/lib/chat-input.js +++ b/pkg/interface/chat/src/js/components/lib/chat-input.js @@ -281,7 +281,7 @@ export class ChatInput extends Component { } return ( -
+
-
+