Merge remote-tracking branch 'origin/release/next-js'

This commit is contained in:
Liam Fitzgerald 2021-04-13 16:00:03 +10:00
commit 59c1a7356e
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
24 changed files with 258 additions and 248 deletions

View File

@ -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=?]

View File

@ -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);
}

View File

@ -16,5 +16,5 @@ export function useCopy(copied: string, display: string) {
display,
]);
return { copyDisplay, doCopy };
return { copyDisplay, doCopy, didCopy };
}

View File

@ -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 };
}

View File

@ -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--/;

View File

@ -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,9 +450,6 @@ export const MessageAuthor = ({
return (
<Box display='flex' alignItems='flex-start' {...rest}>
<Box
onClick={() => {
setShowOverlay(true);
}}
height={24}
pr={2}
mt={'1px'}
@ -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}
<GraphContentWide
{...bind}
width="100%"
post={msg}
transcluded={transcluded}
api={api}
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>
</Box>
);
};

View File

@ -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

View File

@ -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}

View File

@ -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>

View File

@ -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,20 +102,17 @@ 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}
post={post}
showOurContact
/>
</Box>
</Box>
);
}

View File

@ -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'}
>

View File

@ -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,35 +40,18 @@ 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}
>
<ProfileOverlay ship={ship} api={api} display="inline">
<Text
onClick={() => toggleOverlay()}
marginLeft={first? 0 : 1}
marginRight={1}
px={1}
@ -80,6 +63,5 @@ export function Mention(props: {
{name}
</Text>
</ProfileOverlay>
</Box>
);
}

View File

@ -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'

View File

@ -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}
/>

View File

@ -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)}`;

View File

@ -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);

View File

@ -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"

View File

@ -54,6 +54,7 @@ function GroupFeed(props) {
return;
}
api.graph.getNewest(graphResource.ship, graphResource.name, 100);
api.hark.markCountAsRead(association, '/', 'post');
}, [graphPath]);
if (!graphPath) {

View File

@ -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>
);
}

View File

@ -151,6 +151,7 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
</StatelessAsyncButton>
</Col>
) : preview ? (
<>
<GroupSummary
metadata={preview.metadata}
memberCount={preview?.members}
@ -184,14 +185,16 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
</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

View File

@ -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';

View File

@ -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
}