mirror of
https://github.com/urbit/shrub.git
synced 2024-12-21 01:41:37 +03:00
Merge pull request #5361 from urbit/lf/post-assembly-grabbag
garden/landscape: fixes
This commit is contained in:
commit
a3664da492
@ -232,8 +232,8 @@
|
|||||||
:: if the new chad is a site, we're instantly done
|
:: if the new chad is a site, we're instantly done
|
||||||
::
|
::
|
||||||
?: ?=(%site -.href.docket)
|
?: ?=(%site -.href.docket)
|
||||||
:- ~[add-fact:cha]
|
|
||||||
=. charges (new-chad:cha %site ~)
|
=. charges (new-chad:cha %site ~)
|
||||||
|
:- ~[add-fact:cha]
|
||||||
state
|
state
|
||||||
::
|
::
|
||||||
=. by-base (~(put by by-base) base.href.docket desk)
|
=. by-base (~(put by by-base) base.href.docket desk)
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"lint": "eslint --cache \"**/*.{js,jsx,ts,tsx}\"",
|
"lint": "eslint --cache \"**/*.{js,jsx,ts,tsx}\"",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"test": "echo \"No test yet\"",
|
"test": "tsc --noEmit",
|
||||||
"tsc": "tsc --noEmit"
|
"tsc": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -8,7 +8,7 @@ import { Dialog, DialogClose, DialogContent, DialogTrigger } from './Dialog';
|
|||||||
import { DocketHeader } from './DocketHeader';
|
import { DocketHeader } from './DocketHeader';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
import { VatMeta } from './VatMeta';
|
import { VatMeta } from './VatMeta';
|
||||||
import useDocketState, { ChargeWithDesk } from '../state/docket';
|
import useDocketState, { ChargeWithDesk, useTreaty } from '../state/docket';
|
||||||
import { getAppHref, getAppName } from '../state/util';
|
import { getAppHref, getAppName } from '../state/util';
|
||||||
import { addRecentApp } from '../nav/search/Home';
|
import { addRecentApp } from '../nav/search/Home';
|
||||||
import { TreatyMeta } from './TreatyMeta';
|
import { TreatyMeta } from './TreatyMeta';
|
||||||
@ -52,6 +52,7 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
|||||||
const [ship, desk] = getRemoteDesk(docket, vat);
|
const [ship, desk] = getRemoteDesk(docket, vat);
|
||||||
const publisher = vat?.arak?.rail?.publisher ?? ship;
|
const publisher = vat?.arak?.rail?.publisher ?? ship;
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
const treaty = useTreaty(ship, desk);
|
||||||
|
|
||||||
const installApp = async () => {
|
const installApp = async () => {
|
||||||
if (installStatus === 'installed') {
|
if (installStatus === 'installed') {
|
||||||
@ -135,18 +136,20 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
|||||||
</PillButton>
|
</PillButton>
|
||||||
</div>
|
</div>
|
||||||
</DocketHeader>
|
</DocketHeader>
|
||||||
|
<div className="space-y-6">
|
||||||
{vat ? (
|
{vat ? (
|
||||||
<>
|
<>
|
||||||
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
|
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
|
||||||
<VatMeta vat={vat} />
|
<VatMeta vat={vat} />
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{'chad' in docket ? null : (
|
{!treaty ? null : (
|
||||||
<>
|
<>
|
||||||
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
|
<hr className="-mx-5 sm:-mx-8 border-gray-50" />
|
||||||
<TreatyMeta treaty={docket} />
|
<TreatyMeta treaty={treaty} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import moment from 'moment';
|
|
||||||
import { Vat } from '@urbit/api/hood';
|
import { Vat } from '@urbit/api/hood';
|
||||||
|
|
||||||
import { Attribute } from './Attribute';
|
import { Attribute } from './Attribute';
|
||||||
@ -12,12 +11,6 @@ export function VatMeta(props: { vat: Vat }) {
|
|||||||
const pluralUpdates = next?.length !== 1;
|
const pluralUpdates = next?.length !== 1;
|
||||||
return (
|
return (
|
||||||
<div className="mt-5 sm:mt-8 space-y-5 sm:space-y-8">
|
<div className="mt-5 sm:mt-8 space-y-5 sm:space-y-8">
|
||||||
<Attribute title="Developer Desk" attr="desk">
|
|
||||||
{ship}/{foreignDesk}
|
|
||||||
</Attribute>
|
|
||||||
<Attribute title="Last Software Update" attr="case">
|
|
||||||
{moment(cass.da).format('YYYY.MM.DD')}
|
|
||||||
</Attribute>
|
|
||||||
<Attribute title="Desk Hash" attr="hash">
|
<Attribute title="Desk Hash" attr="hash">
|
||||||
{hash}
|
{hash}
|
||||||
</Attribute>
|
</Attribute>
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
cite,
|
cite,
|
||||||
GraphNotifIndex,
|
|
||||||
GroupNotifIndex,
|
|
||||||
IndexedNotification,
|
|
||||||
NotificationGraphConfig,
|
NotificationGraphConfig,
|
||||||
Post,
|
Post,
|
||||||
Unreads
|
Unreads
|
||||||
} from '@urbit/api';
|
} from '@urbit/api';
|
||||||
import { patp } from 'urbit-ob';
|
|
||||||
import bigInt, { BigInteger } from 'big-integer';
|
import bigInt, { BigInteger } from 'big-integer';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import f from 'lodash/fp';
|
import f from 'lodash/fp';
|
||||||
import { pluralize } from './util';
|
|
||||||
import useMetadataState from '../state/metadata';
|
|
||||||
import { emptyHarkStats } from '../state/hark';
|
import { emptyHarkStats } from '../state/hark';
|
||||||
|
|
||||||
export function getLastSeen(
|
export function getLastSeen(
|
||||||
@ -60,94 +54,3 @@ export function isWatching(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNotificationKey(
|
|
||||||
time: BigInteger,
|
|
||||||
notification: IndexedNotification
|
|
||||||
): string {
|
|
||||||
const base = time.toString();
|
|
||||||
if ('graph' in notification.index) {
|
|
||||||
const { graph, index, description } = notification.index.graph;
|
|
||||||
return `${base}-${graph}-${index}-${description}`;
|
|
||||||
} else if ('group' in notification.index) {
|
|
||||||
const { group } = notification.index.group;
|
|
||||||
return `${base}-${group}`;
|
|
||||||
}
|
|
||||||
return `${base}-unknown`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function notificationReferent(not: IndexedNotification) {
|
|
||||||
if ('graph' in not.index) {
|
|
||||||
return not.index.graph.graph;
|
|
||||||
} else {
|
|
||||||
return not.index.group.group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function describeNotification(notification: IndexedNotification) {
|
|
||||||
function group(idx: GroupNotifIndex) {
|
|
||||||
switch (idx.description) {
|
|
||||||
case 'add-members':
|
|
||||||
return 'joined';
|
|
||||||
case 'remove-members':
|
|
||||||
return 'left';
|
|
||||||
default:
|
|
||||||
return idx.description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function graph(idx: GraphNotifIndex, plural: boolean, singleAuthor: boolean) {
|
|
||||||
const isDm = idx.graph === `/ship/~${window.ship}/dm-inbox`;
|
|
||||||
if (isDm) {
|
|
||||||
return 'New DM from ';
|
|
||||||
}
|
|
||||||
switch (idx.description) {
|
|
||||||
case 'post':
|
|
||||||
return 'Your post received replies in';
|
|
||||||
case 'link':
|
|
||||||
return `New link${plural ? 's' : ''} in`;
|
|
||||||
case 'comment':
|
|
||||||
return `New comment${plural ? 's' : ''} on`;
|
|
||||||
case 'note':
|
|
||||||
return `New Note${plural ? 's' : ''} in`;
|
|
||||||
// @ts-ignore need better types
|
|
||||||
case 'edit-note':
|
|
||||||
return `updated ${pluralize('note', plural)} in`;
|
|
||||||
case 'mention':
|
|
||||||
return 'You were mentioned in';
|
|
||||||
case 'message':
|
|
||||||
if (isDm) {
|
|
||||||
return 'messaged you';
|
|
||||||
}
|
|
||||||
return `New message${plural ? 's' : ''} in`;
|
|
||||||
default: return idx.description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ('group' in notification.index) {
|
|
||||||
return group(notification.index.group);
|
|
||||||
} else if ('graph' in notification.index) {
|
|
||||||
// @ts-ignore needs better type guard
|
|
||||||
const contents = notification.notification?.contents?.graph ?? ([] as Post[]);
|
|
||||||
return graph(
|
|
||||||
notification.index.graph,
|
|
||||||
contents.length > 1,
|
|
||||||
_.uniq(_.map(contents, 'author')).length === 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getReferent(notification: IndexedNotification) {
|
|
||||||
const meta = useMetadataState.getState();
|
|
||||||
if ('graph' in notification.index) {
|
|
||||||
if (notification.index.graph.graph === `/ship/~${window.ship}/dm-inbox`) {
|
|
||||||
const [, ship] = notification.index.graph.index.split('/');
|
|
||||||
return cite(patp(ship));
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
meta.associations.graph[notification.index.graph.graph]?.metadata
|
|
||||||
?.title ?? notification.index.graph
|
|
||||||
);
|
|
||||||
} else if ('group' in notification.index) {
|
|
||||||
return (
|
|
||||||
meta.associations.groups[notification.index.group.group]?.metadata?.title ??
|
|
||||||
notification.index.group.group
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { GraphNotificationContents, GraphNotifIndex } from '@urbit/api';
|
|
||||||
|
|
||||||
export function getParentIndex(
|
|
||||||
idx: GraphNotifIndex,
|
|
||||||
contents: GraphNotificationContents
|
|
||||||
) {
|
|
||||||
const origIndex = contents[0].index.slice(1).split('/');
|
|
||||||
const ret = (i: string[]) => `/${i.join('/')}`;
|
|
||||||
switch (idx.description) {
|
|
||||||
case 'link':
|
|
||||||
return '/';
|
|
||||||
case 'comment':
|
|
||||||
return ret(origIndex.slice(0, 1));
|
|
||||||
case 'note':
|
|
||||||
return '/';
|
|
||||||
case 'mention':
|
|
||||||
return undefined;
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,6 +42,7 @@ export interface HarkState {
|
|||||||
notificationsGroupConfig: string[];
|
notificationsGroupConfig: string[];
|
||||||
unreads: Unreads;
|
unreads: Unreads;
|
||||||
archiveNote: (bin: HarkBin, lid: HarkLid) => Promise<void>;
|
archiveNote: (bin: HarkBin, lid: HarkLid) => Promise<void>;
|
||||||
|
readCount: (path: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useHarkState = createState<HarkState>(
|
const useHarkState = createState<HarkState>(
|
||||||
@ -171,5 +172,4 @@ export function useHarkGraphIndex(graph: string, index: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.hark = useHarkState.getState;
|
|
||||||
export default useHarkState;
|
export default useHarkState;
|
||||||
|
@ -1,302 +0,0 @@
|
|||||||
import { Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
|
|
||||||
import { Association, GraphNotificationContents, GraphNotifIndex, Post } from '@urbit/api';
|
|
||||||
import { BigInteger } from 'big-integer';
|
|
||||||
import { patp } from 'urbit-ob';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { pluralize } from '~/logic/lib/util';
|
|
||||||
import useGroupState from '~/logic/state/group';
|
|
||||||
import {
|
|
||||||
useAssocForGraph,
|
|
||||||
useAssocForGroup
|
|
||||||
} from '~/logic/state/metadata';
|
|
||||||
import Author from '~/views/components/Author';
|
|
||||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
|
||||||
import { Header } from './header';
|
|
||||||
|
|
||||||
const TruncBox = styled(Box)<{ truncate?: number }>`
|
|
||||||
-webkit-line-clamp: ${p => p.truncate ?? 'unset'};
|
|
||||||
display: -webkit-box;
|
|
||||||
overflow: hidden;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
color: ${p => p.theme.colors.black};
|
|
||||||
`;
|
|
||||||
|
|
||||||
function describeNotification(
|
|
||||||
description: string,
|
|
||||||
plural: boolean,
|
|
||||||
isDm: boolean,
|
|
||||||
singleAuthor: boolean
|
|
||||||
): string {
|
|
||||||
switch (description) {
|
|
||||||
case 'post':
|
|
||||||
return singleAuthor ? 'replied to you' : 'Your post received replies';
|
|
||||||
case 'link':
|
|
||||||
return `New link${plural ? 's' : ''} in`;
|
|
||||||
case 'comment':
|
|
||||||
return `New comment${plural ? 's' : ''} on`;
|
|
||||||
case 'note':
|
|
||||||
return `New Note${plural ? 's' : ''} in`;
|
|
||||||
case 'edit-note':
|
|
||||||
return `updated ${pluralize('note', plural)} in`;
|
|
||||||
case 'mention':
|
|
||||||
return singleAuthor ? 'mentioned you in' : 'You were mentioned in';
|
|
||||||
case 'message':
|
|
||||||
if (isDm) {
|
|
||||||
return 'messaged you';
|
|
||||||
}
|
|
||||||
return `New message${plural ? 's' : ''} in`;
|
|
||||||
default:
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContentSummary({ icon, name, author, to }) {
|
|
||||||
return (
|
|
||||||
<Link to={to}>
|
|
||||||
<Col
|
|
||||||
gapY={1}
|
|
||||||
flexDirection={['column', 'row']}
|
|
||||||
alignItems={['flex-start', 'center']}
|
|
||||||
>
|
|
||||||
<Row
|
|
||||||
alignItems="center"
|
|
||||||
gapX={2}
|
|
||||||
p={1}
|
|
||||||
width="fit-content"
|
|
||||||
borderRadius={2}
|
|
||||||
border={1}
|
|
||||||
borderColor="lightGray"
|
|
||||||
>
|
|
||||||
<Icon display="block" icon={icon} />
|
|
||||||
<Text verticalAlign="baseline" fontWeight="medium">
|
|
||||||
{name}
|
|
||||||
</Text>
|
|
||||||
</Row>
|
|
||||||
<Row ml={[0, 1]} alignItems="center">
|
|
||||||
<Text lineHeight={1} fontWeight="medium" mr={1}>
|
|
||||||
by
|
|
||||||
</Text>
|
|
||||||
<Author
|
|
||||||
sigilPadding={6}
|
|
||||||
size={24}
|
|
||||||
dontShowTime
|
|
||||||
ship={author}
|
|
||||||
showImage
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GraphNodeContent = ({ post, mod, index, hidden, association }) => {
|
|
||||||
const { contents } = post;
|
|
||||||
const idx = index.slice(1).split('/');
|
|
||||||
const url = getNodeUrl(mod, hidden, association?.group, association?.resource, index);
|
|
||||||
if (mod === 'graph-validator-link' && idx.length === 1) {
|
|
||||||
const [{ text: title }] = contents;
|
|
||||||
return (
|
|
||||||
<ContentSummary to={url} icon="Links" name={title} author={post.author} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (mod === 'graph-validator-publish' && idx[1] === '1') {
|
|
||||||
const [{ text: title }] = contents;
|
|
||||||
return (
|
|
||||||
<ContentSummary to={url} icon="Note" name={title} author={post.author} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<TruncBox truncate={8}>
|
|
||||||
<GraphContent contents={post.contents} showOurContact />
|
|
||||||
</TruncBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function getNodeUrl(
|
|
||||||
mod: string,
|
|
||||||
hidden: boolean,
|
|
||||||
groupPath: string,
|
|
||||||
graph: string,
|
|
||||||
index: string
|
|
||||||
) {
|
|
||||||
const graphValidator = 'graph-validator-';
|
|
||||||
const rmValidator = mod.slice(graphValidator.length);
|
|
||||||
if (hidden && mod === 'graph-validator-chat') {
|
|
||||||
groupPath = '/messages';
|
|
||||||
} else if (hidden) {
|
|
||||||
groupPath = '/home';
|
|
||||||
}
|
|
||||||
const graphUrl = `/~landscape${groupPath}/resource/${rmValidator}${graph}`;
|
|
||||||
const idx = index.slice(1).split('/');
|
|
||||||
if (mod === 'graph-validator-publish') {
|
|
||||||
const [noteId, kind, commId] = idx;
|
|
||||||
const selected = kind === '2' ? `?selected=${commId}` : '';
|
|
||||||
return `${graphUrl}/note/${noteId}${selected}`;
|
|
||||||
} else if (mod === 'graph-validator-link') {
|
|
||||||
const [linkId, commId] = idx;
|
|
||||||
return `${graphUrl}/index/${linkId}${commId ? `?selected=${commId}` : ''}`;
|
|
||||||
} else if (mod === 'graph-validator-chat') {
|
|
||||||
if (idx.length > 0) {
|
|
||||||
return `${graphUrl}?msg=${idx[0]}`;
|
|
||||||
}
|
|
||||||
return graphUrl;
|
|
||||||
} else if (mod === 'graph-validator-post') {
|
|
||||||
return `/~landscape${groupPath}/feed/thread${index}`;
|
|
||||||
} else if (mod === 'graph-validator-dm') {
|
|
||||||
return `/~landscape${groupPath}/dm/${patp(idx[0])}`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PostsByAuthor {
|
|
||||||
author: string;
|
|
||||||
posts: Post[];
|
|
||||||
}
|
|
||||||
const GraphNodes = (props: {
|
|
||||||
posts: Post[];
|
|
||||||
hideAuthors?: boolean;
|
|
||||||
index: string;
|
|
||||||
mod: string;
|
|
||||||
association: Association;
|
|
||||||
hidden: boolean;
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
posts,
|
|
||||||
mod,
|
|
||||||
hidden,
|
|
||||||
index,
|
|
||||||
hideAuthors = false,
|
|
||||||
association
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const postsByConsecAuthor = _.reduce(
|
|
||||||
posts,
|
|
||||||
(acc: PostsByAuthor[], val: Post, key: number) => {
|
|
||||||
const lent = acc.length;
|
|
||||||
if (lent > 0 && acc?.[lent - 1]?.author === val.author) {
|
|
||||||
const last = acc[lent - 1];
|
|
||||||
const rest = acc.slice(0, -1);
|
|
||||||
return [...rest, { ...last, posts: [...last.posts, val] }];
|
|
||||||
}
|
|
||||||
return [...acc, { author: val.author, posts: [val] }];
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{_.map(postsByConsecAuthor, ({ posts, author }, idx) => {
|
|
||||||
const time = posts[0]?.['time-sent'];
|
|
||||||
return (
|
|
||||||
<Col key={idx} flexGrow={1} alignItems="flex-start">
|
|
||||||
{!hideAuthors && (
|
|
||||||
<Author
|
|
||||||
size={24}
|
|
||||||
sigilPadding={6}
|
|
||||||
showImage
|
|
||||||
ship={author}
|
|
||||||
date={time}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Col gapY={2} py={hideAuthors ? 0 : 2} width="100%">
|
|
||||||
{_.map(posts, post => (
|
|
||||||
<GraphNodeContent
|
|
||||||
key={post.index}
|
|
||||||
post={post}
|
|
||||||
mod={mod}
|
|
||||||
index={index}
|
|
||||||
association={association}
|
|
||||||
hidden={hidden}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Col>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function GraphNotification(props: {
|
|
||||||
index: GraphNotifIndex;
|
|
||||||
contents: GraphNotificationContents;
|
|
||||||
read: boolean;
|
|
||||||
time: number;
|
|
||||||
timebox: BigInteger;
|
|
||||||
}) {
|
|
||||||
const { contents, index, read, time, timebox } = props;
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const authors = _.uniq(_.map(contents, 'author'));
|
|
||||||
const singleAuthor = authors.length === 1;
|
|
||||||
const { graph, mark } = index;
|
|
||||||
const association = useAssocForGraph(graph);
|
|
||||||
const dm = mark === 'graph-validator-dm';
|
|
||||||
const desc = describeNotification(
|
|
||||||
index.description,
|
|
||||||
contents.length !== 1,
|
|
||||||
dm,
|
|
||||||
singleAuthor
|
|
||||||
);
|
|
||||||
const groupAssociation = useAssocForGroup(association?.group ?? '');
|
|
||||||
const groups = useGroupState(state => state.groups);
|
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
|
||||||
if(dm) {
|
|
||||||
history.push(`/~landscape/messages/dm/~${authors[0]}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const first = contents[0];
|
|
||||||
history.push(
|
|
||||||
getNodeUrl(
|
|
||||||
index.mark,
|
|
||||||
groups[association?.group]?.hidden,
|
|
||||||
association?.group,
|
|
||||||
association?.resource,
|
|
||||||
first.index
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [timebox, index, read, history.push, authors, dm]);
|
|
||||||
|
|
||||||
const authorsInHeader =
|
|
||||||
dm ||
|
|
||||||
((index.description === 'mention' || index.description === 'post') &&
|
|
||||||
singleAuthor);
|
|
||||||
const hideAuthors =
|
|
||||||
authorsInHeader ||
|
|
||||||
index.description === 'note' ||
|
|
||||||
index.description === 'link';
|
|
||||||
const channelTitle = dm ? undefined : association?.metadata?.title ?? graph;
|
|
||||||
const groupTitle = groupAssociation?.metadata?.title;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header
|
|
||||||
time={time}
|
|
||||||
authors={authorsInHeader ? authors : []}
|
|
||||||
channelTitle={channelTitle}
|
|
||||||
description={desc}
|
|
||||||
groupTitle={groupTitle}
|
|
||||||
content
|
|
||||||
/>
|
|
||||||
<Col onClick={onClick} gapY={2} flexGrow={1} width="100%" gridArea="main">
|
|
||||||
<GraphNodes
|
|
||||||
hideAuthors={hideAuthors}
|
|
||||||
posts={contents.slice(0, 4)}
|
|
||||||
mod={index.mark}
|
|
||||||
index={contents?.[0].index}
|
|
||||||
association={association}
|
|
||||||
hidden={groups[association?.group]?.hidden}
|
|
||||||
/>
|
|
||||||
{contents.length > 4 && (
|
|
||||||
<Text mb={2} gray>
|
|
||||||
+ {contents.length - 4} more
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
import { Col } from '@tlon/indigo-react';
|
|
||||||
import {
|
|
||||||
GroupNotificationContents,
|
|
||||||
GroupNotifIndex,
|
|
||||||
GroupUpdate
|
|
||||||
} from '@urbit/api';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import React, { ReactElement } from 'react';
|
|
||||||
import { useAssocForGroup } from '~/logic/state/metadata';
|
|
||||||
import { Header } from './header';
|
|
||||||
|
|
||||||
function describeNotification(description: string, plural: boolean) {
|
|
||||||
switch (description) {
|
|
||||||
case 'add-members':
|
|
||||||
return 'joined';
|
|
||||||
case 'remove-members':
|
|
||||||
return 'left';
|
|
||||||
default:
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGroupUpdateParticipants(update: GroupUpdate): string[] {
|
|
||||||
if ('addMembers' in update) {
|
|
||||||
return update.addMembers.ships;
|
|
||||||
}
|
|
||||||
if ('removeMembers' in update) {
|
|
||||||
return update.removeMembers.ships;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GroupNotificationProps {
|
|
||||||
index: GroupNotifIndex;
|
|
||||||
contents: GroupNotificationContents;
|
|
||||||
time: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GroupNotification(props: GroupNotificationProps): ReactElement {
|
|
||||||
const { contents, index, time } = props;
|
|
||||||
|
|
||||||
const authors = _.flatten(_.map(contents, getGroupUpdateParticipants));
|
|
||||||
|
|
||||||
const { group } = index;
|
|
||||||
const desc = describeNotification(index.description, contents.length !== 1);
|
|
||||||
|
|
||||||
const association = useAssocForGroup(group);
|
|
||||||
const groupTitle = association?.metadata?.title ?? group;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col>
|
|
||||||
<Header
|
|
||||||
time={time}
|
|
||||||
authors={authors}
|
|
||||||
description={desc}
|
|
||||||
groupTitle={groupTitle}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,7 @@ export interface AuthorProps {
|
|||||||
size?: number;
|
size?: number;
|
||||||
lineHeight?: string | number;
|
lineHeight?: string | number;
|
||||||
isRelativeTime?: boolean;
|
isRelativeTime?: boolean;
|
||||||
dontShowTime: boolean;
|
dontShowTime?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-lines-per-function
|
// eslint-disable-next-line max-lines-per-function
|
||||||
|
@ -25,7 +25,7 @@ import { CommentItem } from './CommentItem';
|
|||||||
import airlock from '~/logic/api';
|
import airlock from '~/logic/api';
|
||||||
import useGraphState from '~/logic/state/graph';
|
import useGraphState from '~/logic/state/graph';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { toHarkPlace } from '~/logic/lib/util';
|
import { toHarkPath, toHarkPlace } from '~/logic/lib/util';
|
||||||
|
|
||||||
interface CommentsProps {
|
interface CommentsProps {
|
||||||
comments: GraphNode;
|
comments: GraphNode;
|
||||||
@ -134,7 +134,8 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
|||||||
}, [comments.post?.index, association.resource]);
|
}, [comments.post?.index, association.resource]);
|
||||||
|
|
||||||
const unreads = useHarkState(state => state.unreads);
|
const unreads = useHarkState(state => state.unreads);
|
||||||
const readCount = children.length - getUnreadCount(unreads, association.resource, parentIndex);
|
const harkPath = toHarkPath(association.resource, parentIndex);
|
||||||
|
const readCount = children.length - getUnreadCount(unreads, harkPath);
|
||||||
|
|
||||||
const canComment = isWriter(group, association.resource) || association.metadata.vip === 'reader-comments';
|
const canComment = isWriter(group, association.resource) || association.metadata.vip === 'reader-comments';
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import useGroupState from '~/logic/state/group';
|
|||||||
import useMetadataState, { useAssocForGraph } from '~/logic/state/metadata';
|
import useMetadataState, { useAssocForGraph } from '~/logic/state/metadata';
|
||||||
import { PropFunc } from '~/types';
|
import { PropFunc } from '~/types';
|
||||||
import { Header } from '~/views/apps/notifications/header';
|
import { Header } from '~/views/apps/notifications/header';
|
||||||
import { NotificationWrapper } from '~/views/apps/notifications/notification';
|
|
||||||
import { MetadataIcon } from '~/views/landscape/components/MetadataIcon';
|
import { MetadataIcon } from '~/views/landscape/components/MetadataIcon';
|
||||||
import { StatelessAsyncButton } from '../StatelessAsyncButton';
|
import { StatelessAsyncButton } from '../StatelessAsyncButton';
|
||||||
import airlock from '~/logic/api';
|
import airlock from '~/logic/api';
|
||||||
@ -297,7 +296,7 @@ export function GroupInvite(props: GroupInviteProps): ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationWrapper>
|
<>
|
||||||
<Header content {...headerProps} />
|
<Header content {...headerProps} />
|
||||||
<Row
|
<Row
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@ -321,6 +320,6 @@ export function GroupInvite(props: GroupInviteProps): ReactElement {
|
|||||||
/>
|
/>
|
||||||
</ResponsiveRow>
|
</ResponsiveRow>
|
||||||
</Row>
|
</Row>
|
||||||
</NotificationWrapper>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import { deSig, Graphs, UnreadStats } from '@urbit/api';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { SidebarAppConfig } from './types';
|
|
||||||
|
|
||||||
export function useGraphModule(
|
|
||||||
graphKeys: Set<string>,
|
|
||||||
graphs: Graphs,
|
|
||||||
graphUnreads: Record<string, Record<string, UnreadStats>>
|
|
||||||
): SidebarAppConfig {
|
|
||||||
const getStatus = useCallback(
|
|
||||||
(s: string) => {
|
|
||||||
const [, , host, name] = s.split('/');
|
|
||||||
const graphKey = `${deSig(host)}/${name}`;
|
|
||||||
if (!graphKeys.has(graphKey)) {
|
|
||||||
return 'unsubscribed';
|
|
||||||
}
|
|
||||||
|
|
||||||
const notifications = graphUnreads?.[s]?.['/']?.notifications;
|
|
||||||
if (
|
|
||||||
notifications &&
|
|
||||||
((typeof notifications === 'number' && notifications > 0)
|
|
||||||
|| typeof notifications === 'object' && notifications.length)
|
|
||||||
) {
|
|
||||||
return 'notification';
|
|
||||||
}
|
|
||||||
|
|
||||||
const unreads = graphUnreads?.[s]?.['/']?.unreads;
|
|
||||||
if (typeof unreads === 'number' ? unreads > 0 : unreads?.size ?? 0 > 0) {
|
|
||||||
return 'unread';
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
[graphs, graphKeys, graphUnreads]
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastUpdated = useCallback((s: string) => {
|
|
||||||
// cant get link timestamps without loading posts
|
|
||||||
const last = graphUnreads?.[s]?.['/']?.last;
|
|
||||||
if(last) {
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
const stat = getStatus(s);
|
|
||||||
if(stat === 'unsubscribed') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}, [getStatus, graphUnreads]);
|
|
||||||
|
|
||||||
return { getStatus, lastUpdated };
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement, useCallback } from 'react';
|
import React, { ReactElement, useCallback } from 'react';
|
||||||
import { Associations, Graph } from '@urbit/api';
|
import { Associations, Graph, Unreads } from '@urbit/api';
|
||||||
import { patp, patp2dec } from 'urbit-ob';
|
import { patp, patp2dec } from 'urbit-ob';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
@ -13,9 +13,8 @@ import useMetadataState from '~/logic/state/metadata';
|
|||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { useShortcut } from '~/logic/state/settings';
|
import { useShortcut } from '~/logic/state/settings';
|
||||||
|
|
||||||
function sidebarSort(pending: Set<string>): Record<SidebarSort, (a: string, b: string) => number> {
|
function sidebarSort(unreads: Unreads, pending: Set<string>): Record<SidebarSort, (a: string, b: string) => number> {
|
||||||
const { associations } = useMetadataState.getState();
|
const { associations } = useMetadataState.getState();
|
||||||
const { unreads } = useHarkState.getState();
|
|
||||||
const alphabetical = (a: string, b: string) => {
|
const alphabetical = (a: string, b: string) => {
|
||||||
const aAssoc = associations[a];
|
const aAssoc = associations[a];
|
||||||
const bAssoc = associations[b];
|
const bAssoc = associations[b];
|
||||||
@ -102,9 +101,10 @@ export function SidebarList(props: {
|
|||||||
const inbox = useInbox();
|
const inbox = useInbox();
|
||||||
const graphKeys = useGraphState(s => s.graphKeys);
|
const graphKeys = useGraphState(s => s.graphKeys);
|
||||||
const pending = useGraphState(s => s.pendingDms);
|
const pending = useGraphState(s => s.pendingDms);
|
||||||
|
const unreads = useHarkState(s => s.unreads);
|
||||||
|
|
||||||
const ordered = getItems(associations, workspace, inbox, pending)
|
const ordered = getItems(associations, workspace, inbox, pending)
|
||||||
.sort(sidebarSort(pending)[config.sortBy]);
|
.sort(sidebarSort(unreads, pending)[config.sortBy]);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user