Merge branch 'next/groups' into groups/embed-fix

This commit is contained in:
rcrdlbl 2022-04-28 18:45:25 -04:00 committed by GitHub
commit 00de041625
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 185 additions and 150 deletions

View File

@ -34,7 +34,10 @@ export const ProviderLink = ({
>
<Avatar size={size} {...provider} />
<div className="flex-1 text-black">
<p className="font-mono">{provider.nickname || <ShipName name={provider.shipName} />}</p>
<div className="flex font-mono space-x-4">
<ShipName name={provider.shipName} />
<span className="text-gray-500">{provider.nickname}</span>
</div>
{provider.status && size === 'default' && <p className="font-normal">{provider.status}</p>}
</div>
</Link>

View File

@ -103,10 +103,8 @@ export const Leap = React.forwardRef(
(value: string) => {
const onlySymbols = !value.match(/[\w]/g);
const normValue = normalizeMatchString(value, onlySymbols);
return matches.find(
(m) =>
(m.display && normalizeMatchString(m.display, onlySymbols).startsWith(normValue)) ||
normalizeMatchString(m.value, onlySymbols).startsWith(normValue)
return matches.find((m) =>
normalizeMatchString(m.value, onlySymbols).startsWith(normValue)
);
},
[matches]
@ -141,7 +139,7 @@ export const Leap = React.forwardRef(
const value = input.value.trim();
const isDeletion = (e.nativeEvent as InputEvent).inputType === 'deleteContentBackward';
const inputMatch = getMatch(value);
const matchValue = inputMatch?.display || inputMatch?.value;
const matchValue = inputMatch?.value;
if (matchValue && inputRef.current && !isDeletion) {
inputRef.current.value = matchValue;
@ -207,8 +205,8 @@ export const Leap = React.forwardRef(
const currentIndex = selectedMatch
? matches.findIndex((m) => {
const matchValue = m.display || m.value;
const searchValue = selectedMatch.display || selectedMatch.value;
const matchValue = m.value;
const searchValue = selectedMatch.value;
return matchValue === searchValue;
})
: 0;
@ -216,9 +214,8 @@ export const Leap = React.forwardRef(
const index = (unsafeIndex + matches.length) % matches.length;
const newMatch = matches[index];
const matchValue = newMatch.display || newMatch.value;
useLeapStore.setState({
rawInput: matchValue,
rawInput: newMatch.value,
// searchInput: matchValue,
selectedMatch: newMatch
});
@ -227,7 +224,6 @@ export const Leap = React.forwardRef(
[selection, rawInput, match, matches, selectedMatch]
);
return (
<div className="relative z-50 w-full">
<form
@ -257,7 +253,7 @@ export const Leap = React.forwardRef(
type="text"
ref={inputRef}
placeholder={selection ? '' : 'Search'}
className="flex-1 w-full h-full px-2 h4 text-base rounded-full bg-transparent outline-none"
className="flex-1 w-full h-full px-2 text-base bg-transparent rounded-full outline-none h4"
value={rawInput}
onClick={toggleSearch}
onFocus={onFocus}
@ -266,14 +262,14 @@ export const Leap = React.forwardRef(
autoComplete="off"
aria-autocomplete="both"
aria-controls={dropdown}
aria-activedescendant={selectedMatch?.display || selectedMatch?.value}
aria-activedescendant={selectedMatch?.value}
/>
) : null}
</form>
{menu === 'search' && (
<Link
to="/"
className="absolute top-1/2 right-2 flex-none circle-button w-8 h-8 text-gray-400 bg-gray-50 default-ring -translate-y-1/2"
className="absolute flex-none w-8 h-8 text-gray-400 top-1/2 right-2 circle-button bg-gray-50 default-ring -translate-y-1/2"
onClick={() => select(null)}
>
<Cross className="w-3 h-3 fill-current" />

View File

@ -19,7 +19,7 @@
"@tlon/indigo-light": "^1.0.7",
"@tlon/indigo-react": "^1.2.27",
"@tlon/sigil-js": "^1.4.3",
"@urbit/api": "^2.1.0",
"@urbit/api": "^2.1.1",
"@urbit/http-api": "^2.1.0",
"any-ascii": "^0.1.7",
"aws-sdk": "^2.830.0",

View File

@ -40,7 +40,7 @@ export async function bootstrapApi() {
useLocalState.setState({ subscription: 'connected' });
};
await useMetadataState.getState().initialize(airlock);
useMetadataState.getState().initialize(airlock);
const subs = [
useGroupState,

View File

@ -34,7 +34,8 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
}
client.current = new S3Client({
credentials: s3.credentials,
endpoint: s3.credentials.endpoint
endpoint: s3.credentials.endpoint,
signatureVersion: 'v4'
});
}
}, [gcp.token, s3.credentials]);

View File

@ -11,8 +11,7 @@ export function getTitleFromWorkspace(
case 'messages':
return 'Messages';
case 'group':
const association = associations.groups[workspace.group];
return association?.metadata?.title || '';
return associations.groups[workspace.group]?.metadata?.title || 'Groups';
}
}

View File

@ -1,5 +1,6 @@
import { acceptDm, cite, Content, declineDm, deSig, Post } from '@urbit/api';
import React, { useCallback, useEffect } from 'react';
import Helmet from 'react-helmet';
import _ from 'lodash';
import bigInt from 'big-integer';
import { Box, Row, Col, Text, Center } from '@tlon/indigo-react';
@ -49,6 +50,21 @@ function quoteReply(post: Post) {
return `${reply}\n\n~${post.author}:`;
}
export function DmHelmet(props: DmHelmetProps) {
const { ship } = props;
const hark = useHarkDm(ship);
const unreadCount = hark.count;
const contact = useContact(ship);
const { hideNicknames } = useSettingsState(selectCalmState);
const showNickname = !hideNicknames && Boolean(contact);
const nickname = showNickname ? contact!.nickname : cite(ship) ?? ship;
return(
<Helmet defer={false}>
<title>{unreadCount ? `(${String(unreadCount)}) ` : ''}{ nickname }</title>
</Helmet>
);
}
export function DmResource(props: DmResourceProps) {
const { ship } = props;
const dm = useDM(ship);

View File

@ -1,7 +1,6 @@
import React from 'react';
import {
Icon,
Center,
Row,
Text,
Col,
@ -39,7 +38,7 @@ export interface LinkBlockItemProps {
}
export function LinkBlockItem(props: LinkBlockItemProps & CenterProps) {
const { node, summary, size, m, border = 1, objectFit, ...rest } = props;
const { node, summary, m, border = 1, objectFit, ...rest } = props;
const { post, children } = node;
const { contents, index, author } = post;
@ -66,70 +65,69 @@ export function LinkBlockItem(props: LinkBlockItemProps & CenterProps) {
history.push(`${pathname}/index${index}${search}`);
};
return (
<Center
<Box
onClick={onClick}
position="relative"
m={m}
border={border}
borderColor="lightGray"
position="relative"
borderRadius="1"
height={size}
width={size}
m={m}
maxHeight="100%"
{...rest}
{...bind}
>
<AsyncFallback fallback={<RemoteContentEmbedFallback url={url} />}>
{isReference ? (
summary ? (
<RemoteContentPermalinkEmbed
reference={content[0] as ReferenceContent}
/>
) : (
<PermalinkEmbed
link={referenceToPermalink(content[0] as ReferenceContent).link}
transcluded={0}
/>
)
) : isImage ? (
<RemoteContentImageEmbed
url={url}
tall
stretch
objectFit={objectFit ? objectFit : "cover"}
/>
) : isAudio ? (
<AudioPlayer title={title} url={url} />
) : isOembed ? (
<RemoteContentOembed tall={!summary} renderUrl={false} url={url} thumbnail={summary} oembed={oembed} />
) : (
<RemoteContentEmbedFallback url={url} />
)}
</AsyncFallback>
<Box
backgroundColor="white"
display={summary && hovering ? 'block' : 'none'}
width="100%"
height="64px"
position="absolute"
left="0"
bottom="0"
>
<Col width="100%" height="100%" p="2" justifyContent="space-between">
<Row justifyContent="space-between" width="100%">
<Text textOverflow="ellipsis" whiteSpace="nowrap" overflow="hidden">
{title}
</Text>
<Row gapX="1" alignItems="center">
<Icon icon="Chat" color="black" />
<Text>{children.size}</Text>
<Col height="100%" justifyContent="center" alignItems="center">
<AsyncFallback fallback={<RemoteContentEmbedFallback url={url} />}>
{isReference ? (
summary ? (
<RemoteContentPermalinkEmbed
reference={content[0] as ReferenceContent}
/>
) : (
<PermalinkEmbed
link={referenceToPermalink(content[0] as ReferenceContent).link}
transcluded={0}
/>
)
) : isImage ? (
<RemoteContentImageEmbed
url={url}
tall
stretch
objectFit={objectFit ? objectFit : "cover"}
/>
) : isAudio ? (
<AudioPlayer title={title} url={url} />
) : isOembed ? (
<RemoteContentOembed tall={!summary} renderUrl={false} url={url} thumbnail={summary} oembed={oembed} />
) : (
<RemoteContentEmbedFallback url={url} />
)}
</AsyncFallback>
<Box
backgroundColor="white"
display={summary && hovering ? 'block' : 'none'}
width="100%"
height="64px"
position="absolute"
left="0"
bottom="0"
>
<Col width="100%" height="100%" p="2" justifyContent="space-between">
<Row justifyContent="space-between" width="100%">
<Text textOverflow="ellipsis" whiteSpace="nowrap" overflow="hidden">
{title}
</Text>
<Row gapX="1" alignItems="center">
<Icon icon="Chat" color="black" />
<Text>{children.size}</Text>
</Row>
</Row>
</Row>
<Row width="100%">
<Author ship={author} date={post['time-sent']} showImage></Author>
</Row>
</Col>
</Box>
</Center>
<Row width="100%">
<Author ship={author} date={post['time-sent']} showImage></Author>
</Row>
</Col>
</Box>
</Col>
</Box>
);
}

View File

@ -1,4 +1,4 @@
import { Col, Row, RowProps } from '@tlon/indigo-react';
import { Center, Col, Row, RowProps } from '@tlon/indigo-react';
import { Association, GraphNode, markEachAsRead, TextContent, UrlContent } from '@urbit/api';
import React, { useEffect } from 'react';
import { useGroup } from '~/logic/state/group';
@ -27,21 +27,13 @@ export function LinkDetail(props: LinkDetailProps) {
return (
/* @ts-ignore indio props?? */
<Row height="100%" width="100%" flexDirection={['column', 'column', 'row']} {...rest}>
<LinkBlockItem
minWidth="0"
minHeight="0"
height={["50%", "50%", "100%"]}
width={["100%", "100%", "calc(100% - 350px)"]}
flexGrow={0}
border={0}
node={node}
objectFit="contain"
/>
<Center flex="3 1 75%" overflowY="scroll" >
<LinkBlockItem maxHeight="100%" border={0} node={node} objectFit="contain" />
</Center>
<Col
minHeight="0"
flexShrink={1}
width={['100%', '100%', '350px']}
flexGrow={0}
flex="1 25%"
maxWidth={['auto', 'auto', '45ch']}
maxHeight={['50%', '50%', 'unset']}
gapY={[2,4]}
borderLeft={[0, 0, 1]}
borderTop={[1, 1, 0]}

View File

@ -90,33 +90,33 @@ export function EditProfile(props: any): ReactElement {
const onSubmit = async (values: any, actions: any) => {
try {
Object.keys(values).forEach((key) => {
for (const key in values) {
const newValue = key !== 'color' ? values[key] : uxToHex(values[key]);
if (newValue !== contact[key]) {
if (key === 'isPublic') {
airlock.poke(setPublic(newValue));
return;
} else if (key === 'groups') {
const toRemove: string[] = _.difference(
contact?.groups || [],
newValue
);
const toAdd: string[] = _.difference(
newValue,
contact?.groups || []
);
toRemove.forEach(e =>
airlock.poke(editContact(ship, { 'remove-group': resourceFromPath(e) }))
);
toAdd.forEach(e =>
airlock.poke(editContact(ship, { 'add-group': resourceFromPath(e) }))
);
} else if (key !== 'last-updated' && key !== 'isPublic') {
airlock.poke(editContact(ship, { [key]: newValue }));
return;
if (newValue === contact[key] || key === 'last-updated') {
continue;
} else if (key === 'isPublic') {
await airlock.poke(setPublic(newValue));
} else if (key === 'groups') {
const toRemove: string[] = _.difference(
contact?.groups || [],
newValue
);
const toAdd: string[] = _.difference(
newValue,
contact?.groups || []
);
for (const i in toRemove) {
const group = resourceFromPath(toRemove[i]);
await airlock.poke(editContact(ship, { 'remove-group': group }));
}
for (const i in toAdd) {
const group = resourceFromPath(toAdd[i]);
await airlock.poke(editContact(ship, { 'add-group': group }));
}
} else {
await airlock.poke(editContact(ship, { [key]: newValue }));
}
});
}
history.push(`/~profile/${ship}`);
} catch (e) {
console.error(e);

View File

@ -3,7 +3,7 @@ import moment from 'moment';
import React, { ReactElement, ReactNode } from 'react';
import { Sigil } from '~/logic/lib/sigil';
import { useCopy } from '~/logic/lib/useCopy';
import { cite, uxToHex } from '~/logic/lib/util';
import { cite, deSig, uxToHex } from '~/logic/lib/util';
import { useContact } from '~/logic/state/contact';
import { useDark } from '~/logic/state/join';
import useSettingsState, { selectCalmState, useShowNickname } from '~/logic/state/settings';
@ -52,7 +52,7 @@ function Author(props: AuthorProps & PropFunc<typeof Box>): ReactElement {
const { hideAvatars } = useSettingsState(selectCalmState);
const name = showNickname && contact ? contact.nickname : cite(ship);
const stamp = moment(date);
const { copyDisplay, doCopy } = useCopy(`~${ship}`, name);
const { copyDisplay, doCopy } = useCopy(`~${deSig(ship)}`, name);
const sigil = fullNotIcon ? (
<Sigil ship={ship} size={size} color={color} padding={sigilPadding} />

View File

@ -97,6 +97,7 @@ export function RemoteContentImageEmbed(
objectFit="cover"
borderRadius={2}
onError={onError}
style={{ imageRendering: '-webkit-optimize-contrast' }}
{...props}
/>
</Box>

View File

@ -8,7 +8,7 @@ import {
} from 'react-router-dom';
import { useShortcut } from '~/logic/state/settings';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import { getGroupFromWorkspace, getTitleFromWorkspace } from '~/logic/lib/workspace';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';
@ -22,7 +22,7 @@ import { Skeleton } from './Skeleton';
import { EmptyGroupHome } from './Home/EmptyGroupHome';
import { Join } from './Join/Join';
import { Resource } from './Resource';
import { DmResource } from '~/views/apps/chat/DmResource';
import { DmResource, DmHelmet } from '~/views/apps/chat/DmResource';
import { UnjoinedResource } from '~/views/components/UnjoinedResource';
import { NewChannel } from './NewChannel';
import { GroupHome } from './Home/GroupHome';
@ -126,16 +126,18 @@ export function GroupsPane(props: GroupsPaneProps) {
const { ship } = match.params as Record<string, string>;
return (
<Skeleton
mobileHide
recentGroups={recentGroups}
selected={ship}
{...props}
baseUrl={match.path}
> <DmResource ship={ship} />
</Skeleton>
<>
<DmHelmet ship={ship} />
<Skeleton
mobileHide
recentGroups={recentGroups}
selected={ship}
{...props}
baseUrl={match.path}
> <DmResource ship={ship} />
</Skeleton>
</>
);
}}
/>
@ -180,7 +182,7 @@ export function GroupsPane(props: GroupsPaneProps) {
const appPath = `/ship/${host}/${name}`;
const association = associations.graph[appPath];
const resourceUrl = `${baseUrl}/join/${app}${appPath}`;
let title = groupAssociation?.metadata?.title ?? 'Groups';
let title = getTitleFromWorkspace(associations, workspace);
if (!association) {
return <Loading />;
@ -252,7 +254,7 @@ export function GroupsPane(props: GroupsPaneProps) {
render={(routeProps) => {
const shouldHideSidebar =
routeProps.location.pathname.includes('/feed');
const title = groupAssociation?.metadata?.title ?? 'Groups';
const title = getTitleFromWorkspace(associations, workspace);
return (
<>
<Helmet defer={false}>

View File

@ -10,16 +10,17 @@
[%2 network:zero:store]
[%3 network:one:store]
[%4 network:store]
state-5
[%5 network:store]
state-6
==
::
+$ state-5 [%5 network:store]
::-
+$ state-6 [%6 network:store]
++ orm orm:store
++ orm-log orm-log:store
++ mar %graph-update-3
--
::
=| state-5
=| state-6
=* state -
::
%- agent:dbug
@ -81,7 +82,21 @@
(gas:orm-log ~ [now.bowl logged-update] ~)
==
::
%5 [cards this(state old)]
%5
%_ $
-.old %6
::
update-logs.old
%- ~(rut by update-logs.old)
|= [=resource:store =update-log:store]
^- update-log:store
?: =(our.bowl entity.resource)
update-log
%+ gas:orm-log *update-log:store
(scag 2 (tap:orm-log update-log))
==
::
%6 [cards this(state old)]
==
::
++ on-watch
@ -138,6 +153,15 @@
%tags ~|('cannot send %tags as poke' !!)
%tag-queries ~|('cannot send %tag-queries as poke' !!)
==
++ put-update-log
|= [=resource:store =update-log:store =time =logged-update:store]
^- update-log:store
?: =(our.bowl entity.resource)
(put:orm-log update-log time logged-update)
%+ gas:orm-log *update-log:store
:~ (need (pry:orm-log update-log))
[time logged-update]
==
::
++ add-graph
|= $: =time
@ -194,7 +218,7 @@
?> is-valid
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [time [%add-nodes resource nodes]])
(put-update-log resource update-log time [time %add-nodes resource nodes])
::
:- (give [/updates]~ [%add-nodes resource nodes])
%_ state
@ -332,7 +356,9 @@
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [time [%remove-posts resource indices]])
%^ put-update-log resource
update-log
[time time %remove-posts resource indices]
:- (give [/updates]~ [%remove-posts resource indices])
%_ state
update-logs (~(put by update-logs) resource update-log)
@ -419,8 +445,9 @@
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [time [%add-signatures uid signatures]])
::
%^ put-update-log resource
update-log
[time time %add-signatures uid signatures]
:- (give [/updates]~ [%add-signatures uid signatures])
%_ state
update-logs (~(put by update-logs) resource update-log)
@ -467,9 +494,9 @@
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
%^ put:orm-log update-log
time
[time [%remove-signatures uid signatures]]
%^ put-update-log resource
update-log
[time time %remove-signatures uid signatures]
::
:- (give [/updates]~ [%remove-signatures uid signatures])
%_ state

View File

@ -1,10 +1,10 @@
:~ title+'Groups'
info+'A suite of applications to communicate on Urbit'
color+0xee.5432
glob-http+['https://bootstrap.urbit.org/glob-0v6.v8tfj.52ls5.409gr.rnt3f.090hm.glob' 0v6.v8tfj.52ls5.409gr.rnt3f.090hm]
glob-http+['https://bootstrap.urbit.org/glob-0v7.bmftr.90ktq.cma0h.da190.bs8b1.glob' 0v7.bmftr.90ktq.cma0h.da190.bs8b1]
base+'landscape'
version+[1 0 10]
version+[1 0 11]
website+'https://tlon.io'
license+'MIT'
==

View File

@ -758,9 +758,9 @@
--
++ import
|= [arc=* our=ship]
^- (quip card:agent:gall [%5 network])
^- (quip card:agent:gall [%6 network])
|^
=/ sty [%5 (remake-network ;;(tree-network +.arc))]
=/ sty [%6 (remake-network ;;(tree-network +.arc))]
:_ sty
%+ turn ~(tap by graphs.sty)
|= [rid=resource =marked-graph]

View File

@ -198,7 +198,7 @@ export const isWriter = (group: Group, resource: string, ship: string) => {
if (typeof writers === 'undefined') {
return true;
} else {
return writers.includes(ship) || admins.includes(ship);
return [...writers].includes(ship) || admins.includes(ship);
}
};

View File

@ -1,6 +1,6 @@
{
"name": "@urbit/api",
"version": "2.1.0",
"version": "2.1.1",
"description": "A library that provides bindings and types for Urbit's various userspace desks",
"repository": {
"type": "git",