Merge branch 'release/next-js' into la/chat-delete

This commit is contained in:
Logan Allen 2021-05-06 12:44:04 -05:00
commit 147fae2dd2
17 changed files with 346 additions and 192 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2773b91958c18c7537cb568e01bcf83056447bfef981c5ed4b6688cc3ab69936
size 10739521
oid sha256:e60e340322fb6cc14507f29d7a25cec1a37a5890ed931ece96e4d5fc45d19466
size 10869373

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v2.9br5i.hl1e5.cda79.75gdi.jj5hc :: DO NOT MOVE FROM LINE 8
++ hash 0vp51an.2c81t.2dc6j.oibjo.0mbfd
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.2eee573a41d00825e88a.js"></script>
<script src="/~landscape/js/bundle/index.b8ba33f37b3f5dd608d6.js"></script>
</body>
</html>

View File

@ -3,15 +3,14 @@
:::: /hoon/code/hood/gen
::
/? 310
::
::::
::
:- %say
/- *sole
/+ *generators
:- %ask
|= $: [now=@da eny=@uvJ bec=beak]
[arg=?(~ [%reset ~]) ~]
==
=* our p.bec
:- %helm-code
^- (sole-result [%helm-code ?(~ %reset)])
?~ arg
=/ code=tape
%+ slag 1
@ -20,11 +19,23 @@
=/ step=tape
%+ scow %ud
.^(@ud %j /(scot %p our)/step/(scot %da now)/(scot %p our))
%- %- slog
:~ [%leaf code]
[%leaf (weld "current step=" step)]
[%leaf "use |code %reset to invalidate this and generate a new code"]
==
~
::
%+ print 'use |code %reset to invalidate this and generate a new code'
%+ print leaf+(weld "current step=" step)
%+ print leaf+code
(produce [%helm-code ~])
::
?> =(%reset -.arg)
%reset
%+ print 'continue?'
%+ print 'warning: resetting your code closes all web sessions'
%+ prompt
[%& %project "y/n: "]
%+ parse
;~ pose
(cold %.y (mask "yY"))
(cold %.n (mask "nN"))
==
|= reset=?
?. reset
no-product
(produce [%helm-code %reset])

View File

@ -114,6 +114,15 @@
state-3
state-4
==
:: +diplomatic: only renegotiate if versions changed
::
:: If %.n please leave note as to why renegotiation necessary
::
:: - Fixing incorrectly held unversioned subscriptions
::
++ diplomatic
^- ?
%.n
::
++ default
|* [pull-hook=* =config]
@ -239,6 +248,7 @@
=/ kick=(list card)
?: ?& =(min-version.config prev-min-version.old)
=(version.config prev-version.old)
diplomatic
==
~
(poke-self:pass kick+!>(%kick))^~
@ -439,6 +449,7 @@
?~ tan tr-core
?. versioned
(tr-ap-og:tr-cleanup |.((on-pull-nack:og rid u.tan)))
%- (slog leaf+"versioned nack for {<rid>} in {<dap.bowl>}" u.tan)
=/ pax
(kick-mule:virt rid |.((on-pull-kick:og rid)))
?~ pax tr-failed-kick
@ -463,18 +474,18 @@
:: subscription
tr-core
(tr-suspend-pub-ver min-version.config)
=/ =vase
=/ =^cage
(convert-to:ver cage)
=/ =wire
(make-wire /store)
=+ resources=(~(gas in *(set resource)) (resource-for-update:og vase))
=+ resources=(~(gas in *(set resource)) (resource-for-update:og q.cage))
?> ?| no-validate.config
?& (check-src resources)
(~(has in resources) rid)
== ==
=/ =mark
(append-version:ver version.config)
(tr-emit (~(poke-our pass wire) store-name.config mark vase))
(tr-emit (~(poke-our pass wire) store-name.config cage))
--
::
++ tr-kick

View File

