mirror of
https://github.com/urbit/shrub.git
synced 2024-12-22 10:21:31 +03:00
Merge pull request #4441 from urbit/james/chatmessage
chat: ChatMessage spacing refactor
This commit is contained in:
commit
e9a9863b22
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable max-lines-per-function */
|
||||||
import React, {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -17,7 +18,14 @@ import {
|
|||||||
useShowNickname,
|
useShowNickname,
|
||||||
useHovering
|
useHovering
|
||||||
} from '~/logic/lib/util';
|
} from '~/logic/lib/util';
|
||||||
import { Group, Association, Contacts, Post, Groups, Associations } from '@urbit/api';
|
import {
|
||||||
|
Group,
|
||||||
|
Association,
|
||||||
|
Contacts,
|
||||||
|
Post,
|
||||||
|
Groups,
|
||||||
|
Associations
|
||||||
|
} from '~/types';
|
||||||
import TextContent from './content/text';
|
import TextContent from './content/text';
|
||||||
import CodeContent from './content/code';
|
import CodeContent from './content/code';
|
||||||
import RemoteContent from '~/views/components/RemoteContent';
|
import RemoteContent from '~/views/components/RemoteContent';
|
||||||
@ -28,34 +36,47 @@ import Timestamp from '~/views/components/Timestamp';
|
|||||||
|
|
||||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||||
|
|
||||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
interface DayBreakProps {
|
||||||
<Row
|
when: string;
|
||||||
flexShrink={0}
|
shimTop?: boolean;
|
||||||
ref={ref}
|
}
|
||||||
color='blue'
|
|
||||||
alignItems='center'
|
|
||||||
fontSize='0'
|
|
||||||
position='absolute'
|
|
||||||
width='100%'
|
|
||||||
py='2'
|
|
||||||
>
|
|
||||||
<Rule borderColor='blue' display={['none', 'block']} m='0' width='2rem' />
|
|
||||||
<Text flexShrink='0' display='block' zIndex='2' mx='4' color='blue'>
|
|
||||||
New messages below
|
|
||||||
</Text>
|
|
||||||
<Rule borderColor='blue' flexGrow='1' m='0' />
|
|
||||||
<Rule style={{ width: 'calc(50% - 48px)' }} borderColor='blue' m='0' />
|
|
||||||
</Row>
|
|
||||||
));
|
|
||||||
|
|
||||||
export const DayBreak = ({ when }) => (
|
export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
|
||||||
<Row pb='3' alignItems='center' justifyContent='center' width='100%'>
|
<Row
|
||||||
<Text gray>
|
px={2}
|
||||||
|
height={5}
|
||||||
|
mb={2}
|
||||||
|
justifyContent='center'
|
||||||
|
alignItems='center'
|
||||||
|
mt={shimTop ? '-8px' : '0'}
|
||||||
|
>
|
||||||
|
<Rule borderColor='lightGray' />
|
||||||
|
<Text gray flexShrink='0' fontSize={0} px={2}>
|
||||||
{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}
|
{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Rule borderColor='lightGray' />
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||||
|
<Row
|
||||||
|
position='absolute'
|
||||||
|
ref={ref}
|
||||||
|
px={2}
|
||||||
|
mt={2}
|
||||||
|
height={5}
|
||||||
|
justifyContent='center'
|
||||||
|
alignItems='center'
|
||||||
|
width='100%'
|
||||||
|
>
|
||||||
|
<Rule borderColor='lightBlue' />
|
||||||
|
<Text color='blue' fontSize={0} flexShrink='0' px={2}>
|
||||||
|
New messages below
|
||||||
|
</Text>
|
||||||
|
<Rule borderColor='lightBlue' />
|
||||||
|
</Row>
|
||||||
|
));
|
||||||
|
|
||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
measure(element): void;
|
measure(element): void;
|
||||||
msg: Post;
|
msg: Post;
|
||||||
@ -74,6 +95,7 @@ interface ChatMessageProps {
|
|||||||
history: unknown;
|
history: unknown;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
|
renderSigil?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ChatMessage extends Component<ChatMessageProps> {
|
export default class ChatMessage extends Component<ChatMessageProps> {
|
||||||
@ -114,19 +136,26 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
associations
|
associations
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const renderSigil = Boolean(
|
let { renderSigil } = this.props;
|
||||||
(nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1
|
|
||||||
);
|
if (renderSigil === undefined) {
|
||||||
|
renderSigil = Boolean(
|
||||||
|
(nextMsg && msg.author !== nextMsg.author) ||
|
||||||
|
!nextMsg ||
|
||||||
|
msg.number === 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const dayBreak =
|
const dayBreak =
|
||||||
nextMsg &&
|
nextMsg &&
|
||||||
new Date(msg['time-sent']).getDate() !==
|
new Date(msg['time-sent']).getDate() !==
|
||||||
new Date(nextMsg['time-sent']).getDate();
|
new Date(nextMsg['time-sent']).getDate();
|
||||||
|
|
||||||
const containerClass = `${
|
const containerClass = `${isPending ? 'o-40' : ''} ${className}`;
|
||||||
renderSigil ? 'cf pl2 lh-copy' : 'items-top cf hide-child'
|
|
||||||
} ${isPending ? 'o-40' : ''} ${className}`;
|
|
||||||
|
|
||||||
const timestamp = moment.unix(msg['time-sent'] / 1000);
|
const timestamp = moment
|
||||||
|
.unix(msg['time-sent'] / 1000)
|
||||||
|
.format(renderSigil ? 'h:mm A' : 'h:mm');
|
||||||
|
|
||||||
const reboundMeasure = (event) => {
|
const reboundMeasure = (event) => {
|
||||||
return measure(this.divRef.current);
|
return measure(this.divRef.current);
|
||||||
@ -157,34 +186,24 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
bg={highlighted ? 'washedBlue' : 'white'}
|
|
||||||
flexShrink={0}
|
|
||||||
width='100%'
|
|
||||||
display='flex'
|
|
||||||
flexWrap='wrap'
|
|
||||||
pt={this.props.pt ? this.props.pt : renderSigil ? 3 : 0}
|
|
||||||
pr={3}
|
|
||||||
pb={isLastMessage ? 3 : 0}
|
|
||||||
ref={this.divRef}
|
ref={this.divRef}
|
||||||
|
pt={renderSigil ? 2 : 0}
|
||||||
|
pb={2}
|
||||||
className={containerClass}
|
className={containerClass}
|
||||||
style={style}
|
style={style}
|
||||||
mb={1}
|
|
||||||
position='relative'
|
|
||||||
>
|
>
|
||||||
{dayBreak && !isLastRead ? <DayBreak when={msg['time-sent']} /> : null}
|
{dayBreak && !isLastRead ? (
|
||||||
|
<DayBreak when={msg['time-sent']} shimTop={renderSigil} />
|
||||||
|
) : null}
|
||||||
{renderSigil ? (
|
{renderSigil ? (
|
||||||
<MessageWithSigil {...messageProps} />
|
<>
|
||||||
|
<MessageAuthor pb={'2px'} {...messageProps} />
|
||||||
|
<Message pl={5} pr={3} {...messageProps} />
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<MessageWithoutSigil {...messageProps} />
|
<Message pl={5} pr={3} timestampHover {...messageProps} />
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box style={unreadContainerStyle}>
|
||||||
flexShrink={0}
|
|
||||||
fontSize={0}
|
|
||||||
position='relative'
|
|
||||||
width='100%'
|
|
||||||
overflow='visible'
|
|
||||||
style={unreadContainerStyle}
|
|
||||||
>
|
|
||||||
{isLastRead ? (
|
{isLastRead ? (
|
||||||
<UnreadMarker
|
<UnreadMarker
|
||||||
dayBreak={dayBreak}
|
dayBreak={dayBreak}
|
||||||
@ -198,40 +217,25 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageProps {
|
export const MessageAuthor = ({
|
||||||
msg: Post;
|
timestamp,
|
||||||
timestamp: string;
|
contacts,
|
||||||
group: Group;
|
msg,
|
||||||
association: Association;
|
measure,
|
||||||
contacts: Contacts;
|
group,
|
||||||
containerClass: string;
|
api,
|
||||||
isPending: boolean;
|
associations,
|
||||||
style: any;
|
groups,
|
||||||
measure(element): void;
|
scrollWindow,
|
||||||
scrollWindow: HTMLDivElement;
|
...rest
|
||||||
associations: Associations;
|
}) => {
|
||||||
groups: Groups;
|
const dark = useLocalState((state) => state.dark);
|
||||||
}
|
|
||||||
|
|
||||||
export const MessageWithSigil = (props) => {
|
const datestamp = moment
|
||||||
const {
|
.unix(msg['time-sent'] / 1000)
|
||||||
msg,
|
.format(DATESTAMP_FORMAT);
|
||||||
contacts,
|
const contact =
|
||||||
association,
|
`~${msg.author}` in contacts ? contacts[`~${msg.author}`] : false;
|
||||||
associations,
|
|
||||||
groups,
|
|
||||||
group,
|
|
||||||
measure,
|
|
||||||
api,
|
|
||||||
history,
|
|
||||||
scrollWindow,
|
|
||||||
fontSize
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const dark = useLocalState(state => state.dark);
|
|
||||||
|
|
||||||
const stamp = moment.unix(msg['time-sent'] / 1000);
|
|
||||||
const contact = `~${msg.author}` in contacts ? contacts[`~${msg.author}`] : false;
|
|
||||||
const showNickname = useShowNickname(contact);
|
const showNickname = useShowNickname(contact);
|
||||||
const { hideAvatars } =
|
const { hideAvatars } =
|
||||||
useLocalState(({ hideAvatars }) =>
|
useLocalState(({ hideAvatars }) =>
|
||||||
@ -255,7 +259,7 @@ export const MessageWithSigil = (props) => {
|
|||||||
const [showOverlay, setShowOverlay] = useState(false);
|
const [showOverlay, setShowOverlay] = useState(false);
|
||||||
|
|
||||||
const toggleOverlay = () => {
|
const toggleOverlay = () => {
|
||||||
setShowOverlay(value => !value);
|
setShowOverlay((value) => !value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showCopyNotice = () => {
|
const showCopyNotice = () => {
|
||||||
@ -289,17 +293,16 @@ export const MessageWithSigil = (props) => {
|
|||||||
padding={2}
|
padding={2}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box display='flex' alignItems='center' {...rest}>
|
||||||
<Box
|
<Box
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowOverlay(true);
|
setShowOverlay(true);
|
||||||
}}
|
}}
|
||||||
className='fl v-top pt1'
|
|
||||||
height={16}
|
height={16}
|
||||||
pr={3}
|
pr={2}
|
||||||
pl={2}
|
pl={2}
|
||||||
|
cursor='pointer'
|
||||||
position='relative'
|
position='relative'
|
||||||
>
|
>
|
||||||
{showOverlay && (
|
{showOverlay && (
|
||||||
@ -328,11 +331,11 @@ export const MessageWithSigil = (props) => {
|
|||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
fontSize={0}
|
fontSize={0}
|
||||||
mr={3}
|
mr={2}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
mono={nameMono}
|
mono={nameMono}
|
||||||
fontWeight={nameMono ? '400' : '500'}
|
fontWeight={nameMono ? '400' : '500'}
|
||||||
className={'mw5 db truncate pointer'}
|
className={'pointer'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
writeText(`~${msg.author}`);
|
writeText(`~${msg.author}`);
|
||||||
showCopyNotice();
|
showCopyNotice();
|
||||||
@ -341,36 +344,25 @@ export const MessageWithSigil = (props) => {
|
|||||||
>
|
>
|
||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
<Timestamp stamp={stamp} fontSize={0}/>
|
<Text flexShrink={0} fontSize={0} gray>
|
||||||
|
{timestamp}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
flexShrink={0}
|
||||||
|
fontSize={0}
|
||||||
|
gray
|
||||||
|
ml={2}
|
||||||
|
display={['none', hovering ? 'block' : 'none']}
|
||||||
|
>
|
||||||
|
{datestamp}
|
||||||
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
|
|
||||||
{msg.contents.map((c, i) => (
|
|
||||||
<MessageContent
|
|
||||||
key={i}
|
|
||||||
contacts={contacts}
|
|
||||||
content={c}
|
|
||||||
measure={measure}
|
|
||||||
scrollWindow={scrollWindow}
|
|
||||||
fontSize={fontSize}
|
|
||||||
group={group}
|
|
||||||
api={api}
|
|
||||||
associations={associations}
|
|
||||||
groups={groups}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</ContentBox>
|
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContentBox = styled(Box)`
|
export const Message = ({
|
||||||
& > :first-child {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const MessageWithoutSigil = ({
|
|
||||||
timestamp,
|
timestamp,
|
||||||
contacts,
|
contacts,
|
||||||
msg,
|
msg,
|
||||||
@ -379,115 +371,95 @@ export const MessageWithoutSigil = ({
|
|||||||
api,
|
api,
|
||||||
associations,
|
associations,
|
||||||
groups,
|
groups,
|
||||||
scrollWindow
|
scrollWindow,
|
||||||
|
timestampHover,
|
||||||
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { hovering, bind } = useHovering();
|
const { hovering, bind } = useHovering();
|
||||||
return (
|
return (
|
||||||
<>
|
<Box position='relative' {...rest}>
|
||||||
<Timestamp
|
{timestampHover ? (
|
||||||
stamp={timestamp}
|
<Text
|
||||||
date={false}
|
display={hovering ? 'block' : 'none'}
|
||||||
display={hovering ? 'block' : 'none'}
|
position='absolute'
|
||||||
pt='2px'
|
left='0'
|
||||||
lineHeight='tall'
|
top='3px'
|
||||||
position='absolute'
|
fontSize={0}
|
||||||
left={1}
|
gray
|
||||||
/>
|
>
|
||||||
<ContentBox
|
{timestamp}
|
||||||
flexShrink={0}
|
</Text>
|
||||||
fontSize='14px'
|
) : (
|
||||||
className='clamp-message'
|
<></>
|
||||||
style={{ flexGrow: 1 }}
|
)}
|
||||||
{...bind}
|
<Box {...bind}>
|
||||||
pl={6}
|
{msg.contents.map((content, i) => {
|
||||||
>
|
switch (Object.keys(content)[0]) {
|
||||||
{msg.contents.map((c, i) => (
|
case 'text':
|
||||||
<MessageContent
|
return (
|
||||||
key={i}
|
<TextContent
|
||||||
contacts={contacts}
|
associations={associations}
|
||||||
content={c}
|
groups={groups}
|
||||||
group={group}
|
measure={measure}
|
||||||
measure={measure}
|
api={api}
|
||||||
scrollWindow={scrollWindow}
|
fontSize={1}
|
||||||
groups={groups}
|
lineHeight={'20px'}
|
||||||
associations={associations}
|
content={content}
|
||||||
api={api}
|
/>
|
||||||
/>
|
);
|
||||||
))}
|
case 'code':
|
||||||
</ContentBox>
|
return <CodeContent content={content} />;
|
||||||
</>
|
case 'url':
|
||||||
);
|
return (
|
||||||
};
|
<Box
|
||||||
|
flexShrink={0}
|
||||||
export const MessageContent = ({
|
fontSize={1}
|
||||||
content,
|
lineHeight='20px'
|
||||||
contacts,
|
color='black'
|
||||||
api,
|
>
|
||||||
associations,
|
<RemoteContent
|
||||||
groups,
|
url={content.url}
|
||||||
measure,
|
onLoad={measure}
|
||||||
scrollWindow,
|
imageProps={{
|
||||||
fontSize,
|
style: {
|
||||||
group
|
maxWidth: 'min(100%,18rem)',
|
||||||
}) => {
|
display: 'inline-block',
|
||||||
if ('code' in content) {
|
marginTop: '0.5rem'
|
||||||
return <CodeContent content={content} />;
|
}
|
||||||
} else if ('url' in content) {
|
}}
|
||||||
return (
|
videoProps={{
|
||||||
<Box
|
style: {
|
||||||
mx='2px'
|
maxWidth: '18rem',
|
||||||
flexShrink={0}
|
display: 'block',
|
||||||
fontSize={fontSize ? fontSize : '14px'}
|
marginTop: '0.5rem'
|
||||||
lineHeight='tall'
|
}
|
||||||
color='black'
|
}}
|
||||||
>
|
textProps={{
|
||||||
<RemoteContent
|
style: {
|
||||||
url={content.url}
|
fontSize: 'inherit',
|
||||||
onLoad={measure}
|
borderBottom: '1px solid',
|
||||||
imageProps={{
|
textDecoration: 'none'
|
||||||
style: {
|
}
|
||||||
maxWidth: 'min(100%,18rem)',
|
}}
|
||||||
display: 'block'
|
/>
|
||||||
}
|
</Box>
|
||||||
}}
|
);
|
||||||
videoProps={{
|
case 'mention':
|
||||||
style: {
|
return (
|
||||||
maxWidth: '18rem',
|
<Mention
|
||||||
display: 'block'
|
group={group}
|
||||||
}
|
scrollWindow={scrollWindow}
|
||||||
}}
|
ship={content.mention}
|
||||||
textProps={{
|
contact={contacts?.[`~${content.mention}`]}
|
||||||
style: {
|
/>
|
||||||
fontSize: 'inherit',
|
);
|
||||||
borderBottom: '1px solid',
|
default:
|
||||||
textDecoration: 'none'
|
return null;
|
||||||
}
|
}
|
||||||
}}
|
})}
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
</Box>
|
||||||
} else if ('text' in content) {
|
);
|
||||||
return (
|
|
||||||
<TextContent
|
|
||||||
associations={associations}
|
|
||||||
groups={groups}
|
|
||||||
measure={measure}
|
|
||||||
api={api}
|
|
||||||
fontSize={fontSize}
|
|
||||||
content={content}
|
|
||||||
/>);
|
|
||||||
} else if ('mention' in content) {
|
|
||||||
return (
|
|
||||||
<Mention
|
|
||||||
group={group}
|
|
||||||
scrollWindow={scrollWindow}
|
|
||||||
ship={content.mention}
|
|
||||||
contact={contacts?.[`~${content.mention}`]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessagePlaceholder = ({
|
export const MessagePlaceholder = ({
|
||||||
|
@ -4,7 +4,15 @@ import _ from 'lodash';
|
|||||||
import bigInt, { BigInteger } from 'big-integer';
|
import bigInt, { BigInteger } from 'big-integer';
|
||||||
|
|
||||||
import { Col } from '@tlon/indigo-react';
|
import { Col } from '@tlon/indigo-react';
|
||||||
import { Patp, Contacts, Association, Associations, Group, Groups, Graph } from '@urbit/api';
|
import {
|
||||||
|
Patp,
|
||||||
|
Contacts,
|
||||||
|
Association,
|
||||||
|
Associations,
|
||||||
|
Group,
|
||||||
|
Groups,
|
||||||
|
Graph
|
||||||
|
} from '@urbit/api';
|
||||||
|
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
|
|
||||||
@ -33,7 +41,7 @@ type ChatWindowProps = RouteComponentProps<{
|
|||||||
scrollTo?: number;
|
scrollTo?: number;
|
||||||
associations: Associations;
|
associations: Associations;
|
||||||
groups: Groups;
|
groups: Groups;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface ChatWindowState {
|
interface ChatWindowState {
|
||||||
fetchPending: boolean;
|
fetchPending: boolean;
|
||||||
@ -42,7 +50,10 @@ interface ChatWindowState {
|
|||||||
unreadIndex: BigInteger;
|
unreadIndex: BigInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
export default class ChatWindow extends Component<
|
||||||
|
ChatWindowProps,
|
||||||
|
ChatWindowState
|
||||||
|
> {
|
||||||
private virtualList: VirtualScroller | null;
|
private virtualList: VirtualScroller | null;
|
||||||
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||||
private prevSize = 0;
|
private prevSize = 0;
|
||||||
@ -79,7 +90,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
window.addEventListener('blur', this.handleWindowBlur);
|
window.addEventListener('blur', this.handleWindowBlur);
|
||||||
window.addEventListener('focus', this.handleWindowFocus);
|
window.addEventListener('focus', this.handleWindowFocus);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if(this.props.scrollTo) {
|
if (this.props.scrollTo) {
|
||||||
this.scrollToUnread();
|
this.scrollToUnread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +106,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
calculateUnreadIndex() {
|
calculateUnreadIndex() {
|
||||||
const { graph, unreadCount } = this.props;
|
const { graph, unreadCount } = this.props;
|
||||||
const unreadIndex = graph.keys()[unreadCount];
|
const unreadIndex = graph.keys()[unreadCount];
|
||||||
if(!unreadIndex || unreadCount === 0) {
|
if (!unreadIndex || unreadCount === 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
unreadIndex: bigInt.zero
|
unreadIndex: bigInt.zero
|
||||||
});
|
});
|
||||||
@ -128,8 +139,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.calculateUnreadIndex();
|
this.calculateUnreadIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.prevSize !== graph.size) {
|
if (this.prevSize !== graph.size) {
|
||||||
if(this.state.unreadIndex.eq(bigInt.zero)) {
|
if (this.state.unreadIndex.eq(bigInt.zero)) {
|
||||||
this.calculateUnreadIndex();
|
this.calculateUnreadIndex();
|
||||||
this.scrollToUnread();
|
this.scrollToUnread();
|
||||||
}
|
}
|
||||||
@ -153,7 +164,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
|
|
||||||
scrollToUnread() {
|
scrollToUnread() {
|
||||||
const { unreadIndex } = this.state;
|
const { unreadIndex } = this.state;
|
||||||
if(unreadIndex.eq(bigInt.zero)) {
|
if (unreadIndex.eq(bigInt.zero)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +173,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
|
|
||||||
dismissUnread() {
|
dismissUnread() {
|
||||||
const { association } = this.props;
|
const { association } = this.props;
|
||||||
if (this.state.fetchPending)
|
if (this.state.fetchPending) return;
|
||||||
return;
|
if (this.props.unreadCount === 0) return;
|
||||||
if (this.props.unreadCount === 0)
|
|
||||||
return;
|
|
||||||
this.props.api.hark.markCountAsRead(association, '/', 'message');
|
this.props.api.hark.markCountAsRead(association, '/', 'message');
|
||||||
this.props.api.hark.markCountAsRead(association, '/', 'mention');
|
this.props.api.hark.markCountAsRead(association, '/', 'mention');
|
||||||
}
|
}
|
||||||
@ -173,26 +182,31 @@ return;
|
|||||||
async fetchMessages(newer: boolean, force = false): Promise<void> {
|
async fetchMessages(newer: boolean, force = false): Promise<void> {
|
||||||
const { api, station, graph } = this.props;
|
const { api, station, graph } = this.props;
|
||||||
|
|
||||||
if ( this.state.fetchPending && !force) {
|
if (this.state.fetchPending && !force) {
|
||||||
return new Promise((resolve, reject) => {});
|
return new Promise((resolve, reject) => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ fetchPending: true });
|
this.setState({ fetchPending: true });
|
||||||
|
|
||||||
const [,, ship, name] = station.split('/');
|
const [, , ship, name] = station.split('/');
|
||||||
const currSize = graph.size;
|
const currSize = graph.size;
|
||||||
if(newer && !this.loadedNewest) {
|
if (newer && !this.loadedNewest) {
|
||||||
const [index] = graph.peekLargest()!;
|
const [index] = graph.peekLargest()!;
|
||||||
await api.graph.getYoungerSiblings(ship,name, 20, `/${index.toString()}`);
|
await api.graph.getYoungerSiblings(
|
||||||
if(currSize === graph.size) {
|
ship,
|
||||||
|
name,
|
||||||
|
20,
|
||||||
|
`/${index.toString()}`
|
||||||
|
);
|
||||||
|
if (currSize === graph.size) {
|
||||||
console.log('loaded all newest');
|
console.log('loaded all newest');
|
||||||
this.loadedNewest = true;
|
this.loadedNewest = true;
|
||||||
}
|
}
|
||||||
} else if(!newer && !this.loadedOldest) {
|
} else if (!newer && !this.loadedOldest) {
|
||||||
const [index] = graph.peekSmallest()!;
|
const [index] = graph.peekSmallest()!;
|
||||||
await api.graph.getOlderSiblings(ship,name, 20, `/${index.toString()}`);
|
await api.graph.getOlderSiblings(ship, name, 20, `/${index.toString()}`);
|
||||||
this.calculateUnreadIndex();
|
this.calculateUnreadIndex();
|
||||||
if(currSize === graph.size) {
|
if (currSize === graph.size) {
|
||||||
console.log('loaded all oldest');
|
console.log('loaded all oldest');
|
||||||
this.loadedOldest = true;
|
this.loadedOldest = true;
|
||||||
}
|
}
|
||||||
@ -209,17 +223,14 @@ return;
|
|||||||
}
|
}
|
||||||
|
|
||||||
dismissIfLineVisible() {
|
dismissIfLineVisible() {
|
||||||
if (this.props.unreadCount === 0)
|
if (this.props.unreadCount === 0) return;
|
||||||
return;
|
if (!this.unreadMarkerRef.current || !this.virtualList?.window) return;
|
||||||
if (!this.unreadMarkerRef.current || !this.virtualList?.window)
|
|
||||||
return;
|
|
||||||
const parent = this.unreadMarkerRef.current.parentElement?.parentElement;
|
const parent = this.unreadMarkerRef.current.parentElement?.parentElement;
|
||||||
if (!parent)
|
if (!parent) return;
|
||||||
return;
|
|
||||||
const { scrollTop, scrollHeight, offsetHeight } = this.virtualList.window;
|
const { scrollTop, scrollHeight, offsetHeight } = this.virtualList.window;
|
||||||
if (
|
if (
|
||||||
(scrollHeight - parent.offsetTop > scrollTop)
|
scrollHeight - parent.offsetTop > scrollTop &&
|
||||||
&& (scrollHeight - parent.offsetTop < scrollTop + offsetHeight)
|
scrollHeight - parent.offsetTop < scrollTop + offsetHeight
|
||||||
) {
|
) {
|
||||||
this.dismissUnread();
|
this.dismissUnread();
|
||||||
}
|
}
|
||||||
@ -241,26 +252,39 @@ return;
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const unreadMarkerRef = this.unreadMarkerRef;
|
const unreadMarkerRef = this.unreadMarkerRef;
|
||||||
|
const messageProps = {
|
||||||
const messageProps = { association, group, contacts, unreadMarkerRef, history, api, groups, associations };
|
association,
|
||||||
|
group,
|
||||||
|
contacts,
|
||||||
|
unreadMarkerRef,
|
||||||
|
history,
|
||||||
|
api,
|
||||||
|
groups,
|
||||||
|
associations
|
||||||
|
};
|
||||||
const keys = graph.keys().reverse();
|
const keys = graph.keys().reverse();
|
||||||
const unreadIndex = graph.keys()[this.props.unreadCount];
|
const unreadIndex = graph.keys()[this.props.unreadCount];
|
||||||
const unreadMsg = unreadIndex && graph.get(unreadIndex);
|
const unreadMsg = unreadIndex && graph.get(unreadIndex);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col height='100%' overflow='hidden' position="relative">
|
<Col height='100%' overflow='hidden' position='relative'>
|
||||||
<UnreadNotice
|
<UnreadNotice
|
||||||
unreadCount={unreadCount}
|
unreadCount={unreadCount}
|
||||||
unreadMsg={unreadCount === 1 && unreadMsg && unreadMsg?.post.author === window.ship ? false : unreadMsg}
|
unreadMsg={
|
||||||
|
unreadCount === 1 &&
|
||||||
|
unreadMsg &&
|
||||||
|
unreadMsg?.post.author === window.ship
|
||||||
|
? false
|
||||||
|
: unreadMsg
|
||||||
|
}
|
||||||
dismissUnread={this.dismissUnread}
|
dismissUnread={this.dismissUnread}
|
||||||
onClick={this.scrollToUnread}
|
onClick={this.scrollToUnread}
|
||||||
/>
|
/>
|
||||||
<VirtualScroller
|
<VirtualScroller
|
||||||
ref={(list) => {
|
ref={(list) => {
|
||||||
this.virtualList = list;
|
this.virtualList = list;
|
||||||
}}
|
}}
|
||||||
origin="bottom"
|
origin='bottom'
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
onStartReached={() => {
|
onStartReached={() => {
|
||||||
this.setState({ idle: false });
|
this.setState({ idle: false });
|
||||||
@ -271,20 +295,35 @@ return;
|
|||||||
size={graph.size}
|
size={graph.size}
|
||||||
renderer={({ index, measure, scrollWindow }) => {
|
renderer={({ index, measure, scrollWindow }) => {
|
||||||
const msg = graph.get(index)?.post;
|
const msg = graph.get(index)?.post;
|
||||||
if (!msg)
|
if (!msg) return null;
|
||||||
return null;
|
|
||||||
if (!this.state.initialized) {
|
if (!this.state.initialized) {
|
||||||
return <MessagePlaceholder key={index.toString()} height="64px" index={index} />;
|
return (
|
||||||
|
<MessagePlaceholder
|
||||||
|
key={index.toString()}
|
||||||
|
height='64px'
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
|
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
|
||||||
const isLastMessage = index.eq(graph.peekLargest()?.[0] ?? bigInt.zero);
|
const isLastMessage = index.eq(
|
||||||
|
graph.peekLargest()?.[0] ?? bigInt.zero
|
||||||
|
);
|
||||||
const highlighted = bigInt(this.props.scrollTo || -1).eq(index);
|
const highlighted = bigInt(this.props.scrollTo || -1).eq(index);
|
||||||
const graphIdx = keys.findIndex(idx => idx.eq(index));
|
const graphIdx = keys.findIndex((idx) => idx.eq(index));
|
||||||
const prevIdx = keys[graphIdx+1];
|
const prevIdx = keys[graphIdx + 1];
|
||||||
const nextIdx = keys[graphIdx-1];
|
const nextIdx = keys[graphIdx - 1];
|
||||||
|
|
||||||
const isLastRead: boolean = this.state.unreadIndex.eq(index);
|
const isLastRead: boolean = this.state.unreadIndex.eq(index);
|
||||||
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
const props = {
|
||||||
|
measure,
|
||||||
|
highlighted,
|
||||||
|
scrollWindow,
|
||||||
|
isPending,
|
||||||
|
isLastRead,
|
||||||
|
isLastMessage,
|
||||||
|
msg,
|
||||||
|
...messageProps
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={index.toString()}
|
key={index.toString()}
|
||||||
@ -302,4 +341,3 @@ return null;
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
||||||
import urbitOb from 'urbit-ob';
|
import urbitOb from 'urbit-ob';
|
||||||
import { Text } from '@tlon/indigo-react';
|
import { Text } from '@tlon/indigo-react';
|
||||||
import { GroupLink } from "~/views/components/GroupLink";
|
import { GroupLink } from '~/views/components/GroupLink';
|
||||||
|
|
||||||
const DISABLED_BLOCK_TOKENS = [
|
const DISABLED_BLOCK_TOKENS = [
|
||||||
'indentedCode',
|
'indentedCode',
|
||||||
@ -26,44 +26,60 @@ const DISABLED_INLINE_TOKENS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const renderers = {
|
const renderers = {
|
||||||
inlineCode: ({language, value}) => {
|
inlineCode: ({ language, value }) => {
|
||||||
return <Text mono p='1' backgroundColor='washedGray' fontSize='0' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
|
return (
|
||||||
|
<Text
|
||||||
|
mono
|
||||||
|
p='1'
|
||||||
|
backgroundColor='washedGray'
|
||||||
|
fontSize='0'
|
||||||
|
style={{ whiteSpace: 'preWrap' }}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
paragraph: ({ children }) => {
|
paragraph: ({ children }) => {
|
||||||
return (<Text fontSize="1">{children}</Text>);
|
return (
|
||||||
|
<Text fontSize='1' lineHeight={'20px'}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
code: ({language, value}) => {
|
code: ({ language, value }) => {
|
||||||
return <Text
|
return (
|
||||||
p='1'
|
<Text
|
||||||
className='clamp-message'
|
p='1'
|
||||||
display='block'
|
className='clamp-message'
|
||||||
borderRadius='1'
|
display='block'
|
||||||
mono
|
borderRadius='1'
|
||||||
fontSize='0'
|
mono
|
||||||
backgroundColor='washedGray'
|
fontSize='0'
|
||||||
overflowX='auto'
|
backgroundColor='washedGray'
|
||||||
style={{ whiteSpace: 'pre'}}>
|
overflowX='auto'
|
||||||
{value}
|
style={{ whiteSpace: 'pre' }}
|
||||||
</Text>
|
>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageMarkdown = React.memo(props => {
|
const MessageMarkdown = React.memo((props) => {
|
||||||
const { source, ...rest } = props;
|
const { source, ...rest } = props;
|
||||||
const blockCode = source.split('```');
|
const blockCode = source.split('```');
|
||||||
const codeLines = blockCode.map(codes => codes.split("\n"));
|
const codeLines = blockCode.map((codes) => codes.split('\n'));
|
||||||
const lines = codeLines.reduce((acc, val, i) => {
|
const lines = codeLines.reduce((acc, val, i) => {
|
||||||
if((i % 2) === 1) {
|
if (i % 2 === 1) {
|
||||||
return [...acc, `\`\`\`${val.join('\n')}\`\`\``];
|
return [...acc, `\`\`\`${val.join('\n')}\`\`\``];
|
||||||
} else {
|
} else {
|
||||||
return [...acc, ...val];
|
return [...acc, ...val];
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return lines.map((line, i) => (
|
return lines.map((line, i) => (
|
||||||
<>
|
<>
|
||||||
{ i !== 0 && <br />}
|
{i !== 0 && <br />}
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
{...rest}
|
{...rest}
|
||||||
source={line}
|
source={line}
|
||||||
@ -71,25 +87,31 @@ const MessageMarkdown = React.memo(props => {
|
|||||||
renderers={renderers}
|
renderers={renderers}
|
||||||
allowNode={(node, index, parent) => {
|
allowNode={(node, index, parent) => {
|
||||||
if (
|
if (
|
||||||
node.type === 'blockquote'
|
node.type === 'blockquote' &&
|
||||||
&& parent.type === 'root'
|
parent.type === 'root' &&
|
||||||
&& node.children.length
|
node.children.length &&
|
||||||
&& node.children[0].type === 'paragraph'
|
node.children[0].type === 'paragraph' &&
|
||||||
&& node.children[0].position.start.offset < 2
|
node.children[0].position.start.offset < 2
|
||||||
) {
|
) {
|
||||||
node.children[0].children[0].value = '>' + node.children[0].children[0].value;
|
node.children[0].children[0].value =
|
||||||
|
'>' + node.children[0].children[0].value;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}}
|
}}
|
||||||
plugins={[[RemarkDisableTokenizers, {
|
plugins={[
|
||||||
block: DISABLED_BLOCK_TOKENS,
|
[
|
||||||
inline: DISABLED_INLINE_TOKENS
|
RemarkDisableTokenizers,
|
||||||
}]]}
|
{
|
||||||
/>
|
block: DISABLED_BLOCK_TOKENS,
|
||||||
|
inline: DISABLED_INLINE_TOKENS
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
))
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function TextContent(props) {
|
export default function TextContent(props) {
|
||||||
@ -98,12 +120,13 @@ export default function TextContent(props) {
|
|||||||
const group = content.text.match(
|
const group = content.text.match(
|
||||||
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/
|
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z0-9-])+([/-])?)+/
|
||||||
);
|
);
|
||||||
const isGroupLink = ((group !== null) // matched possible chatroom
|
const isGroupLink =
|
||||||
&& (group[2].length > 2) // possible ship?
|
group !== null && // matched possible chatroom
|
||||||
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
group[2].length > 2 && // possible ship?
|
||||||
&& (group[0] === content.text))) // entire message is room name?
|
urbitOb.isValidPatp(group[2]) && // valid patp?
|
||||||
|
group[0] === content.text; // entire message is room name?
|
||||||
|
|
||||||
if(isGroupLink) {
|
if (isGroupLink) {
|
||||||
const resource = `/ship/${content.text}`;
|
const resource = `/ship/${content.text}`;
|
||||||
return (
|
return (
|
||||||
<GroupLink
|
<GroupLink
|
||||||
@ -120,7 +143,13 @@ export default function TextContent(props) {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Text mx="2px" flexShrink={0} color='black' fontSize={props.fontSize ? props.fontSize : '14px'} lineHeight="tall" style={{ overflowWrap: 'break-word' }}>
|
<Text
|
||||||
|
flexShrink={0}
|
||||||
|
color='black'
|
||||||
|
fontSize={props.fontSize ? props.fontSize : '14px'}
|
||||||
|
lineHeight={props.lineHeight ? props.lineHeight : '20px'}
|
||||||
|
style={{ overflowWrap: 'break-word' }}
|
||||||
|
>
|
||||||
<MessageMarkdown source={content.text} />
|
<MessageMarkdown source={content.text} />
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
import React, { ReactElement, useCallback } from 'react';
|
import React, { ReactNode, useCallback } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import _ from 'lodash';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { Row, Box, Col, Text, Anchor, Icon, Action } from '@tlon/indigo-react';
|
import { Row, Box, Col, Text, Anchor, Icon, Action } from '@tlon/indigo-react';
|
||||||
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
GraphNotifIndex,
|
GraphNotifIndex,
|
||||||
GraphNotificationContents,
|
GraphNotificationContents,
|
||||||
Associations,
|
Associations,
|
||||||
Rolodex,
|
Rolodex,
|
||||||
Groups
|
Groups
|
||||||
} from '@urbit/api';
|
} from '~/types';
|
||||||
|
|
||||||
import { Header } from './header';
|
import { Header } from './header';
|
||||||
import { cite, deSig, pluralize } from '~/logic/lib/util';
|
import { cite, deSig, pluralize } from '~/logic/lib/util';
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
|
import RichText from '~/views/components/RichText';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { getSnippet } from '~/logic/lib/publish';
|
import { getSnippet } from '~/logic/lib/publish';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { MentionText } from '~/views/components/MentionText';
|
import { MentionText } from '~/views/components/MentionText';
|
||||||
import { MessageWithoutSigil } from '../chat/components/ChatMessage';
|
import ChatMessage from '../chat/components/ChatMessage';
|
||||||
import Timestamp from '~/views/components/Timestamp';
|
|
||||||
|
|
||||||
function getGraphModuleIcon(module: string): string {
|
function getGraphModuleIcon(module: string) {
|
||||||
if (module === 'link') {
|
if (module === 'link') {
|
||||||
return 'Collection';
|
return 'Collection';
|
||||||
}
|
}
|
||||||
@ -58,16 +57,24 @@ function describeNotification(description: string, plural: boolean): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphUrl = ({ url, title }): ReactElement => (
|
const GraphUrl = ({ url, title }) => (
|
||||||
<Box borderRadius="2" p="2" bg="scales.black05">
|
<Box borderRadius='2' p='2' bg='scales.black05'>
|
||||||
<Anchor underline={false} target="_blank" color="black" href={url}>
|
<Anchor underline={false} target='_blank' color='black' href={url}>
|
||||||
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
<Icon verticalAlign='bottom' mr='2' icon='ArrowExternal' />
|
||||||
{title}
|
{title}
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const GraphNodeContent = ({ group, post, contacts, mod, index }): ReactElement => {
|
const GraphNodeContent = ({
|
||||||
|
group,
|
||||||
|
post,
|
||||||
|
contacts,
|
||||||
|
mod,
|
||||||
|
description,
|
||||||
|
index,
|
||||||
|
remoteContentPolicy
|
||||||
|
}) => {
|
||||||
const { contents } = post;
|
const { contents } = post;
|
||||||
const idx = index.slice(1).split('/');
|
const idx = index.slice(1).split('/');
|
||||||
if (mod === 'link') {
|
if (mod === 'link') {
|
||||||
@ -75,39 +82,39 @@ const GraphNodeContent = ({ group, post, contacts, mod, index }): ReactElement =
|
|||||||
const [{ text }, { url }] = contents;
|
const [{ text }, { url }] = contents;
|
||||||
return <GraphUrl title={text} url={url} />;
|
return <GraphUrl title={text} url={url} />;
|
||||||
} else if (idx.length === 3) {
|
} else if (idx.length === 3) {
|
||||||
return <MentionText
|
return (
|
||||||
content={contents}
|
<MentionText content={contents} contacts={contacts} group={group} />
|
||||||
contacts={contacts}
|
);
|
||||||
group={group}
|
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (mod === 'publish') {
|
if (mod === 'publish') {
|
||||||
if (idx[1] === '2') {
|
if (idx[1] === '2') {
|
||||||
return <MentionText
|
return (
|
||||||
content={contents}
|
<MentionText
|
||||||
group={group}
|
content={contents}
|
||||||
contacts={contacts}
|
group={group}
|
||||||
fontSize='14px'
|
contacts={contacts}
|
||||||
lineHeight="tall"
|
fontSize='14px'
|
||||||
/>;
|
lineHeight='tall'
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (idx[1] === '1') {
|
} else if (idx[1] === '1') {
|
||||||
const [{ text: header }, { text: body }] = contents;
|
const [{ text: header }, { text: body }] = contents;
|
||||||
const snippet = getSnippet(body);
|
const snippet = getSnippet(body);
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
<Box mb="2" fontWeight="500">
|
<Box mb='2' fontWeight='500'>
|
||||||
<Text>{header}</Text>
|
<Text>{header}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box overflow="hidden" maxHeight="400px" position="relative">
|
<Box overflow='hidden' maxHeight='400px' position='relative'>
|
||||||
<Text lineHeight="tall">{snippet}</Text>
|
<Text lineHeight='tall'>{snippet}</Text>
|
||||||
<FilterBox
|
<FilterBox
|
||||||
width="100%"
|
width='100%'
|
||||||
zIndex="1"
|
zIndex='1'
|
||||||
height="calc(100% - 2em)"
|
height='calc(100% - 2em)'
|
||||||
bottom="-4px"
|
bottom='-4px'
|
||||||
position="absolute"
|
position='absolute'
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Col>
|
</Col>
|
||||||
@ -115,31 +122,40 @@ const GraphNodeContent = ({ group, post, contacts, mod, index }): ReactElement =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mod === 'chat') {
|
if (mod === 'chat') {
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
width="100%"
|
width='100%'
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
flexWrap="wrap"
|
flexWrap='wrap'
|
||||||
|
marginLeft='-32px'
|
||||||
>
|
>
|
||||||
<MessageWithoutSigil
|
<ChatMessage
|
||||||
containerClass="items-top cf hide-child"
|
renderSigil={false}
|
||||||
measure={() => {}}
|
containerClass='items-top cf hide-child'
|
||||||
group={group}
|
measure={() => {}}
|
||||||
contacts={contacts}
|
group={group}
|
||||||
groups={{}}
|
contacts={contacts}
|
||||||
associations={{ graph: {}, groups: {} }}
|
groups={{}}
|
||||||
msg={post}
|
associations={{ graph: {}, groups: {} }}
|
||||||
fontSize='0'
|
msg={post}
|
||||||
pt='2'
|
fontSize='0'
|
||||||
/>
|
pt='2'
|
||||||
</Row>);
|
/>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNodeUrl(mod: string, hidden: boolean, groupPath: string, graph: string, index: string): string {
|
function getNodeUrl(
|
||||||
|
mod: string,
|
||||||
|
hidden: boolean,
|
||||||
|
groupPath: string,
|
||||||
|
graph: string,
|
||||||
|
index: string
|
||||||
|
) {
|
||||||
if (hidden && mod === 'chat') {
|
if (hidden && mod === 'chat') {
|
||||||
groupPath = '/messages';
|
groupPath = '/messages';
|
||||||
} else if (hidden) {
|
} else if (hidden) {
|
||||||
@ -181,40 +197,46 @@ const GraphNode = ({
|
|||||||
ship={`~${author}`}
|
ship={`~${author}`}
|
||||||
size={16}
|
size={16}
|
||||||
icon
|
icon
|
||||||
color={'#000000'}
|
color={`#000000`}
|
||||||
classes="mix-blend-diff"
|
classes='mix-blend-diff'
|
||||||
padding={2}
|
padding={2}
|
||||||
/>
|
/>
|
||||||
) : <Box style={{ width: '16px' }}></Box>;
|
) : (
|
||||||
|
<Box style={{ width: '16px' }}></Box>
|
||||||
|
);
|
||||||
|
|
||||||
const groupContacts = contacts[groupPath] ?? {};
|
const groupContacts = contacts[groupPath] ?? {};
|
||||||
|
|
||||||
const nodeUrl = getNodeUrl(mod, group?.hidden, groupPath, graph, index);
|
const nodeUrl = getNodeUrl(mod, group?.hidden, groupPath, graph, index);
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
if(!read) {
|
if (!read) {
|
||||||
onRead();
|
onRead();
|
||||||
}
|
}
|
||||||
history.push(nodeUrl);
|
history.push(nodeUrl);
|
||||||
}, [read, onRead]);
|
}, [read, onRead]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row onClick={onClick} gapX="2" pt={showContact ? 2 : 0}>
|
<Row onClick={onClick} gapX='2' pt={showContact ? 2 : 0}>
|
||||||
<Col>{img}</Col>
|
<Col>{img}</Col>
|
||||||
<Col flexGrow={1} alignItems="flex-start">
|
<Col flexGrow={1} alignItems='flex-start'>
|
||||||
{showContact && <Row
|
{showContact && (
|
||||||
mb="2"
|
<Row
|
||||||
height="16px"
|
mb='2'
|
||||||
alignItems="center"
|
height='16px'
|
||||||
p="1"
|
alignItems='center'
|
||||||
backgroundColor="white"
|
p='1'
|
||||||
>
|
backgroundColor='white'
|
||||||
<Text mono title={author}>
|
>
|
||||||
{cite(author)}
|
<Text mono title={author}>
|
||||||
</Text>
|
{cite(author)}
|
||||||
<Timestamp stamp={moment(time)} date={false} ml={2} />
|
</Text>
|
||||||
</Row>}
|
<Text ml='2' gray>
|
||||||
<Row width="100%" p="1" flexDirection="column">
|
{moment(time).format('HH:mm')}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
<Row width='100%' p='1' flexDirection='column'>
|
||||||
<GraphNodeContent
|
<GraphNodeContent
|
||||||
contacts={groupContacts}
|
contacts={groupContacts}
|
||||||
post={post}
|
post={post}
|
||||||
@ -256,7 +278,7 @@ export function GraphNotification(props: {
|
|||||||
return api.hark['read'](timebox, { graph: index });
|
return api.hark['read'](timebox, { graph: index });
|
||||||
}, [api, timebox, index, read]);
|
}, [api, timebox, index, read]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@ -271,7 +293,7 @@ return (
|
|||||||
description={desc}
|
description={desc}
|
||||||
associations={props.associations}
|
associations={props.associations}
|
||||||
/>
|
/>
|
||||||
<Box flexGrow={1} width="100%" pl={5} gridArea="main">
|
<Box flexGrow={1} width='100%' pl={5} gridArea='main'>
|
||||||
{_.map(contents, (content, idx) => (
|
{_.map(contents, (content, idx) => (
|
||||||
<GraphNode
|
<GraphNode
|
||||||
post={content}
|
post={content}
|
||||||
|
@ -48,21 +48,19 @@ export function Mention(props: {
|
|||||||
const group = props.group ?? { hidden: true };
|
const group = props.group ?? { hidden: true };
|
||||||
const [showOverlay, setShowOverlay] = useState(false);
|
const [showOverlay, setShowOverlay] = useState(false);
|
||||||
|
|
||||||
const toggleOverlay = useCallback(
|
const toggleOverlay = useCallback(() => {
|
||||||
() => {
|
setShowOverlay((value) => !value);
|
||||||
setShowOverlay(value => !value);
|
}, [showOverlay]);
|
||||||
},
|
|
||||||
[showOverlay]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position='relative' display='inline-block' cursor='pointer'>
|
<Box position='relative' display='inline-block' cursor='pointer'>
|
||||||
<Text
|
<Text
|
||||||
onClick={() => toggleOverlay()}
|
onClick={() => toggleOverlay()}
|
||||||
mx='2px'
|
mx={1}
|
||||||
px='2px'
|
px={1}
|
||||||
bg='washedBlue'
|
bg='washedBlue'
|
||||||
color='blue'
|
color='blue'
|
||||||
|
fontSize={showNickname ? 1 : 0}
|
||||||
mono={!showNickname}
|
mono={!showNickname}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
Loading…
Reference in New Issue
Block a user