diff --git a/pkg/arvo/mar/graph/validator/post.hoon b/pkg/arvo/mar/graph/validator/post.hoon index 208890fcb..a12e62e72 100644 --- a/pkg/arvo/mar/graph/validator/post.hoon +++ b/pkg/arvo/mar/graph/validator/post.hoon @@ -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=?] diff --git a/pkg/interface/src/logic/lib/hark.ts b/pkg/interface/src/logic/lib/hark.ts index b65a09603..54dd37e09 100644 --- a/pkg/interface/src/logic/lib/hark.ts +++ b/pkg/interface/src/logic/lib/hark.ts @@ -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); } diff --git a/pkg/interface/src/logic/lib/useCopy.ts b/pkg/interface/src/logic/lib/useCopy.ts index f0f8ced44..c214a1690 100644 --- a/pkg/interface/src/logic/lib/useCopy.ts +++ b/pkg/interface/src/logic/lib/useCopy.ts @@ -16,5 +16,5 @@ export function useCopy(copied: string, display: string) { display, ]); - return { copyDisplay, doCopy }; + return { copyDisplay, doCopy, didCopy }; } diff --git a/pkg/interface/src/logic/lib/useLazyScroll.ts b/pkg/interface/src/logic/lib/useLazyScroll.ts index dffb0e550..a7b7235ee 100644 --- a/pkg/interface/src/logic/lib/useLazyScroll.ts +++ b/pkg/interface/src/logic/lib/useLazyScroll.ts @@ -11,6 +11,7 @@ export function distanceToBottom(el: HTMLElement) { export function useLazyScroll( ref: RefObject, + ready: boolean, margin: number, count: number, loadMore: () => Promise @@ -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 }; } diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 5e880f38c..6e6ef0740 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -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--/; diff --git a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx index b2eee5b91..306d221ef 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx @@ -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 ( { - 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} {timestamp} @@ -538,7 +512,6 @@ export const Message = ({ ...rest }) => { const { hovering, bind } = useHovering(); - const contacts = useContactState((state) => state.contacts); return ( {timestampHover ? ( @@ -557,66 +530,14 @@ export const Message = ({ ) : ( <> )} - - {msg.contents.map((content, i) => { - switch (Object.keys(content)[0]) { - case 'text': - return ( - - ); - case 'code': - return ; - case 'reference': - const { link } = referenceToPermalink(content); - return ( - - ); - case 'url': - return ( - - - - ); - case 'mention': - const first = (i) => i === 0; - return ( - - ); - default: - return null; - } - })} - + ); }; diff --git a/pkg/interface/src/views/apps/notifications/inbox.tsx b/pkg/interface/src/views/apps/notifications/inbox.tsx index 867159a77..486177fcf 100644 --- a/pkg/interface/src/views/apps/notifications/inbox.tsx +++ b/pkg/interface/src/views/apps/notifications/inbox.tsx @@ -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 diff --git a/pkg/interface/src/views/apps/publish/PublishResource.tsx b/pkg/interface/src/views/apps/publish/PublishResource.tsx index 619b7502a..ca34b37fe 100644 --- a/pkg/interface/src/views/apps/publish/PublishResource.tsx +++ b/pkg/interface/src/views/apps/publish/PublishResource.tsx @@ -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 ( - + { - if (windowRef.current && !query.has('selected')) { - windowRef.current.parentElement.scrollTop = 0; - } - }, [note, windowRef]); return ( {'<- Notebook Index'} diff --git a/pkg/interface/src/views/components/CommentItem.tsx b/pkg/interface/src/views/components/CommentItem.tsx index 126b67913..c54fac7e6 100644 --- a/pkg/interface/src/views/components/CommentItem.tsx +++ b/pkg/interface/src/views/components/CommentItem.tsx @@ -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 { - - - + transcluded={0} + api={api} + post={post} + showOurContact + /> ); } diff --git a/pkg/interface/src/views/components/GroupLink.tsx b/pkg/interface/src/views/components/GroupLink.tsx index 32696d2c5..9e36db9a9 100644 --- a/pkg/interface/src/views/components/GroupLink.tsx +++ b/pkg/interface/src/views/components/GroupLink.tsx @@ -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(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 ? ( - - - - ) : ( - - ) + modal: }); 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'} > diff --git a/pkg/interface/src/views/components/MentionText.tsx b/pkg/interface/src/views/components/MentionText.tsx index 56d4d0958..a4259af90 100644 --- a/pkg/interface/src/views/components/MentionText.tsx +++ b/pkg/interface/src/views/components/MentionText.tsx @@ -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 ( - - - toggleOverlay()} - marginLeft={first? 0 : 1} - marginRight={1} - px={1} - bg='washedBlue' - color='blue' - fontSize={showNickname ? 1 : 0} - mono={!showNickname} - > - {name} - - - + + + {name} + + ); } diff --git a/pkg/interface/src/views/components/ProfileOverlay.tsx b/pkg/interface/src/views/components/ProfileOverlay.tsx index 2c3c691c1..2e9432748 100644 --- a/pkg/interface/src/views/components/ProfileOverlay.tsx +++ b/pkg/interface/src/views/components/ProfileOverlay.tsx @@ -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' > + + {!isOwn && ( + history.push(`/~landscape/dm/${ship}`)} + /> + )} + { textOnly && ()} @@ -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} /> diff --git a/pkg/interface/src/views/components/ShipSearch.tsx b/pkg/interface/src/views/components/ShipSearch.tsx index a33b8745b..8cc350f1b 100644 --- a/pkg/interface/src/views/components/ShipSearch.tsx +++ b/pkg/interface/src/views/components/ShipSearch.tsx @@ -157,7 +157,7 @@ export function ShipSearch>( 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)}`; diff --git a/pkg/interface/src/views/landscape/components/Graph/GraphContentWide.tsx b/pkg/interface/src/views/landscape/components/Graph/GraphContentWide.tsx new file mode 100644 index 000000000..c4eb5ae49 --- /dev/null +++ b/pkg/interface/src/views/landscape/components/Graph/GraphContentWide.tsx @@ -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 +) { + const { post, transcluded = 0, showOurContact, api, ...rest } = props; + + return ( + + {post.contents.map((content, i) => { + switch (Object.keys(content)[0]) { + case "text": + return ( + + ); + case "code": + return ; + case "reference": + const { link } = referenceToPermalink(content as ReferenceContent); + return ( + + ); + case "url": + return ( + + + + ); + case "mention": + const first = (i) => i === 0; + return ( + + ); + default: + return null; + } + })} + + ); +} + +export const GraphContentWide = React.memo(GraphContentWideInner); diff --git a/pkg/interface/src/views/apps/chat/components/content/code.js b/pkg/interface/src/views/landscape/components/Graph/content/code.js similarity index 100% rename from pkg/interface/src/views/apps/chat/components/content/code.js rename to pkg/interface/src/views/landscape/components/Graph/content/code.js diff --git a/pkg/interface/src/views/apps/chat/components/content/text.js b/pkg/interface/src/views/landscape/components/Graph/content/text.js similarity index 100% rename from pkg/interface/src/views/apps/chat/components/content/text.js rename to pkg/interface/src/views/landscape/components/Graph/content/text.js diff --git a/pkg/interface/src/views/landscape/components/GroupSummary.tsx b/pkg/interface/src/views/landscape/components/GroupSummary.tsx index 8e3d42299..e4eb205bd 100644 --- a/pkg/interface/src/views/landscape/components/GroupSummary.tsx +++ b/pkg/interface/src/views/landscape/components/GroupSummary.tsx @@ -23,7 +23,7 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc): R anchorRef ); return ( - + p.truncate ?? 'unset'}; + -webkit-box-orient: vertical; + +`; export function PostContent(props) { const { post, isParent, api, isReply } = props; - const contacts = useContactState(state => state.contacts); return ( - - + - + ); } diff --git a/pkg/interface/src/views/landscape/components/JoinGroup.tsx b/pkg/interface/src/views/landscape/components/JoinGroup.tsx index b0a6d0c47..13a214b60 100644 --- a/pkg/interface/src/views/landscape/components/JoinGroup.tsx +++ b/pkg/interface/src/views/landscape/components/JoinGroup.tsx @@ -151,47 +151,50 @@ export function JoinGroup(props: JoinGroupProps): ReactElement { ) : preview ? ( - - { Object.keys(preview.channels).length > 0 && ( - - - Channels - - - {Object.values(preview.channels).map(({ metadata }: any) => ( - - - {metadata.title} - - ))} - - - )} + <> + + { Object.keys(preview.channels).length > 0 && ( + + + Channels + + + {Object.values(preview.channels).map(({ metadata }: any) => ( + + + {metadata.title} + + ))} + + + )} + onConfirm(preview.group)} > Join {preview.metadata.title} - + ) : ( s.unreads?.graph?.[feedPath ?? ""]?.["/"]?.unreads as number ?? 0 + ); return ( - {( isFeedEnabled ) ? ( + {( !!feedPath) ? ( Group Feed + + { unreadCount > 0 && unreadCount} + ) : null }