mirror of
https://github.com/urbit/shrub.git
synced 2024-12-03 05:43:18 +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))
|
url+(crip (apix:en-purl:html url.sep))
|
||||||
::
|
::
|
||||||
$exp
|
$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
|
$ire
|
||||||
=+ num=(~(get by known) top.sep)
|
=+ num=(~(get by known) top.sep)
|
||||||
@ -2402,7 +2410,11 @@
|
|||||||
==
|
==
|
||||||
::
|
::
|
||||||
$exp
|
$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 ~
|
?~ res.sep ~
|
||||||
=- [' ' (tr-chow (dec wyd) ' ' -)]~
|
=- [' ' (tr-chow (dec wyd) ' ' -)]~
|
||||||
~(ram re (snag 0 `(list tank)`res.sep))
|
~(ram re (snag 0 `(list tank)`res.sep))
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
++ lank ::: tank as string arr
|
++ lank ::: tank as string arr
|
||||||
|= a/tank
|
|= a/tank
|
||||||
^- json
|
^- json
|
||||||
a+(turn (wash [0 1.024] a) tape)
|
a+(turn (wash [0 80] a) tape)
|
||||||
::
|
::
|
||||||
++ dank ::: tank
|
++ dank ::: tank
|
||||||
|= a/tank
|
|= a/tank
|
||||||
@ -577,7 +577,7 @@
|
|||||||
:+ ~ u.exp
|
:+ ~ u.exp
|
||||||
=+ res=((ot res+(ar dank) ~) a)
|
=+ res=((ot res+(ar dank) ~) a)
|
||||||
?^ res u.res
|
?^ 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
|
++ atta ::: attache
|
||||||
^- $-(json (unit attache))
|
^- $-(json (unit attache))
|
||||||
|
@ -15904,7 +15904,6 @@
|
|||||||
|* tem=rule
|
|* tem=rule
|
||||||
%- star
|
%- star
|
||||||
;~ pose
|
;~ pose
|
||||||
whit
|
|
||||||
;~(pfix bas tem)
|
;~(pfix bas tem)
|
||||||
;~(less tem prn)
|
;~(less tem prn)
|
||||||
==
|
==
|
||||||
|
@ -208,12 +208,11 @@
|
|||||||
%give
|
%give
|
||||||
%response
|
%response
|
||||||
%start
|
%start
|
||||||
:- 404
|
::
|
||||||
:~ ['content-type' 'text/html']
|
%+ complete-http-start-event
|
||||||
['content-length' '156']
|
:- 404
|
||||||
==
|
['content-type' 'text/html']~
|
||||||
[~ (error-page:http-server-gate 404 %.n '/' ~)]
|
[~ (error-page:http-server-gate 404 %.n '/' ~)]
|
||||||
complete=%.y
|
|
||||||
== ==
|
== ==
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -384,12 +383,11 @@
|
|||||||
^= expected-move
|
^= expected-move
|
||||||
:~ :* duct=~[/http-blah] %give %response
|
:~ :* duct=~[/http-blah] %give %response
|
||||||
%start
|
%start
|
||||||
:- 500
|
::
|
||||||
:~ ['content-type' 'text/html']
|
%+ complete-http-start-event
|
||||||
['content-length' '180']
|
:- 500
|
||||||
==
|
['content-type' 'text/html']~
|
||||||
[~ (internal-server-error:http-server-gate %.n '/' ~)]
|
[~ (internal-server-error:http-server-gate %.n '/' ~)]
|
||||||
complete=%.y
|
|
||||||
== == ==
|
== == ==
|
||||||
::
|
::
|
||||||
;: weld
|
;: weld
|
||||||
@ -736,14 +734,13 @@
|
|||||||
==
|
==
|
||||||
^= expected-move
|
^= expected-move
|
||||||
:~ :* duct=~[/http-blah] %give %response
|
:~ :* duct=~[/http-blah] %give %response
|
||||||
:* %start
|
%start
|
||||||
:- 200
|
::
|
||||||
:~ ['content-type' 'text/plain']
|
%+ complete-http-start-event
|
||||||
['content-length' '13']
|
:- 200
|
||||||
==
|
['content-type' 'text/plain']~
|
||||||
`[13 'one two three']
|
`[13 'one two three']
|
||||||
%.y
|
== == ==
|
||||||
== == == ==
|
|
||||||
::
|
::
|
||||||
;: weld
|
;: weld
|
||||||
results1
|
results1
|
||||||
@ -870,11 +867,10 @@
|
|||||||
%give
|
%give
|
||||||
%response
|
%response
|
||||||
%start
|
%start
|
||||||
:- 403
|
|
||||||
:~ ['content-type' 'text/html']
|
|
||||||
['content-length' '182']
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
|
%+ complete-http-start-event
|
||||||
|
:- 403
|
||||||
|
['content-type' 'text/html']~
|
||||||
:- ~
|
:- ~
|
||||||
%- error-page:http-server-gate :*
|
%- error-page:http-server-gate :*
|
||||||
403
|
403
|
||||||
@ -882,8 +878,6 @@
|
|||||||
'/~/channel/1234567890abcdef'
|
'/~/channel/1234567890abcdef'
|
||||||
~
|
~
|
||||||
==
|
==
|
||||||
::
|
|
||||||
complete=%.y
|
|
||||||
== ==
|
== ==
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -2050,12 +2044,11 @@
|
|||||||
%give
|
%give
|
||||||
%response
|
%response
|
||||||
%start
|
%start
|
||||||
:- 200
|
::
|
||||||
:~ ['content-type' 'text/html']
|
%+ complete-http-start-event
|
||||||
['content-length' '1752']
|
:- 200
|
||||||
==
|
['content-type' 'text/html']~
|
||||||
[~ (login-page:http-server-gate `'/~landscape/inner-path' ~nul)]
|
[~ (login-page:http-server-gate `'/~landscape/inner-path' ~nul)]
|
||||||
complete=%.y
|
|
||||||
== ==
|
== ==
|
||||||
==
|
==
|
||||||
:: a response post redirects back to the application, setting cookie
|
:: a response post redirects back to the application, setting cookie
|
||||||
@ -2201,4 +2194,12 @@
|
|||||||
:: This is the default code for a fakeship.
|
:: This is the default code for a fakeship.
|
||||||
::
|
::
|
||||||
[~ ~ %noun !>(.~lidlut-tabwed-savheb-loslux)]
|
[~ ~ %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;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fs-italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.bg-v-light-gray {
|
.bg-v-light-gray {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
@ -116,6 +124,16 @@ h2 {
|
|||||||
-webkit-box-orient: vertical;
|
-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 {
|
.lh-16 {
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
}
|
}
|
||||||
|
@ -226,8 +226,8 @@ export class ChatScreen extends Component {
|
|||||||
numPeers={peers.length} />
|
numPeers={peers.length} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="overflow-y-scroll pt3 flex flex-column-reverse"
|
className="overflow-y-scroll pt3 pb2 flex flex-column-reverse"
|
||||||
style={{ height: 'calc(100% - 157px)' }}
|
style={{ height: 'calc(100% - 157px)', resize: 'vertical' }}
|
||||||
onScroll={this.onScroll}>
|
onScroll={this.onScroll}>
|
||||||
<div ref={ el => { this.scrollElement = el; }}></div>
|
<div ref={ el => { this.scrollElement = el; }}></div>
|
||||||
{chatMessages}
|
{chatMessages}
|
||||||
|
@ -54,7 +54,9 @@ export class ChatInput extends Component {
|
|||||||
setTimeout(closure, 2000);*/
|
setTimeout(closure, 2000);*/
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
message: ""
|
message: '',
|
||||||
|
messageType: 'lin',
|
||||||
|
clipboard: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.textareaRef = React.createRef();
|
this.textareaRef = React.createRef();
|
||||||
@ -100,22 +102,165 @@ export class ChatInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
messageChange(event) {
|
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() {
|
messageSubmit() {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
|
|
||||||
|
if (state.message === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let message = {
|
let message = {
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
aut: window.ship,
|
aut: window.ship,
|
||||||
wen: Date.now(),
|
wen: Date.now(),
|
||||||
aud: [props.station],
|
aud: [props.station],
|
||||||
sep: {
|
sep: this.speechFromInput(
|
||||||
lin: {
|
state.message,
|
||||||
msg: state.message,
|
state.messageType,
|
||||||
pat: false
|
state.clipboard
|
||||||
}
|
)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
props.api.hall(
|
props.api.hall(
|
||||||
@ -125,7 +270,8 @@ export class ChatInput extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
message: ""
|
message: '',
|
||||||
|
messageType: 'lin'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +297,7 @@ export class ChatInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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={{
|
<div className="fl" style={{
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
flexBasis: 32,
|
flexBasis: 32,
|
||||||
@ -159,9 +305,12 @@ export class ChatInput extends Component {
|
|||||||
}}>
|
}}>
|
||||||
<Sigil ship={window.ship} size={32} />
|
<Sigil ship={window.ship} size={32} />
|
||||||
</div>
|
</div>
|
||||||
<div className="fr h-100 flex" style={{ flexGrow: 1, height: 40 }}>
|
<div className="fr h-100 flex" style={{ flexGrow: 1 }}>
|
||||||
<input className="ml2 bn"
|
<textarea
|
||||||
style={{ flexGrow: 1, height: 40 }}
|
className={'ml2 mt2 mr2 bn ' +
|
||||||
|
this.getSpeechStyle(state.messageType, state.clipboard)
|
||||||
|
}
|
||||||
|
style={{ flexGrow: 1, height: 40, resize: 'none' }}
|
||||||
ref={this.textareaRef}
|
ref={this.textareaRef}
|
||||||
placeholder={props.placeholder}
|
placeholder={props.placeholder}
|
||||||
value={state.message}
|
value={state.message}
|
||||||
|
@ -5,62 +5,168 @@ import moment from 'moment';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export class Message extends Component {
|
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 (
|
return (
|
||||||
<p className="body-regular-400 v-top">
|
<p className={`body-regular-400 v-top ${action ? 'fs-italic' : ''}`}>
|
||||||
{content}
|
{content}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderUrl(url) {
|
||||||
const { props } = this;
|
|
||||||
|
|
||||||
let content = _.get(
|
|
||||||
props.msg,
|
|
||||||
'sep.lin.msg',
|
|
||||||
'<unknown message type>'
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let url = new URL(content);
|
let urlObject = new URL(url);
|
||||||
let imgMatch =
|
let imgMatch =
|
||||||
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM)$/
|
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM)$/
|
||||||
.exec(
|
.exec(
|
||||||
url.pathname
|
urlObject.pathname
|
||||||
);
|
);
|
||||||
if (imgMatch) {
|
if (imgMatch) {
|
||||||
return (
|
return this.renderImageUrl(url);
|
||||||
<img
|
|
||||||
src={content}
|
|
||||||
style={{
|
|
||||||
width:"50%",
|
|
||||||
maxWidth: '250px'
|
|
||||||
}}
|
|
||||||
></img>
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
let url = this.urlTransmogrifier(content);
|
let localUrl = this.localizeUrl(url);
|
||||||
|
return this.renderAnchor(localUrl, url);
|
||||||
return (
|
|
||||||
<a className="body-regular"
|
|
||||||
href={url}
|
|
||||||
target="_blank">{url}</a>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return this.renderMessage(content);
|
console.error('url render error', e);
|
||||||
|
return this.renderAnchor(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
urlTransmogrifier(url) {
|
renderImageUrl(url) {
|
||||||
if (typeof url !== 'string') { throw 'Only transmogrify strings!'; }
|
return this.renderAnchor(url, (
|
||||||
const ship = window.ship;
|
<img
|
||||||
if (url.indexOf('arvo://') === 0) {
|
src={url}
|
||||||
return url.split('arvo://')[1];
|
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() {
|
render() {
|
||||||
@ -82,10 +188,10 @@ export class Message extends Component {
|
|||||||
<div className="fl mr2">
|
<div className="fl mr2">
|
||||||
<Sigil ship={props.msg.aut} size={36} />
|
<Sigil ship={props.msg.aut} size={36} />
|
||||||
</div>
|
</div>
|
||||||
<div className="fr" style={{ flexGrow: 1, marginTop: -8 }}>
|
<div className="fr clamp-message" style={{ flexGrow: 1, marginTop: -8 }}>
|
||||||
<div className="hide-child">
|
<div className="hide-child">
|
||||||
<p className="v-top label-small-mono gray dib mr3">
|
<p className="v-top label-small-mono gray dib mr3">
|
||||||
~{props.msg.aut}
|
{this.renderAuthor()}
|
||||||
</p>
|
</p>
|
||||||
<p className="v-top label-small-mono gray dib">{timestamp}</p>
|
<p className="v-top label-small-mono gray dib">{timestamp}</p>
|
||||||
<p className="v-top label-small-mono ml2 gray dib child">
|
<p className="v-top label-small-mono ml2 gray dib child">
|
||||||
@ -105,12 +211,11 @@ export class Message extends Component {
|
|||||||
minHeight: 'min-content'
|
minHeight: 'min-content'
|
||||||
}}>
|
}}>
|
||||||
<p className="child pl3 pr2 label-small-mono gray dib">{timestamp}</p>
|
<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()}
|
{this.renderContent()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export class Root extends Component {
|
|||||||
let internalStation = host + '/hall-internal-' + circle;
|
let internalStation = host + '/hall-internal-' + circle;
|
||||||
|
|
||||||
if (internalStation in state.configs) {
|
if (internalStation in state.configs) {
|
||||||
unreads[cir] =
|
unreads[cir] =
|
||||||
state.configs[internalStation].red <=
|
state.configs[internalStation].red <=
|
||||||
messages[cir][messages[cir].length - 1].num;
|
messages[cir][messages[cir].length - 1].num;
|
||||||
} else {
|
} else {
|
||||||
@ -87,6 +87,18 @@ export class Root extends Component {
|
|||||||
inviteConfig = configs[`~${window.ship}/i`];
|
inviteConfig = configs[`~${window.ship}/i`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderChannelsSidebar = (props) => (
|
||||||
|
<Sidebar
|
||||||
|
circles={circles}
|
||||||
|
messagePreviews={messagePreviews}
|
||||||
|
invites={invites}
|
||||||
|
unreads={unreads}
|
||||||
|
api={api}
|
||||||
|
inviteConfig={inviteConfig}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div>
|
<div>
|
||||||
@ -94,17 +106,7 @@ export class Root extends Component {
|
|||||||
render={ (props) => {
|
render={ (props) => {
|
||||||
return (
|
return (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
sidebar={
|
sidebar={renderChannelsSidebar(props)}>
|
||||||
<Sidebar
|
|
||||||
circles={circles}
|
|
||||||
messagePreviews={messagePreviews}
|
|
||||||
invites={invites}
|
|
||||||
unreads={unreads}
|
|
||||||
api={api}
|
|
||||||
inviteConfig={inviteConfig}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<div className="w-100 h-100 fr" style={{ flexGrow: 1 }}>
|
<div className="w-100 h-100 fr" style={{ flexGrow: 1 }}>
|
||||||
<div className="dt w-100 h-100">
|
<div className="dt w-100 h-100">
|
||||||
<div className="dtc center v-mid w-100 h-100 bg-white">
|
<div className="dtc center v-mid w-100 h-100 bg-white">
|
||||||
@ -119,18 +121,8 @@ export class Root extends Component {
|
|||||||
return (
|
return (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
spinner={this.state.spinner}
|
spinner={this.state.spinner}
|
||||||
sidebar={
|
sidebar={renderChannelsSidebar(props)}>
|
||||||
<Sidebar
|
<NewScreen
|
||||||
circles={circles}
|
|
||||||
messagePreviews={messagePreviews}
|
|
||||||
invites={invites}
|
|
||||||
unreads={unreads}
|
|
||||||
api={api}
|
|
||||||
inviteConfig={inviteConfig}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<NewScreen
|
|
||||||
setSpinner={this.setSpinner}
|
setSpinner={this.setSpinner}
|
||||||
api={api}
|
api={api}
|
||||||
circles={circles}
|
circles={circles}
|
||||||
@ -143,17 +135,7 @@ export class Root extends Component {
|
|||||||
render={ (props) => {
|
render={ (props) => {
|
||||||
return (
|
return (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
sidebar={
|
sidebar={renderDefaultSidebar(props)}>
|
||||||
<Sidebar
|
|
||||||
circles={circles}
|
|
||||||
messagePreviews={messagePreviews}
|
|
||||||
invites={invites}
|
|
||||||
unreads={unreads}
|
|
||||||
api={api}
|
|
||||||
inviteConfig={inviteConfig}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<LandingScreen
|
<LandingScreen
|
||||||
api={api}
|
api={api}
|
||||||
configs={configs}
|
configs={configs}
|
||||||
@ -164,24 +146,14 @@ export class Root extends Component {
|
|||||||
}} />
|
}} />
|
||||||
<Route exact path="/~chat/:ship/:station"
|
<Route exact path="/~chat/:ship/:station"
|
||||||
render={ (props) => {
|
render={ (props) => {
|
||||||
let station =
|
let station =
|
||||||
props.match.params.ship
|
props.match.params.ship
|
||||||
+ "/" +
|
+ "/" +
|
||||||
props.match.params.station;
|
props.match.params.station;
|
||||||
let messages = state.messages[station] || [];
|
let messages = state.messages[station] || [];
|
||||||
return (
|
return (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
sidebar={
|
sidebar={renderChannelsSidebar(props) }>
|
||||||
<Sidebar
|
|
||||||
circles={circles}
|
|
||||||
messagePreviews={messagePreviews}
|
|
||||||
invites={invites}
|
|
||||||
unreads={unreads}
|
|
||||||
api={api}
|
|
||||||
inviteConfig={inviteConfig}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<ChatScreen
|
<ChatScreen
|
||||||
api={api}
|
api={api}
|
||||||
configs={configs}
|
configs={configs}
|
||||||
@ -197,19 +169,9 @@ export class Root extends Component {
|
|||||||
render={ (props) => {
|
render={ (props) => {
|
||||||
return (
|
return (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
sidebar={
|
sidebar={renderChannelsSidebar(props) }>
|
||||||
<Sidebar
|
|
||||||
circles={circles}
|
|
||||||
messagePreviews={messagePreviews}
|
|
||||||
invites={invites}
|
|
||||||
unreads={unreads}
|
|
||||||
api={api}
|
|
||||||
inviteConfig={inviteConfig}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<MemberScreen
|
<MemberScreen
|
||||||
{...props}
|
{...props}
|
||||||
api={api}
|
api={api}
|
||||||
peers={state.peers}
|
peers={state.peers}
|
||||||
/>
|
/>
|
||||||
@ -221,18 +183,8 @@ export class Root extends Component {
|
|||||||
return (
|
return (
|
||||||
<Skeleton
|
<Skeleton
|
||||||
spinner={this.state.spinner}
|
spinner={this.state.spinner}
|
||||||
sidebar={
|
sidebar={renderChannelsSidebar(props) }>
|
||||||
<Sidebar
|
<SettingsScreen
|
||||||
circles={circles}
|
|
||||||
messagePreviews={messagePreviews}
|
|
||||||
invites={invites}
|
|
||||||
unreads={unreads}
|
|
||||||
api={api}
|
|
||||||
inviteConfig={inviteConfig}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<SettingsScreen
|
|
||||||
{...props}
|
{...props}
|
||||||
setSpinner={this.setSpinner}
|
setSpinner={this.setSpinner}
|
||||||
api={api}
|
api={api}
|
||||||
|
@ -83,6 +83,30 @@ export class Sidebar extends Component {
|
|||||||
this.props.history.push('/~chat/new');
|
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() {
|
render() {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
let station = props.match.params.ship + '/' + props.match.params.station;
|
let station = props.match.params.ship + '/' + props.match.params.station;
|
||||||
@ -93,7 +117,9 @@ export class Sidebar extends Component {
|
|||||||
})
|
})
|
||||||
.map((cir) => {
|
.map((cir) => {
|
||||||
let msg = props.messagePreviews[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 aut = !!msg ? msg.gam.aut : '';
|
||||||
let wen = !!msg ? msg.gam.wen : 0;
|
let wen = !!msg ? msg.gam.wen : 0;
|
||||||
let datetime =
|
let datetime =
|
||||||
|
@ -23,6 +23,12 @@ pre {
|
|||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
|
Loading…
Reference in New Issue
Block a user