mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 16:51:42 +03:00
chat-fe: pretty group joining link
This commit is contained in:
parent
afb0424efd
commit
303dc6b3bd
@ -11,6 +11,7 @@ import React, {
|
|||||||
import { Box } from "@tlon/indigo-react";
|
import { Box } from "@tlon/indigo-react";
|
||||||
import { useOutsideClick } from "./useOutsideClick";
|
import { useOutsideClick } from "./useOutsideClick";
|
||||||
import { ModalOverlay } from "~/views/components/ModalOverlay";
|
import { ModalOverlay } from "~/views/components/ModalOverlay";
|
||||||
|
import {Portal} from "~/views/components/Portal";
|
||||||
|
|
||||||
type ModalFunc = (dismiss: () => void) => JSX.Element;
|
type ModalFunc = (dismiss: () => void) => JSX.Element;
|
||||||
interface UseModalProps {
|
interface UseModalProps {
|
||||||
@ -49,6 +50,7 @@ export function useModal(props: UseModalProps): UseModalResult {
|
|||||||
const modal = useMemo(
|
const modal = useMemo(
|
||||||
() =>
|
() =>
|
||||||
!inner ? null : (
|
!inner ? null : (
|
||||||
|
<Portal>
|
||||||
<ModalOverlay
|
<ModalOverlay
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
maxWidth="500px"
|
maxWidth="500px"
|
||||||
@ -65,6 +67,7 @@ export function useModal(props: UseModalProps): UseModalResult {
|
|||||||
>
|
>
|
||||||
{inner}
|
{inner}
|
||||||
</ModalOverlay>
|
</ModalOverlay>
|
||||||
|
</Portal>
|
||||||
),
|
),
|
||||||
[inner, dismiss]
|
[inner, dismiss]
|
||||||
);
|
);
|
||||||
|
@ -111,6 +111,8 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
envelopes={[]}
|
envelopes={[]}
|
||||||
contacts={contacts}
|
contacts={contacts}
|
||||||
association={props.association}
|
association={props.association}
|
||||||
|
associations={props.associations}
|
||||||
|
groups={props.groups}
|
||||||
group={group}
|
group={group}
|
||||||
ship={owner}
|
ship={owner}
|
||||||
station={station}
|
station={station}
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
useShowNickname,
|
useShowNickname,
|
||||||
useHovering
|
useHovering
|
||||||
} from '~/logic/lib/util';
|
} from '~/logic/lib/util';
|
||||||
import { Group, Association, Contacts, Post } from '~/types';
|
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';
|
||||||
@ -108,7 +108,9 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
history,
|
history,
|
||||||
api,
|
api,
|
||||||
highlighted,
|
highlighted,
|
||||||
fontSize
|
fontSize,
|
||||||
|
groups,
|
||||||
|
associations
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const renderSigil = Boolean(
|
const renderSigil = Boolean(
|
||||||
@ -145,7 +147,9 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
api,
|
api,
|
||||||
scrollWindow,
|
scrollWindow,
|
||||||
highlighted,
|
highlighted,
|
||||||
fontSize
|
fontSize,
|
||||||
|
associations,
|
||||||
|
groups,
|
||||||
};
|
};
|
||||||
|
|
||||||
const unreadContainerStyle = {
|
const unreadContainerStyle = {
|
||||||
@ -206,6 +210,8 @@ interface MessageProps {
|
|||||||
style: any;
|
style: any;
|
||||||
measure(element): void;
|
measure(element): void;
|
||||||
scrollWindow: HTMLDivElement;
|
scrollWindow: HTMLDivElement;
|
||||||
|
associations: Associations;
|
||||||
|
groups: Groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageWithSigil = (props) => {
|
export const MessageWithSigil = (props) => {
|
||||||
@ -214,6 +220,8 @@ export const MessageWithSigil = (props) => {
|
|||||||
timestamp,
|
timestamp,
|
||||||
contacts,
|
contacts,
|
||||||
association,
|
association,
|
||||||
|
associations,
|
||||||
|
groups,
|
||||||
group,
|
group,
|
||||||
measure,
|
measure,
|
||||||
api,
|
api,
|
||||||
@ -355,6 +363,9 @@ export const MessageWithSigil = (props) => {
|
|||||||
scrollWindow={scrollWindow}
|
scrollWindow={scrollWindow}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
group={group}
|
group={group}
|
||||||
|
api={api}
|
||||||
|
associations={associations}
|
||||||
|
groups={groups}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ContentBox>
|
</ContentBox>
|
||||||
@ -375,6 +386,9 @@ export const MessageWithoutSigil = ({
|
|||||||
msg,
|
msg,
|
||||||
measure,
|
measure,
|
||||||
group,
|
group,
|
||||||
|
api,
|
||||||
|
associations,
|
||||||
|
groups,
|
||||||
scrollWindow
|
scrollWindow
|
||||||
}) => {
|
}) => {
|
||||||
const { hovering, bind } = useHovering();
|
const { hovering, bind } = useHovering();
|
||||||
@ -409,6 +423,9 @@ export const MessageWithoutSigil = ({
|
|||||||
group={group}
|
group={group}
|
||||||
measure={measure}
|
measure={measure}
|
||||||
scrollWindow={scrollWindow}
|
scrollWindow={scrollWindow}
|
||||||
|
groups={groups}
|
||||||
|
associations={associations}
|
||||||
|
api={api}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ContentBox>
|
</ContentBox>
|
||||||
@ -419,6 +436,9 @@ export const MessageWithoutSigil = ({
|
|||||||
export const MessageContent = ({
|
export const MessageContent = ({
|
||||||
content,
|
content,
|
||||||
contacts,
|
contacts,
|
||||||
|
api,
|
||||||
|
associations,
|
||||||
|
groups,
|
||||||
measure,
|
measure,
|
||||||
scrollWindow,
|
scrollWindow,
|
||||||
fontSize,
|
fontSize,
|
||||||
@ -461,7 +481,15 @@ export const MessageContent = ({
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
} else if ('text' in content) {
|
} else if ('text' in content) {
|
||||||
return <TextContent fontSize={fontSize} content={content} />;
|
return (
|
||||||
|
<TextContent
|
||||||
|
associations={associations}
|
||||||
|
groups={groups}
|
||||||
|
measure={measure}
|
||||||
|
api={api}
|
||||||
|
fontSize={fontSize}
|
||||||
|
content={content}
|
||||||
|
/>);
|
||||||
} else if ('mention' in content) {
|
} else if ('mention' in content) {
|
||||||
return (
|
return (
|
||||||
<Mention
|
<Mention
|
||||||
|
@ -6,8 +6,8 @@ import bigInt, { BigInteger } from 'big-integer';
|
|||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { Patp, Path } from "~/types/noun";
|
import { Patp, Path } from "~/types/noun";
|
||||||
import { Contacts } from "~/types/contact-update";
|
import { Contacts } from "~/types/contact-update";
|
||||||
import { Association } from "~/types/metadata-update";
|
import { Association, Associations } from "~/types/metadata-update";
|
||||||
import { Group } from "~/types/group-update";
|
import { Group, Groups } from "~/types/group-update";
|
||||||
import { Envelope, IMessage } from "~/types/chat-update";
|
import { Envelope, IMessage } from "~/types/chat-update";
|
||||||
import { Graph } from "~/types";
|
import { Graph } from "~/types";
|
||||||
|
|
||||||
@ -39,6 +39,8 @@ type ChatWindowProps = RouteComponentProps<{
|
|||||||
station: any;
|
station: any;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
scrollTo?: number;
|
scrollTo?: number;
|
||||||
|
associations: Associations;
|
||||||
|
groups: Groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatWindowState {
|
interface ChatWindowState {
|
||||||
@ -247,13 +249,15 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
contacts,
|
contacts,
|
||||||
mailboxSize,
|
mailboxSize,
|
||||||
graph,
|
graph,
|
||||||
history
|
history,
|
||||||
|
groups,
|
||||||
|
associations
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const unreadMarkerRef = this.unreadMarkerRef;
|
const unreadMarkerRef = this.unreadMarkerRef;
|
||||||
|
|
||||||
|
|
||||||
const messageProps = { association, group, contacts, unreadMarkerRef, history, api };
|
const messageProps = { 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];
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component, useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
||||||
import RemarkBreaks from 'remark-breaks';
|
import RemarkBreaks from 'remark-breaks';
|
||||||
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";
|
||||||
|
|
||||||
const DISABLED_BLOCK_TOKENS = [
|
const DISABLED_BLOCK_TOKENS = [
|
||||||
'indentedCode',
|
'indentedCode',
|
||||||
@ -73,28 +74,29 @@ const MessageMarkdown = React.memo(props => (
|
|||||||
plugins={[RemarkBreaks]} />
|
plugins={[RemarkBreaks]} />
|
||||||
));
|
));
|
||||||
|
|
||||||
|
export default function TextContent(props) {
|
||||||
export default class TextContent extends Component {
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { props } = this;
|
|
||||||
const content = props.content;
|
const content = props.content;
|
||||||
|
|
||||||
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-])+([/-])?)+/
|
||||||
);
|
);
|
||||||
if ((group !== null) // matched possible chatroom
|
const isGroupLink = ((group !== null) // matched possible chatroom
|
||||||
&& (group[2].length > 2) // possible ship?
|
&& (group[2].length > 2) // possible ship?
|
||||||
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
&& (urbitOb.isValidPatp(group[2]) // valid patp?
|
||||||
&& (group[0] === content.text))) { // entire message is room name?
|
&& (group[0] === content.text))) // entire message is room name?
|
||||||
|
|
||||||
|
console.log(isGroupLink);
|
||||||
|
|
||||||
|
if(isGroupLink) {
|
||||||
|
const resource = `/ship/${content.text}`;
|
||||||
return (
|
return (
|
||||||
<Text mx="2px" flexShrink={0} fontSize={props.fontSize ? props.fontSize : '14px'} color='black' lineHeight="tall">
|
<GroupLink
|
||||||
<Link
|
measure={props.measure}
|
||||||
className="bb b--black b--white-d mono"
|
resource={resource}
|
||||||
to={'/~landscape/join/' + group.input}>
|
api={props.api}
|
||||||
{content.text}
|
associations={props.associations}
|
||||||
</Link>
|
groups={props.groups}
|
||||||
</Text>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
@ -104,4 +106,3 @@ export default class TextContent extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
75
pkg/interface/src/views/components/GroupLink.tsx
Normal file
75
pkg/interface/src/views/components/GroupLink.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import React, { useEffect, useState, useLayoutEffect } from "react";
|
||||||
|
|
||||||
|
import { Box, Text, Row, Button, Action } from "@tlon/indigo-react";
|
||||||
|
import GlobalApi from "~/logic/api/global";
|
||||||
|
import { Associations, Groups } from "~/types";
|
||||||
|
import { MetadataIcon } from "../landscape/components/MetadataIcon";
|
||||||
|
import {JoinGroup} from "../landscape/components/JoinGroup";
|
||||||
|
import {useModal} from "~/logic/lib/useModal";
|
||||||
|
|
||||||
|
export function GroupLink(props: {
|
||||||
|
api: GlobalApi;
|
||||||
|
resource: string;
|
||||||
|
associations: Associations;
|
||||||
|
groups: Groups;
|
||||||
|
measure: () => void;
|
||||||
|
}) {
|
||||||
|
const { resource, api, measure } = props;
|
||||||
|
const name = resource.slice(6);
|
||||||
|
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||||
|
|
||||||
|
const { modal, showModal } = useModal({
|
||||||
|
modal: (
|
||||||
|
<JoinGroup
|
||||||
|
groups={props.groups}
|
||||||
|
associations={props.associations}
|
||||||
|
api={api}
|
||||||
|
autojoin={name}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const joined = resource in props.associations.groups;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setPreview(await api.metadata.preview(resource));
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setPreview(null);
|
||||||
|
};
|
||||||
|
}, [resource]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
measure();
|
||||||
|
}, [preview]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{modal}
|
||||||
|
<Row
|
||||||
|
width="fit-content"
|
||||||
|
flexShrink={1}
|
||||||
|
alignItems="center"
|
||||||
|
border="1"
|
||||||
|
borderColor="lightGray"
|
||||||
|
borderRadius="2"
|
||||||
|
py="2"
|
||||||
|
px="2"
|
||||||
|
>
|
||||||
|
{preview ? (
|
||||||
|
<>
|
||||||
|
<MetadataIcon height="4" width="4" metadata={preview.metadata} />
|
||||||
|
<Text ml="2" fontWeight="medium">{preview.metadata.title}</Text>
|
||||||
|
<Button disabled={joined} onClick={showModal} ml="4" primary>
|
||||||
|
{joined ? "Joined" : "Join"}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Text mono>{name}</Text>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -45,6 +45,7 @@ interface JoinGroupProps {
|
|||||||
groups: Groups;
|
groups: Groups;
|
||||||
associations: Associations;
|
associations: Associations;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
|
autojoin?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Autojoin(props: { autojoin: string | null }) {
|
function Autojoin(props: { autojoin: string | null }) {
|
||||||
|
Loading…
Reference in New Issue
Block a user