mirror of
https://github.com/urbit/shrub.git
synced 2024-12-02 08:55:07 +03:00
Merge branch 'master' into test-predicate
This commit is contained in:
commit
96b8b3fe98
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2317,7 +2317,15 @@
|
||||
url+(crip (apix:en-purl:html url.sep))
|
||||
::
|
||||
$exp
|
||||
mor+~[txt+"# {(trip exp.sep)}" tan+res.sep]
|
||||
=/ texp=tape ['>' ' ' (trip exp.sep)]
|
||||
:- %mor
|
||||
|- ^- (list sole-effect:sole-sur)
|
||||
?: =("" texp) [tan+res.sep ~]
|
||||
=/ newl (find "\0a" texp)
|
||||
?~ newl [txt+texp $(texp "")]
|
||||
=+ (trim u.newl texp)
|
||||
:- txt+(scag u.newl texp)
|
||||
$(texp [' ' ' ' (slag +(u.newl) texp)])
|
||||
::
|
||||
$ire
|
||||
=+ num=(~(get by known) top.sep)
|
||||
@ -2402,7 +2410,11 @@
|
||||
==
|
||||
::
|
||||
$exp
|
||||
:- (tr-chow wyd '#' ' ' (trip exp.sep))
|
||||
=+ texp=(trip exp.sep)
|
||||
=+ newline=(find "\0a" texp)
|
||||
=? texp ?=(^ newline)
|
||||
(weld (scag u.newline texp) " ...")
|
||||
:- (tr-chow wyd '#' ' ' texp)
|
||||
?~ res.sep ~
|
||||
=- [' ' (tr-chow (dec wyd) ' ' -)]~
|
||||
~(ram re (snag 0 `(list tank)`res.sep))
|
||||
|
@ -65,7 +65,7 @@
|
||||
++ lank ::: tank as string arr
|
||||
|= a/tank
|
||||
^- json
|
||||
a+(turn (wash [0 1.024] a) tape)
|
||||
a+(turn (wash [0 80] a) tape)
|
||||
::
|
||||
++ dank ::: tank
|
||||
|= a/tank
|
||||
@ -577,7 +577,7 @@
|
||||
:+ ~ u.exp
|
||||
=+ res=((ot res+(ar dank) ~) a)
|
||||
?^ res u.res
|
||||
p:(mule |.([(sell (slap !>(..zuse) (ream u.exp)))]~)) ::TODO oldz
|
||||
p:(mule |.([(sell (slap !>(..^zuse) (ream u.exp)))]~)) ::TODO oldz
|
||||
::
|
||||
++ atta ::: attache
|
||||
^- $-(json (unit attache))
|
||||
|
@ -15904,7 +15904,6 @@
|
||||
|* tem=rule
|
||||
%- star
|
||||
;~ pose
|
||||
whit
|
||||
;~(pfix bas tem)
|
||||
;~(less tem prn)
|
||||
==
|
||||
|
@ -208,12 +208,11 @@
|
||||
%give
|
||||
%response
|
||||
%start
|
||||
:- 404
|
||||
:~ ['content-type' 'text/html']
|
||||
['content-length' '156']
|
||||
==
|
||||
::
|
||||
%+ complete-http-start-event
|
||||
:- 404
|
||||
['content-type' 'text/html']~
|
||||
[~ (error-page:http-server-gate 404 %.n '/' ~)]
|
||||
complete=%.y
|
||||
== ==
|
||||
==
|
||||
::
|
||||
@ -384,12 +383,11 @@
|
||||
^= expected-move
|
||||
:~ :* duct=~[/http-blah] %give %response
|
||||
%start
|
||||
:- 500
|
||||
:~ ['content-type' 'text/html']
|
||||
['content-length' '180']
|
||||
==
|
||||
::
|
||||
%+ complete-http-start-event
|
||||
:- 500
|
||||
['content-type' 'text/html']~
|
||||
[~ (internal-server-error:http-server-gate %.n '/' ~)]
|
||||
complete=%.y
|
||||
== == ==
|
||||
::
|
||||
;: weld
|
||||
@ -736,14 +734,13 @@
|
||||
==
|
||||
^= expected-move
|
||||
:~ :* duct=~[/http-blah] %give %response
|
||||
:* %start
|
||||
:- 200
|
||||
:~ ['content-type' 'text/plain']
|
||||
['content-length' '13']
|
||||
==
|
||||
`[13 'one two three']
|
||||
%.y
|
||||
== == == ==
|
||||
%start
|
||||
::
|
||||
%+ complete-http-start-event
|
||||
:- 200
|
||||
['content-type' 'text/plain']~
|
||||
`[13 'one two three']
|
||||
== == ==
|
||||
::
|
||||
;: weld
|
||||
results1
|
||||
@ -870,11 +867,10 @@
|
||||
%give
|
||||
%response
|
||||
%start
|
||||
:- 403
|
||||
:~ ['content-type' 'text/html']
|
||||
['content-length' '182']
|
||||
==
|
||||
::
|
||||
%+ complete-http-start-event
|
||||
:- 403
|
||||
['content-type' 'text/html']~
|
||||
:- ~
|
||||
%- error-page:http-server-gate :*
|
||||
403
|
||||
@ -882,8 +878,6 @@
|
||||
'/~/channel/1234567890abcdef'
|
||||
~
|
||||
==
|
||||
::
|
||||
complete=%.y
|
||||
== ==
|
||||
==
|
||||
::
|
||||
@ -2050,12 +2044,11 @@
|
||||
%give
|
||||
%response
|
||||
%start
|
||||
:- 200
|
||||
:~ ['content-type' 'text/html']
|
||||
['content-length' '1752']
|
||||
==
|
||||
::
|
||||
%+ complete-http-start-event
|
||||
:- 200
|
||||
['content-type' 'text/html']~
|
||||
[~ (login-page:http-server-gate `'/~landscape/inner-path' ~nul)]
|
||||
complete=%.y
|
||||
== ==
|
||||
==
|
||||
:: a response post redirects back to the application, setting cookie
|
||||
@ -2201,4 +2194,12 @@
|
||||
:: This is the default code for a fakeship.
|
||||
::
|
||||
[~ ~ %noun !>(.~lidlut-tabwed-savheb-loslux)]
|
||||
:: produce the body of a %start http-event with the correct content-length
|
||||
::
|
||||
++ complete-http-start-event
|
||||
|= [response-header:http data=(unit octs)]
|
||||
=- [[status-code -] data %.y]
|
||||
?~ data headers
|
||||
%+ weld headers
|
||||
['content-length' (crip ((d-co:co 1) p.u.data))]~
|
||||
--
|
||||
|
@ -88,6 +88,14 @@ h2 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fs-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.td-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bg-v-light-gray {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
@ -116,6 +124,16 @@ h2 {
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.clamp-message {
|
||||
max-width: calc(100% - 36px - 1.5rem);
|
||||
}
|
||||
|
||||
.clamp-attachment {
|
||||
overflow: scroll;
|
||||
max-height: 10em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.lh-16 {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
@ -226,8 +226,8 @@ export class ChatScreen extends Component {
|
||||
numPeers={peers.length} />
|
||||
</div>
|
||||
<div
|
||||
className="overflow-y-scroll pt3 flex flex-column-reverse"
|
||||
style={{ height: 'calc(100% - 157px)' }}
|
||||
className="overflow-y-scroll pt3 pb2 flex flex-column-reverse"
|
||||
style={{ height: 'calc(100% - 157px)', resize: 'vertical' }}
|
||||
onScroll={this.onScroll}>
|
||||
<div ref={ el => { this.scrollElement = el; }}></div>
|
||||
{chatMessages}
|
||||
|
@ -54,7 +54,9 @@ export class ChatInput extends Component {
|
||||
setTimeout(closure, 2000);*/
|
||||
|
||||
this.state = {
|
||||
message: ""
|
||||
message: '',
|
||||
messageType: 'lin',
|
||||
clipboard: null
|
||||
};
|
||||
|
||||
this.textareaRef = React.createRef();
|
||||
@ -100,22 +102,165 @@ export class ChatInput extends Component {
|
||||
}
|
||||
|
||||
messageChange(event) {
|
||||
this.setState({message: 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 'exp';
|
||||
} else if (input.indexOf('\n') >= 0) {
|
||||
return 'fat';
|
||||
} else if (input[0] === '@') {
|
||||
return 'lin@';
|
||||
} else if (this.isUrl(input)) {
|
||||
return 'url';
|
||||
} else {
|
||||
return 'lin';
|
||||
}
|
||||
}
|
||||
|
||||
getSpeechStyle(type, clipboard) {
|
||||
switch (type) {
|
||||
case 'lin@':
|
||||
return 'fs-italic';
|
||||
case 'url':
|
||||
return 'td-underline';
|
||||
case 'exp':
|
||||
return 'code';
|
||||
case 'fat':
|
||||
if (clipboard) return 'code';
|
||||
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),
|
||||
pat: false
|
||||
} };
|
||||
} else {
|
||||
return {url};
|
||||
}
|
||||
}
|
||||
|
||||
speechFromInput(content, type, clipboard) {
|
||||
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);
|
||||
//
|
||||
case 'exp':
|
||||
// remove leading #
|
||||
content = content.slice(1);
|
||||
// remove insignificant leading whitespace.
|
||||
// aces might be relevant to style.
|
||||
while (content[0] === '\n') {
|
||||
content = content.slice(1);
|
||||
}
|
||||
return { exp: {
|
||||
exp: 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: { text: lines.slice(1).join('\n') }
|
||||
} },
|
||||
} };
|
||||
}
|
||||
//
|
||||
default:
|
||||
throw new Error('Unimplemented speech type', type);
|
||||
}
|
||||
}
|
||||
|
||||
messageSubmit() {
|
||||
const { props, state } = this;
|
||||
|
||||
if (state.message === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
state.clipboard
|
||||
)
|
||||
};
|
||||
|
||||
props.api.hall(
|
||||
@ -125,7 +270,8 @@ export class ChatInput extends Component {
|
||||
);
|
||||
|
||||
this.setState({
|
||||
message: ""
|
||||
message: '',
|
||||
messageType: 'lin'
|
||||
});
|
||||
}
|
||||
|
||||
@ -151,7 +297,7 @@ export class ChatInput extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt2 pa3 cf flex black bt b--black-30">
|
||||
<div className="pa3 cf flex black bt b--black-30" style={{ flexGrow: 1 }}>
|
||||
<div className="fl" style={{
|
||||
marginTop: 4,
|
||||
flexBasis: 32,
|
||||
@ -159,9 +305,12 @@ export class ChatInput extends Component {
|
||||
}}>
|
||||
<Sigil ship={window.ship} size={32} />
|
||||
</div>
|
||||
<div className="fr h-100 flex" style={{ flexGrow: 1, height: 40 }}>
|
||||
<input className="ml2 bn"
|
||||
style={{ flexGrow: 1, height: 40 }}
|
||||
<div className="fr h-100 flex" style={{ flexGrow: 1 }}>
|
||||
<textarea
|
||||
className={'ml2 mt2 mr2 bn ' +
|
||||
this.getSpeechStyle(state.messageType, state.clipboard)
|
||||
}
|
||||
style={{ flexGrow: 1, height: 40, resize: 'none' }}
|
||||
ref={this.textareaRef}
|
||||
placeholder={props.placeholder}
|
||||
value={state.message}
|
||||
|
@ -5,62 +5,168 @@ 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 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')) {
|
||||
return this.renderSpeech(speech.app.sep);
|
||||
} else if (_.has(speech, 'fat')) {
|
||||
return this.renderFat(speech.fat.sep, speech.fat.tac);
|
||||
} else {
|
||||
return this.renderUnknown();
|
||||
}
|
||||
}
|
||||
|
||||
renderUnknown() {
|
||||
return this.renderLin('<unknown message type>')
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return (
|
||||
<p className="body-regular-400 v-top">
|
||||
<p className={`body-regular-400 v-top ${action ? 'fs-italic' : ''}`}>
|
||||
{content}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { props } = this;
|
||||
|
||||
let content = _.get(
|
||||
props.msg,
|
||||
'sep.lin.msg',
|
||||
'<unknown message type>'
|
||||
);
|
||||
|
||||
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 (
|
||||
<img
|
||||
src={content}
|
||||
style={{
|
||||
width:"50%",
|
||||
maxWidth: '250px'
|
||||
}}
|
||||
></img>
|
||||
)
|
||||
return this.renderImageUrl(url);
|
||||
} else {
|
||||
let url = this.urlTransmogrifier(content);
|
||||
|
||||
return (
|
||||
<a className="body-regular"
|
||||
href={url}
|
||||
target="_blank">{url}</a>
|
||||
)
|
||||
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) {
|
||||
if (typeof url !== 'string') { throw 'Only transmogrify strings!'; }
|
||||
const ship = window.ship;
|
||||
if (url.indexOf('arvo://') === 0) {
|
||||
return url.split('arvo://')[1];
|
||||
renderImageUrl(url) {
|
||||
return this.renderAnchor(url, (
|
||||
<img
|
||||
src={url}
|
||||
style={{
|
||||
width:"50%",
|
||||
maxWidth: '250px'
|
||||
}}
|
||||
></img>
|
||||
));
|
||||
}
|
||||
|
||||
renderAnchor(href, content) {
|
||||
content = content || href;
|
||||
return (
|
||||
<a className="body-regular"
|
||||
href={href}
|
||||
target="_blank">{content}</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderExp(expression, result) {
|
||||
return (<>
|
||||
<p>
|
||||
<pre className="clamp-attachment pa1 mt0 mb0 bg-light-gray">
|
||||
{expression}
|
||||
</pre>
|
||||
<pre className="clamp-attachment pa1 mt0 mb0">
|
||||
{result[0].join('\n')}
|
||||
</pre>
|
||||
</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
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 (<details>
|
||||
<summary className="inter fs-italic">{'Attached: ' + title}</summary>
|
||||
{ _.has(content, 'text')
|
||||
? (title === 'long-form')
|
||||
? this.renderParagraphs(content.text.split('\n'))
|
||||
: this.renderPlaintext(content.text)
|
||||
: _.has(content, 'tank')
|
||||
? this.renderPlaintext(content.tank.join('\n'))
|
||||
: null
|
||||
}
|
||||
</details>);
|
||||
}
|
||||
|
||||
renderParagraphs(paragraphs) {
|
||||
return (<div className="clamp-attachment">
|
||||
{paragraphs.map(p => (<p className="mt2">{p}</p>))}
|
||||
</div>);
|
||||
}
|
||||
|
||||
renderPlaintext(text) {
|
||||
return (<pre className="clamp-attachment">{text}</pre>);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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!'; }
|
||||
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.
|
||||
return url.slice(arvo.length);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -82,10 +188,10 @@ export class Message extends Component {
|
||||
<div className="fl mr2">
|
||||
<Sigil ship={props.msg.aut} size={36} />
|
||||
</div>
|
||||
<div className="fr" style={{ flexGrow: 1, marginTop: -8 }}>
|
||||
<div className="fr clamp-message" style={{ flexGrow: 1, marginTop: -8 }}>
|
||||
<div className="hide-child">
|
||||
<p className="v-top label-small-mono gray dib mr3">
|
||||
~{props.msg.aut}
|
||||
{this.renderAuthor()}
|
||||
</p>
|
||||
<p className="v-top label-small-mono gray dib">{timestamp}</p>
|
||||
<p className="v-top label-small-mono ml2 gray dib child">
|
||||
@ -105,12 +211,11 @@ export class Message extends Component {
|
||||
minHeight: 'min-content'
|
||||
}}>
|
||||
<p className="child pl3 pr2 label-small-mono gray dib">{timestamp}</p>
|
||||
<div className="fr" style={{ flexGrow: 1 }}>
|
||||
<div className="fr clamp-message" style={{ flexGrow: 1 }}>
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) => (
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
@ -94,17 +106,7 @@ export class Root extends Component {
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
sidebar={
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
}>
|
||||
sidebar={renderChannelsSidebar(props)}>
|
||||
<div className="w-100 h-100 fr" style={{ flexGrow: 1 }}>
|
||||
<div className="dt w-100 h-100">
|
||||
<div className="dtc center v-mid w-100 h-100 bg-white">
|
||||
@ -119,18 +121,8 @@ export class Root extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={this.state.spinner}
|
||||
sidebar={
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
}>
|
||||
<NewScreen
|
||||
sidebar={renderChannelsSidebar(props)}>
|
||||
<NewScreen
|
||||
setSpinner={this.setSpinner}
|
||||
api={api}
|
||||
circles={circles}
|
||||
@ -143,17 +135,7 @@ export class Root extends Component {
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
sidebar={
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
}>
|
||||
sidebar={renderDefaultSidebar(props)}>
|
||||
<LandingScreen
|
||||
api={api}
|
||||
configs={configs}
|
||||
@ -164,24 +146,14 @@ export class Root extends Component {
|
||||
}} />
|
||||
<Route exact path="/~chat/:ship/:station"
|
||||
render={ (props) => {
|
||||
let station =
|
||||
let station =
|
||||
props.match.params.ship
|
||||
+ "/" +
|
||||
props.match.params.station;
|
||||
let messages = state.messages[station] || [];
|
||||
return (
|
||||
<Skeleton
|
||||
sidebar={
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
}>
|
||||
sidebar={renderChannelsSidebar(props) }>
|
||||
<ChatScreen
|
||||
api={api}
|
||||
configs={configs}
|
||||
@ -197,19 +169,9 @@ export class Root extends Component {
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
sidebar={
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
}>
|
||||
sidebar={renderChannelsSidebar(props) }>
|
||||
<MemberScreen
|
||||
{...props}
|
||||
{...props}
|
||||
api={api}
|
||||
peers={state.peers}
|
||||
/>
|
||||
@ -221,18 +183,8 @@ export class Root extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={this.state.spinner}
|
||||
sidebar={
|
||||
<Sidebar
|
||||
circles={circles}
|
||||
messagePreviews={messagePreviews}
|
||||
invites={invites}
|
||||
unreads={unreads}
|
||||
api={api}
|
||||
inviteConfig={inviteConfig}
|
||||
{...props}
|
||||
/>
|
||||
}>
|
||||
<SettingsScreen
|
||||
sidebar={renderChannelsSidebar(props) }>
|
||||
<SettingsScreen
|
||||
{...props}
|
||||
setSpinner={this.setSpinner}
|
||||
api={api}
|
||||
|
@ -83,6 +83,30 @@ export class Sidebar extends Component {
|
||||
this.props.history.push('/~chat/new');
|
||||
}
|
||||
|
||||
summarizeMessage(speech) {
|
||||
const fallback = '...';
|
||||
if (_.has(speech, 'lin')) {
|
||||
return speech.lin.msg;
|
||||
} else if (_.has(speech, 'url')) {
|
||||
return speech.url;
|
||||
} else if (_.has(speech, 'exp')) {
|
||||
return '# ' + speech.exp.exp;
|
||||
} else if (_.has(speech, 'ire')) {
|
||||
return this.summarizeMessage(speech.ire.sep);
|
||||
} else if (_.has(speech, 'app')) {
|
||||
return this.summarizeMessage(speech.app.sep);
|
||||
} else if (_.has(speech, 'fat')) {
|
||||
const msg = this.summarizeMessage(speech.fat.sep);
|
||||
if (msg !== '' && msg !== fallback) return msg;
|
||||
return 'Attachment' +
|
||||
(_.has(speech, 'fat.tac.name.nom')
|
||||
? ': ' + speech.fat.tac.name.nom
|
||||
: '');
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let station = props.match.params.ship + '/' + props.match.params.station;
|
||||
@ -93,7 +117,9 @@ export class Sidebar extends Component {
|
||||
})
|
||||
.map((cir) => {
|
||||
let msg = props.messagePreviews[cir];
|
||||
let content = _.get(msg, 'gam.sep.lin.msg', 'No messages yet');
|
||||
let content = _.has(msg, 'gam.sep')
|
||||
? this.summarizeMessage(msg.gam.sep)
|
||||
: 'No messages yet';
|
||||
let aut = !!msg ? msg.gam.aut : '';
|
||||
let wen = !!msg ? msg.gam.wen : 0;
|
||||
let datetime =
|
||||
|
@ -23,6 +23,12 @@ pre {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 8px;
|
||||
background-color: #f9f9f9;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
|
Loading…
Reference in New Issue
Block a user