@ -73,6 +73,16 @@
state-1
state-2
==
:: +diplomatic: only renegotiate if versions changed
::
:: If %.n please leave note as to why renegotiation necessary
::
:: - Fixing incorrectly held unversioned subscriptions
::
++ diplomatic
^- ?
%.n
::
++ push-hook
|* =config
$_ ^|
@ -221,6 +231,7 @@
|= [prev-min-version=@ud prev-version=@ud]
?: ?& =(min-version.config prev-min-version)
=(prev-version version.config)
diplomatic
==
:: bail on kick if we didn't change versions
~
@ -291,23 +302,21 @@
?. (supported:ver mark)
:_ this
(fact-init-kick:io version+!>(min-version.config))
=/ =vase
(convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource))
:_ this
[%give %fact ~ mark vase]~
=- [%give %fact ~ -]~
(convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource))
::
++ unversioned
?> ?=([%ship @ @ *] t.path)
?. =(min-version.config 0)
~& >>> "unversioned req from: {<src.bowl>}, nooping"
`this
=/ =resource
(de-path:resource t.path)
=/ =vase
%+ convert-to:ver update-mark.config
=/ =vase
(initial-watch:og t.t.t.t.path resource)
:_ this
[%give %fact ~ update-mark.config vase]~
?. =(min-version.config 0)
~& >>> "unversioned req from: {<src.bowl>}, nooping"
~
[%give %fact ~ (convert-to:ver update-mark.config vase)]~
--
::
++ on-agent
@ -461,10 +470,7 @@
|= [fact-ver=@ud paths=(set path)]
=/ =mark
(append-version:ver fact-ver)
=/ =^cage
:- mark
(convert-from:ver mark q.cage)
(fact:io cage ~(tap in paths))
(fact:io (convert-from:ver mark q.cage) ~(tap in paths))
:: TODO: deprecate
++ unversioned
?. =(min-version.config 0) ~
@ -474,18 +480,15 @@
%- ~(gas in *(set path))
(turn (incoming-subscriptions prefix) tail)
?: =(0 ~(wyt in unversioned)) ~
=/ =^cage
:- update-mark.config
(convert-from:ver update-mark.config q.cage)
(fact:io cage ~(tap in unversioned))^~
(fact:io (convert-from:ver update-mark.config q.cage) ~(tap in unversioned))^~
--
::
++ forward-update
|= =cage
^- (list card:agent:gall)
=- lis
=/ vas
(convert-to:ver cage)
=/ vas=vase
q:(convert-to:ver cage)
%+ roll (resource-for-update q.cage)
|= [rid=resource [lis=(list card:agent:gall) tf-vas=(unit vase)]]
^- [(list card:agent:gall) (unit vase)]

View File

@ -29,11 +29,12 @@
&((gte ver min) (lte ver version))
::
++ convert-to
|= =cage
^- vase
?: =(p.cage current-version)
q.cage
((tube-to p.cage) q.cage)
|= [=mark =vase]
^- cage
:- current-version
?: =(mark current-version)
vase
((tube-to mark) vase)
::
++ tube-to
|= =mark
@ -44,10 +45,11 @@
.^(tube:clay %cc (scry:io %home /[current-version]/[mark]))
::
++ convert-from
|= =cage
^- vase
?: =(p.cage current-version)
q.cage
((tube-from p.cage) q.cage)
|= [=mark =vase]
^- cage
:- mark
?: =(mark current-version)
vase
((tube-from mark) vase)
--

View File

@ -3,7 +3,7 @@ import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer';
import { buntPost } from '~/logic/lib/post';
import { unixToDa } from '~/logic/lib/util';
//import tokenizeMessage from './tokenizeMessage';
import tokenizeMessage from './tokenizeMessage';
export function newPost(
title: string,
@ -20,8 +20,7 @@ export function newPost(
signatures: []
};
// re-enable on mainnet deploy
//const tokenisedBody = tokenizeMessage(body);
const tokenisedBody = tokenizeMessage(body);
const revContainer: Post = { ...root, index: root.index + '/1' };
const commentsContainer = { ...root, index: root.index + '/2' };
@ -29,8 +28,7 @@ export function newPost(
const firstRevision: Post = {
...revContainer,
index: revContainer.index + '/1',
contents: [{ text: title }, { text: body }]
//contents: [{ text: title }, { text: body } ...tokenisedBody]
contents: [{ text: title }, ...tokenisedBody]
};
const nodes = {
@ -59,14 +57,12 @@ export function newPost(
export function editPost(rev: number, noteId: BigInteger, title: string, body: string) {
const now = Date.now();
// reenable
//const tokenisedBody = tokenizeMessage(body);
const tokenisedBody = tokenizeMessage(body);
const newRev: Post = {
author: `~${window.ship}`,
index: `/${noteId.toString()}/1/${rev}`,
'time-sent': now,
//contents: [{ text: title }, ...tokenisedBody],
contents: [{ text: title }, { text: body }],
contents: [{ text: title }, ...tokenisedBody],
hash: null,
signatures: []
};

View File

@ -98,7 +98,7 @@ class WeatherTile extends React.Component {
<Text color='black' cursor='pointer'
onClick={() => this.locationSubmit()}
>
Detect ->
Detect {'->'}
</Text>
);
}
@ -177,7 +177,7 @@ class WeatherTile extends React.Component {
<Text>Weather</Text>
</Box>
<Text style={{ cursor: 'pointer' }}>
-> Set location
{'->'} Set location
</Text>
</Box>
);
@ -226,7 +226,7 @@ class WeatherTile extends React.Component {
this.setState({ manualEntry: !this.state.manualEntry })
}
>
Weather ->
Weather {'->'}
</Text>
</Text>
@ -281,7 +281,7 @@ class WeatherTile extends React.Component {
this.setState({ manualEntry: !this.state.manualEntry })
}
>
->
{'->'}
</Text>
</Text>
</Box>

View File

@ -20,7 +20,7 @@ function TranscludedLinkNode(props: {
api: GlobalApi;
}) {
const { node, api, assoc, transcluded } = props;
const idx = node.post.index.slice(1).split('/');
const idx = node?.post?.index?.slice(1)?.split('/') ?? [];
switch (idx.length) {
case 1:
@ -91,7 +91,7 @@ function TranscludedPublishNode(props: {
}) {
const { node, assoc, transcluded, api } = props;
const group = useGroupForAssoc(assoc)!;
const idx = node.post.index.slice(1).split('/');
const idx = node?.post?.index?.slice(1)?.split('/') ?? [];
switch (idx.length) {
case 1:
const post = node.children

View File

@ -27,18 +27,10 @@ interface NoteProps {
group: Group;
}
const renderers = {
link: ({ href, children }) => {
return (
<Anchor display="inline" target="_blank" href={href}>{children}</Anchor>
);
}
};
export function NoteContent({ post, api }) {
return (
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
<GraphContent tall contents={post.contents.slice(1)} showOurContact api={api} />
<GraphContent tall={true} contents={post.contents.slice(1)} showOurContact api={api} />
</Box>
);
}

View File

@ -56,28 +56,26 @@ const codeToMdAst = (content: CodeContent) => {
children: [
{
type: 'code',
value: content.code.expression
value: content.code.expression,
},
{
type: 'code',
value: (content.code.output || []).join('\n')
}
]
value: (content.code.output || []).join('\n'),
},
],
};
}
};
const contentToMdAst = (tall: boolean) => (
content: Content
): [StitchMode, any] => {
if ('text' in content) {
return ['merge', tall ? parseTall(content.text) : parseWide(content.text)] as [StitchMode, any];
} else if ('code' in content) {
return [
'block',
codeToMdAst(content)
];
'merge',
tall ? parseTall(content.text) : parseWide(content.text),
] as [StitchMode, any];
} else if ('code' in content) {
return ['block', codeToMdAst(content)];
} else if ('reference' in content) {
return [
'block',
@ -171,7 +169,9 @@ export function asParent<T extends BlockContent>(node: T): Parent | undefined {
function stitchMerge(a: Root, b: Root) {
const aChildren = a.children;
const bChildren = b.children;
if (last(aChildren)?.type === bChildren[0]?.type) {
const lastType = last(aChildren)?.type;
if (lastType === bChildren[0]?.type) {
const aGrandchild = getChildren(last(aChildren));
const bGrandchild = getChildren(bChildren[0]);
const mergedPara = {
@ -230,16 +230,16 @@ const header = ({ children, depth, ...rest }) => {
const level = depth;
const inner =
level === 1 ? (
<H1>{children}</H1>
<H1 display='block'>{children}</H1>
) : level === 2 ? (
<H2>{children}</H2>
<H2 display='block'>{children}</H2>
) : level === 3 ? (
<H3>{children}</H3>
<H3 display='block'>{children}</H3>
) : (
<H4>{children}</H4>
<H4 display='block'>{children}</H4>
);
return (
<Box {...rest} mt="2" mb="4">
<Box {...rest}>
{inner}
</Box>
);
@ -247,6 +247,12 @@ const header = ({ children, depth, ...rest }) => {
const renderers = {
heading: header,
break: () => {
return <Box display='block' width='100%' height={2}></Box>
},
thematicBreak: () => {
return <Box display='block' width='100%' height={2}></Box>
},
inlineCode: ({ language, value }) => {
return (
<Text
@ -260,7 +266,20 @@ const renderers = {
</Text>
);
},
strong: ({ children }) => {
return (
<Text fontWeight="bold" lineHeight='1'>
{children}
</Text>
);
},
emphasis: ({ children }) => {
return (
<Text fontStyle="italic" fontSize="1" lineHeight={'20px'}>
{children}
</Text>
)
},
blockquote: ({ children, tall, ...rest }) => {
return (
<Text
@ -270,6 +289,7 @@ const renderers = {
color="black"
paddingLeft={2}
py="1"
mb="1"
>
{children}
</Text>
@ -319,7 +339,7 @@ const renderers = {
},
link: (props) => {
return (
<Anchor href={props.href} borderBottom="1" color="black">
<Anchor href={props.url} borderBottom="1" color="black" target="_blank">
{props.children}
</Anchor>
);
@ -332,36 +352,55 @@ const renderers = {
);
},
'graph-mention': ({ ship }) => <Mention api={{} as any} ship={ship} />,
'graph-url': ({ url }) => (
<Box my="2" flexShrink={0}>
'image': ({ url }) => (
<Box mt="1" mb="2" flexShrink={0}>
<RemoteContent key={url} url={url} />
</Box>
),
'graph-reference': ({ api, reference }) => {
'graph-url': ({ url }) => (
<Box mt="1" mb="2" flexShrink={0}>
<RemoteContent key={url} url={url} />
</Box>
),
'graph-reference': ({ api, reference, transcluded }) => {
const { link } = referenceToPermalink({ reference });
return (
<PermalinkEmbed api={api} link={link} transcluded={0} showOurContact />
<Box mb="2" flexShrink={0}>
<PermalinkEmbed
api={api}
link={link}
transcluded={transcluded}
showOurContact
/>
</Box>
);
},
root: ({ children }) => <Col gapY="2">{children}</Col>,
root: ({ tall, children }) =>
tall ? <Col display='grid' style={{ 'row-gap': '1rem' }}>{children}</Col> : <Box>{children}</Box>,
text: ({ value }) => value,
};
export function Graphdown<T extends {} = {}>(
props: {
ast: GraphAstNode;
transcluded: number;
tall?: boolean;
depth?: number;
} & T
) {
const { ast, depth = 0, ...rest } = props;
const { ast, transcluded, tall, depth = 0, ...rest } = props;
const { type, children = [], ...nodeRest } = ast;
const Renderer = renderers[ast.type] ?? (() => `unknown element: ${type}`);
return (
<Renderer depth={depth} {...rest} {...nodeRest}>
<Renderer transcluded={transcluded} depth={depth} {...rest} {...nodeRest} tall={tall}>
{children.map((c) => (
<Graphdown depth={depth+1} {...rest} ast={c} />
<Graphdown
transcluded={transcluded}
depth={depth + 1}
{...rest}
ast={c}
/>
))}
</Renderer>
);
@ -385,11 +424,10 @@ export const GraphContent = React.memo(function GraphContent(
api,
...rest
} = props;
const [,ast] = stitchAsts(contents.map(contentToMdAst(tall)));
const [, ast] = stitchAsts(contents.map(contentToMdAst(tall)));
return (
<Box {...rest}>
<Graphdown api={api} ast={ast} />
<Graphdown transcluded={transcluded} api={api} ast={ast} tall={tall} />
</Box>
);
});

View File

@ -0,0 +1,54 @@
import React from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import { Col, Text, Box } from '@tlon/indigo-react';
import { ShipSearch } from '~/views/components/ShipSearch';
import { resourceFromPath } from '~/logic/lib/group';
import { deSig } from '~/logic/lib/util';
import { AsyncButton } from '~/views/components/AsyncButton';
const formSchema = Yup.object({
ships: Yup.array(Yup.string()).min(1, 'Must invite at least one ship')
});
export const MessageInvite = (props) => {
const { association, api } = { ...props };
const initialValues: FormSchema = { ships: [] };
const onSubmit = async ({ ships, description }: FormSchema, actions) => {
try {
const { ship, name } = resourceFromPath(association.group);
await api.groups.invite(
ship,
name,
_.compact(ships).map(s => `~${deSig(s)}`),
`Inviting you to a DM with ~${ship}`
);
actions.setStatus({ success: null });
} catch (e) {
console.error(e);
actions.setStatus({ error: e.message });
}
};
return (
<Col p={3}>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={formSchema}
validateOnBlur
>
<Form>
<Box mb={3}>
<Text fontSize={2} bold>Add to Group Message</Text>
</Box>
<ShipSearch id='ships' label='Invitees' autoFocus />
<AsyncButton border={0} primary loadingText='Inviting...'>
Invite
</AsyncButton>
</Form>
</Formik>
</Col>
);
};
export default MessageInvite;

View File

@ -29,34 +29,37 @@ type FormSchema = {
moduleType: 'chat' | 'publish' | 'link';
} & ChannelWriteFieldSchema;
const formSchema = (members?: string[]) => Yup.object({
name: Yup.string(),
description: Yup.string(),
ships: Yup.array(Yup.string()),
moduleType: Yup.string().required('Must choose channel type'),
writers: members ? shipSearchSchemaInGroup(members) : shipSearchSchema,
writePerms: Yup.string()
});
const formSchema = (members?: string[]) =>
Yup.object({
name: Yup.string(),
description: Yup.string(),
ships: Yup.array(Yup.string()),
moduleType: Yup.string().required('Must choose channel type'),
writers: members ? shipSearchSchemaInGroup(members) : shipSearchSchema,
writePerms: Yup.string()
});
interface NewChannelProps {
type NewChannelProps = {
api: GlobalApi;
group?: string;
workspace: Workspace;
baseUrl?: string;
}
existingMembers: string[];
} & PropFunc<typeof Col>;
export function NewChannel(props: NewChannelProps): ReactElement {
const history = useHistory();
const { api, group, workspace } = props;
const { api, group, workspace, existingMembers, ...rest } = props;
const groups = useGroupState(state => state.groups);
const waiter = useWaitForProps({ groups }, 5000);
const onSubmit = async (values: FormSchema, actions) => {
const name = (values.name) ? values.name : values.moduleType;
const resId: string = stringToSymbol(values.name)
+ ((workspace?.type !== 'messages') ? `-${Math.floor(Math.random() * 10000)}`
: '');
const name = values.name ? values.name : values.moduleType;
const resId: string =
stringToSymbol(values.name) +
(workspace?.type !== 'messages'
? `-${Math.floor(Math.random() * 10000)}`
: '');
try {
let { description, moduleType, ships, writers } = values;
ships = ships.filter(e => e !== '');
@ -80,9 +83,9 @@ export function NewChannel(props: NewChannelProps): ReactElement {
const resource = resourceFromPath(group);
writers = _.compact(writers).map(s => `~${s}`);
const us = `~${window.ship}`;
if(values.writePerms === 'self') {
if (values.writePerms === 'self') {
await api.groups.addTag(resource, tag, [us]);
} else if(values.writePerms === 'subset') {
} else if (values.writePerms === 'subset') {
writers.push(us);
await api.groups.addTag(resource, tag, writers);
}
@ -97,10 +100,14 @@ export function NewChannel(props: NewChannelProps): ReactElement {
}
if (!group) {
await waiter(p => Boolean(p.groups?.[`/ship/~${window.ship}/${resId}`]));
await waiter(p =>
Boolean(p.groups?.[`/ship/~${window.ship}/${resId}`])
);
}
actions.setStatus({ success: null });
const resourceUrl = (location.pathname.includes('/messages')) ? '/~landscape/messages' : parentPath(location.pathname);
const resourceUrl = location.pathname.includes('/messages')
? '/~landscape/messages'
: parentPath(location.pathname);
history.push(
`${resourceUrl}/resource/${moduleType}/ship/~${window.ship}/${resId}`
);
@ -113,7 +120,12 @@ export function NewChannel(props: NewChannelProps): ReactElement {
const members = group ? Array.from(groups[group]?.members) : undefined;
return (
<Col overflowY="auto" p={3} backgroundColor="white">
<Col
overflowY='auto'
p={3}
backgroundColor='white'
{...rest}
>
<Box
pb='3'
display={workspace?.type === 'messages' ? 'none' : ['block', 'none']}
@ -122,80 +134,84 @@ export function NewChannel(props: NewChannelProps): ReactElement {
<Text>{'<- Back'}</Text>
</Box>
<Box>
<Text fontSize={2} bold>{workspace?.type === 'messages' ? 'Direct Message' : 'New Channel'}</Text>
<Text fontSize={2} bold>
{workspace?.type === 'messages' && existingMembers ? 'New Group ' : null}
{workspace?.type === 'messages' ? 'Direct Message' : 'New Channel'}
</Text>
</Box>
<Formik
validationSchema={formSchema(members)}
initialValues={{
moduleType: (workspace?.type === 'home') ? 'publish' : 'chat',
moduleType: workspace?.type === 'home' ? 'publish' : 'chat',
name: '',
description: '',
group: '',
ships: [],
ships: existingMembers ? [...existingMembers] : [],
writePerms: 'everyone',
writers: []
}}
onSubmit={onSubmit}
>
<Form>
<Col
maxWidth="348px"
gapY="4"
>
<Col pt={4} gapY="2" display={(workspace?.type === 'messages') ? 'none' : 'flex'}>
<Box fontSize="1" color="black" mb={2}>Channel Type</Box>
<Form>
<Col maxWidth='348px' gapY='4'>
<Col
pt={4}
gapY='2'
display={workspace?.type === 'messages' ? 'none' : 'flex'}
>
<Box fontSize='1' color='black' mb={2}>
Channel Type
</Box>
<IconRadio
display={!(workspace?.type === 'home') ? 'flex' : 'none'}
icon="Chat"
label="Chat"
id="chat"
name="moduleType"
icon='Chat'
label='Chat'
id='chat'
name='moduleType'
/>
<IconRadio
icon="Notebook"
label="Notebook"
id="publish"
name="moduleType"
icon='Notebook'
label='Notebook'
id='publish'
name='moduleType'
/>
<IconRadio
icon="Collection"
label="Collection"
id="link"
name="moduleType"
icon='Collection'
label='Collection'
id='link'
name='moduleType'
/>
</Col>
<Input
display={workspace?.type === 'messages' ? 'none' : 'flex'}
id="name"
label="Name"
caption="Provide a name for your channel"
placeholder="eg. My Channel"
display={workspace?.type === 'messages' ? 'none' : 'flex'}
id='name'
label='Name'
caption='Provide a name for your channel'
placeholder='eg. My Channel'
/>
<Input
display={workspace?.type === 'messages' ? 'none' : 'flex'}
id="description"
label="Description"
caption="What's your channel about?"
placeholder="Channel description"
display={workspace?.type === 'messages' ? 'none' : 'flex'}
id='description'
label='Description'
caption="What's your channel about?"
placeholder='Channel description'
/>
{(workspace?.type === 'home' || workspace?.type === 'messages') ? (
<ShipSearch
id="ships"
label="Invitees"
/>) : (
<ChannelWritePerms />
)}
<Box justifySelf="start">
{workspace?.type === 'home' || workspace?.type === 'messages' ? (
<ShipSearch id='ships' label='Invitees' />
) : (
<ChannelWritePerms />
)}
<Box justifySelf='start'>
<AsyncButton
primary
loadingText="Creating..."
type="submit"
border
primary
loadingText='Creating...'
type='submit'
border
>
Create
</AsyncButton>
</Box>
<FormError message="Channel creation failed" />
<FormError message='Channel creation failed' />
</Col>
</Form>
</Formik>

View File

@ -1,14 +1,16 @@
import { Box, Col, Icon, Text } from '@tlon/indigo-react';
import { Association } from '@urbit/api/metadata';
import React, { ReactElement, ReactNode, useRef } from 'react';
import React, { ReactElement, ReactNode, useRef, useState, useCallback } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import urbitOb from 'urbit-ob';
import { Dropdown } from '~/views/components/Dropdown';
import GlobalApi from '~/logic/api/global';
import { isWriter } from '~/logic/lib/group';
import { getItemTitle } from '~/logic/lib/util';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import { MessageInvite } from '~/views/landscape/components/MessageInvite';
import RichText from '~/views/components/RichText';
const TruncatedText = styled(RichText)`
@ -27,7 +29,7 @@ type ResourceSkeletonProps = {
};
export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
const { association, baseUrl, children } = props;
const { association, baseUrl, children, api } = props;
let app = association['app-name'];
if (association?.metadata?.config && 'graph' in association.metadata.config) {
app = association.metadata.config.graph;
@ -36,7 +38,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
const groups = useGroupState(state => state.groups);
const group = groups[association.group];
let workspace = association.group;
const actionsRef = useRef(null);
const [actionsWidth, setActionsWidth] = useState(0);
if (group?.hidden && app === 'chat') {
workspace = '/messages';
@ -99,7 +101,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
maxWidth={association?.metadata?.description ? ['100%', '50%'] : 'none'}
mr='2'
ml='1'
flexShrink={[1, 0]}
flexShrink={1}
>
{title}
</Text>
@ -122,13 +124,45 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
</TruncatedText>
);
const WriterControls = () => (
<Link to={resourcePath('/new')}>
<Text bold pr='3' color='blue'>
+ New Post
</Text>
</Link>
);
const ExtraControls = () => {
if (workspace === '/messages' && isOwn && !resource.startsWith('dm-')) {
return (
<Dropdown
flexShrink={0}
dropWidth='300px'
width='auto'
alignY='top'
alignX='right'
options={
<Col
backgroundColor='white'
border={1}
borderRadius={2}
borderColor='lightGray'
color='washedGray'
boxShadow='0px 0px 0px 3px'
>
<MessageInvite association={association} api={api} />
</Col>
}
>
<Text bold pr='3' color='blue'>
+ Add Ship
</Text>
</Dropdown>
);
}
if (canWrite) {
return (
<Link to={resourcePath('/new')}>
<Text bold pr='3' color='blue'>
+ New Post
</Text>
</Link>
);
}
return null;
};
const MenuControl = () => (
<Link to={`${baseUrl}/settings`}>
@ -136,12 +170,9 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
</Link>
);
const actRef = actionsRef.current;
let actionsWidth = 0;
if (actRef) {
actionsWidth = actRef.clientWidth;
}
const actionsRef = useCallback((actionsRef) => {
setActionsWidth(actionsRef?.getBoundingClientRect().width);
}, [rid]);
return (
<Col width='100%' height='100%' overflow='hidden'>
@ -173,7 +204,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
flexShrink={0}
ref={actionsRef}
>
{canWrite && <WriterControls />}
{ExtraControls()}
<MenuControl />
</Box>
</Box>

View File

@ -52,9 +52,9 @@ export function SidebarListHeader(props: {
const noun = (props.workspace?.type === 'messages') ? 'Messages' : 'Channels';
let feedPath: any = '';
if (metadata?.config && 'group' in metadata.config) {
feedPath = metadata.config.group ?? '';
let feedPath: any = null;
if (metadata?.config && 'group' in metadata?.config && metadata.config?.group && 'resource' in metadata.config.group) {
feedPath = metadata.config.group.resource;
}
const unreadCount = useHarkState(
@ -63,7 +63,7 @@ export function SidebarListHeader(props: {
return (
<Box>
{( feedPath) ? (
{( !!feedPath) ? (
<Row
flexShrink={0}
alignItems="center"

View File

@ -16,7 +16,7 @@ function sortBigInt(a: BigInteger, b: BigInteger) {
}
export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
root: Record<string, V> = {}
cachedIter: [BigInteger, V][] = [];
cachedIter: [BigInteger, V][] = null;
[immerable] = true;
constructor(items: [BigInteger, V][] = []) {