mirror of
https://github.com/urbit/shrub.git
synced 2025-01-04 18:43:46 +03:00
Merge branch 'mp/avatar-incl-displays' (#2788)
* mp/avatar-incl-displays: publish: show avatars if set link: show avatars if set groups: show avatars if set chat: display avatars if set Signed-off-by: Matilde Park <matilde@tlon.io>
This commit is contained in:
commit
d7fb181827
@ -31,7 +31,6 @@ const MARKDOWN_CONFIG = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export class ChatInput extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -117,7 +116,6 @@ export class ChatInput extends Component {
|
||||
return;
|
||||
}
|
||||
this.setState({ patpSearch: match[1].toLowerCase() });
|
||||
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
@ -276,6 +274,15 @@ export class ChatInput extends Component {
|
||||
const sigilClass = props.ownerContact
|
||||
? '' : 'mix-blend-diff';
|
||||
|
||||
const img = (props.ownerContact && (props.ownerContact.avatar !== null))
|
||||
? <img src={props.ownerContact.avatar} height={24} width={24} className="dib" />
|
||||
: <Sigil
|
||||
ship={window.ship}
|
||||
size={24}
|
||||
color={`#${color}`}
|
||||
classes={sigilClass}
|
||||
/>;
|
||||
|
||||
const candidates = _.chain(this.props.envelopes)
|
||||
.defaultTo([])
|
||||
.map('author')
|
||||
@ -324,12 +331,7 @@ export class ChatInput extends Component {
|
||||
height: 24
|
||||
}}
|
||||
>
|
||||
<Sigil
|
||||
ship={window.ship}
|
||||
size={24}
|
||||
color={`#${color}`}
|
||||
classes={sigilClass}
|
||||
/>
|
||||
{img}
|
||||
</div>
|
||||
<div
|
||||
className="fr h-100 flex bg-gray0-d lh-copy pl2 w-100 items-center"
|
||||
|
@ -1,11 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { uxToHex, cite } from '/lib/util';
|
||||
|
||||
|
||||
export class MemberElement extends Component {
|
||||
|
||||
onRemove() {
|
||||
const { props } = this;
|
||||
props.api.groups.remove([`~${props.ship}`], props.path);
|
||||
@ -24,7 +21,8 @@ export class MemberElement extends Component {
|
||||
} else if (window.ship !== props.ship && window.ship === props.owner) {
|
||||
actionElem = (
|
||||
<a onClick={this.onRemove.bind(this)}
|
||||
className="w-20 dib list-ship black white-d f8 pointer">
|
||||
className="w-20 dib list-ship black white-d f8 pointer"
|
||||
>
|
||||
Ban
|
||||
</a>
|
||||
);
|
||||
@ -34,20 +32,24 @@ export class MemberElement extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
let name = !!props.contact
|
||||
const name = props.contact
|
||||
? `${props.contact.nickname} (${cite(props.ship)})` : `${cite(props.ship)}`;
|
||||
let color = !!props.contact ? uxToHex(props.contact.color) : '000000';
|
||||
const color = props.contact ? uxToHex(props.contact.color) : '000000';
|
||||
|
||||
const img = (props.contact && (props.contact.avatar !== null))
|
||||
? <img src={props.contact.avatar} height={32} width={32} className="dib" />
|
||||
: <Sigil ship={props.ship} size={32} color={`#${color}`} />;
|
||||
|
||||
return (
|
||||
<div className="flex mb2">
|
||||
<Sigil ship={props.ship} size={32} color={`#${color}`} />
|
||||
{img}
|
||||
<p className={
|
||||
"w-70 mono list-ship dib v-mid black white-d ml2 nowrap f8"
|
||||
}>{name}</p>
|
||||
'w-70 mono list-ship dib v-mid black white-d ml2 nowrap f8'
|
||||
}
|
||||
>{name}</p>
|
||||
{actionElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from "react";
|
||||
import { Sigil } from "/components/lib/icons/sigil";
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import {
|
||||
ProfileOverlay,
|
||||
OVERLAY_HEIGHT
|
||||
} from "/components/lib/profile-overlay";
|
||||
} from '/components/lib/profile-overlay';
|
||||
|
||||
export class OverlaySigil extends Component {
|
||||
constructor() {
|
||||
@ -49,7 +49,6 @@ export class OverlaySigil extends Component {
|
||||
const parent = this.containerRef.current.offsetParent;
|
||||
const { offsetTop } = this.containerRef.current;
|
||||
|
||||
|
||||
let bottomSpace, topSpace;
|
||||
|
||||
if(navigator.userAgent.includes('Firefox')) {
|
||||
@ -58,7 +57,6 @@ export class OverlaySigil extends Component {
|
||||
} else {
|
||||
topSpace = offsetTop + parent.scrollHeight - parent.clientHeight - parent.scrollTop;
|
||||
bottomSpace = parent.clientHeight - topSpace - OVERLAY_HEIGHT;
|
||||
|
||||
}
|
||||
this.setState({
|
||||
topSpace,
|
||||
@ -69,12 +67,22 @@ export class OverlaySigil extends Component {
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
return (
|
||||
|
||||
const img = (props.contact && (props.contact.avatar !== null))
|
||||
? <img src={props.contact.avatar} height={24} width={24} className="dib" />
|
||||
: <Sigil
|
||||
ship={props.ship}
|
||||
size={24}
|
||||
color={props.color}
|
||||
classes={props.sigilClass}
|
||||
/>;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={this.profileShow}
|
||||
className={props.className + " pointer relative"}
|
||||
className={props.className + ' pointer relative'}
|
||||
ref={this.containerRef}
|
||||
style={{ height: "24px" }}
|
||||
style={{ height: '24px' }}
|
||||
>
|
||||
{state.profileClicked && (
|
||||
<ProfileOverlay
|
||||
@ -87,12 +95,7 @@ export class OverlaySigil extends Component {
|
||||
onDismiss={this.profileHide}
|
||||
/>
|
||||
)}
|
||||
<Sigil
|
||||
ship={props.ship}
|
||||
size={24}
|
||||
color={props.color}
|
||||
classes={props.sigilClass}
|
||||
/>
|
||||
{img}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { cite } from "/lib/util";
|
||||
import { Sigil } from "/components/lib/icons/sigil";
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { cite } from '/lib/util';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
|
||||
export const OVERLAY_HEIGHT = 250;
|
||||
|
||||
@ -14,13 +14,13 @@ export class ProfileOverlay extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("mousedown", this.onDocumentClick);
|
||||
document.addEventListener("touchstart", this.onDocumentClick);
|
||||
document.addEventListener('mousedown', this.onDocumentClick);
|
||||
document.addEventListener('touchstart', this.onDocumentClick);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mousedown", this.onDocumentClick);
|
||||
document.removeEventListener("touchstart", this.onDocumentClick);
|
||||
document.removeEventListener('mousedown', this.onDocumentClick);
|
||||
document.removeEventListener('touchstart', this.onDocumentClick);
|
||||
}
|
||||
|
||||
onDocumentClick(event) {
|
||||
@ -38,21 +38,31 @@ export class ProfileOverlay extends Component {
|
||||
|
||||
let top, bottom;
|
||||
if (topSpace < OVERLAY_HEIGHT / 2) {
|
||||
top = `0px`;
|
||||
top = '0px';
|
||||
}
|
||||
if (bottomSpace < OVERLAY_HEIGHT / 2) {
|
||||
bottom = `0px`;
|
||||
bottom = '0px';
|
||||
}
|
||||
if (!(top || bottom)) {
|
||||
bottom = `-${Math.round(OVERLAY_HEIGHT / 2)}px`;
|
||||
}
|
||||
const containerStyle = { top, bottom, left: "100%" };
|
||||
const containerStyle = { top, bottom, left: '100%' };
|
||||
|
||||
const isOwn = window.ship === ship;
|
||||
|
||||
const identityHref = group["group-path"].startsWith("/~/")
|
||||
? "/~groups/me"
|
||||
: `/~groups/view${group["group-path"]}/${window.ship}`;
|
||||
const identityHref = group['group-path'].startsWith('/~/')
|
||||
? '/~groups/me'
|
||||
: `/~groups/view${group['group-path']}/${window.ship}`;
|
||||
|
||||
const img = (contact && (contact.avatar !== null))
|
||||
? <img src={contact.avatar} height={160} width={160} className="brt2 dib" />
|
||||
: <Sigil
|
||||
ship={ship}
|
||||
size={160}
|
||||
color={color}
|
||||
classes="brt2"
|
||||
svgClass="brt2"
|
||||
/>;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -60,14 +70,8 @@ export class ProfileOverlay extends Component {
|
||||
style={containerStyle}
|
||||
className="flex-col shadow-6 br2 bg-white bg-gray0-d inter absolute z-1 f9 lh-solid"
|
||||
>
|
||||
<div style={{ height: "160px" }}>
|
||||
<Sigil
|
||||
ship={ship}
|
||||
size={160}
|
||||
color={color}
|
||||
classes="brt2"
|
||||
svgClass="brt2"
|
||||
/>
|
||||
<div style={{ height: '160px', width: '160px' }}>
|
||||
{img}
|
||||
</div>
|
||||
<div className="pv3 pl3 pr2">
|
||||
{contact && contact.nickname && (
|
||||
|
@ -116,7 +116,7 @@ export class ContactCard extends Component {
|
||||
if (
|
||||
(state.avatarToSet === '') ||
|
||||
(
|
||||
!!props.contact.avatar &&
|
||||
Boolean(props.contact.avatar) &&
|
||||
'url' in props.contact.avatar &&
|
||||
state.avatarToSet === props.contact.avatar.url
|
||||
)
|
||||
@ -129,7 +129,7 @@ export class ContactCard extends Component {
|
||||
awaiting: true,
|
||||
type: 'Saving to group'
|
||||
}, (() => {
|
||||
api.contactEdit(props.path, ship, {
|
||||
api.contactEdit(props.path, ship, {
|
||||
avatar: {
|
||||
url: state.avatarToSet
|
||||
}
|
||||
@ -235,14 +235,6 @@ export class ContactCard extends Component {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'removeAvatar': {
|
||||
this.setState({ awaiting: true, type: 'Removing from group' }, (() => {
|
||||
api.contactEdit(props.path, ship, { avatar: null }).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'removeEmail': {
|
||||
this.setState({ emailToSet: '', awaiting: true, type: 'Removing from group' }, (() => {
|
||||
api.contactEdit(props.path, ship, { email: '' }).then(() => {
|
||||
@ -404,9 +396,10 @@ export class ContactCard extends Component {
|
||||
|
||||
const avatar = (hasAvatar)
|
||||
? <span>
|
||||
<img className="dib h-auto"
|
||||
<img className="dib h-auto"
|
||||
width={128}
|
||||
src={props.contact.avatar} />
|
||||
src={props.contact.avatar}
|
||||
/>
|
||||
<EditElement
|
||||
title="Avatar Image URL"
|
||||
defaultValue={defaultValue.avatar}
|
||||
@ -519,7 +512,7 @@ export class ContactCard extends Component {
|
||||
const hexColor = uxToHex(currentColor);
|
||||
|
||||
const avatar =
|
||||
('avatar' in props.contact && props.contact.avatar !== 'TODO') ?
|
||||
('avatar' in props.contact && props.contact.avatar !== null) ?
|
||||
<img className="dib h-auto" width={128} src={props.contact.avatar} /> :
|
||||
<Sigil
|
||||
ship={props.ship}
|
||||
|
@ -3,33 +3,39 @@ import { Route, Link } from 'react-router-dom';
|
||||
import { Sigil } from '../lib/icons/sigil';
|
||||
import { uxToHex, cite } from '../../lib/util';
|
||||
|
||||
|
||||
export class ContactItem extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let selectedClass = (props.selected) ? "bg-gray4 bg-gray1-d" : "";
|
||||
let hexColor = uxToHex(props.color);
|
||||
let name = (props.nickname) ? props.nickname : cite(props.ship);
|
||||
const selectedClass = (props.selected) ? 'bg-gray4 bg-gray1-d' : '';
|
||||
const hexColor = uxToHex(props.color);
|
||||
const name = (props.nickname) ? props.nickname : cite(props.ship);
|
||||
|
||||
const prefix = props.share ? 'share' : 'view';
|
||||
const suffix = !props.share ? `/${props.ship}` : '';
|
||||
|
||||
const img = (props.avatar !== null)
|
||||
? <img className="dib" src={props.avatar} height={32} width={32} />
|
||||
: <Sigil
|
||||
ship={props.ship}
|
||||
color={'#' + hexColor}
|
||||
size={32}
|
||||
key={`${props.ship}.sidebar.${hexColor}`}
|
||||
/>;
|
||||
|
||||
let prefix = props.share ? 'share' : 'view';
|
||||
let suffix = !props.share ? `/${props.ship}` : '';
|
||||
return (
|
||||
<Link to={`/~groups/${prefix}` + props.path + suffix}>
|
||||
<div className=
|
||||
{"pl4 pt1 pb1 f9 flex justify-start content-center " + selectedClass}
|
||||
{'pl4 pt1 pb1 f9 flex justify-start content-center ' + selectedClass}
|
||||
>
|
||||
<Sigil
|
||||
ship={props.ship}
|
||||
color={"#" + hexColor}
|
||||
size={32}
|
||||
key={`${props.ship}.sidebar.${hexColor}`} />
|
||||
{img}
|
||||
<p
|
||||
className={
|
||||
"f9 w-70 dib v-mid ml2 nowrap " +
|
||||
((props.nickname) ? "" : "mono")}
|
||||
'f9 w-70 dib v-mid ml2 nowrap ' +
|
||||
((props.nickname) ? '' : 'mono')}
|
||||
style={{ paddingTop: 6 }}
|
||||
title={props.ship}>
|
||||
title={props.ship}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ContactItem } from '/components/lib/contact-item';
|
||||
import { ShareSheet } from '/components/lib/share-sheet';
|
||||
import { Sigil } from '../lib/icons/sigil';
|
||||
@ -11,29 +11,29 @@ export class ContactSidebar extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
awaiting: false
|
||||
}
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let group = new Set(Array.from(props.group));
|
||||
let responsiveClasses =
|
||||
props.activeDrawer === "contacts" ? "db" : "dn db-ns";
|
||||
const group = new Set(Array.from(props.group));
|
||||
const responsiveClasses =
|
||||
props.activeDrawer === 'contacts' ? 'db' : 'dn db-ns';
|
||||
|
||||
let me = (window.ship in props.contacts)
|
||||
const me = (window.ship in props.contacts)
|
||||
? props.contacts[window.ship]
|
||||
: (window.ship in props.defaultContacts)
|
||||
? props.defaultContacts[window.ship]
|
||||
: { color: '0x0', nickname: null };
|
||||
: { color: '0x0', nickname: null, avatar: null };
|
||||
|
||||
let shareSheet =
|
||||
const shareSheet =
|
||||
!(window.ship in props.contacts) ?
|
||||
( <ShareSheet
|
||||
ship={window.ship}
|
||||
nickname={me.nickname}
|
||||
color={me.color}
|
||||
path={props.path}
|
||||
selected={props.path + "/" + window.ship === props.selectedContact}
|
||||
selected={props.path + '/' + window.ship === props.selectedContact}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
@ -41,27 +41,29 @@ export class ContactSidebar extends Component {
|
||||
<ContactItem
|
||||
ship={window.ship}
|
||||
nickname={me.nickname}
|
||||
avatar={me.avatar}
|
||||
color={me.color}
|
||||
path={props.path}
|
||||
selected={props.path + "/" + window.ship === props.selectedContact}
|
||||
selected={props.path + '/' + window.ship === props.selectedContact}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
group.delete(window.ship);
|
||||
|
||||
let contactItems =
|
||||
const contactItems =
|
||||
Object.keys(props.contacts)
|
||||
.filter(c => c !== window.ship)
|
||||
.map((contact) => {
|
||||
group.delete(contact);
|
||||
let path = props.path + "/" + contact;
|
||||
let obj = props.contacts[contact];
|
||||
const path = props.path + '/' + contact;
|
||||
const obj = props.contacts[contact];
|
||||
return (
|
||||
<ContactItem
|
||||
key={contact}
|
||||
ship={contact}
|
||||
nickname={obj.nickname}
|
||||
color={obj.color}
|
||||
avatar={obj.avatar}
|
||||
path={props.path}
|
||||
selected={path === props.selectedContact}
|
||||
share={false}
|
||||
@ -69,62 +71,68 @@ export class ContactSidebar extends Component {
|
||||
);
|
||||
});
|
||||
|
||||
let adminOpt = (props.path.includes(`~${window.ship}/`))
|
||||
? "dib" : "dn";
|
||||
const adminOpt = (props.path.includes(`~${window.ship}/`))
|
||||
? 'dib' : 'dn';
|
||||
|
||||
let groupItems =
|
||||
const groupItems =
|
||||
Array.from(group).map((member) => {
|
||||
return (
|
||||
<div
|
||||
key={member}
|
||||
className={"pl4 pt1 pb1 f9 flex justify-start content-center " +
|
||||
"bg-white bg-gray0-d relative"}>
|
||||
className={'pl4 pt1 pb1 f9 flex justify-start content-center ' +
|
||||
'bg-white bg-gray0-d relative'}
|
||||
>
|
||||
<Sigil
|
||||
ship={member}
|
||||
color="#000000"
|
||||
size={32}
|
||||
classes="mix-blend-diff"
|
||||
/>
|
||||
/>
|
||||
<p className="f9 w-70 dib v-mid ml2 nowrap mono truncate"
|
||||
style={{ paddingTop: 6, color: '#aaaaaa' }}
|
||||
title={member}>
|
||||
title={member}
|
||||
>
|
||||
{cite(member)}
|
||||
</p>
|
||||
<p className={"v-mid f9 mh3 red2 pointer " + adminOpt}
|
||||
style={{paddingTop: 6}}
|
||||
<p className={'v-mid f9 mh3 red2 pointer ' + adminOpt}
|
||||
style={{ paddingTop: 6 }}
|
||||
onClick={() => {
|
||||
this.setState({awaiting: true}, (() => {
|
||||
this.setState({ awaiting: true }, (() => {
|
||||
props.api.groupRemove(props.path, [`~${member}`])
|
||||
.then(() => {
|
||||
this.setState({awaiting: false})
|
||||
})
|
||||
}))
|
||||
}}>
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}));
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
let detailHref = `/~groups/detail${props.path}`
|
||||
const detailHref = `/~groups/detail${props.path}`;
|
||||
|
||||
return (
|
||||
<div className={"bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 " +
|
||||
"flex-basis-100-s flex-basis-30-ns mw5-m mw5-l mw5-xl relative " +
|
||||
"overflow-hidden flex-shrink-0 " + responsiveClasses}>
|
||||
<div className={'bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 ' +
|
||||
'flex-basis-100-s flex-basis-30-ns mw5-m mw5-l mw5-xl relative ' +
|
||||
'overflow-hidden flex-shrink-0 ' + responsiveClasses}
|
||||
>
|
||||
<div className="pt3 pb5 pl3 f8 db dn-m dn-l dn-xl">
|
||||
<Link to="/~groups/">{"⟵ All Groups"}</Link>
|
||||
<Link to="/~groups/">{'⟵ All Groups'}</Link>
|
||||
</div>
|
||||
<div className="overflow-auto h-100">
|
||||
<Link
|
||||
to={"/~groups/add" + props.path}
|
||||
to={'/~groups/add' + props.path}
|
||||
className={((props.path.includes(window.ship))
|
||||
? "dib"
|
||||
: "dn")}>
|
||||
? 'dib'
|
||||
: 'dn')}
|
||||
>
|
||||
<p className="f9 pl4 pt0 pt4-m pt4-l pt4-xl green2 bn">Add to Group</p>
|
||||
</Link>
|
||||
<Link to={detailHref}
|
||||
className="dib dn-m dn-l dn-xl f9 pl4 pt0 pt4-m pt4-l pt4-xl gray2 bn">Channels</Link>
|
||||
className="dib dn-m dn-l dn-xl f9 pl4 pt0 pt4-m pt4-l pt4-xl gray2 bn"
|
||||
>Channels</Link>
|
||||
{shareSheet}
|
||||
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">Members</h2>
|
||||
{contactItems}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from './icons/sigil';
|
||||
import { cite } from '../../lib/util';
|
||||
import moment from 'moment';
|
||||
@ -13,7 +13,7 @@ export class CommentItem extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.updateTimeSinceNewestMessageInterval = setInterval( () => {
|
||||
this.setState({timeSinceComment: this.getTimeSinceComment()});
|
||||
this.setState({ timeSinceComment: this.getTimeSinceComment() });
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
@ -25,30 +25,35 @@ export class CommentItem extends Component {
|
||||
}
|
||||
|
||||
getTimeSinceComment() {
|
||||
return !!this.props.time ?
|
||||
return this.props.time ?
|
||||
moment.unix(this.props.time / 1000).from(moment.utc())
|
||||
: '';
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
const props = this.props;
|
||||
|
||||
let member = this.props.member || false;
|
||||
const member = props.member || false;
|
||||
|
||||
let pending = !!this.props.pending ? "o-60" : "";
|
||||
const pending = props.pending ? 'o-60' : '';
|
||||
|
||||
const img = (props.avatar)
|
||||
? <img src={props.avatar} height={36} width={36} className="dib" />
|
||||
: <Sigil
|
||||
ship={'~' + props.ship}
|
||||
size={36}
|
||||
color={'#' + props.color}
|
||||
classes={(member ? 'mix-blend-diff' : '')}
|
||||
/>;
|
||||
|
||||
return (
|
||||
<div className={"w-100 pv3 " + pending}>
|
||||
<div className={'w-100 pv3 ' + pending}>
|
||||
<div className="flex bg-white bg-gray0-d">
|
||||
<Sigil
|
||||
ship={"~" + props.ship}
|
||||
size={36}
|
||||
color={"#" + props.color}
|
||||
classes={(member ? "mix-blend-diff" : "")}
|
||||
/>
|
||||
{img}
|
||||
<p className="gray2 f9 flex items-center ml2">
|
||||
<span className={"black white-d " + props.nameClass}
|
||||
title={props.ship}>
|
||||
<span className={'black white-d ' + props.nameClass}
|
||||
title={props.ship}
|
||||
>
|
||||
{props.nickname ? props.nickname : cite(props.ship)}
|
||||
</span>
|
||||
<span className="ml2">
|
||||
@ -58,8 +63,8 @@ export class CommentItem extends Component {
|
||||
</div>
|
||||
<p className="inter f8 pv3 white-d">{props.content}</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommentItem
|
||||
export default CommentItem;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component } from 'react';
|
||||
import { CommentItem } from './comment-item';
|
||||
import { CommentsPagination } from './comments-pagination';
|
||||
|
||||
@ -12,12 +12,12 @@ export class Comments extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let page = this.props.commentPage;
|
||||
const page = this.props.commentPage;
|
||||
if (!this.props.comments ||
|
||||
!this.props.comments[page] ||
|
||||
this.props.comments.local[page]
|
||||
) {
|
||||
this.setState({requested: this.props.commentPage});
|
||||
this.setState({ requested: this.props.commentPage });
|
||||
api.getCommentsPage(
|
||||
this.props.resourcePath,
|
||||
this.props.url,
|
||||
@ -26,35 +26,34 @@ export class Comments extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
const props = this.props;
|
||||
|
||||
let page = props.commentPage;
|
||||
const page = props.commentPage;
|
||||
|
||||
let commentsObj = !!props.comments
|
||||
const commentsObj = props.comments
|
||||
? props.comments
|
||||
: {};
|
||||
|
||||
let commentsPage = !!commentsObj[page]
|
||||
const commentsPage = commentsObj[page]
|
||||
? commentsObj[page]
|
||||
: {};
|
||||
|
||||
let total = !!props.comments
|
||||
const total = props.comments
|
||||
? props.comments.totalPages
|
||||
: 1;
|
||||
|
||||
let commentsList = Object.keys(commentsPage)
|
||||
const commentsList = Object.keys(commentsPage)
|
||||
.map((entry) => {
|
||||
const commentObj = commentsPage[entry];
|
||||
const { ship, time, udon } = commentObj;
|
||||
|
||||
let commentObj = commentsPage[entry]
|
||||
let { ship, time, udon } = commentObj;
|
||||
|
||||
let contacts = !!props.contacts
|
||||
const contacts = props.contacts
|
||||
? props.contacts
|
||||
: {};
|
||||
|
||||
const {nickname, color, member} = getContactDetails(contacts[ship]);
|
||||
const { nickname, color, member, avatar } = getContactDetails(contacts[ship]);
|
||||
|
||||
let nameClass = nickname ? "inter" : "mono";
|
||||
const nameClass = nickname ? 'inter' : 'mono';
|
||||
|
||||
return(
|
||||
<CommentItem
|
||||
@ -65,10 +64,11 @@ export class Comments extends Component {
|
||||
nickname={nickname}
|
||||
nameClass={nameClass}
|
||||
color={color}
|
||||
avatar={avatar}
|
||||
member={member}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{commentsList}
|
||||
@ -80,10 +80,11 @@ export class Comments extends Component {
|
||||
linkIndex={props.linkIndex}
|
||||
url={props.url}
|
||||
commentPage={props.commentPage}
|
||||
total={total}/>
|
||||
total={total}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Comments;
|
||||
export default Comments;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
@ -16,7 +16,7 @@ export class LinkItem extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.updateTimeSinceNewestMessageInterval = setInterval( () => {
|
||||
this.setState({timeSinceLinkPost: this.getTimeSinceLinkPost()});
|
||||
this.setState({ timeSinceLinkPost: this.getTimeSinceLinkPost() });
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ export class LinkItem extends Component {
|
||||
}
|
||||
|
||||
getTimeSinceLinkPost() {
|
||||
return !!this.props.timestamp ?
|
||||
return this.props.timestamp ?
|
||||
moment.unix(this.props.timestamp / 1000).from(moment.utc())
|
||||
: '';
|
||||
}
|
||||
@ -38,61 +38,68 @@ export class LinkItem extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let props = this.props;
|
||||
const mono = (props.nickname) ? 'inter white-d' : 'mono white-d';
|
||||
|
||||
let mono = (props.nickname) ? "inter white-d" : "mono white-d";
|
||||
|
||||
let URLparser = new RegExp(/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/);
|
||||
const URLparser = new RegExp(/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/);
|
||||
|
||||
let hostname = URLparser.exec(props.url);
|
||||
|
||||
const seenState = props.seen
|
||||
? "gray2"
|
||||
: "green2 pointer";
|
||||
? 'gray2'
|
||||
: 'green2 pointer';
|
||||
const seenAction = props.seen
|
||||
? ()=>{}
|
||||
: this.markPostAsSeen
|
||||
? () => {}
|
||||
: this.markPostAsSeen;
|
||||
|
||||
if (hostname) {
|
||||
hostname = hostname[4];
|
||||
}
|
||||
|
||||
let comments = props.comments + " comment" + ((props.comments === 1) ? "" : "s");
|
||||
const comments = props.comments + ' comment' + ((props.comments === 1) ? '' : 's');
|
||||
|
||||
let member = this.props.member || false;
|
||||
const member = this.props.member || false;
|
||||
|
||||
const img = (this.props.avatar)
|
||||
? <img src={this.props.avatar} height={38} width={38} className="dib" />
|
||||
: <Sigil
|
||||
ship={'~' + props.ship}
|
||||
size={38}
|
||||
color={'#' + props.color}
|
||||
classes={(member ? 'mix-blend-diff' : '')}
|
||||
/>;
|
||||
return (
|
||||
<div className="w-100 pv3 flex bg-white bg-gray0-d">
|
||||
<Sigil
|
||||
ship={"~" + props.ship}
|
||||
size={38}
|
||||
color={"#" + props.color}
|
||||
classes={(member ? "mix-blend-diff" : "")}
|
||||
/>
|
||||
{img}
|
||||
<div className="flex flex-column ml2 flex-auto">
|
||||
<a href={props.url}
|
||||
className="w-100 flex"
|
||||
target="_blank"
|
||||
onClick={this.markPostAsSeen}>
|
||||
onClick={this.markPostAsSeen}
|
||||
>
|
||||
<p className="f8 truncate">{props.title}
|
||||
</p>
|
||||
<span className="gray2 dib v-btm ml2 f8 flex-shrink-0">{hostname} ↗</span>
|
||||
</a>
|
||||
<div className="w-100 pt1">
|
||||
<span className={"f9 pr2 dib " + mono}
|
||||
title={props.ship}>
|
||||
<span className={'f9 pr2 dib ' + mono}
|
||||
title={props.ship}
|
||||
>
|
||||
{(props.nickname)
|
||||
? props.nickname
|
||||
: cite(props.ship)}
|
||||
</span>
|
||||
<span
|
||||
className={seenState + " f9 inter pr3 dib"}
|
||||
onClick={this.markPostAsSeen}>
|
||||
className={seenState + ' f9 inter pr3 dib'}
|
||||
onClick={this.markPostAsSeen}
|
||||
>
|
||||
{this.state.timeSinceLinkPost}
|
||||
</span>
|
||||
<Link to=
|
||||
{makeRoutePath(props.resourcePath, props.popout, props.page, props.url, props.linkIndex)}
|
||||
onClick={this.markPostAsSeen}>
|
||||
onClick={this.markPostAsSeen}
|
||||
>
|
||||
<span className="f9 inter gray2 dib">
|
||||
{comments}
|
||||
</span>
|
||||
@ -100,8 +107,8 @@ export class LinkItem extends Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LinkItem
|
||||
export default LinkItem;
|
||||
|
@ -1,14 +1,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { uxToHex, cite } from '/lib/util';
|
||||
|
||||
|
||||
export class MemberElement extends Component {
|
||||
|
||||
onRemove() {
|
||||
const { props } = this;
|
||||
//TODO don't really need to use link-view here, but should we anyway?
|
||||
api.groups.remove(props.groupPath, [`~${props.ship}`]);
|
||||
}
|
||||
|
||||
@ -25,7 +21,8 @@ export class MemberElement extends Component {
|
||||
} else if (props.amOwner && window.ship !== props.ship) {
|
||||
actionElem = (
|
||||
<a onClick={this.onRemove.bind(this)}
|
||||
className="w-20 dib list-ship black white-d f8 pointer">
|
||||
className="w-20 dib list-ship black white-d f8 pointer"
|
||||
>
|
||||
Ban
|
||||
</a>
|
||||
);
|
||||
@ -35,16 +32,21 @@ export class MemberElement extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
let name = !!props.contact
|
||||
const name = props.contact
|
||||
? `${props.contact.nickname} (${cite(props.ship)})`
|
||||
: `${cite(props.ship)}`;
|
||||
let color = !!props.contact ? uxToHex(props.contact.color) : '000000';
|
||||
const color = props.contact ? uxToHex(props.contact.color) : '000000';
|
||||
|
||||
const img = props.contact.avatar
|
||||
? <img src={props.contact.avatar} height={32} width={32} className="dib" />
|
||||
: <Sigil ship={props.ship} size={32} color={`#${color}`} />;
|
||||
|
||||
return (
|
||||
<div className="flex mb2">
|
||||
<Sigil ship={props.ship} size={32} color={`#${color}`} />
|
||||
<p className={"w-70 mono list-ship dib v-mid black white-d ml2 nowrap f8"}
|
||||
title={props.ship}>
|
||||
{img}
|
||||
<p className={'w-70 mono list-ship dib v-mid black white-d ml2 nowrap f8'}
|
||||
title={props.ship}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
{actionElem}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component } from 'react';
|
||||
import { LinksTabBar } from './lib/links-tabbar';
|
||||
import { LinkPreview } from './lib/link-detail-preview';
|
||||
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
|
||||
import { api } from '../api';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Comments } from './lib/comments';
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
import { LoadingScreen } from './loading';
|
||||
@ -14,7 +14,7 @@ export class LinkDetail extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
comment: "",
|
||||
comment: '',
|
||||
data: props.data,
|
||||
commentFocus: false,
|
||||
pending: new Set(),
|
||||
@ -43,14 +43,14 @@ export class LinkDetail extends Component {
|
||||
if (this.props.url !== prevProps.url) {
|
||||
this.updateData(this.props.data);
|
||||
}
|
||||
if (prevProps.comments && prevProps.comments["0"] &&
|
||||
this.props.comments && this.props.comments["0"]) {
|
||||
let prevFirstComment = prevProps.comments["0"][0];
|
||||
let thisFirstComment = this.props.comments["0"][0];
|
||||
if (prevProps.comments && prevProps.comments['0'] &&
|
||||
this.props.comments && this.props.comments['0']) {
|
||||
const prevFirstComment = prevProps.comments['0'][0];
|
||||
const thisFirstComment = this.props.comments['0'][0];
|
||||
if ((prevFirstComment && prevFirstComment.udon) &&
|
||||
(thisFirstComment && thisFirstComment.udon)) {
|
||||
if (this.state.pending.has(thisFirstComment.udon)) {
|
||||
let pending = this.state.pending;
|
||||
const pending = this.state.pending;
|
||||
pending.delete(thisFirstComment.udon);
|
||||
this.setState({
|
||||
pending: pending
|
||||
@ -61,9 +61,9 @@ export class LinkDetail extends Component {
|
||||
}
|
||||
|
||||
onClickPost() {
|
||||
let url = this.props.url || "";
|
||||
const url = this.props.url || '';
|
||||
|
||||
let pending = this.state.pending;
|
||||
const pending = this.state.pending;
|
||||
pending.add(this.state.comment);
|
||||
this.setState({ pending: pending, disabled: true });
|
||||
|
||||
@ -72,9 +72,8 @@ export class LinkDetail extends Component {
|
||||
url,
|
||||
this.state.comment
|
||||
).then(() => {
|
||||
this.setState({ comment: "", disabled: false });
|
||||
this.setState({ comment: '', disabled: false });
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
setComment(event) {
|
||||
@ -82,37 +81,37 @@ export class LinkDetail extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
const props = this.props;
|
||||
|
||||
const data = this.state.data || props.data;
|
||||
|
||||
if (!data.ship) {
|
||||
return <LoadingScreen/>;
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
let ship = data.ship || "zod";
|
||||
let title = data.title || "";
|
||||
let url = data.url || "";
|
||||
const ship = data.ship || 'zod';
|
||||
const title = data.title || '';
|
||||
const url = data.url || '';
|
||||
|
||||
const commentCount = props.comments
|
||||
? props.comments.totalItems
|
||||
: data.commentCount || 0;
|
||||
|
||||
let comments = commentCount + " comment" + (commentCount === 1 ? "" : "s");
|
||||
const comments = commentCount + ' comment' + (commentCount === 1 ? '' : 's');
|
||||
|
||||
const { nickname } = getContactDetails(props.contacts[ship]);
|
||||
|
||||
let activeClasses = this.state.comment
|
||||
? "black white-d pointer"
|
||||
: "gray2 b--gray2";
|
||||
const activeClasses = this.state.comment
|
||||
? 'black white-d pointer'
|
||||
: 'gray2 b--gray2';
|
||||
|
||||
let focus = (this.state.commentFocus)
|
||||
? "b--black b--white-d"
|
||||
: "b--gray4 b--gray2-d";
|
||||
const focus = (this.state.commentFocus)
|
||||
? 'b--black b--white-d'
|
||||
: 'b--gray4 b--gray2-d';
|
||||
|
||||
let our = getContactDetails(props.contacts[window.ship]);
|
||||
const our = getContactDetails(props.contacts[window.ship]);
|
||||
|
||||
let pendingArray = Array.from(this.state.pending).map((com, i) => {
|
||||
const pendingArray = Array.from(this.state.pending).map((com, i) => {
|
||||
return(
|
||||
<CommentItem
|
||||
key={i}
|
||||
@ -124,23 +123,25 @@ export class LinkDetail extends Component {
|
||||
member={our.member}
|
||||
time={new Date().getTime()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-hidden flex flex-column">
|
||||
<div
|
||||
className={"pl4 pt2 flex relative overflow-x-scroll " +
|
||||
"overflow-x-auto-l overflow-x-auto-xl flex-shrink-0 " +
|
||||
"bb bn-m bn-l bn-xl b--gray4"}
|
||||
style={{ height: 48 }}>
|
||||
className={'pl4 pt2 flex relative overflow-x-scroll ' +
|
||||
'overflow-x-auto-l overflow-x-auto-xl flex-shrink-0 ' +
|
||||
'bb bn-m bn-l bn-xl b--gray4'}
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}
|
||||
/>
|
||||
<Link
|
||||
className="dib f9 fw4 pt2 gray2 lh-solid"
|
||||
to={makeRoutePath(props.resourcePath, props.popout, props.page)}>
|
||||
to={makeRoutePath(props.resourcePath, props.popout, props.page)}
|
||||
>
|
||||
{`<- ${props.resource.metadata.title}`}
|
||||
</Link>
|
||||
<LinksTabBar {...props} popout={props.popout} resourcePath={props.resourcePath} />
|
||||
@ -159,19 +160,19 @@ export class LinkDetail extends Component {
|
||||
time={this.state.data.time}
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className={"relative ba br1 mt6 mb6 " + focus}>
|
||||
<div className={'relative ba br1 mt6 mb6 ' + focus}>
|
||||
<textarea
|
||||
className="w-100 bg-gray0-d white-d f8 pa2 pr8"
|
||||
style={{
|
||||
resize: "none",
|
||||
resize: 'none',
|
||||
height: 75
|
||||
}}
|
||||
placeholder="Leave a comment on this link"
|
||||
onChange={this.setComment}
|
||||
onKeyDown={e => {
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
(e.getModifierState("Control") || e.metaKey) &&
|
||||
e.key === "Enter"
|
||||
(e.getModifierState('Control') || e.metaKey) &&
|
||||
e.key === 'Enter'
|
||||
) {
|
||||
this.onClickPost();
|
||||
}
|
||||
@ -182,14 +183,15 @@ export class LinkDetail extends Component {
|
||||
/>
|
||||
<button
|
||||
className={
|
||||
"f8 bg-gray0-d ml2 absolute " + activeClasses
|
||||
'f8 bg-gray0-d ml2 absolute ' + activeClasses
|
||||
}
|
||||
disabled={!this.state.comment || this.state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{
|
||||
bottom: 12,
|
||||
right: 8
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,16 +1,15 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component } from 'react';
|
||||
import { LoadingScreen } from './loading';
|
||||
import { MessageScreen } from '/components/lib/message-screen';
|
||||
import { LinksTabBar } from './lib/links-tabbar';
|
||||
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
|
||||
import { Route, Link } from "react-router-dom";
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { LinkItem } from '/components/lib/link-item.js';
|
||||
import { LinkSubmit } from '/components/lib/link-submit.js';
|
||||
import { Pagination } from '/components/lib/pagination.js';
|
||||
|
||||
import { makeRoutePath, getContactDetails } from '../lib/util';
|
||||
|
||||
//TODO Avatar support once it's in
|
||||
export class Links extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -38,44 +37,44 @@ export class Links extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
const props = this.props;
|
||||
|
||||
if (!props.resource.metadata.title) {
|
||||
return <LoadingScreen/>;
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
let linkPage = props.page;
|
||||
const linkPage = props.page;
|
||||
|
||||
let links = !!props.links[linkPage]
|
||||
const links = props.links[linkPage]
|
||||
? props.links[linkPage]
|
||||
: {};
|
||||
|
||||
let currentPage = !!props.page
|
||||
const currentPage = props.page
|
||||
? Number(props.page)
|
||||
: 0;
|
||||
|
||||
let totalPages = !!props.links
|
||||
const totalPages = props.links
|
||||
? Number(props.links.totalPages)
|
||||
: 1;
|
||||
|
||||
let LinkList = (<LoadingScreen/>);
|
||||
let LinkList = (<LoadingScreen />);
|
||||
if (props.links && props.links.totalItems === 0) {
|
||||
LinkList = (
|
||||
<MessageScreen text="Start by posting a link to this collection."/>
|
||||
<MessageScreen text="Start by posting a link to this collection." />
|
||||
);
|
||||
} else if (Object.keys(links).length > 0) {
|
||||
LinkList = Object.keys(links)
|
||||
.map((linkIndex) => {
|
||||
let linksObj = props.links[linkPage];
|
||||
let { title, url, time, ship } = linksObj[linkIndex];
|
||||
const linksObj = props.links[linkPage];
|
||||
const { title, url, time, ship } = linksObj[linkIndex];
|
||||
const seen = props.seen[url];
|
||||
let members = {};
|
||||
const members = {};
|
||||
|
||||
const commentCount = props.comments[url]
|
||||
? props.comments[url].totalItems
|
||||
: linksObj[linkIndex].commentCount || 0;
|
||||
|
||||
const {nickname, color, member} = getContactDetails(props.contacts[ship]);
|
||||
const { nickname, color, member, avatar } = getContactDetails(props.contacts[ship]);
|
||||
|
||||
return (
|
||||
<LinkItem
|
||||
@ -89,33 +88,38 @@ export class Links extends Component {
|
||||
nickname={nickname}
|
||||
ship={ship}
|
||||
color={color}
|
||||
avatar={avatar}
|
||||
member={member}
|
||||
comments={commentCount}
|
||||
resourcePath={props.resourcePath}
|
||||
popout={props.popout}
|
||||
/>
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-100 w-100 overflow-hidden flex flex-column">
|
||||
className="h-100 w-100 overflow-hidden flex flex-column"
|
||||
>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: "1rem" }}>
|
||||
<Link to="/~link">{"⟵ All Channels"}</Link>
|
||||
style={{ height: '1rem' }}
|
||||
>
|
||||
<Link to="/~link">{'⟵ All Channels'}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={`pl4 pt2 flex relative overflow-x-scroll
|
||||
overflow-x-auto-l overflow-x-auto-xl flex-shrink-0
|
||||
bb b--gray4 b--gray1-d bg-gray0-d`}
|
||||
style={{ height: 48 }}>
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}/>
|
||||
popout={props.popout}
|
||||
/>
|
||||
<Link to={makeRoutePath(props.resourcePath, props.popout, props.page)} className="pt2">
|
||||
<h2 className={`dib f9 fw4 lh-solid v-top`}>
|
||||
<h2 className={'dib f9 fw4 lh-solid v-top'}>
|
||||
{props.resource.metadata.title}
|
||||
</h2>
|
||||
</Link>
|
||||
@ -123,12 +127,13 @@ export class Links extends Component {
|
||||
{...props}
|
||||
popout={props.popout}
|
||||
page={props.page}
|
||||
resourcePath={props.resourcePath}/>
|
||||
resourcePath={props.resourcePath}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100 mt6 flex justify-center overflow-y-scroll ph4 pb4">
|
||||
<div className="w-100 mw7">
|
||||
<div className="flex">
|
||||
<LinkSubmit resourcePath={props.resourcePath}/>
|
||||
<LinkSubmit resourcePath={props.resourcePath} />
|
||||
</div>
|
||||
<div className="pb4">
|
||||
{LinkList}
|
||||
@ -144,8 +149,8 @@ export class Links extends Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Links;
|
||||
export default Links;
|
||||
|
@ -1,6 +1,3 @@
|
||||
import _ from 'lodash';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export function makeRoutePath(
|
||||
resource, popout = false, page = 0, url = null, index = 0, compage = 0
|
||||
) {
|
||||
@ -19,7 +16,8 @@ export function makeRoutePath(
|
||||
}
|
||||
|
||||
export function amOwnerOfGroup(groupPath) {
|
||||
if (!groupPath) return false;
|
||||
if (!groupPath)
|
||||
return false;
|
||||
const groupOwner = /(\/~)?\/~([a-z-]{3,})\/.*/.exec(groupPath)[2];
|
||||
return (window.ship === groupOwner);
|
||||
}
|
||||
@ -28,12 +26,13 @@ export function getContactDetails(contact) {
|
||||
const member = !contact;
|
||||
contact = contact || {
|
||||
'nickname': '',
|
||||
'avatar': 'TODO',
|
||||
'avatar': null,
|
||||
'color': '0x0'
|
||||
};
|
||||
const nickname = contact.nickname || '';
|
||||
const color = uxToHex(contact.color || '0x0');
|
||||
return {nickname, color, member};
|
||||
const avatar = contact.avatar || null;
|
||||
return { nickname, color, member, avatar };
|
||||
}
|
||||
|
||||
// encodes string into base64url,
|
||||
@ -55,8 +54,8 @@ export function base64urlDecode(string) {
|
||||
}
|
||||
|
||||
export function isPatTa(str) {
|
||||
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str)
|
||||
return !!r;
|
||||
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str);
|
||||
return Boolean(r);
|
||||
}
|
||||
|
||||
// encode the string into @ta-safe format, using logic from +wood.
|
||||
@ -86,7 +85,7 @@ export function stringToTa(string) {
|
||||
) {
|
||||
add = char;
|
||||
} else {
|
||||
//TODO behavior for unicode doesn't match +wood's,
|
||||
// TODO behavior for unicode doesn't match +wood's,
|
||||
// but we can probably get away with that for now.
|
||||
add = '~' + charCode.toString(16) + '.';
|
||||
}
|
||||
@ -103,13 +102,13 @@ export function stringToTa(string) {
|
||||
(javascript Date object)
|
||||
*/
|
||||
export function daToDate(st) {
|
||||
var dub = function(n) {
|
||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
||||
const dub = function(n) {
|
||||
return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString();
|
||||
};
|
||||
var da = st.split('..');
|
||||
var bigEnd = da[0].split('.');
|
||||
var lilEnd = da[1].split('.');
|
||||
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
const da = st.split('..');
|
||||
const bigEnd = da[0].split('.');
|
||||
const lilEnd = da[1].split('.');
|
||||
const ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
return new Date(ds);
|
||||
}
|
||||
|
||||
@ -121,8 +120,8 @@ export function daToDate(st) {
|
||||
*/
|
||||
|
||||
export function dateToDa(d, mil) {
|
||||
var fil = function(n) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
const fil = function(n) {
|
||||
return n >= 10 ? n : '0' + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
@ -131,7 +130,7 @@ export function dateToDa(d, mil) {
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
`${mil ? '..0000' : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -149,41 +148,41 @@ export function uxToHex(ux) {
|
||||
|
||||
// trim patps to match dojo, chat-cli
|
||||
export function cite(ship) {
|
||||
let patp = ship, shortened = "";
|
||||
if (patp.startsWith("~")) {
|
||||
let patp = ship, shortened = '';
|
||||
if (patp.startsWith('~')) {
|
||||
patp = patp.substr(1);
|
||||
}
|
||||
// comet
|
||||
if (patp.length === 56) {
|
||||
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
|
||||
shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56);
|
||||
return shortened;
|
||||
}
|
||||
// moon
|
||||
if (patp.length === 27) {
|
||||
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
|
||||
shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27);
|
||||
return shortened;
|
||||
}
|
||||
return `~${patp}`;
|
||||
}
|
||||
|
||||
export function alphabetiseAssociations(associations) {
|
||||
let result = {};
|
||||
const result = {};
|
||||
Object.keys(associations).sort((a, b) => {
|
||||
let aName = a.substr(1);
|
||||
let bName = b.substr(1);
|
||||
if (associations[a].metadata && associations[a].metadata.title) {
|
||||
aName = associations[a].metadata.title !== ""
|
||||
aName = associations[a].metadata.title !== ''
|
||||
? associations[a].metadata.title
|
||||
: a.substr(1);
|
||||
}
|
||||
if (associations[b].metadata && associations[b].metadata.title) {
|
||||
bName = associations[b].metadata.title !== ""
|
||||
bName = associations[b].metadata.title !== ''
|
||||
? associations[b].metadata.title
|
||||
: b.substr(1);
|
||||
}
|
||||
return aName.toLowerCase().localeCompare(bName.toLowerCase());
|
||||
}).map((each) => {
|
||||
result[each] = associations[each];
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ import moment from 'moment';
|
||||
import { Sigil } from './icons/sigil';
|
||||
import { CommentInput } from './comment-input';
|
||||
import { uxToHex, cite } from '../../lib/util';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
export class CommentItem extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@ -20,7 +19,7 @@ export class CommentItem extends Component {
|
||||
past: function(input) {
|
||||
return input === 'just now'
|
||||
? input
|
||||
: input + ' ago'
|
||||
: input + ' ago';
|
||||
},
|
||||
s : 'just now',
|
||||
future : 'in %s',
|
||||
@ -33,15 +32,14 @@ export class CommentItem extends Component {
|
||||
M : '1 month',
|
||||
MM : '%d months',
|
||||
y : '1 year',
|
||||
yy : '%d years',
|
||||
yy : '%d years'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
commentEdit() {
|
||||
let commentPath = Object.keys(this.props.comment)[0];
|
||||
let commentBody = this.props.comment[commentPath].content;
|
||||
const commentPath = Object.keys(this.props.comment)[0];
|
||||
const commentBody = this.props.comment[commentPath].content;
|
||||
this.setState({ commentBody });
|
||||
this.props.onEdit();
|
||||
}
|
||||
@ -53,7 +51,7 @@ export class CommentItem extends Component {
|
||||
commentChange(e) {
|
||||
this.setState({
|
||||
commentBody: e.target.value
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onUpdate() {
|
||||
@ -61,28 +59,39 @@ export class CommentItem extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let pending = !!this.props.pending ? "o-60" : "";
|
||||
let commentData = this.props.comment[Object.keys(this.props.comment)[0]];
|
||||
let content = commentData.content.split("\n").map((line, i)=> {
|
||||
const pending = this.props.pending ? 'o-60' : '';
|
||||
const commentData = this.props.comment[Object.keys(this.props.comment)[0]];
|
||||
const content = commentData.content.split('\n').map((line, i) => {
|
||||
return (
|
||||
<p className="mb2" key={i}>{line}</p>
|
||||
)
|
||||
);
|
||||
});
|
||||
let date = moment(commentData["date-created"]).fromNow();
|
||||
const date = moment(commentData['date-created']).fromNow();
|
||||
|
||||
let contact = !!(commentData.author.substr(1) in this.props.contacts)
|
||||
const contact = commentData.author.substr(1) in this.props.contacts
|
||||
? this.props.contacts[commentData.author.substr(1)] : false;
|
||||
|
||||
let name = commentData.author;
|
||||
let color = "#000000";
|
||||
let classes = "mix-blend-diff";
|
||||
let color = '#000000';
|
||||
let classes = 'mix-blend-diff';
|
||||
let avatar = null;
|
||||
if (contact) {
|
||||
name = (contact.nickname.length > 0)
|
||||
? contact.nickname : commentData.author;
|
||||
color = `#${uxToHex(contact.color)}`;
|
||||
classes = "";
|
||||
classes = '';
|
||||
avatar = contact.avatar;
|
||||
}
|
||||
|
||||
const img = (avatar !== null)
|
||||
? <img src={avatar} height={24} width={24} className="dib" />
|
||||
: <Sigil
|
||||
ship={commentData.author}
|
||||
size={24}
|
||||
color={color}
|
||||
classes={classes}
|
||||
/>;
|
||||
|
||||
if (name === commentData.author) {
|
||||
name = cite(commentData.author);
|
||||
}
|
||||
@ -93,17 +102,13 @@ export class CommentItem extends Component {
|
||||
|| window.ship !== commentData.author.slice(1);
|
||||
|
||||
return (
|
||||
<div className={"mb8 " + pending}>
|
||||
<div className={'mb8 ' + pending}>
|
||||
<div className="flex mv3 bg-white bg-gray0-d">
|
||||
<Sigil
|
||||
ship={commentData.author}
|
||||
size={24}
|
||||
color={color}
|
||||
classes={classes}
|
||||
/>
|
||||
<div className={"f9 mh2 pt1 " +
|
||||
(contact.nickname ? null : "mono")}
|
||||
title={commentData.author}>
|
||||
{img}
|
||||
<div className={'f9 mh2 pt1 ' +
|
||||
(contact.nickname ? null : 'mono')}
|
||||
title={commentData.author}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
<div className="f9 gray3 pt1">{date}</div>
|
||||
@ -121,11 +126,14 @@ export class CommentItem extends Component {
|
||||
<div className="f8 lh-solid mb2">
|
||||
{ !editing && content }
|
||||
{ editing && (
|
||||
<CommentInput style={{resize:'vertical'}}
|
||||
ref={(el) => {this.focusTextArea(el)}}
|
||||
<CommentInput style={{ resize:'vertical' }}
|
||||
ref={(el) => {
|
||||
this.focusTextArea(el);
|
||||
}}
|
||||
onChange={this.commentChange}
|
||||
value={this.state.commentBody}
|
||||
onSubmit={this.onUpdate.bind(this)}>
|
||||
onSubmit={this.onUpdate.bind(this)}
|
||||
>
|
||||
</CommentInput>
|
||||
)}
|
||||
</div>
|
||||
@ -139,10 +147,9 @@ export class CommentItem extends Component {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommentItem
|
||||
export default CommentItem;
|
||||
|
Loading…
Reference in New Issue
Block a user