mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 08:32:39 +03:00
Merge remote-tracking branch 'origin/release/next-js'
This commit is contained in:
commit
59c1a7356e
@ -22,8 +22,9 @@
|
||||
++ notification-kind
|
||||
^- (unit notif-kind:hark)
|
||||
=/ len (lent index.p.i)
|
||||
?: =(1 len) ~
|
||||
`[%post [(dec len) len] %none %children]
|
||||
=/ =mode:hark
|
||||
?:(=(1 len) %count %none)
|
||||
`[%post [(dec len) len] mode %children]
|
||||
::
|
||||
++ transform-add-nodes
|
||||
|= [=index =post =atom was-parent-modified=?]
|
||||
|
@ -31,6 +31,6 @@ export function getNotificationCount(
|
||||
): number {
|
||||
const unread = unreads.graph?.[path] || {};
|
||||
return Object.keys(unread)
|
||||
.map(index => unread[index]?.notifications || 0)
|
||||
.map(index => unread[index]?.notifications?.length || 0)
|
||||
.reduce(f.add, 0);
|
||||
}
|
||||
|
@ -16,5 +16,5 @@ export function useCopy(copied: string, display: string) {
|
||||
display,
|
||||
]);
|
||||
|
||||
return { copyDisplay, doCopy };
|
||||
return { copyDisplay, doCopy, didCopy };
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ export function distanceToBottom(el: HTMLElement) {
|
||||
|
||||
export function useLazyScroll(
|
||||
ref: RefObject<HTMLElement>,
|
||||
ready: boolean,
|
||||
margin: number,
|
||||
count: number,
|
||||
loadMore: () => Promise<boolean>
|
||||
@ -41,7 +42,7 @@ export function useLazyScroll(
|
||||
}, [count]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current || isDone) {
|
||||
if (!ref.current || isDone || !ready) {
|
||||
return;
|
||||
}
|
||||
const scroll = ref.current;
|
||||
@ -57,7 +58,7 @@ export function useLazyScroll(
|
||||
return () => {
|
||||
ref.current?.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
}, [ref?.current, count]);
|
||||
}, [ref?.current, count, ready]);
|
||||
|
||||
return { isDone, isLoading };
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import _ from 'lodash';
|
||||
import f, { compose, memoize } from 'lodash/fp';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
@ -400,11 +400,15 @@ interface useHoveringInterface {
|
||||
|
||||
export const useHovering = (): useHoveringInterface => {
|
||||
const [hovering, setHovering] = useState(false);
|
||||
const bind = {
|
||||
onMouseOver: () => setHovering(true),
|
||||
onMouseLeave: () => setHovering(false)
|
||||
};
|
||||
return { hovering, bind };
|
||||
const onMouseOver = useCallback(() => setHovering(true), [])
|
||||
const onMouseLeave = useCallback(() => setHovering(false), [])
|
||||
const bind = useMemo(() => ({
|
||||
onMouseOver,
|
||||
onMouseLeave,
|
||||
}), [onMouseLeave, onMouseOver]);
|
||||
|
||||
|
||||
return useMemo(() => ({ hovering, bind }), [hovering, bind]);
|
||||
};
|
||||
|
||||
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
||||
|
@ -29,8 +29,8 @@ import {
|
||||
Groups,
|
||||
Associations
|
||||
} from '~/types';
|
||||
import TextContent from './content/text';
|
||||
import CodeContent from './content/code';
|
||||
import TextContent from '../../../landscape/components/Graph/content/text';
|
||||
import CodeContent from '../../../landscape/components/Graph/content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
@ -42,8 +42,7 @@ import useContactState from '~/logic/state/contact';
|
||||
import { useIdlingState } from '~/logic/lib/idling';
|
||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||
import {useCopy} from '~/logic/lib/useCopy';
|
||||
import {PermalinkEmbed} from '../../permalinks/embed';
|
||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
||||
import {GraphContentWide} from '~/views/landscape/components/Graph/GraphContentWide';
|
||||
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
@ -396,12 +395,11 @@ export const MessageAuthor = ({
|
||||
msg.author !== window.ship) &&
|
||||
`~${msg.author}` in contacts
|
||||
? contacts[`~${msg.author}`]
|
||||
: false;
|
||||
: undefined;
|
||||
|
||||
const showNickname = useShowNickname(contact);
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const shipName = showNickname ? contact.nickname : cite(msg.author);
|
||||
const copyNotice = 'Copied';
|
||||
const shipName = showNickname && contact?.nickname || cite(msg.author) || `~${msg.author}`;
|
||||
const color = contact
|
||||
? `#${uxToHex(contact.color)}`
|
||||
: dark
|
||||
@ -412,28 +410,10 @@ export const MessageAuthor = ({
|
||||
: dark
|
||||
? 'mix-blend-diff'
|
||||
: 'mix-blend-darken';
|
||||
const [displayName, setDisplayName] = useState(shipName);
|
||||
const [nameMono, setNameMono] = useState(showNickname ? false : true);
|
||||
|
||||
const { copyDisplay, doCopy, didCopy } = useCopy(`~${msg.author}`, shipName);
|
||||
const { hovering, bind } = useHovering();
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
|
||||
const toggleOverlay = () => {
|
||||
setShowOverlay((value) => !value);
|
||||
};
|
||||
|
||||
const showCopyNotice = () => {
|
||||
setDisplayName(copyNotice);
|
||||
setNameMono(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const resetDisplay = () => {
|
||||
setDisplayName(shipName);
|
||||
setNameMono(showNickname ? false : true);
|
||||
};
|
||||
const timer = setTimeout(() => resetDisplay(), 800);
|
||||
return () => clearTimeout(timer);
|
||||
}, [shipName, displayName]);
|
||||
const nameMono = !(showNickname || didCopy);
|
||||
|
||||
const img =
|
||||
contact?.avatar && !hideAvatars ? (
|
||||
@ -470,10 +450,7 @@ export const MessageAuthor = ({
|
||||
return (
|
||||
<Box display='flex' alignItems='flex-start' {...rest}>
|
||||
<Box
|
||||
onClick={() => {
|
||||
setShowOverlay(true);
|
||||
}}
|
||||
height={24}
|
||||
height={24}
|
||||
pr={2}
|
||||
mt={'1px'}
|
||||
pl={'12px'}
|
||||
@ -500,13 +477,10 @@ export const MessageAuthor = ({
|
||||
mono={nameMono}
|
||||
fontWeight={nameMono ? '400' : '500'}
|
||||
cursor='pointer'
|
||||
onClick={() => {
|
||||
writeText(`~${msg.author}`);
|
||||
showCopyNotice();
|
||||
}}
|
||||
onClick={doCopy}
|
||||
title={`~${msg.author}`}
|
||||
>
|
||||
{displayName}
|
||||
{copyDisplay}
|
||||
</Text>
|
||||
<Text flexShrink={0} fontSize={0} gray>
|
||||
{timestamp}
|
||||
@ -538,7 +512,6 @@ export const Message = ({
|
||||
...rest
|
||||
}) => {
|
||||
const { hovering, bind } = useHovering();
|
||||
const contacts = useContactState((state) => state.contacts);
|
||||
return (
|
||||
<Box width="100%" position='relative' {...rest}>
|
||||
{timestampHover ? (
|
||||
@ -557,66 +530,14 @@ export const Message = ({
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Box width="100%" {...bind}>
|
||||
{msg.contents.map((content, i) => {
|
||||
switch (Object.keys(content)[0]) {
|
||||
case 'text':
|
||||
return (
|
||||
<TextContent
|
||||
key={i}
|
||||
api={api}
|
||||
fontSize={1}
|
||||
lineHeight={'20px'}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
case 'code':
|
||||
return <CodeContent key={i} content={content} />;
|
||||
case 'reference':
|
||||
const { link } = referenceToPermalink(content);
|
||||
return (
|
||||
<PermalinkEmbed
|
||||
link={link}
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
showOurContact={showOurContact}
|
||||
/>
|
||||
);
|
||||
case 'url':
|
||||
return (
|
||||
<Box
|
||||
key={i}
|
||||
flexShrink={0}
|
||||
fontSize={1}
|
||||
lineHeight='20px'
|
||||
color='black'
|
||||
width="fit-content"
|
||||
maxWidth="min(500px, 100%)"
|
||||
>
|
||||
<RemoteContent
|
||||
key={content.url}
|
||||
url={content.url}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
case 'mention':
|
||||
const first = (i) => i === 0;
|
||||
return (
|
||||
<Mention
|
||||
key={i}
|
||||
first={first(i)}
|
||||
group={group}
|
||||
scrollWindow={scrollWindow}
|
||||
ship={content.mention}
|
||||
contact={contacts?.[`~${content.mention}`]}
|
||||
api={api}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Box>
|
||||
<GraphContentWide
|
||||
{...bind}
|
||||
width="100%"
|
||||
post={msg}
|
||||
transcluded={transcluded}
|
||||
api={api}
|
||||
showOurContact={showOurContact}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -25,6 +25,7 @@ import { Invites } from './invites';
|
||||
import { useLazyScroll } from '~/logic/lib/useLazyScroll';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useInviteState from '~/logic/state/invite';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
type DatedTimebox = [BigInteger, Timebox];
|
||||
|
||||
@ -64,6 +65,10 @@ export default function Inbox(props: {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const ready = useHarkState(
|
||||
s => Object.keys(s.unreads.graph).length > 0
|
||||
);
|
||||
|
||||
const notificationState = useHarkState(state => state.notifications);
|
||||
const archivedNotifications = useHarkState(state => state.archivedNotifications);
|
||||
|
||||
@ -109,6 +114,7 @@ export default function Inbox(props: {
|
||||
|
||||
const { isDone, isLoading } = useLazyScroll(
|
||||
scrollRef,
|
||||
ready,
|
||||
0.2,
|
||||
_.flatten(notifications).length,
|
||||
loadMore
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import { Association } from '@urbit/api';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { RouteComponentProps, useLocation } from 'react-router-dom';
|
||||
import { NotebookRoutes } from './components/NotebookRoutes';
|
||||
|
||||
type PublishResourceProps = StoreState & {
|
||||
@ -17,9 +17,21 @@ export function PublishResource(props: PublishResourceProps) {
|
||||
const { association, api, baseUrl, notebooks } = props;
|
||||
const rid = association.resource;
|
||||
const [, , ship, book] = rid.split('/');
|
||||
const location = useLocation();
|
||||
const scrollRef = useRef(null)
|
||||
useEffect(() => {
|
||||
const search = new URLSearchParams(location.search);
|
||||
if(search.has('selected') || search.has('edit') || !scrollRef.current) {
|
||||
return;
|
||||
}
|
||||
scrollRef.current.scrollTop = 0;
|
||||
|
||||
|
||||
|
||||
}, [location])
|
||||
|
||||
return (
|
||||
<Box height="100%" width="100%" overflowY="auto">
|
||||
<Box ref={scrollRef} height="100%" width="100%" overflowY="auto">
|
||||
<NotebookRoutes
|
||||
api={api}
|
||||
ship={ship}
|
||||
|
@ -94,12 +94,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
|
||||
const { doCopy, copyDisplay } = useCopy(permalink, 'Copy Link');
|
||||
|
||||
const windowRef = React.useRef(null);
|
||||
useEffect(() => {
|
||||
if (windowRef.current && !query.has('selected')) {
|
||||
windowRef.current.parentElement.scrollTop = 0;
|
||||
}
|
||||
}, [note, windowRef]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -112,7 +106,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
width="100%"
|
||||
gridRowGap={4}
|
||||
mx="auto"
|
||||
ref={windowRef}
|
||||
>
|
||||
<Link to={rootUrl}>
|
||||
<Text>{'<- Notebook Index'}</Text>
|
||||
|
@ -15,6 +15,7 @@ import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||
import {useCopy} from '~/logic/lib/useCopy';
|
||||
import { getPermalinkForGraph} from '~/logic/lib/permalinks';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import {GraphContentWide} from '../landscape/components/Graph/GraphContentWide';
|
||||
|
||||
const ClickBox = styled(Box)`
|
||||
cursor: pointer;
|
||||
@ -101,19 +102,16 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
||||
</Row>
|
||||
</Author>
|
||||
</Row>
|
||||
<Box
|
||||
<GraphContentWide
|
||||
borderRadius="1"
|
||||
p="1"
|
||||
mb="1"
|
||||
backgroundColor={props.highlighted ? 'washedBlue' : 'white'}
|
||||
>
|
||||
<MentionText
|
||||
transcluded={0}
|
||||
api={api}
|
||||
group={group}
|
||||
content={post?.contents}
|
||||
/>
|
||||
</Box>
|
||||
transcluded={0}
|
||||
api={api}
|
||||
post={post}
|
||||
showOurContact
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState, useLayoutEffect, ReactElement } from 'react';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Box, Text, Row, Col } from '@tlon/indigo-react';
|
||||
import { Associations, Groups } from '@urbit/api';
|
||||
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
||||
import { JoinGroup } from '../landscape/components/JoinGroup';
|
||||
@ -23,27 +22,12 @@ export function GroupLink(
|
||||
const name = resource.slice(6);
|
||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const { save, restore } = useVirtual();
|
||||
const history = useHistory();
|
||||
const joined = resource in associations.groups;
|
||||
|
||||
const { save, restore } = useVirtual();
|
||||
|
||||
const { modal, showModal } = useModal({
|
||||
modal:
|
||||
joined && preview ? (
|
||||
<Box width="fit-content" p="4">
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview.members}
|
||||
channelCount={preview?.['channel-count']}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<JoinGroup
|
||||
api={api}
|
||||
autojoin={name}
|
||||
/>
|
||||
)
|
||||
modal: <JoinGroup api={api} autojoin={name} />
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -72,7 +56,9 @@ export function GroupLink(
|
||||
alignItems="center"
|
||||
py="2"
|
||||
pr="2"
|
||||
onClick={showModal}
|
||||
onClick={
|
||||
joined ? () => history.push(`/~landscape/ship/${name}`) : showModal
|
||||
}
|
||||
cursor='pointer'
|
||||
opacity={preview ? '1' : '0.6'}
|
||||
>
|
||||
|
@ -6,7 +6,7 @@ import RichText from '~/views/components/RichText';
|
||||
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
|
||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useContactState, {useContact} from '~/logic/state/contact';
|
||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
|
||||
@ -40,46 +40,28 @@ export function MentionText(props: MentionTextProps) {
|
||||
}
|
||||
|
||||
export function Mention(props: {
|
||||
contact: Contact;
|
||||
group: Group;
|
||||
scrollWindow?: HTMLElement;
|
||||
ship: string;
|
||||
first?: Boolean;
|
||||
api: any;
|
||||
}) {
|
||||
const { ship, scrollWindow, first, api, ...rest } = props;
|
||||
let { contact } = props;
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
contact = contact?.color ? contact : contacts?.[`~${ship}`];
|
||||
const history = useHistory();
|
||||
const { ship, first, api, ...rest } = props;
|
||||
const contact = useContact(ship);
|
||||
const showNickname = useShowNickname(contact);
|
||||
const name = showNickname ? contact?.nickname : cite(ship);
|
||||
const group = props.group ?? { hidden: true };
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
|
||||
const toggleOverlay = useCallback(() => {
|
||||
setShowOverlay((value) => !value);
|
||||
}, [showOverlay]);
|
||||
|
||||
return (
|
||||
<Box position='relative' display='inline-block' cursor='pointer' {...rest}>
|
||||
<ProfileOverlay
|
||||
ship={ship}
|
||||
api={api}
|
||||
>
|
||||
<Text
|
||||
onClick={() => toggleOverlay()}
|
||||
marginLeft={first? 0 : 1}
|
||||
marginRight={1}
|
||||
px={1}
|
||||
bg='washedBlue'
|
||||
color='blue'
|
||||
fontSize={showNickname ? 1 : 0}
|
||||
mono={!showNickname}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
</ProfileOverlay>
|
||||
</Box>
|
||||
<ProfileOverlay ship={ship} api={api} display="inline">
|
||||
<Text
|
||||
marginLeft={first? 0 : 1}
|
||||
marginRight={1}
|
||||
px={1}
|
||||
bg='washedBlue'
|
||||
color='blue'
|
||||
fontSize={showNickname ? 1 : 0}
|
||||
mono={!showNickname}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
</ProfileOverlay>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { PureComponent, useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
||||
import React, { PureComponent, useCallback, useEffect, useRef, useState, useMemo, ReactNode } from 'react';
|
||||
import { Contact, Group, uxToHex } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
@ -40,6 +40,7 @@ const FixedOverlay = styled(Col)`
|
||||
type ProfileOverlayProps = BoxProps & {
|
||||
ship: string;
|
||||
api: any;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ProfileOverlay = (props: ProfileOverlayProps) => {
|
||||
@ -150,6 +151,16 @@ const ProfileOverlay = (props: ProfileOverlayProps) => {
|
||||
padding={3}
|
||||
justifyContent='center'
|
||||
>
|
||||
<Row color='black' padding={3} position='absolute' top={0} left={0}>
|
||||
{!isOwn && (
|
||||
<Icon
|
||||
icon='Chat'
|
||||
size={16}
|
||||
cursor='pointer'
|
||||
onClick={() => history.push(`/~landscape/dm/${ship}`)}
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
<Box
|
||||
alignSelf='center'
|
||||
height='72px'
|
||||
|
@ -135,7 +135,7 @@ return;
|
||||
return (
|
||||
<Row
|
||||
alignItems="center"
|
||||
maxWidth="20rem"
|
||||
maxWidth="min(100%, 20rem)"
|
||||
gapX="1" borderRadius="1" backgroundColor="washedGray">
|
||||
{ textOnly && (<Icon ml="2" display="block" icon="ArrowExternal" />)}
|
||||
<BaseAnchor
|
||||
@ -149,8 +149,8 @@ return;
|
||||
textOverflow="ellipsis"
|
||||
minWidth="0"
|
||||
width={textOnly ? "calc(100% - 24px)" : "fit-content"}
|
||||
maxWidth="min(500px, 100%)"
|
||||
style={{ color: 'inherit', textDecoration: 'none', ...style }}
|
||||
className="word-break-all"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@ -218,6 +218,8 @@ return;
|
||||
style={style}
|
||||
onLoad={onLoad}
|
||||
objectFit="contain"
|
||||
height="100%"
|
||||
width="100%"
|
||||
{...audioProps}
|
||||
{...props}
|
||||
/>
|
||||
@ -237,6 +239,8 @@ return;
|
||||
style={style}
|
||||
onLoad={onLoad}
|
||||
objectFit="contain"
|
||||
height="100%"
|
||||
width="100%"
|
||||
{...videoProps}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -157,7 +157,7 @@ export function ShipSearch<I extends string, V extends Value<I>>(
|
||||
setFieldValue(name(), newValue);
|
||||
};
|
||||
|
||||
const error = _.compact(errors[id] as string[]);
|
||||
const error = _.compact((_.isString(errors[id]) ? [errors[id]] : errors[id] as string[]) as any);
|
||||
|
||||
const isExact = useCallback((s: string) => {
|
||||
const ship = `~${deSig(s)}`;
|
||||
|
@ -0,0 +1,82 @@
|
||||
import React from "react";
|
||||
import { Post, ReferenceContent } from "@urbit/api";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import TextContent from "./content/text";
|
||||
import CodeContent from "./content/code";
|
||||
import RemoteContent from "~/views/components/RemoteContent";
|
||||
import { Mention } from "~/views/components/MentionText";
|
||||
import { PermalinkEmbed } from "~/views/apps/permalinks/embed";
|
||||
import { referenceToPermalink } from "~/logic/lib/permalinks";
|
||||
import { PropFunc } from "~/types";
|
||||
|
||||
function GraphContentWideInner(
|
||||
props: {
|
||||
transcluded?: number;
|
||||
post: Post;
|
||||
api: GlobalApi;
|
||||
showOurContact: boolean;
|
||||
} & PropFunc<typeof Box>
|
||||
) {
|
||||
const { post, transcluded = 0, showOurContact, api, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Box {...rest}>
|
||||
{post.contents.map((content, i) => {
|
||||
switch (Object.keys(content)[0]) {
|
||||
case "text":
|
||||
return (
|
||||
<TextContent
|
||||
key={i}
|
||||
api={api}
|
||||
fontSize={1}
|
||||
lineHeight={"20px"}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
case "code":
|
||||
return <CodeContent key={i} content={content} />;
|
||||
case "reference":
|
||||
const { link } = referenceToPermalink(content as ReferenceContent);
|
||||
return (
|
||||
<PermalinkEmbed
|
||||
link={link}
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
showOurContact={showOurContact}
|
||||
/>
|
||||
);
|
||||
case "url":
|
||||
return (
|
||||
<Box
|
||||
key={i}
|
||||
flexShrink={0}
|
||||
fontSize={1}
|
||||
lineHeight="20px"
|
||||
color="black"
|
||||
width="fit-content"
|
||||
maxWidth="min(500px, 100%)"
|
||||
>
|
||||
<RemoteContent key={content.url} url={content.url} />
|
||||
</Box>
|
||||
);
|
||||
case "mention":
|
||||
const first = (i) => i === 0;
|
||||
return (
|
||||
<Mention
|
||||
key={i}
|
||||
first={first(i)}
|
||||
ship={content.mention}
|
||||
api={api}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export const GraphContentWide = React.memo(GraphContentWideInner);
|
@ -23,7 +23,7 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc<typeof Col>): R
|
||||
anchorRef
|
||||
);
|
||||
return (
|
||||
<Col {...rest} ref={anchorRef} gapY="4">
|
||||
<Col {...rest} ref={anchorRef} gapY="4" maxWidth={['100%', '288px']}>
|
||||
<Row gapX="2" width="100%">
|
||||
<MetadataIcon
|
||||
width="40px"
|
||||
|
@ -54,6 +54,7 @@ function GroupFeed(props) {
|
||||
return;
|
||||
}
|
||||
api.graph.getNewest(graphResource.ship, graphResource.name, 100);
|
||||
api.hark.markCountAsRead(association, '/', 'post');
|
||||
}, [graphPath]);
|
||||
|
||||
if (!graphPath) {
|
||||
|
@ -1,30 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import { MentionText } from '~/views/components/MentionText';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import { Col, Box } from '@tlon/indigo-react';
|
||||
import { GraphContentWide } from "~/views/landscape/components/Graph/GraphContentWide";
|
||||
import styled from 'styled-components';
|
||||
|
||||
const TruncatedBox = styled(Col)`
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: ${p => p.truncate ?? 'unset'};
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
`;
|
||||
|
||||
export function PostContent(props) {
|
||||
const { post, isParent, api, isReply } = props;
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
|
||||
return (
|
||||
<Col
|
||||
<TruncatedBox
|
||||
display="-webkit-box"
|
||||
width="100%"
|
||||
pl="2"
|
||||
pr="2"
|
||||
pb={isParent || isReply ? "0" : "2"}
|
||||
maxHeight={ isParent ? "none" : "300px" }
|
||||
px="2"
|
||||
pb="2"
|
||||
truncate={isParent ? null : 8}
|
||||
textOverflow="ellipsis"
|
||||
overflow="hidden"
|
||||
display="inline-block">
|
||||
<MentionText
|
||||
contacts={contacts}
|
||||
content={post.contents}
|
||||
api={api}
|
||||
>
|
||||
<GraphContentWide
|
||||
transcluded={0}
|
||||
post={post}
|
||||
api={api}
|
||||
showOurContact
|
||||
/>
|
||||
</Col>
|
||||
</TruncatedBox>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -151,47 +151,50 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
||||
</StatelessAsyncButton>
|
||||
</Col>
|
||||
) : preview ? (
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview?.members}
|
||||
channelCount={preview?.['channel-count']}
|
||||
>
|
||||
{ Object.keys(preview.channels).length > 0 && (
|
||||
<Col
|
||||
gapY="2"
|
||||
p="2"
|
||||
borderRadius="2"
|
||||
border="1"
|
||||
borderColor="washedGray"
|
||||
bg="washedBlue"
|
||||
maxHeight="300px"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Text gray fontSize="1">
|
||||
Channels
|
||||
</Text>
|
||||
<Box width="100%" flexShrink="0">
|
||||
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||
<Row width="100%">
|
||||
<Icon
|
||||
mr="2"
|
||||
color="blue"
|
||||
icon={getModuleIcon(metadata?.config?.graph) as any}
|
||||
/>
|
||||
<Text color="blue">{metadata.title} </Text>
|
||||
</Row>
|
||||
))}
|
||||
</Box>
|
||||
</Col>
|
||||
)}
|
||||
<>
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview?.members}
|
||||
channelCount={preview?.['channel-count']}
|
||||
>
|
||||
{ Object.keys(preview.channels).length > 0 && (
|
||||
<Col
|
||||
gapY="2"
|
||||
p="2"
|
||||
borderRadius="2"
|
||||
border="1"
|
||||
borderColor="washedGray"
|
||||
bg="washedBlue"
|
||||
maxHeight="300px"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Text gray fontSize="1">
|
||||
Channels
|
||||
</Text>
|
||||
<Box width="100%" flexShrink="0">
|
||||
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||
<Row width="100%">
|
||||
<Icon
|
||||
mr="2"
|
||||
color="blue"
|
||||
icon={getModuleIcon(metadata?.config?.graph) as any}
|
||||
/>
|
||||
<Text color="blue">{metadata.title} </Text>
|
||||
</Row>
|
||||
))}
|
||||
</Box>
|
||||
</Col>
|
||||
)}
|
||||
</GroupSummary>
|
||||
<StatelessAsyncButton
|
||||
marginTop={3}
|
||||
primary
|
||||
name="join"
|
||||
onClick={() => onConfirm(preview.group)}
|
||||
>
|
||||
Join {preview.metadata.title}
|
||||
</StatelessAsyncButton>
|
||||
</GroupSummary>
|
||||
</>
|
||||
) : (
|
||||
<Col width="100%" gapY="4">
|
||||
<Formik
|
||||
|
@ -86,11 +86,7 @@ export function SidebarItem(props: {
|
||||
let color = 'lightGray';
|
||||
|
||||
if (isSynced) {
|
||||
if (hasUnread || hasNotification) {
|
||||
color = 'black';
|
||||
} else {
|
||||
color = 'gray';
|
||||
}
|
||||
}
|
||||
|
||||
const fontWeight = (hasUnread || hasNotification) ? '500' : 'normal';
|
||||
|
@ -24,6 +24,7 @@ import { Workspace } from '~/types/workspace';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import {IS_SAFARI} from '~/logic/lib/platform';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
|
||||
export function SidebarListHeader(props: {
|
||||
api: GlobalApi;
|
||||
@ -54,15 +55,15 @@ export function SidebarListHeader(props: {
|
||||
|
||||
const noun = (props.workspace?.type === 'messages') ? 'Messages' : 'Channels';
|
||||
|
||||
const isFeedEnabled =
|
||||
metadata &&
|
||||
metadata.config &&
|
||||
metadata.config.group &&
|
||||
'resource' in metadata.config.group;
|
||||
const feedPath = metadata?.config?.group?.resource;
|
||||
|
||||
const unreadCount = useHarkState(
|
||||
s => s.unreads?.graph?.[feedPath ?? ""]?.["/"]?.unreads as number ?? 0
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{( isFeedEnabled ) ? (
|
||||
{( !!feedPath) ? (
|
||||
<Row
|
||||
flexShrink="0"
|
||||
alignItems="center"
|
||||
@ -90,6 +91,9 @@ export function SidebarListHeader(props: {
|
||||
<Text>
|
||||
Group Feed
|
||||
</Text>
|
||||
<Text mr="1" color="blue">
|
||||
{ unreadCount > 0 && unreadCount}
|
||||
</Text>
|
||||
</Row>
|
||||
) : null
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user